From 21e529c6c9f9cad6fcda970ea0e28996d81136dc Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 8 Oct 2019 13:23:03 +1100 Subject: [PATCH 01/64] Forward messages from secondary devices to primary device. --- .../conversation/ConversationActivity.java | 21 ++++-- .../database/helpers/SQLCipherOpenHelper.java | 8 ++- .../securesms/jobs/PushDecryptJob.java | 67 ++++++++++++++++--- .../securesms/loki/LokiMessageDatabase.kt | 32 +++++++-- 4 files changed, 106 insertions(+), 22 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 25ee20998d..7bad8bb144 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -128,6 +128,7 @@ import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.database.Database; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -158,6 +159,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate; import org.thoughtcrime.securesms.loki.LokiAPIUtilities; import org.thoughtcrime.securesms.loki.LokiThreadDatabase; +import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate; import org.thoughtcrime.securesms.loki.LokiUserDatabase; import org.thoughtcrime.securesms.loki.MentionCandidateSelectionView; @@ -2988,7 +2990,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity // region Loki @Override public void acceptFriendRequest(@NotNull MessageRecord friendRequest) { - String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(this.threadId).getAddress().toString(); + // Send the accept to the original friend request thread id + LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(this); + long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id); + long threadId = originalThreadID < 0 ? this.threadId : originalThreadID; + + String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString(); SignalServiceMessageSender messageSender = ApplicationContext.getInstance(this).communicationModule.provideSignalMessageSender(); SignalServiceAddress address = new SignalServiceAddress(contactID); SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), ""); @@ -2996,8 +3003,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity AsyncTask.execute(() -> { try { messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter - DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(this.threadId, LokiThreadFriendRequestStatus.FRIENDS); - DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS); + lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); } catch (Exception e) { Log.d("Loki", "Failed to send background message to: " + contactID + "."); } @@ -3006,8 +3013,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void rejectFriendRequest(@NotNull MessageRecord friendRequest) { - DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(this.threadId, LokiThreadFriendRequestStatus.NONE); - String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(this.threadId).getAddress().toString(); + LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(this); + long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id); + long threadId = originalThreadID < 0 ? this.threadId : originalThreadID; + + DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.NONE); + String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString(); DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(contactID); } // endregion diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 9216a2314b..115cd1090d 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -70,6 +70,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV1 = 22; private static final int lokiV2 = 23; private static final int lokiV3 = 24; + private static final int lokiV4 = 25; private static final int DATABASE_VERSION = lokiV3; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes private static final String DATABASE_NAME = "signal.db"; @@ -128,7 +129,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand()); db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand()); - db.execSQL(LokiMessageDatabase.getCreateTableCommand()); + db.execSQL(LokiMessageDatabase.getCreateMessageFriendRequestTableCommand()); + db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand()); db.execSQL(LokiThreadDatabase.getCreateFriendRequestTableCommand()); db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand()); db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand()); @@ -504,6 +506,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE part ADD COLUMN url TEXT"); } + if (oldVersion < lokiV4) { + db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 625efd9455..d475731b36 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -97,6 +97,7 @@ import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.whispersystems.libsignal.state.PreKeyBundle; import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.guava.Optional; @@ -749,13 +750,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType { @NonNull Optional messageServerIDOrNull) throws StorageFailedException { - notifyTypingStoppedFromIncomingMessage(getMessageDestination(content, message), content.getSender(), content.getSenderDevice()); + Recipient originalRecipient = getMessageDestination(content, message); + Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); + + notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice()); Optional quote = getValidatedQuote(message.getQuote()); Optional> sharedContacts = getContacts(message.getSharedContacts()); Optional> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or("")); Optional sticker = getStickerAttachment(message.getSticker()); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), message.getTimestamp(), -1, + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(primaryDeviceRecipient.getAddress(), message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(), quote, sharedContacts, linkPreviews, sticker); @@ -798,8 +802,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Loki - Store message server ID updateGroupChatMessageServerID(messageServerIDOrNull, insertResult); + // Loki - Update mapping of message to original thread id if (insertResult.isPresent()) { MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); + + ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); + LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); + long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient); + lokiMessageDatabase.setOriginalThreadID(insertResult.get().getMessageId(), originalThreadId); } } @@ -912,20 +922,21 @@ public class PushDecryptJob extends BaseJob implements InjectableType { { SmsDatabase database = DatabaseFactory.getSmsDatabase(context); String body = message.getBody().isPresent() ? message.getBody().get() : ""; - Recipient recipient = getMessageDestination(content, message); + Recipient originalRecipient = getMessageDestination(content, message); + Recipient primaryDeviceRecipient = message.isGroupUpdate() ? originalRecipient : getMessagePrimaryDestination(content, message); - if (message.getExpiresInSeconds() != recipient.getExpireMessages()) { + if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) { handleExpirationUpdate(content, message, Optional.absent()); } - Long threadId; + Long threadId = null; if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) { threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second; } else { - notifyTypingStoppedFromIncomingMessage(recipient, content.getSender(), content.getSenderDevice()); + notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice()); - IncomingTextMessage _textMessage = new IncomingTextMessage(Address.fromSerialized(content.getSender()), + IncomingTextMessage _textMessage = new IncomingTextMessage(primaryDeviceRecipient.getAddress(), content.getSenderDevice(), message.getTimestamp(), body, message.getGroupInfo(), @@ -940,8 +951,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Insert the message into the database Optional insertResult = database.insertMessageInbox(textMessage); - if (insertResult.isPresent()) threadId = insertResult.get().getThreadId(); - else threadId = null; + Long messageId = null; + if (insertResult.isPresent()) { + threadId = insertResult.get().getThreadId(); + messageId = insertResult.get().getMessageId(); + } if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get()); @@ -954,6 +968,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Loki - Store message server ID updateGroupChatMessageServerID(messageServerIDOrNull, insertResult); + // Loki - Update mapping of message to original thread id + if (messageId != null) { + ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); + LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); + long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient); + lokiMessageDatabase.setOriginalThreadID(messageId, originalThreadId); + } + boolean isGroupMessage = message.getGroupInfo().isPresent(); if (threadId != null && !isGroupMessage) { MessageNotifier.updateNotification(context, threadId); @@ -1496,6 +1518,33 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } + private Recipient getMessagePrimaryDestination(SignalServiceContent content, SignalServiceDataMessage message) { + if (message.getGroupInfo().isPresent()) { + return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)), false); + } else { + SettableFuture device = new SettableFuture<>(); + String contentSender = content.getSender(); + + // Get the primary device + LokiStorageAPI.shared.getPrimaryDevicePublicKey(contentSender).success(primaryDevice -> { + String publicKey = primaryDevice == null ? contentSender : primaryDevice; + device.set(publicKey); + return Unit.INSTANCE; + }).fail(exception -> { + device.set(contentSender); + return Unit.INSTANCE; + }); + + try { + String primarySender = device.get(); + return Recipient.from(context, Address.fromSerialized(primarySender), false); + } catch (Exception e) { + Log.d("Loki", "Failed to get primary device public key for message. " + e.getMessage()); + return Recipient.from(context, Address.fromSerialized(content.getSender()), false); + } + } + } + private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) { if (message.getGroupInfo().isPresent()) { return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)), false); diff --git a/src/org/thoughtcrime/securesms/loki/LokiMessageDatabase.kt b/src/org/thoughtcrime/securesms/loki/LokiMessageDatabase.kt index 26d93228e4..817b76f5dd 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiMessageDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiMessageDatabase.kt @@ -12,11 +12,14 @@ import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestS class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol { companion object { - private val tableName = "loki_message_friend_request_database" + private val messageFriendRequestTableName = "loki_message_friend_request_database" + private val messageThreadMappingTableName = "loki_message_thread_mapping_database" private val messageID = "message_id" private val serverID = "server_id" private val friendRequestStatus = "friend_request_status" - @JvmStatic val createTableCommand = "CREATE TABLE $tableName ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);" + private val threadID = "thread_id" + @JvmStatic val createMessageFriendRequestTableCommand = "CREATE TABLE $messageFriendRequestTableName ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);" + @JvmStatic val createMessageToThreadMappingTableCommand = "CREATE TABLE $messageThreadMappingTableName ($messageID INTEGER PRIMARY KEY, $threadID INTEGER);" } override fun getQuoteServerID(quoteID: Long, quoteeHexEncodedPublicKey: String): Long? { @@ -26,14 +29,14 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab fun getServerID(messageID: Long): Long? { val database = databaseHelper.readableDatabase - return database.get(tableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor -> + return database.get(messageFriendRequestTableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor -> cursor.getInt(Companion.serverID) }?.toLong() } fun getMessageID(serverID: Long): Long? { val database = databaseHelper.readableDatabase - return database.get(tableName, "${Companion.serverID} = ?", arrayOf( serverID.toString() )) { cursor -> + return database.get(messageFriendRequestTableName, "${Companion.serverID} = ?", arrayOf( serverID.toString() )) { cursor -> cursor.getInt(messageID) }?.toLong() } @@ -43,12 +46,27 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab val contentValues = ContentValues(2) contentValues.put(Companion.messageID, messageID) contentValues.put(Companion.serverID, serverID) - database.insertOrUpdate(tableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) + database.insertOrUpdate(messageFriendRequestTableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) + } + + fun getOriginalThreadID(messageID: Long): Long { + val database = databaseHelper.readableDatabase + return database.get(messageThreadMappingTableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor -> + cursor.getInt(Companion.threadID) + }?.toLong() ?: -1L + } + + fun setOriginalThreadID(messageID: Long, threadID: Long) { + val database = databaseHelper.writableDatabase + val contentValues = ContentValues(2) + contentValues.put(Companion.messageID, messageID) + contentValues.put(Companion.threadID, threadID) + database.insertOrUpdate(messageThreadMappingTableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) } fun getFriendRequestStatus(messageID: Long): LokiMessageFriendRequestStatus { val database = databaseHelper.readableDatabase - val result = database.get(tableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor -> + val result = database.get(messageFriendRequestTableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor -> cursor.getInt(friendRequestStatus) } return if (result != null) { @@ -63,7 +81,7 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab val contentValues = ContentValues(2) contentValues.put(Companion.messageID, messageID) contentValues.put(Companion.friendRequestStatus, friendRequestStatus.rawValue) - database.insertOrUpdate(tableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) + database.insertOrUpdate(messageFriendRequestTableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) val threadID = DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID) notifyConversationListeners(threadID) } From f24b191164b9b7755f5afe4d90095a132222a064 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 23 Oct 2019 11:53:38 +1100 Subject: [PATCH 02/64] Re-enable multi-device --- res/layout/activity_seed.xml | 3 +-- res/xml/preferences.xml | 2 +- .../securesms/ApplicationPreferencesActivity.java | 4 +--- src/org/thoughtcrime/securesms/loki/SeedActivity.kt | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/res/layout/activity_seed.xml b/res/layout/activity_seed.xml index cd79c01125..1ac6b7e57a 100644 --- a/res/layout/activity_seed.xml +++ b/res/layout/activity_seed.xml @@ -123,8 +123,7 @@ android:layout_height="50dp" android:background="@color/transparent" android:textColor="@color/signal_primary" - android:text="Link Device (Coming Soon)" - android:alpha="0.24" + android:text="Link Device" android:elevation="0dp" android:stateListAnimator="@null" /> diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 666ee32098..270c9254d3 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -42,7 +42,7 @@ android:icon="@drawable/icon_qr_code"/> Date: Wed, 23 Oct 2019 12:40:25 +1100 Subject: [PATCH 03/64] Disable input if we have a pending request to any other linked device and we're not friends with any of them --- .../conversation/ConversationActivity.java | 76 +++++++++++++++++-- .../securesms/loki/MultiDeviceUtilities.kt | 18 +++++ 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 7bad8bb144..5ba7f8efbe 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -128,7 +128,6 @@ import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.Database; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -163,6 +162,7 @@ import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate; import org.thoughtcrime.securesms.loki.LokiUserDatabase; import org.thoughtcrime.securesms.loki.MentionCandidateSelectionView; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.MediaSendActivity; import org.thoughtcrime.securesms.mms.AttachmentManager; @@ -231,6 +231,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.loki.api.LokiAPI; +import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.Mention; @@ -2190,12 +2191,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void updateInputPanel() { - boolean hasPendingFriendRequest = !recipient.isGroupRecipient() && DatabaseFactory.getLokiThreadDatabase(this).hasPendingFriendRequest(threadId); + if (recipient.isGroupRecipient()) { + updateToggleButtonState(); + inputPanel.setEnabled(true); + inputPanel.composeText.requestFocus(); + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(inputPanel.composeText, 0); + } + boolean hasPendingFriendRequest = !recipient.isGroupRecipient() && hasPendingFriendRequestWithAnyLinkedDevice(); + boolean isFriendsWithAnyLinkedDevices = isFriendsWithAnyLinkedDevice(); + boolean shouldEnableInput = isFriendsWithAnyLinkedDevices || !hasPendingFriendRequest; updateToggleButtonState(); - inputPanel.setEnabled(!hasPendingFriendRequest); - int hintID = hasPendingFriendRequest ? R.string.activity_conversation_pending_friend_request_hint : R.string.activity_conversation_default_hint; + inputPanel.setEnabled(shouldEnableInput); + int hintID = shouldEnableInput ? R.string.activity_conversation_pending_friend_request_hint : R.string.activity_conversation_default_hint; inputPanel.setHint(getResources().getString(hintID)); - if (!hasPendingFriendRequest) { + if (!shouldEnableInput) { inputPanel.composeText.requestFocus(); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); inputMethodManager.showSoftInput(inputPanel.composeText, 0); @@ -3021,5 +3031,61 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString(); DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(contactID); } + + public boolean isFriendsWithAnyLinkedDevice() { + if (recipient.isGroupRecipient()) return true; + SettableFuture future = new SettableFuture<>(); + LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); + + MultiDeviceUtilitiesKt.getAllDeviceFriendRequestStatus(this, recipient.getAddress().serialize(), storageAPI).success(map -> { + for (LokiThreadFriendRequestStatus status : map.values()) { + if (status == LokiThreadFriendRequestStatus.FRIENDS) { + future.set(true); + break; + } + } + + if (!future.isDone()) { future.set(false); } + return Unit.INSTANCE; + }).fail(e -> { + future.set(false); + return Unit.INSTANCE; + }); + + try { + return future.get(); + } catch (Exception e) { + return false; + } + } + + public boolean hasPendingFriendRequestWithAnyLinkedDevice() { + if (recipient.isGroupRecipient()) return false; + SettableFuture future = new SettableFuture<>(); + LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); + + MultiDeviceUtilitiesKt.getAllDeviceFriendRequestStatus(this, recipient.getAddress().serialize(), storageAPI).success(map -> { + for (LokiThreadFriendRequestStatus status : map.values()) { + // Break out of the loop as soon as one of the device has a pending friend request + if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { + future.set(true); + break; + } + } + + // If we reached this and we haven't set anything on the future then it means that we don't have a pending request + if (!future.isDone()) { future.set(false); } + return Unit.INSTANCE; + }).fail(e -> { + future.set(false); + return Unit.INSTANCE; + }); + + try { + return future.get(); + } catch (Exception e) { + return false; + } + } // endregion } diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index adaee4ef49..e7b8129e12 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.loki import android.content.Context import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred +import nl.komponents.kovenant.functional.bind +import nl.komponents.kovenant.functional.map import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory @@ -17,6 +19,22 @@ import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus +fun getAllDeviceFriendRequestStatus(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI): Promise, Exception> { + val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) + return storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> + val map = mutableMapOf() + + for (devicePublicKey in keys) { + val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false) + val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device) + val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID) + map.put(devicePublicKey, friendRequestStatus); + } + + map + } +} + fun getAllDevicePublicKeys(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI, block: (devicePublicKey: String, isFriend: Boolean, friendCount: Int) -> Unit) { val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).success { items -> From 1c1685ae9d9a7f1ad176e173c603af95aa5529e7 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 23 Oct 2019 15:29:56 +1100 Subject: [PATCH 04/64] Fix multidevice. Fix authorisation grant not being sent. --- .../securesms/ApplicationContext.java | 7 +++- .../ApplicationPreferencesActivity.java | 32 +++++++++++++------ .../securesms/ConversationListActivity.java | 1 + .../conversation/ConversationActivity.java | 2 +- .../securesms/loki/MultiDeviceUtilities.kt | 20 ++++++++++++ .../securesms/loki/SeedActivity.kt | 16 ---------- .../notifications/MarkReadReceiver.java | 4 ++- 7 files changed, 53 insertions(+), 29 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 6384e42d47..a17d5327c4 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -188,6 +188,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc }; // Loki - Set up public chat manager lokiPublicChatManager = new LokiPublicChatManager(this); + + // Loki - Update device mappings + setUpStorageAPIIfNeeded(); + if (IdentityKeyUtil.hasIdentityKey(this)) { + LokiStorageAPI.Companion.getShared().updateUserDeviceMappings(); + } } @Override @@ -199,7 +205,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc // Loki - Start long polling if needed startLongPollingIfNeeded(); lokiPublicChatManager.startPollersIfNeeded(); - setUpStorageAPIIfNeeded(); } @Override diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 57330e0685..b95405a8a1 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -39,9 +39,12 @@ import android.support.v7.app.AlertDialog; import android.support.v7.preference.Preference; import android.widget.Toast; +import org.jetbrains.annotations.NotNull; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.loki.DeviceLinkingDialog; +import org.thoughtcrime.securesms.loki.DeviceLinkingDialogDelegate; import org.thoughtcrime.securesms.loki.DeviceLinkingView; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; import org.thoughtcrime.securesms.loki.QRCodeDialog; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment; @@ -52,6 +55,7 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.signalservice.loki.api.PairingAuthorisation; import org.whispersystems.signalservice.loki.crypto.MnemonicCodec; import org.whispersystems.signalservice.loki.utilities.Analytics; import org.whispersystems.signalservice.loki.utilities.SerializationKt; @@ -168,15 +172,15 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS)); */ this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS) - .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS)); + .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_NOTIFICATIONS)); this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION) - .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APP_PROTECTION)); + .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_APP_PROTECTION)); /* this.findPreference(PREFERENCE_CATEGORY_APPEARANCE) .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE)); */ this.findPreference(PREFERENCE_CATEGORY_CHATS) - .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS)); + .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_CHATS)); /* this.findPreference(PREFERENCE_CATEGORY_DEVICES) .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES)); @@ -184,19 +188,19 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED)); */ this.findPreference(PREFERENCE_CATEGORY_PUBLIC_KEY) - .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_PUBLIC_KEY)); + .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_PUBLIC_KEY)); this.findPreference(PREFERENCE_CATEGORY_QR_CODE) - .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_QR_CODE)); + .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_QR_CODE)); Preference linkDevicePreference = this.findPreference(PREFERENCE_CATEGORY_LINK_DEVICE); // Hide if this is a slave device linkDevicePreference.setVisible(isMasterDevice); - linkDevicePreference.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_LINK_DEVICE)); + linkDevicePreference.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_LINK_DEVICE)); Preference seedPreference = this.findPreference(PREFERENCE_CATEGORY_SEED); // Hide if this is a slave device seedPreference.setVisible(isMasterDevice); - seedPreference.setOnPreferenceClickListener(new CategoryClickListener((PREFERENCE_CATEGORY_SEED))); + seedPreference.setOnPreferenceClickListener(new CategoryClickListener(getContext(), (PREFERENCE_CATEGORY_SEED))); if (VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { tintIcons(getActivity()); @@ -289,10 +293,12 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA this.findPreference(PREFERENCE_CATEGORY_SEED).setIcon(seed); } - private class CategoryClickListener implements Preference.OnPreferenceClickListener { + private class CategoryClickListener implements Preference.OnPreferenceClickListener, DeviceLinkingDialogDelegate { private String category; + private Context context; - CategoryClickListener(String category) { + CategoryClickListener(Context context,String category) { + this.context = context; this.category = category; } @@ -345,7 +351,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA QRCodeDialog.INSTANCE.show(getContext()); break; case PREFERENCE_CATEGORY_LINK_DEVICE: - DeviceLinkingDialog.Companion.show(getContext(), DeviceLinkingView.Mode.Master, null); + DeviceLinkingDialog.Companion.show(getContext(), DeviceLinkingView.Mode.Master, this); break; case PREFERENCE_CATEGORY_SEED: Analytics.Companion.getShared().track("Seed Modal Shown"); @@ -388,6 +394,12 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA return true; } + + @Override public void sendPairingAuthorizedMessage(@NotNull PairingAuthorisation pairingAuthorisation) { + MultiDeviceUtilitiesKt.signAndSendPairingAuthorisationMessage(context, pairingAuthorisation); + } + @Override public void handleDeviceLinkAuthorized(@NotNull PairingAuthorisation pairingAuthorisation) {} + @Override public void handleDeviceLinkingDialogDismissed() {} } private class ProfileClickListener implements Preference.OnPreferenceClickListener { diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index d51d1b3ce9..df31ce5517 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; +import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import java.util.List; diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 5ba7f8efbe..1160cf963f 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -2203,7 +2203,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity boolean shouldEnableInput = isFriendsWithAnyLinkedDevices || !hasPendingFriendRequest; updateToggleButtonState(); inputPanel.setEnabled(shouldEnableInput); - int hintID = shouldEnableInput ? R.string.activity_conversation_pending_friend_request_hint : R.string.activity_conversation_default_hint; + int hintID = shouldEnableInput ? R.string.activity_conversation_default_hint : R.string.activity_conversation_pending_friend_request_hint; inputPanel.setHint(getResources().getString(hintID)); if (!shouldEnableInput) { inputPanel.composeText.requestFocus(); diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index e7b8129e12..304f72b711 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -6,6 +6,7 @@ import nl.komponents.kovenant.deferred import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map import org.thoughtcrime.securesms.ApplicationContext +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.logging.Log @@ -18,6 +19,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus +import org.whispersystems.signalservice.loki.utilities.retryIfNeeded fun getAllDeviceFriendRequestStatus(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI): Promise, Exception> { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) @@ -102,4 +104,22 @@ fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey Log.d("Loki", "Failed to send authorisation message to: $contactHexEncodedPublicKey.") Promise.ofFail(e) } +} + +fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisation: PairingAuthorisation) { + val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() + val signedPairingAuthorisation = pairingAuthorisation.sign(PairingAuthorisation.Type.GRANT, userPrivateKey) + if (signedPairingAuthorisation == null || signedPairingAuthorisation.type != PairingAuthorisation.Type.GRANT) { + Log.d("Loki", "Failed to sign pairing authorization.") + return + } + retryIfNeeded(8) { + sendPairingAuthorisationMessage(context, pairingAuthorisation.secondaryDevicePublicKey, signedPairingAuthorisation).get() + }.fail { + Log.d("Loki", "Failed to send pairing authorization message to ${pairingAuthorisation.secondaryDevicePublicKey}.") + } + DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation) + LokiStorageAPI.shared.updateUserDeviceMappings().fail { exception -> + Log.w("Loki", "Failed to update device mapping") + } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt index a98b6c5a46..1eaea1308d 100644 --- a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt @@ -225,22 +225,6 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate { resetForRegistration() } - override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { - val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).privateKey.serialize() - val signedPairingAuthorisation = pairingAuthorisation.sign(PairingAuthorisation.Type.GRANT, userPrivateKey) - if (signedPairingAuthorisation == null || signedPairingAuthorisation.type != PairingAuthorisation.Type.GRANT) { - Log.d("Loki", "Failed to sign pairing authorization.") - return - } - retryIfNeeded(8) { - sendPairingAuthorisationMessage(this, pairingAuthorisation.secondaryDevicePublicKey, signedPairingAuthorisation).get() - }.fail { - Log.d("Loki", "Failed to send pairing authorization message to ${pairingAuthorisation.secondaryDevicePublicKey}.") - } - DatabaseFactory.getLokiAPIDatabase(this).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation) - LokiStorageAPI.shared.updateUserDeviceMappings() - } - private fun resetForRegistration() { IdentityKeyUtil.delete(this, IdentityKeyUtil.lokiSeedKey) TextSecurePreferences.removeLocalRegistrationId(this) diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index f72f614fd6..9d403bad9a 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -76,7 +76,9 @@ public class MarkReadReceiver extends BroadcastReceiver { for (MarkedMessageInfo messageInfo : markedReadMessages) { scheduleDeletion(context, messageInfo.getExpirationInfo()); - syncMessageIds.add(messageInfo.getSyncMessageId()); + if (!messageInfo.getSyncMessageId().getAddress().isGroup()) { + syncMessageIds.add(messageInfo.getSyncMessageId()); + } } ApplicationContext.getInstance(context) From 24ae0c640bfb4d2b563576605f6306ba9bf4012a Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 24 Oct 2019 12:17:58 +1100 Subject: [PATCH 05/64] Handle friend requests correctly. --- .../conversation/ConversationActivity.java | 2 + .../securesms/jobs/PushDecryptJob.java | 97 +++++++++---------- .../loki/LokiPreKeyBundleDatabase.kt | 12 ++- .../securesms/loki/MultiDeviceUtilities.kt | 22 +++-- 4 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 1160cf963f..1c5291450f 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -3015,6 +3015,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS); lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + Util.runOnMain(this::updateInputPanel); } catch (Exception e) { Log.d("Loki", "Failed to send background message to: " + contactID + "."); } @@ -3030,6 +3031,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.NONE); String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString(); DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(contactID); + updateInputPanel(); } public boolean isFriendsWithAnyLinkedDevice() { diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index d475731b36..209d934aef 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -144,6 +144,7 @@ import javax.inject.Inject; import kotlin.Unit; import network.loki.messenger.R; +import nl.komponents.kovenant.Promise; public class PushDecryptJob extends BaseJob implements InjectableType { @@ -1097,57 +1098,55 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { if (!envelope.isFriendRequest()) { return; } // This handles the case where another user sends us a regular message without authorisation - MultiDeviceUtilitiesKt.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context).success(becomeFriends -> { - if (becomeFriends) { - // Become friends AND update the message they sent - becomeFriendsWithContact(content.getSender()); - // Send them an accept message back + boolean shouldBecomeFriends = MultiDeviceUtilitiesKt.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context); + if (shouldBecomeFriends) { + // Become friends AND update the message they sent + becomeFriendsWithContact(content.getSender()); + // Send them an accept message back + sendBackgroundMessage(content.getSender()); + } else { + // Do regular friend request logic checks + Recipient originalRecipient = getMessageDestination(content, message); + Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); + LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); + SmsDatabase smsMessageDatabase = DatabaseFactory.getSmsDatabase(context); + MmsDatabase mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context); + LokiMessageDatabase lokiMessageDatabase= DatabaseFactory.getLokiMessageDatabase(context); + + long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient); + long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient); + LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID); + int messageCount = smsMessageDatabase.getMessageCountForThread(primaryDeviceThreadID); + 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 + long messageID = smsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, messageCount - 2); // The message before the one that was just received + lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + // Accept the friend request sendBackgroundMessage(content.getSender()); - } else { - // Do regular friend request logic checks - Recipient contactID = getMessageDestination(content, message); - LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); - long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(contactID); - LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID); - SmsDatabase smsMessageDatabase = DatabaseFactory.getSmsDatabase(context); - MmsDatabase mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context); - LokiMessageDatabase lokiMessageDatabase= DatabaseFactory.getLokiMessageDatabase(context); - int messageCount = smsMessageDatabase.getMessageCountForThread(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); - long messageID = smsMessageDatabase.getIDForMessageAtIndex(threadID, messageCount - 2); // The message before the one that was just received - // TODO: MMS - lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); - // Accept the friend request - sendBackgroundMessage(content.getSender()); - } 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); - long messageID = smsMessageDatabase.getIDForMessageAtIndex(threadID, messageCount - 1); // The message that was just received - if (messageID != -1) { - lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING); - } else { - // TODO: The code below is ugly due to Java limitations - lokiMessageDatabase.setFriendRequestStatus(mmsMessageDatabase.getIDForMessageAtIndex(threadID, 0), LokiMessageFriendRequestStatus.REQUEST_PENDING); - } - } + } 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 + long smsMessageID = smsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, messageCount - 1); // The message that was just received + long messageID = smsMessageID != -1 ? smsMessageID : mmsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, 0); + lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING); } - return Unit.INSTANCE; - }); + } } private void sendBackgroundMessage(String contactHexEncodedPublicKey) { diff --git a/src/org/thoughtcrime/securesms/loki/LokiPreKeyBundleDatabase.kt b/src/org/thoughtcrime/securesms/loki/LokiPreKeyBundleDatabase.kt index becd442e51..978abd6e91 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPreKeyBundleDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPreKeyBundleDatabase.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki import android.content.ContentValues import android.content.Context +import net.sqlcipher.Cursor import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.PreKeyUtil import org.thoughtcrime.securesms.database.Database @@ -92,7 +93,14 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) : fun hasPreKeyBundle(hexEncodedPublicKey: String): Boolean { val database = databaseHelper.readableDatabase - val cursor = database.query(tableName, null, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ), null, null, null) - return cursor != null && cursor.count > 0 + var cursor: Cursor? = null + return try { + cursor = database.query(tableName, null, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ), null, null, null) + cursor != null && cursor.count > 0 + } catch (e: Exception) { + false + } finally { + cursor?.close() + } } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 304f72b711..56f534b27b 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.thoughtcrime.securesms.util.concurrent.SettableFuture import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage @@ -51,33 +52,38 @@ fun getAllDevicePublicKeys(context: Context, hexEncodedPublicKey: String, storag } } -fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Promise { +fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Boolean { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val storageAPI = LokiStorageAPI.shared - val deferred = deferred() + val future = SettableFuture() storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey -> if (primaryDevicePublicKey == null) { - deferred.resolve(false) + future.set(false) return@success } val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) if (primaryDevicePublicKey == userHexEncodedPublicKey) { storageAPI.getSecondaryDevicePublicKeys(userHexEncodedPublicKey).success { secondaryDevices -> - deferred.resolve(secondaryDevices.contains(publicKey)) + future.set(secondaryDevices.contains(publicKey)) }.fail { - deferred.resolve(false) + future.set(false) } return@success } val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) if (threadID < 0) { - deferred.resolve(false) + future.set(false) return@success } - deferred.resolve(lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) + future.set(lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) + } + + return try { + future.get() + } catch (e: Exception) { + false } - return deferred.promise } fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise { From 94a9303d7af497ad49c02962f934c738244a7513 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 24 Oct 2019 13:35:14 +1100 Subject: [PATCH 06/64] Fix multi-device ui --- res/layout/profile_preference_view.xml | 8 ++++++++ res/values/strings.xml | 1 + .../thoughtcrime/securesms/ApplicationContext.java | 7 ++++--- .../securesms/ApplicationPreferencesActivity.java | 4 +--- .../securesms/ConversationListActivity.java | 10 +++++++++- .../preferences/widgets/ProfilePreference.java | 14 ++++++++++---- .../securesms/util/TextSecurePreferences.java | 2 +- 7 files changed, 34 insertions(+), 12 deletions(-) diff --git a/res/layout/profile_preference_view.xml b/res/layout/profile_preference_view.xml index 6df515f480..620b984f88 100644 --- a/res/layout/profile_preference_view.xml +++ b/res/layout/profile_preference_view.xml @@ -30,6 +30,14 @@ android:layout_height="wrap_content" tools:text="+14151231234"/> + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 51065f8cc1..eb564c207e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1572,6 +1572,7 @@ Looks like you don\'t have any conversations yet. Get started by messaging a friend. + This is a secondary device Copied to clipboard Share Public Key Show QR Code diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index a17d5327c4..39b6b7bf38 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -190,8 +190,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc lokiPublicChatManager = new LokiPublicChatManager(this); // Loki - Update device mappings - setUpStorageAPIIfNeeded(); - if (IdentityKeyUtil.hasIdentityKey(this)) { + if (setUpStorageAPIIfNeeded()) { LokiStorageAPI.Companion.getShared().updateUserDeviceMappings(); } } @@ -455,14 +454,16 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc } // region Loki - public void setUpStorageAPIIfNeeded() { + public boolean setUpStorageAPIIfNeeded() { String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this); if (userHexEncodedPublicKey != null && IdentityKeyUtil.hasIdentityKey(this)) { boolean isDebugMode = BuildConfig.DEBUG; byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); LokiAPIDatabaseProtocol database = DatabaseFactory.getLokiAPIDatabase(this); LokiStorageAPI.Companion.configure(isDebugMode, userHexEncodedPublicKey, userPrivateKey, database); + return true; } + return false; } public void setUpP2PAPI() { diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index b95405a8a1..279db687a3 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -164,9 +164,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA boolean isMasterDevice = (masterHexEncodedPublicKey == null); Preference profilePreference = this.findPreference(PREFERENCE_CATEGORY_PROFILE); - // Hide if this is a slave device - profilePreference.setVisible(isMasterDevice); - profilePreference.setOnPreferenceClickListener(new ProfileClickListener()); + if (isMasterDevice) { profilePreference.setOnPreferenceClickListener(new ProfileClickListener()); } /* this.findPreference(PREFERENCE_CATEGORY_SMS_MMS) .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS)); diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index df31ce5517..ce99f0a3e2 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -41,6 +41,7 @@ import android.widget.Toast; import org.thoughtcrime.securesms.components.RatingManager; import org.thoughtcrime.securesms.components.SearchToolbar; import org.thoughtcrime.securesms.conversation.ConversationActivity; +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; @@ -194,6 +195,13 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit outline.setOval(0, 0, view.getWidth(), view.getHeight()); } }); + + // Display the correct identicon if we're a secondary device + String currentUser = TextSecurePreferences.getLocalNumber(this); + String recipientAddress = recipient.getAddress().serialize(); + String primaryAddress = TextSecurePreferences.getMasterHexEncodedPublicKey(this); + String profileAddress = (recipientAddress.equalsIgnoreCase(currentUser) && primaryAddress != null) ? primaryAddress : recipientAddress; + profilePictureImageView.setClipToOutline(true); profilePictureImageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @@ -203,7 +211,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit int height = profilePictureImageView.getHeight(); if (width == 0 || height == 0) return true; profilePictureImageView.getViewTreeObserver().removeOnPreDrawListener(this); - JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, recipient.getAddress().serialize().toLowerCase()); + JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, profileAddress.toLowerCase()); profilePictureImageView.setImageDrawable(identicon); return true; } diff --git a/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java b/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java index 0d464dd325..63b82c4bf7 100644 --- a/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java +++ b/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java @@ -30,6 +30,7 @@ public class ProfilePreference extends Preference { private ImageView avatarView; private TextView profileNameView; private TextView profileNumberView; + private TextView profileTagView; @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -64,6 +65,7 @@ public class ProfilePreference extends Preference { avatarView = (ImageView)viewHolder.findViewById(R.id.avatar); profileNameView = (TextView)viewHolder.findViewById(R.id.profile_name); profileNumberView = (TextView)viewHolder.findViewById(R.id.number); + profileTagView = (TextView)viewHolder.findViewById(R.id.tag); refresh(); } @@ -72,13 +74,15 @@ public class ProfilePreference extends Preference { if (profileNumberView == null) return; String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext()); - final Address localAddress = Address.fromSerialized(userHexEncodedPublicKey); + String primaryDevicePublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(getContext()); + String publicKey = primaryDevicePublicKey != null ? primaryDevicePublicKey : userHexEncodedPublicKey; + final Address localAddress = Address.fromSerialized(publicKey); final String profileName = TextSecurePreferences.getProfileName(getContext()); Context context = getContext(); containerView.setOnLongClickListener(v -> { ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Public Key", userHexEncodedPublicKey); + ClipData clip = ClipData.newPlainText("Public Key", publicKey); clipboard.setPrimaryClip(clip); Toast.makeText(context, R.string.activity_settings_public_key_copied_message, Toast.LENGTH_SHORT).show(); return true; @@ -100,7 +104,7 @@ public class ProfilePreference extends Preference { int height = avatarView.getHeight(); if (width == 0 || height == 0) return true; avatarView.getViewTreeObserver().removeOnPreDrawListener(this); - JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, userHexEncodedPublicKey.toLowerCase()); + JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, publicKey.toLowerCase()); avatarView.setImageDrawable(identicon); return true; } @@ -120,7 +124,9 @@ public class ProfilePreference extends Preference { } profileNameView.setVisibility(TextUtils.isEmpty(profileName) ? View.GONE : View.VISIBLE); - profileNumberView.setText(localAddress.toPhoneString()); + + profileTagView.setVisibility(primaryDevicePublicKey == null ? View.GONE : View.VISIBLE); + profileTagView.setText(R.string.activity_settings_secondary_device_tag); } } diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index ed87f3dcae..46499f05f0 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -1183,7 +1183,7 @@ public class TextSecurePreferences { } public static void setMasterHexEncodedPublicKey(Context context, String masterHexEncodedPublicKey) { - setStringPreference(context, "master_hex_encoded_publicKey", masterHexEncodedPublicKey); + setStringPreference(context, "master_hex_encoded_public_key", masterHexEncodedPublicKey); } // endregion } From bb0ac32540b5692f7a208fe6db7a2fd40c07b0a0 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 24 Oct 2019 14:17:12 +1100 Subject: [PATCH 07/64] Correctly reset prekeys for linking. --- .../thoughtcrime/securesms/loki/LokiPreKeyBundleDatabase.kt | 5 +++++ src/org/thoughtcrime/securesms/loki/SeedActivity.kt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPreKeyBundleDatabase.kt b/src/org/thoughtcrime/securesms/loki/LokiPreKeyBundleDatabase.kt index 978abd6e91..6eee3387b1 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPreKeyBundleDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPreKeyBundleDatabase.kt @@ -36,6 +36,11 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) : "$signedPreKeySignature TEXT," + "$identityKey TEXT NOT NULL," + "$deviceID INTEGER," + "$registrationID INTEGER" + ");" } + fun resetAllPreKeyBundleInfo() { + TextSecurePreferences.removeLocalRegistrationId(context) + TextSecurePreferences.setSignedPreKeyRegistered(context, false) + } + fun generatePreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { diff --git a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt index 1eaea1308d..8a537e1de6 100644 --- a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt @@ -227,7 +227,7 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate { private fun resetForRegistration() { IdentityKeyUtil.delete(this, IdentityKeyUtil.lokiSeedKey) - TextSecurePreferences.removeLocalRegistrationId(this) + DatabaseFactory.getLokiPreKeyBundleDatabase(this).resetAllPreKeyBundleInfo() TextSecurePreferences.removeLocalNumber(this) TextSecurePreferences.setHasSeenWelcomeScreen(this, false) TextSecurePreferences.setPromptedPushRegistration(this, false) From 98cfd93b97f291eff16c674e9a40ac22c9b4630f Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 24 Oct 2019 14:26:54 +1100 Subject: [PATCH 08/64] Enable note to self. Fix note to self crashing. --- .../securesms/components/AvatarImageView.java | 10 ++++++++-- .../conversation/ConversationActivity.java | 3 ++- .../thoughtcrime/securesms/jobs/PushDecryptJob.java | 13 ++++++++++++- .../securesms/loki/MultiDeviceUtilities.kt | 9 +++++++++ .../securesms/loki/NewConversationActivity.kt | 5 +++-- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/org/thoughtcrime/securesms/components/AvatarImageView.java b/src/org/thoughtcrime/securesms/components/AvatarImageView.java index 9158ab0689..6eae6b42aa 100644 --- a/src/org/thoughtcrime/securesms/components/AvatarImageView.java +++ b/src/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -107,8 +107,9 @@ public class AvatarImageView extends AppCompatImageView { if (w == 0 || h == 0 || recipient == null) { return; } Drawable image; + Context context = this.getContext(); if (recipient.isGroupRecipient()) { - Context context = this.getContext(); + String name = Optional.fromNullable(recipient.getName()).or(Optional.fromNullable(TextSecurePreferences.getProfileName(context))).or(""); MaterialColor fallbackColor = recipient.getColor(); @@ -119,7 +120,12 @@ public class AvatarImageView extends AppCompatImageView { image = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(context, fallbackColor.toAvatarColor(context)); } else { - image = new JazzIdenticonDrawable(w, h, recipient.getAddress().serialize().toLowerCase()); + // Default to primary device image + String ourPublicKey = TextSecurePreferences.getLocalNumber(context); + String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); + String recipientAddress = recipient.getAddress().serialize(); + String profileAddress = (ourPrimaryDevice != null && ourPublicKey.equals(recipientAddress)) ? ourPrimaryDevice : recipientAddress; + image = new JazzIdenticonDrawable(w, h, profileAddress.toLowerCase()); } setImageDrawable(image); } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 1c5291450f..f09cf1d7aa 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -722,7 +722,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (isSingleConversation() && getRecipient().getContactUri() == null) { inflater.inflate(R.menu.conversation_add_to_contacts, menu); } - */ + if (recipient != null && recipient.isLocalNumber()) { if (isSecureText) menu.findItem(R.id.menu_call_secure).setVisible(false); @@ -734,6 +734,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity muteItem.setVisible(false); } } + */ searchViewItem = menu.findItem(R.id.menu_search); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 209d934aef..f5604b331d 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -305,6 +305,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Optional rawSenderDisplayName = content.senderDisplayName; if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) { setDisplayName(envelope.getSource(), rawSenderDisplayName.get()); + + // If we got a name from our primary device then we also set that + String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); + if (ourPrimaryDevice != null && envelope.getSource().equals(ourPrimaryDevice)) { + TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get()); + } } // TODO: Deleting the display name @@ -1526,7 +1532,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Get the primary device LokiStorageAPI.shared.getPrimaryDevicePublicKey(contentSender).success(primaryDevice -> { - String publicKey = primaryDevice == null ? contentSender : primaryDevice; + String publicKey = (primaryDevice != null) ? primaryDevice : contentSender; + // If our the public key matches our primary device then we need to forward the message to ourselves (Note to self) + String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); + if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) { + publicKey = TextSecurePreferences.getLocalNumber(context); + } device.set(publicKey); return Unit.INSTANCE; }).fail(exception -> { diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 56f534b27b..2410f49d76 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -58,9 +58,11 @@ fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Conte val future = SettableFuture() storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey -> if (primaryDevicePublicKey == null) { + // If the public key doesn't have any other devices then go through regular friend request logic future.set(false) return@success } + // If we are the primary device and the public key is our secondary device then we should become friends val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) if (primaryDevicePublicKey == userHexEncodedPublicKey) { storageAPI.getSecondaryDevicePublicKeys(userHexEncodedPublicKey).success { secondaryDevices -> @@ -70,6 +72,13 @@ fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Conte } return@success } + // If we share the same primary device then we should become friends + val ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) + if (ourPrimaryDevice != null && ourPrimaryDevice == primaryDevicePublicKey) { + future.set(true) + return@success + } + // If we are friends with the primary device then we should become friends val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) if (threadID < 0) { diff --git a/src/org/thoughtcrime/securesms/loki/NewConversationActivity.kt b/src/org/thoughtcrime/securesms/loki/NewConversationActivity.kt index bacbdaa5f4..16be98fd73 100644 --- a/src/org/thoughtcrime/securesms/loki/NewConversationActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/NewConversationActivity.kt @@ -68,8 +68,9 @@ class NewConversationActivity : PassphraseRequiredActionBarActivity(), ScanListe fun startNewConversationIfPossible(hexEncodedPublicKey: String) { if (!PublicKeyValidation.isValid(hexEncodedPublicKey)) { return Toast.makeText(this, R.string.fragment_new_conversation_invalid_public_key_message, Toast.LENGTH_SHORT).show() } val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) - if (hexEncodedPublicKey == userHexEncodedPublicKey) { return Toast.makeText(this, R.string.fragment_new_conversation_note_to_self_not_supported_message, Toast.LENGTH_SHORT).show() } - val contact = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), true) + // If we try to contact our master then redirect to note to self + val contactPublicKey = if (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == hexEncodedPublicKey) userHexEncodedPublicKey else hexEncodedPublicKey + val contact = Recipient.from(this, Address.fromSerialized(contactPublicKey), true) val intent = Intent(this, ConversationActivity::class.java) intent.putExtra(ConversationActivity.ADDRESS_EXTRA, contact.address) intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA)) From efad14fcdc436b8344716bb394fbc1301c95e50c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 24 Oct 2019 17:16:53 +1100 Subject: [PATCH 09/64] Message syncing. --- .../securesms/ApplicationContext.java | 24 +++++ .../SignalCommunicationModule.java | 8 +- .../securesms/jobmanager/Data.java | 22 ++++- .../securesms/jobs/JobManagerFactories.java | 2 + .../securesms/jobs/PushDecryptJob.java | 80 ++++++++++------- .../securesms/jobs/PushMediaSendJob.java | 49 ++++++---- .../securesms/jobs/PushTextSendJob.java | 37 +++++--- .../securesms/loki/LokiMessageSyncEvent.kt | 22 +++++ .../securesms/loki/MultiDeviceUtilities.kt | 65 ++++++++++++++ .../securesms/loki/PushMessageSyncSendJob.kt | 89 +++++++++++++++++++ ...r.java => MessageSenderEventListener.java} | 16 ++-- .../securesms/sms/MessageSender.java | 88 ++++++++++++------ 12 files changed, 403 insertions(+), 99 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/loki/LokiMessageSyncEvent.kt create mode 100644 src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt rename src/org/thoughtcrime/securesms/push/{SecurityEventListener.java => MessageSenderEventListener.java} (50%) diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 39b6b7bf38..70c76ce3ec 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -20,13 +20,17 @@ import android.annotation.SuppressLint; import android.arch.lifecycle.DefaultLifecycleObserver; import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.ProcessLifecycleOwner; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.database.ContentObserver; import android.os.AsyncTask; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.multidex.MultiDexApplication; +import android.support.v4.content.LocalBroadcastManager; import com.crashlytics.android.Crashlytics; import com.google.android.gms.security.ProviderInstaller; @@ -62,6 +66,7 @@ import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.loki.BackgroundPollWorker; import org.thoughtcrime.securesms.loki.LokiAPIDatabase; +import org.thoughtcrime.securesms.loki.LokiMessageSyncEvent; import org.thoughtcrime.securesms.loki.LokiPublicChatManager; import org.thoughtcrime.securesms.loki.LokiRSSFeedPoller; import org.thoughtcrime.securesms.loki.LokiUserDatabase; @@ -77,6 +82,7 @@ import org.thoughtcrime.securesms.service.LocalBackupListener; import org.thoughtcrime.securesms.service.RotateSenderCertificateListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; +import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper; import org.webrtc.PeerConnectionFactory; @@ -141,6 +147,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc private LokiPublicChatAPI lokiPublicChatAPI = null; public SignalCommunicationModule communicationModule; public MixpanelAPI mixpanel; + private BroadcastReceiver syncMessageEventReceiver; private volatile boolean isAppVisible; @@ -193,6 +200,22 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc if (setUpStorageAPIIfNeeded()) { LokiStorageAPI.Companion.getShared().updateUserDeviceMappings(); } + + // Loki - Event listener + syncMessageEventReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Send the sync message to our devices + long messageID = intent.getLongExtra(LokiMessageSyncEvent.MESSAGE_ID, -1); + long timestamp = intent.getLongExtra(LokiMessageSyncEvent.TIMESTAMP, -1); + byte[] message = intent.getByteArrayExtra(LokiMessageSyncEvent.SYNC_MESSAGE); + int ttl = intent.getIntExtra(LokiMessageSyncEvent.TTL, -1); + if (messageID > 0 && timestamp > 0 && message != null && ttl > 0) { + MessageSender.sendSyncMessageToOurDevices(context, messageID, timestamp, message, ttl); + } + } + }; + LocalBroadcastManager.getInstance(this).registerReceiver(syncMessageEventReceiver, new IntentFilter(LokiMessageSyncEvent.MESSAGE_SYNC_EVENT)); } @Override @@ -220,6 +243,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc @Override public void onTerminate() { stopKovenant(); + LocalBroadcastManager.getInstance(this).unregisterReceiver(syncMessageEventReceiver); super.onTerminate(); } diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java index 747c7bfbc1..833c7f895f 100644 --- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java @@ -47,8 +47,9 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob; import org.thoughtcrime.securesms.jobs.TypingSendJob; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; -import org.thoughtcrime.securesms.push.SecurityEventListener; +import org.thoughtcrime.securesms.push.MessageSenderEventListener; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.service.WebRtcCallService; @@ -112,7 +113,8 @@ import network.loki.messenger.BuildConfig; StickerPackDownloadJob.class, MultiDeviceStickerPackOperationJob.class, MultiDeviceStickerPackSyncJob.class, - LinkPreviewRepository.class}) + LinkPreviewRepository.class, + PushMessageSyncSendJob.class}) public class SignalCommunicationModule { @@ -151,7 +153,7 @@ public class SignalCommunicationModule { TextSecurePreferences.isMultiDevice(context), Optional.fromNullable(IncomingMessageObserver.getPipe()), Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()), - Optional.of(new SecurityEventListener(context)), + Optional.of(new MessageSenderEventListener(context)), TextSecurePreferences.getLocalNumber(context), DatabaseFactory.getLokiAPIDatabase(context), DatabaseFactory.getLokiThreadDatabase(context), diff --git a/src/org/thoughtcrime/securesms/jobmanager/Data.java b/src/org/thoughtcrime/securesms/jobmanager/Data.java index f7f77b0aa4..a55069ec38 100644 --- a/src/org/thoughtcrime/securesms/jobmanager/Data.java +++ b/src/org/thoughtcrime/securesms/jobmanager/Data.java @@ -24,6 +24,7 @@ public class Data { @JsonProperty private final Map doubleArrays; @JsonProperty private final Map booleans; @JsonProperty private final Map booleanArrays; + @JsonProperty private final Map byteArrays; public Data(@JsonProperty("strings") @NonNull Map strings, @JsonProperty("stringArrays") @NonNull Map stringArrays, @@ -36,7 +37,8 @@ public class Data { @JsonProperty("doubles") @NonNull Map doubles, @JsonProperty("doubleArrays") @NonNull Map doubleArrays, @JsonProperty("booleans") @NonNull Map booleans, - @JsonProperty("booleanArrays") @NonNull Map booleanArrays) + @JsonProperty("booleanArrays") @NonNull Map booleanArrays, + @JsonProperty("byteArrays") @NonNull Map byteArrays) { this.strings = strings; this.stringArrays = stringArrays; @@ -50,6 +52,7 @@ public class Data { this.doubleArrays = doubleArrays; this.booleans = booleans; this.booleanArrays = booleanArrays; + this.byteArrays = byteArrays; } public boolean hasString(@NonNull String key) { @@ -201,6 +204,14 @@ public class Data { return booleanArrays.get(key); } + public boolean hasByteArray(@NonNull String key) { + return byteArrays.containsKey(key); + } + + public byte[] getByteArray(@NonNull String key) { + throwIfAbsent(byteArrays, key); + return byteArrays.get(key); + } private void throwIfAbsent(@NonNull Map map, @NonNull String key) { if (!map.containsKey(key)) { @@ -223,6 +234,7 @@ public class Data { private final Map doubleArrays = new HashMap<>(); private final Map booleans = new HashMap<>(); private final Map booleanArrays = new HashMap<>(); + private final Map byteArrays = new HashMap<>(); public Builder putString(@NonNull String key, @Nullable String value) { strings.put(key, value); @@ -284,6 +296,11 @@ public class Data { return this; } + public Builder putByteArray(@NonNull String key, @NonNull byte[] value) { + byteArrays.put(key, value); + return this; + } + public Data build() { return new Data(strings, stringArrays, @@ -296,7 +313,8 @@ public class Data { doubles, doubleArrays, booleans, - booleanArrays); + booleanArrays, + byteArrays); } } diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index af769eda20..7309716f9c 100644 --- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; +import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; import java.util.Arrays; import java.util.HashMap; @@ -70,6 +71,7 @@ public final class JobManagerFactories { put(TrimThreadJob.KEY, new TrimThreadJob.Factory()); put(TypingSendJob.KEY, new TypingSendJob.Factory()); put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); + put(PushMessageSyncSendJob.KEY, new PushMessageSyncSendJob.Factory()); }}; } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index f5604b331d..bccaad08a5 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -821,8 +821,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - Recipient recipient = getSyncMessageDestination(message); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + Recipient recipient = getSyncMessagePrimaryDestination(message); OutgoingExpirationUpdateMessage expirationUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, message.getTimestamp(), @@ -842,7 +842,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { throws MmsException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - Recipient recipients = getSyncMessageDestination(message); + Recipient recipients = getSyncMessagePrimaryDestination(message); Optional quote = getValidatedQuote(message.getMessage().getQuote()); Optional sticker = getStickerAttachment(message.getMessage().getSticker()); Optional> sharedContacts = getContacts(message.getMessage().getSharedContacts()); @@ -1172,7 +1172,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { throws MmsException { - Recipient recipient = getSyncMessageDestination(message); + Recipient recipient = getSyncMessagePrimaryDestination(message); String body = message.getMessage().getBody().or(""); long expiresInMillis = message.getMessage().getExpiresInSeconds() * 1000L; @@ -1523,35 +1523,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } - private Recipient getMessagePrimaryDestination(SignalServiceContent content, SignalServiceDataMessage message) { - if (message.getGroupInfo().isPresent()) { - return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)), false); + private Recipient getSyncMessagePrimaryDestination(SentTranscriptMessage message) { + if (message.getMessage().getGroupInfo().isPresent()) { + return getSyncMessageDestination(message); } else { - SettableFuture device = new SettableFuture<>(); - String contentSender = content.getSender(); - - // Get the primary device - LokiStorageAPI.shared.getPrimaryDevicePublicKey(contentSender).success(primaryDevice -> { - String publicKey = (primaryDevice != null) ? primaryDevice : contentSender; - // If our the public key matches our primary device then we need to forward the message to ourselves (Note to self) - String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); - if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) { - publicKey = TextSecurePreferences.getLocalNumber(context); - } - device.set(publicKey); - return Unit.INSTANCE; - }).fail(exception -> { - device.set(contentSender); - return Unit.INSTANCE; - }); - - try { - String primarySender = device.get(); - return Recipient.from(context, Address.fromSerialized(primarySender), false); - } catch (Exception e) { - Log.d("Loki", "Failed to get primary device public key for message. " + e.getMessage()); - return Recipient.from(context, Address.fromSerialized(content.getSender()), false); - } + return getPrimaryDeviceRecipient(message.getDestination().get()); } } @@ -1563,6 +1539,41 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } + private Recipient getMessagePrimaryDestination(SignalServiceContent content, SignalServiceDataMessage message) { + if (message.getGroupInfo().isPresent()) { + return getMessageDestination(content, message); + } else { + return getPrimaryDeviceRecipient(content.getSender()); + } + } + + private Recipient getPrimaryDeviceRecipient(String recipient) { + SettableFuture device = new SettableFuture<>(); + + // Get the primary device + LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient).success(primaryDevice -> { + String publicKey = (primaryDevice != null) ? primaryDevice : recipient; + // If our the public key matches our primary device then we need to forward the message to ourselves (Note to self) + String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); + if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) { + publicKey = TextSecurePreferences.getLocalNumber(context); + } + device.set(publicKey); + return Unit.INSTANCE; + }).fail(exception -> { + device.set(recipient); + return Unit.INSTANCE; + }); + + try { + String primarySender = device.get(); + return Recipient.from(context, Address.fromSerialized(primarySender), false); + } catch (Exception e) { + Log.d("Loki", "Failed to get primary device public key for message. " + e.getMessage()); + return Recipient.from(context, Address.fromSerialized(recipient), false); + } + } + private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient conversationRecipient, @NonNull String sender, int device) { Recipient author = Recipient.from(context, Address.fromSerialized(sender), false); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(conversationRecipient); @@ -1609,6 +1620,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } else if (content.getCallMessage().isPresent() || content.getTypingMessage().isPresent()) { return sender.isBlocked(); + } else if (content.getSyncMessage().isPresent()) { + // We should ignore a sync message if the sender is not one of our devices + boolean isOurDevice = MultiDeviceUtilitiesKt.isOneOfOurDevices(context, sender.getAddress()); + if (!isOurDevice) { Log.w(TAG, "Got a sync message from a device that is not ours!."); } + return !isOurDevice; } return false; diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 263dedc322..6e50e3446d 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; @@ -42,6 +43,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; +import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import java.io.FileNotFoundException; import java.io.IOException; @@ -61,6 +63,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { private static final String KEY_DESTINATION = "destination"; private static final String KEY_IS_FRIEND_REQUEST = "is_friend_request"; private static final String KEY_CUSTOM_FR_MESSAGE = "custom_friend_request_message"; + private static final String KEY_SHOULD_SEND_SYNC_MESSAGE = "should_send_sync_message"; @Inject SignalServiceMessageSender messageSender; @@ -71,34 +74,36 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { private Address destination; // Destination to check whether this is another device we're sending to private boolean isFriendRequest; // Whether this is a friend request message private String customFriendRequestMessage; // If this isn't set then we use the message body + private boolean shouldSendSyncMessage; public PushMediaSendJob(long messageId, Address destination) { this(messageId, messageId, destination); } - public PushMediaSendJob(long templateMessageId, long messageId, Address destination) { this(templateMessageId, messageId, destination, false, null); } - public PushMediaSendJob(long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage) { - this(constructParameters(destination), templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage); + public PushMediaSendJob(long templateMessageId, long messageId, Address destination) { this(templateMessageId, messageId, destination, false, null, false); } + public PushMediaSendJob(long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage, boolean shouldSendSyncMessage) { + this(constructParameters(destination), templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage, shouldSendSyncMessage); } - private PushMediaSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage) { + private PushMediaSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage, boolean shouldSendSyncMessage) { super(parameters); this.templateMessageId = templateMessageId; this.messageId = messageId; this.destination = destination; this.isFriendRequest = isFriendRequest; this.customFriendRequestMessage = customFriendRequestMessage; + this.shouldSendSyncMessage = shouldSendSyncMessage; } @WorkerThread - public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination) { - enqueue(context, jobManager, messageId, messageId, destination); + public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination, boolean shouldSendSyncMessage) { + enqueue(context, jobManager, messageId, messageId, destination, shouldSendSyncMessage); } @WorkerThread - public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination) { - enqueue(context, jobManager, templateMessageId, messageId, destination, false, null); + public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination, boolean shouldSendSyncMessage) { + enqueue(context, jobManager, templateMessageId, messageId, destination, false, null, shouldSendSyncMessage); } @WorkerThread - public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination, Boolean isFriendRequest, @Nullable String customFriendRequestMessage) { + public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination, Boolean isFriendRequest, @Nullable String customFriendRequestMessage, boolean shouldSendSyncMessage) { try { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); OutgoingMediaMessage message = database.getOutgoingMessage(messageId); @@ -111,10 +116,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { List attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId(), destination)).toList(); if (attachmentJobs.isEmpty()) { - jobManager.add(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage)); + jobManager.add(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage, shouldSendSyncMessage)); } else { jobManager.startChain(attachmentJobs) - .then(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage)) + .then(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage, shouldSendSyncMessage)) .enqueue(); } @@ -128,10 +133,11 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { @Override public @NonNull Data serialize() { Data.Builder builder = new Data.Builder() - .putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId) - .putLong(KEY_MESSAGE_ID, messageId) - .putString(KEY_DESTINATION, destination.serialize()) - .putBoolean(KEY_IS_FRIEND_REQUEST, isFriendRequest); + .putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId) + .putLong(KEY_MESSAGE_ID, messageId) + .putString(KEY_DESTINATION, destination.serialize()) + .putBoolean(KEY_IS_FRIEND_REQUEST, isFriendRequest) + .putBoolean(KEY_SHOULD_SEND_SYNC_MESSAGE, shouldSendSyncMessage); if (customFriendRequestMessage != null) { builder.putString(KEY_CUSTOM_FR_MESSAGE, customFriendRequestMessage); } return builder.build(); @@ -271,7 +277,15 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { messageSender.sendMessage(messageId, syncMessage, syncAccess); return syncAccess.isPresent(); } else { - return messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage).getSuccess().isUnidentified(); + LokiSyncMessage syncMessage = null; + if (shouldSendSyncMessage) { + // Set the sync message destination the primary device, this way it will show that we sent a message to the primary device and not a secondary device + String primaryDevice = MultiDeviceUtilitiesKt.getPrimaryDevicePublicKey(address.getNumber()); + SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); + // We also need to use the original message id and not -1 + syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); + } + return messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified(); } } catch (UnregisteredUserException e) { warn(TAG, e); @@ -292,8 +306,9 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { long messageID = data.getLong(KEY_MESSAGE_ID); Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION)); boolean isFriendRequest = data.getBoolean(KEY_IS_FRIEND_REQUEST); + boolean shouldSendSyncMessage = data.getBoolean(KEY_SHOULD_SEND_SYNC_MESSAGE); String frMessage = data.hasString(KEY_CUSTOM_FR_MESSAGE) ? data.getString(KEY_CUSTOM_FR_MESSAGE) : null; - return new PushMediaSendJob(parameters, templateMessageID, messageID, destination, isFriendRequest, frMessage); + return new PushMediaSendJob(parameters, templateMessageID, messageID, destination, isFriendRequest, frMessage, shouldSendSyncMessage); } } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index db24ce9fe8..99dcb6e78b 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.ExpiringMessageManager; @@ -29,6 +30,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; +import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import java.io.IOException; @@ -45,6 +47,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { private static final String KEY_DESTINATION = "destination"; private static final String KEY_IS_FRIEND_REQUEST = "is_friend_request"; private static final String KEY_CUSTOM_FR_MESSAGE = "custom_friend_request_message"; + private static final String KEY_SHOULD_SEND_SYNC_MESSAGE = "should_send_sync_message"; @Inject SignalServiceMessageSender messageSender; @@ -55,29 +58,32 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { private Address destination; // Destination to check whether this is another device we're sending to private boolean isFriendRequest; // Whether this is a friend request message private String customFriendRequestMessage; // If this isn't set then we use the message body + private boolean shouldSendSyncMessage; - public PushTextSendJob(long messageId, Address destination) { this(messageId, messageId, destination); } - public PushTextSendJob(long templateMessageId, long messageId, Address destination) { this(templateMessageId, messageId, destination, false, null); } - public PushTextSendJob(long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage) { - this(constructParameters(destination), templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage); + public PushTextSendJob(long messageId, Address destination) { this(messageId, messageId, destination, false); } + public PushTextSendJob(long templateMessageId, long messageId, Address destination, boolean shouldSendSyncMessage) { this(templateMessageId, messageId, destination, false, null, shouldSendSyncMessage); } + public PushTextSendJob(long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage, boolean shouldSendSyncMessage) { + this(constructParameters(destination), templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage, shouldSendSyncMessage); } - private PushTextSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage) { + private PushTextSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage, boolean shouldSendSyncMessage) { super(parameters); this.templateMessageId = templateMessageId; this.messageId = messageId; this.destination = destination; this.isFriendRequest = isFriendRequest; this.customFriendRequestMessage = customFriendRequestMessage; + this.shouldSendSyncMessage = shouldSendSyncMessage; } @Override public @NonNull Data serialize() { Data.Builder builder = new Data.Builder() - .putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId) - .putLong(KEY_MESSAGE_ID, messageId) - .putString(KEY_DESTINATION, destination.serialize()) - .putBoolean(KEY_IS_FRIEND_REQUEST, isFriendRequest); + .putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId) + .putLong(KEY_MESSAGE_ID, messageId) + .putString(KEY_DESTINATION, destination.serialize()) + .putBoolean(KEY_IS_FRIEND_REQUEST, isFriendRequest) + .putBoolean(KEY_SHOULD_SEND_SYNC_MESSAGE, shouldSendSyncMessage); if (customFriendRequestMessage != null) { builder.putString(KEY_CUSTOM_FR_MESSAGE, customFriendRequestMessage); } return builder.build(); @@ -222,7 +228,15 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { messageSender.sendMessage(messageId, syncMessage, syncAccess); return syncAccess.isPresent(); } else { - return messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage).getSuccess().isUnidentified(); + LokiSyncMessage syncMessage = null; + if (shouldSendSyncMessage) { + // Set the sync message destination the primary device, this way it will show that we sent a message to the primary device and not a secondary device + String primaryDevice = MultiDeviceUtilitiesKt.getPrimaryDevicePublicKey(address.getNumber()); + SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); + // We also need to use the original message id and not -1 + syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); + } + return messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified(); } } catch (UnregisteredUserException e) { warn(TAG, "Failure", e); @@ -241,7 +255,8 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION)); boolean isFriendRequest = data.getBoolean(KEY_IS_FRIEND_REQUEST); String frMessage = data.hasString(KEY_CUSTOM_FR_MESSAGE) ? data.getString(KEY_CUSTOM_FR_MESSAGE) : null; - return new PushTextSendJob(parameters, templateMessageID, messageID, destination, isFriendRequest, frMessage); + boolean shouldSendSyncMessage = data.getBoolean(KEY_SHOULD_SEND_SYNC_MESSAGE); + return new PushTextSendJob(parameters, templateMessageID, messageID, destination, isFriendRequest, frMessage, shouldSendSyncMessage); } } } diff --git a/src/org/thoughtcrime/securesms/loki/LokiMessageSyncEvent.kt b/src/org/thoughtcrime/securesms/loki/LokiMessageSyncEvent.kt new file mode 100644 index 0000000000..426154a3cd --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/LokiMessageSyncEvent.kt @@ -0,0 +1,22 @@ +package org.thoughtcrime.securesms.loki + +import android.content.Context +import android.content.Intent +import android.support.v4.content.LocalBroadcastManager + +object LokiMessageSyncEvent { + const val MESSAGE_SYNC_EVENT = "com.loki-network.messenger.MESSAGE_SYNC_EVENT" + const val MESSAGE_ID = "message_id" + const val TIMESTAMP = "timestamp" + const val SYNC_MESSAGE = "sync_message" + const val TTL = "ttl" + + fun broadcastSecurityUpdateEvent(context: Context, messageID: Long, timestamp: Long, message: ByteArray, ttl: Int) { + val intent = Intent(MESSAGE_SYNC_EVENT) + intent.putExtra(MESSAGE_ID, messageID) + intent.putExtra(TIMESTAMP, timestamp) + intent.putExtra(SYNC_MESSAGE, message) + intent.putExtra(TTL, ttl) + LocalBroadcastManager.getInstance(context).sendBroadcast(intent) + } +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 2410f49d76..d1f696ea94 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -137,4 +137,69 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio LokiStorageAPI.shared.updateUserDeviceMappings().fail { exception -> Log.w("Loki", "Failed to update device mapping") } +} + +fun shouldSendSycMessage(context: Context, address: Address): Boolean { + if (address.isGroup || address.isEmail || address.isMmsGroup) { + return false + } + + // Don't send sync messages if it's our address + val publicKey = address.serialize() + if (publicKey == TextSecurePreferences.getLocalNumber(context)) { + return false + } + + val storageAPI = LokiStorageAPI.shared + val future = SettableFuture() + storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey -> + val isOurPrimaryDevice = primaryDevicePublicKey != null && TextSecurePreferences.getMasterHexEncodedPublicKey(context) == publicKey + // Don't send sync message if the primary device is the same as ours + future.set(!isOurPrimaryDevice) + }.fail { + future.set(false) + } + + return try { + future.get() + } catch (e: Exception) { + false + } +} + +fun isOneOfOurDevices(context: Context, address: Address): Boolean { + if (address.isGroup || address.isEmail || address.isMmsGroup) { + return false + } + + val ourPublicKey = TextSecurePreferences.getLocalNumber(context) + val storageAPI = LokiStorageAPI.shared + val future = SettableFuture() + storageAPI.getAllDevicePublicKeys(ourPublicKey).success { + future.set(it.contains(address.serialize())) + }.fail { + future.set(false) + } + + return try { + future.get() + } catch (e: Exception) { + false + } +} + +fun getPrimaryDevicePublicKey(hexEncodedPublicKey: String): String? { + val storageAPI = LokiStorageAPI.shared + val future = SettableFuture() + storageAPI.getPrimaryDevicePublicKey(hexEncodedPublicKey).success { + future.set(it) + }.fail { + future.set(null) + } + + return try { + future.get() + } catch (e: Exception) { + null + } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt new file mode 100644 index 0000000000..9fb7f9d75e --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt @@ -0,0 +1,89 @@ +package org.thoughtcrime.securesms.loki + +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.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) + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(3) + .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 } + messageSender.lokiSendSyncMessage(messageID, SignalServiceAddress(recipient.toPhoneString()), 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 { + 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) + } + } + } +} diff --git a/src/org/thoughtcrime/securesms/push/SecurityEventListener.java b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java similarity index 50% rename from src/org/thoughtcrime/securesms/push/SecurityEventListener.java rename to src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java index f876a6a91b..e8babe29f5 100644 --- a/src/org/thoughtcrime/securesms/push/SecurityEventListener.java +++ b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java @@ -3,20 +3,17 @@ package org.thoughtcrime.securesms.push; import android.content.Context; import org.thoughtcrime.securesms.crypto.SecurityEvent; -import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.loki.LokiMessageSyncEvent; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -public class SecurityEventListener implements SignalServiceMessageSender.EventListener { +public class MessageSenderEventListener implements SignalServiceMessageSender.EventListener { - private static final String TAG = SecurityEventListener.class.getSimpleName(); + private static final String TAG = MessageSenderEventListener.class.getSimpleName(); private final Context context; - public SecurityEventListener(Context context) { + public MessageSenderEventListener(Context context) { this.context = context.getApplicationContext(); } @@ -24,4 +21,9 @@ public class SecurityEventListener implements SignalServiceMessageSender.EventLi public void onSecurityEvent(SignalServiceAddress textSecureAddress) { SecurityEvent.broadcastSecurityUpdateEvent(context); } + + @Override + public void onSyncEvent(long messageID, long timestamp, byte[] message, int ttl) { + LokiMessageSyncEvent.INSTANCE.broadcastSecurityUpdateEvent(context, messageID, timestamp, message, ttl); + } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 195db0e6d9..de26203e64 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.push.AccountManagerFactory; @@ -149,6 +150,28 @@ public class MessageSender { return allocatedThreadId; } + public static void sendSyncMessageToOurDevices(final Context context, + final long messageID, + final long timestamp, + final byte[] message, + final int ttl) { + String ourPublicKey = TextSecurePreferences.getLocalNumber(context); + LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); + JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); + + storageAPI.getAllDevicePublicKeys(ourPublicKey).success(devices -> { + for (String device : devices) { + // Don't send to ourselves + if (device.equals(ourPublicKey)) { continue; } + + // Create a send job for our device + Address address = Address.fromSerialized(device); + jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl)); + } + return Unit.INSTANCE; + }); + } + public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) { if (!messageRecord.isMms()) throw new AssertionError("Not Group"); sendGroupPush(context, messageRecord.getRecipient(), messageRecord.getId(), filterAddress); @@ -204,22 +227,27 @@ public class MessageSender { jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); return; } + boolean[] hasSentSyncMessage = { false }; MultiDeviceUtilitiesKt.getAllDevicePublicKeys(context, recipientPublicKey, storageAPI, (devicePublicKey, isFriend, friendCount) -> { - Address address = Address.fromSerialized(devicePublicKey); - long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; - - if (isFriend) { - // Send a normal message if the user is friends with the recipient - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address)); - } else { - // Send friend requests to non friends. If the user is friends with any - // of the devices then send out a default friend request message. - boolean isFriendsWithAny = (friendCount > 0); - String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage)); - } + Util.runOnMain(() -> { + Address address = Address.fromSerialized(devicePublicKey); + long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; + if (isFriend) { + // Send a normal message if the user is friends with the recipient + // We should also send a sync message if we haven't already sent one + boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && MultiDeviceUtilitiesKt.shouldSendSycMessage(context, address); + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); + hasSentSyncMessage[0] = shouldSendSyncMessage; + } else { + // Send friend requests to non friends. If the user is friends with any + // of the devices then send out a default friend request message. + boolean isFriendsWithAny = (friendCount > 0); + String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); + } + }); return Unit.INSTANCE; }); } @@ -231,25 +259,31 @@ public class MessageSender { // Just send the message normally if it's a group message String recipientPublicKey = recipient.getAddress().serialize(); if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey)) { - PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress()); + PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false); return; } + boolean[] hasSentSyncMessage = { false }; + MultiDeviceUtilitiesKt.getAllDevicePublicKeys(context, recipientPublicKey, storageAPI, (devicePublicKey, isFriend, friendCount) -> { - Address address = Address.fromSerialized(devicePublicKey); - long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; - - if (isFriend) { - // Send a normal message if the user is friends with the recipient - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address); - } else { - // Send friend requests to non friends. If the user is friends with any - // of the devices then send out a default friend request message. - boolean isFriendsWithAny = friendCount > 0; - String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage); - } + Util.runOnMain(() -> { + Address address = Address.fromSerialized(devicePublicKey); + long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; + if (isFriend) { + // Send a normal message if the user is friends with the recipient + // We should also send a sync message if we haven't already sent one + boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && MultiDeviceUtilitiesKt.shouldSendSycMessage(context, address); + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); + hasSentSyncMessage[0] = shouldSendSyncMessage; + } else { + // Send friend requests to non friends. If the user is friends with any + // of the devices then send out a default friend request message. + boolean isFriendsWithAny = friendCount > 0; + String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false); + } + }); return Unit.INSTANCE; }); From 26800cba82239c27fb8b458103817aeaddb3cad6 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 25 Oct 2019 15:21:19 +1100 Subject: [PATCH 10/64] Send a background message to all devices upon manually accepting a friend request. --- .../conversation/ConversationActivity.java | 5 +-- .../securesms/jobs/PushDecryptJob.java | 22 +++------- .../securesms/sms/MessageSender.java | 44 ++++++++++++++++++- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index f09cf1d7aa..3cac1101ae 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -3007,13 +3007,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity long threadId = originalThreadID < 0 ? this.threadId : originalThreadID; String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString(); - SignalServiceMessageSender messageSender = ApplicationContext.getInstance(this).communicationModule.provideSignalMessageSender(); - SignalServiceAddress address = new SignalServiceAddress(contactID); - SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), ""); Context context = this; AsyncTask.execute(() -> { try { - messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter + MessageSender.sendBackgroundMessageToAllDevices(this, contactID); DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS); lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); Util.runOnMain(this::updateInputPanel); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index bccaad08a5..aaa222bec4 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -87,6 +87,7 @@ import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; +import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage; import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; @@ -524,7 +525,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Log.d("Loki", "Sending a ping back to " + content.getSender() + "."); String contactID = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId).getAddress().toString(); - sendBackgroundMessage(contactID); + MessageSender.sendBackgroundMessage(context, contactID); SecurityEvent.broadcastSecurityUpdateEvent(context); MessageNotifier.updateNotification(context, threadId); @@ -1053,7 +1054,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(authorisation); TextSecurePreferences.setMasterHexEncodedPublicKey(context, authorisation.getPrimaryDevicePublicKey()); // Send a background message to the primary device - sendBackgroundMessage(authorisation.getPrimaryDevicePublicKey()); + MessageSender.sendBackgroundMessage(context, authorisation.getPrimaryDevicePublicKey()); // Propagate the updates to the file server LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); storageAPI.updateUserDeviceMappings(); @@ -1109,7 +1110,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Become friends AND update the message they sent becomeFriendsWithContact(content.getSender()); // Send them an accept message back - sendBackgroundMessage(content.getSender()); + MessageSender.sendBackgroundMessage(context, content.getSender()); } else { // Do regular friend request logic checks Recipient originalRecipient = getMessageDestination(content, message); @@ -1139,7 +1140,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { long messageID = smsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, messageCount - 2); // The message before the one that was just received lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); // Accept the friend request - sendBackgroundMessage(content.getSender()); + MessageSender.sendBackgroundMessage(context, content.getSender()); } 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 @@ -1155,19 +1156,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } - private void sendBackgroundMessage(String contactHexEncodedPublicKey) { - Util.runOnMain(() -> { - SignalServiceMessageSender messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender(); - SignalServiceAddress address = new SignalServiceAddress(contactHexEncodedPublicKey); - SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), ""); - try { - messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter - } catch (Exception e) { - Log.d("Loki", "Failed to send background message to: " + contactHexEncodedPublicKey + "."); - } - }); - } - private long handleSynchronizeSentTextMessage(@NonNull SentTranscriptMessage message) throws MmsException { diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index de26203e64..1dd43d402f 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -54,12 +54,15 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.push.ContactTokenDetails; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus; +import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import java.io.IOException; -import java.util.function.Function; import kotlin.Unit; @@ -67,6 +70,45 @@ public class MessageSender { private static final String TAG = MessageSender.class.getSimpleName(); + public static void sendBackgroundMessageToAllDevices(Context context, String contactHexEncodedPublicKey) { + // Send the background message to the original pubkey + sendBackgroundMessage(context, contactHexEncodedPublicKey); + + // Go through the other devices and only send background messages if we're friends or we have received friend request + LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); + storageAPI.getAllDevicePublicKeys(contactHexEncodedPublicKey).success(devices -> { + for (String device : devices) { + // Don't send message to the device we already have sent to + if (device.equals(contactHexEncodedPublicKey)) { continue; } + Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false); + long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); + if (threadID < 0) { continue; } + LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); + if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { + sendBackgroundMessage(context, device); + } + + // TODO: Do we want to send a custom FR Message if we're not friends and we haven't received a friend request? + } + + return Unit.INSTANCE; + }); + } + + public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) { + Util.runOnMain(() -> { + SignalServiceMessageSender messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender(); + SignalServiceAddress address = new SignalServiceAddress(contactHexEncodedPublicKey); + SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), ""); + try { + // Try send to the original person + messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter + } catch (Exception e) { + Log.d("Loki", "Failed to send background message to: " + contactHexEncodedPublicKey + "."); + } + }); + } + public static long send(final Context context, final OutgoingTextMessage message, final long threadId, From 09f5f50af484ae25a74dcc3b746f27317e347e36 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 25 Oct 2019 16:14:15 +1100 Subject: [PATCH 11/64] Don't render friend requests if we are already friends with a users linked device. --- .../conversation/ConversationActivity.java | 29 +---------- .../securesms/jobs/PushDecryptJob.java | 16 +++++- .../securesms/loki/FriendRequestHandler.kt | 51 +++++++++++++++++++ .../securesms/loki/MultiDeviceUtilities.kt | 28 ++++++++++ .../push/MessageSenderEventListener.java | 13 +++++ .../securesms/sms/MessageSender.java | 7 +-- 6 files changed, 111 insertions(+), 33 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 3cac1101ae..31114f6b80 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -2200,7 +2200,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity inputMethodManager.showSoftInput(inputPanel.composeText, 0); } boolean hasPendingFriendRequest = !recipient.isGroupRecipient() && hasPendingFriendRequestWithAnyLinkedDevice(); - boolean isFriendsWithAnyLinkedDevices = isFriendsWithAnyLinkedDevice(); + boolean isFriendsWithAnyLinkedDevices = MultiDeviceUtilitiesKt.isFriendsWithAnyLinkedDevice(this, recipient); boolean shouldEnableInput = isFriendsWithAnyLinkedDevices || !hasPendingFriendRequest; updateToggleButtonState(); inputPanel.setEnabled(shouldEnableInput); @@ -3032,33 +3032,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity updateInputPanel(); } - public boolean isFriendsWithAnyLinkedDevice() { - if (recipient.isGroupRecipient()) return true; - SettableFuture future = new SettableFuture<>(); - LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); - - MultiDeviceUtilitiesKt.getAllDeviceFriendRequestStatus(this, recipient.getAddress().serialize(), storageAPI).success(map -> { - for (LokiThreadFriendRequestStatus status : map.values()) { - if (status == LokiThreadFriendRequestStatus.FRIENDS) { - future.set(true); - break; - } - } - - if (!future.isDone()) { future.set(false); } - return Unit.INSTANCE; - }).fail(e -> { - future.set(false); - return Unit.INSTANCE; - }); - - try { - return future.get(); - } catch (Exception e) { - return false; - } - } - public boolean hasPendingFriendRequestWithAnyLinkedDevice() { if (recipient.isGroupRecipient()) return false; SettableFuture future = new SettableFuture<>(); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index aaa222bec4..4fa07ac425 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -779,6 +779,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { return; } + // Don't insert friend request if we're already friends with a one of the users other device + if (message.isFriendRequest() && MultiDeviceUtilitiesKt.isFriendsWithAnyLinkedDevice(context, primaryDeviceRecipient)) { + return; + } + Optional insertResult; try { @@ -956,6 +961,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Ignore the message if the body is empty if (textMessage.getMessageBody().length() == 0) { return; } + // Don't insert friend request if we're already friends with a one of the users other device + if (message.isFriendRequest() && MultiDeviceUtilitiesKt.isFriendsWithAnyLinkedDevice(context, primaryDeviceRecipient)) { + return; + } + // Insert the message into the database Optional insertResult = database.insertMessageInbox(textMessage); @@ -1014,13 +1024,13 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private void handlePairingMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) { String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context); if (authorisation.getType() == PairingAuthorisation.Type.REQUEST) { - handlePairingRequestMessage(authorisation, envelope); + handlePairingRequestMessage(authorisation); } else if (authorisation.getSecondaryDevicePublicKey().equals(userHexEncodedPublicKey)) { handlePairingAuthorisationMessage(authorisation, envelope, content); } } - private void handlePairingRequestMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceEnvelope envelope) { + private void handlePairingRequestMessage(@NonNull PairingAuthorisation authorisation) { boolean isValid = isValidPairingMessage(authorisation); DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared(); if (isValid && linkingSession.isListeningForLinkingRequests()) { @@ -1093,6 +1103,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // it must be a friend request accepted message. Declining a friend request doesn't send a message. lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); // Update the last message if needed + // TODO: Fix this logic to update the last friend request message SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); int messageCount = smsDatabase.getMessageCountForThread(threadID); @@ -1137,6 +1148,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // `REQUEST_SENT`. lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); // Since messages are forwarded to the primary device thread, we need to update it there + // TODO: Fix this logic to update the last friend request message long messageID = smsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, messageCount - 2); // The message before the one that was just received lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); // Accept the friend request diff --git a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt new file mode 100644 index 0000000000..d68bc09bdd --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt @@ -0,0 +1,51 @@ +package org.thoughtcrime.securesms.loki + +import android.content.Context +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.ui.alwaysUi +import nl.komponents.kovenant.ui.successUi +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.whispersystems.signalservice.loki.api.LokiStorageAPI +import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus +import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus +import java.lang.IllegalStateException + +object FriendRequestHandler { + enum class ActionType { Sending, Sent, Failed } + + @JvmStatic + fun handleFriendRequest(context: Context, type: ActionType, messageId: Long, threadId: Long) { + // Update thread status + // Note: Do we need to only update these if we're not friends? + if (threadId >= 0) { + val threadFriendStatus = when (type) { + ActionType.Sending -> LokiThreadFriendRequestStatus.REQUEST_SENDING + ActionType.Failed -> LokiThreadFriendRequestStatus.NONE + ActionType.Sent -> LokiThreadFriendRequestStatus.REQUEST_SENT + } + DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, threadFriendStatus) + } + + // Update message status + val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) + if (recipient != null && messageId >= 0) { + val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context) + val messageFriendRequestStatus = messageDatabase.getFriendRequestStatus(messageId) + if (type == ActionType.Sending && messageFriendRequestStatus == LokiMessageFriendRequestStatus.NONE) { + // We only want to update message status if we aren't friends with another of their devices + // This avoids spam in the ui where it would keep telling the user that they sent a friend request on every single message + if (!isFriendsWithAnyLinkedDevice(context, recipient)) { + messageDatabase.setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING) + } + } else if (messageFriendRequestStatus != LokiMessageFriendRequestStatus.NONE) { + // Update the friend request status of the message if we have it + val messageFriendRequestStatus = when (type) { + ActionType.Failed -> LokiMessageFriendRequestStatus.REQUEST_FAILED + ActionType.Sent -> LokiMessageFriendRequestStatus.REQUEST_PENDING + else -> throw IllegalStateException() + } + messageDatabase.setFriendRequestStatus(messageId, messageFriendRequestStatus) + } + } + } +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index d1f696ea94..dcbe57652d 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -202,4 +202,32 @@ fun getPrimaryDevicePublicKey(hexEncodedPublicKey: String): String? { } catch (e: Exception) { null } +} + +fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Boolean { + if (recipient.isGroupRecipient) return true + val future = SettableFuture() + val storageAPI = LokiStorageAPI.shared + + getAllDeviceFriendRequestStatus(context, recipient.address.serialize(), storageAPI).success { map -> + for (status in map.values) { + if (status == LokiThreadFriendRequestStatus.FRIENDS) { + future.set(true) + break + } + } + + if (!future.isDone) { + future.set(false) + } + }.fail { e -> + future.set(false) + } + + try { + return future.get() + } catch (e: Exception) { + return false + } + } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java index e8babe29f5..ec8357b232 100644 --- a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java +++ b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.push; import android.content.Context; import org.thoughtcrime.securesms.crypto.SecurityEvent; +import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.LokiMessageSyncEvent; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -26,4 +27,16 @@ public class MessageSenderEventListener implements SignalServiceMessageSender.Ev public void onSyncEvent(long messageID, long timestamp, byte[] message, int ttl) { LokiMessageSyncEvent.INSTANCE.broadcastSecurityUpdateEvent(context, messageID, timestamp, message, ttl); } + + @Override public void onFriendRequestSending(long messageID, long threadID) { + FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sending, messageID, threadID); + } + + @Override public void onFriendRequestSent(long messageID, long threadID) { + FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sent, messageID, threadID); + } + + @Override public void onFriendRequestSendingFail(long messageID, long threadID) { + FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Failed, messageID, threadID); + } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 1dd43d402f..e6e9fa254c 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; @@ -131,7 +132,7 @@ public class MessageSender { // Loki - Set the message's friend request status as soon as it has hit the database if (message.isFriendRequest) { - DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING); + FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sending, messageId, allocatedThreadId); } sendTextMessage(context, recipient, forceSms, keyExchange, messageId); @@ -167,7 +168,7 @@ public class MessageSender { long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener); // Loki - Set the message's friend request status as soon as it has hit the database if (message.isFriendRequest) { - DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_SENDING); + FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId); } sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn()); } catch (Exception e) { @@ -180,7 +181,7 @@ public class MessageSender { long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener); // Loki - Set the message's friend request status as soon as it has hit the database if (message.isFriendRequest) { - DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_SENDING); + FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId); } sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn()); } catch (MmsException e) { From 9ea2a6c8e4ac2ec5919ed13deb0d1bdf60545c39 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 29 Oct 2019 11:23:30 +1100 Subject: [PATCH 12/64] More fixes for friend request UI for multi device. Remove old FR pending states on new incoming friend request. Always update the last friend request message state and not a new message. --- .../securesms/database/SmsDatabase.java | 18 +++++++ .../securesms/jobs/PushDecryptJob.java | 26 +++------- .../securesms/loki/FriendRequestHandler.kt | 51 +++++++++++++++++-- .../securesms/loki/MultiDeviceUtilities.kt | 11 ++-- .../push/MessageSenderEventListener.java | 6 +-- .../securesms/sms/MessageSender.java | 7 ++- 6 files changed, 84 insertions(+), 35 deletions(-) diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 4fdcfdcc6a..112ddc463b 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -47,6 +47,7 @@ import org.whispersystems.libsignal.util.guava.Optional; import java.io.IOException; import java.security.SecureRandom; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -193,6 +194,23 @@ public class SmsDatabase extends MessagingDatabase { return -1; } + public Set getAllMessageIDs(long threadID) { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + Cursor cursor = null; + Set messageIDs = new HashSet<>(); + try { + cursor = database.query(TABLE_NAME, null, THREAD_ID + " = ?", new String[] { threadID + "" }, null, null, null); + while (cursor != null && cursor.moveToNext()) { + messageIDs.add(cursor.getLong(0)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return messageIDs; + } + public void markAsEndSession(long id) { updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.END_SESSION_BIT); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 4fa07ac425..f3ba69fe73 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -66,6 +66,7 @@ import org.thoughtcrime.securesms.linkpreview.Link; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.LokiAPIUtilities; import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.LokiPreKeyBundleDatabase; @@ -961,11 +962,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Ignore the message if the body is empty if (textMessage.getMessageBody().length() == 0) { return; } - // Don't insert friend request if we're already friends with a one of the users other device - if (message.isFriendRequest() && MultiDeviceUtilitiesKt.isFriendsWithAnyLinkedDevice(context, primaryDeviceRecipient)) { - return; - } - // Insert the message into the database Optional insertResult = database.insertMessageInbox(textMessage); @@ -1103,14 +1099,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // it must be a friend request accepted message. Declining a friend request doesn't send a message. lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); // Update the last message if needed - // TODO: Fix this logic to update the last friend request message - SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); - LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); - int messageCount = smsDatabase.getMessageCountForThread(threadID); - long messageID = smsDatabase.getIDForMessageAtIndex(threadID, messageCount - 1); - if (messageID > -1 && lokiMessageDatabase.getFriendRequestStatus(messageID) != LokiMessageFriendRequestStatus.REQUEST_ACCEPTED) { - lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); - } + FriendRequestHandler.updateLastFriendRequestMessage(context, threadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); } private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { @@ -1134,7 +1123,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient); long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient); LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID); - int messageCount = smsMessageDatabase.getMessageCountForThread(primaryDeviceThreadID); + 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 @@ -1148,9 +1137,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // `REQUEST_SENT`. lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); // Since messages are forwarded to the primary device thread, we need to update it there - // TODO: Fix this logic to update the last friend request message - long messageID = smsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, messageCount - 2); // The message before the one that was just received - lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); // Accept the friend request MessageSender.sendBackgroundMessage(context, content.getSender()); } else if (threadFriendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { @@ -1160,10 +1147,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // 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 - long smsMessageID = smsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, messageCount - 1); // The message that was just received - long messageID = smsMessageID != -1 ? smsMessageID : mmsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, 0); - lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING); + FriendRequestHandler.receivedIncomingFriendRequestMessage(context, primaryDeviceThreadID); } } } diff --git a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt index d68bc09bdd..70a0850f36 100644 --- a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt +++ b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt @@ -14,7 +14,7 @@ object FriendRequestHandler { enum class ActionType { Sending, Sent, Failed } @JvmStatic - fun handleFriendRequest(context: Context, type: ActionType, messageId: Long, threadId: Long) { + fun updateFriendRequestState(context: Context, type: ActionType, messageId: Long, threadId: Long) { // Update thread status // Note: Do we need to only update these if we're not friends? if (threadId >= 0) { @@ -30,14 +30,14 @@ object FriendRequestHandler { val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) if (recipient != null && messageId >= 0) { val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context) - val messageFriendRequestStatus = messageDatabase.getFriendRequestStatus(messageId) - if (type == ActionType.Sending && messageFriendRequestStatus == LokiMessageFriendRequestStatus.NONE) { + val friendRequestStatus = messageDatabase.getFriendRequestStatus(messageId) + if (type == ActionType.Sending) { // We only want to update message status if we aren't friends with another of their devices // This avoids spam in the ui where it would keep telling the user that they sent a friend request on every single message - if (!isFriendsWithAnyLinkedDevice(context, recipient)) { + if (!isFriendsWithAnyLinkedDevice(context, recipient) && friendRequestStatus == LokiMessageFriendRequestStatus.NONE) { messageDatabase.setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING) } - } else if (messageFriendRequestStatus != LokiMessageFriendRequestStatus.NONE) { + } else if (friendRequestStatus != LokiMessageFriendRequestStatus.NONE) { // Update the friend request status of the message if we have it val messageFriendRequestStatus = when (type) { ActionType.Failed -> LokiMessageFriendRequestStatus.REQUEST_FAILED @@ -48,4 +48,45 @@ object FriendRequestHandler { } } } + + @JvmStatic + fun updateLastFriendRequestMessage(context: Context, threadId: Long, status: LokiMessageFriendRequestStatus) { + if (threadId < 0) { return } + + val messages = DatabaseFactory.getSmsDatabase(context).getAllMessageIDs(threadId) + val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context) + val lastMessage = messages.find { + val friendRequestStatus = lokiMessageDatabase.getFriendRequestStatus(it) + friendRequestStatus == LokiMessageFriendRequestStatus.REQUEST_PENDING + } ?: return + + DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessage, status) + } + + @JvmStatic + fun receivedIncomingFriendRequestMessage(context: Context, threadId: Long) { + val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context) + + // We only want to update the last message status if we're not friends with any of their linked devices + // This ensures that we don't spam the UI with accept/decline messages + val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return + if (isFriendsWithAnyLinkedDevice(context, recipient)) { return } + + // Since messages are forwarded to the primary device thread, we need to update it there + val messageCount = smsMessageDatabase.getMessageCountForThread(threadId) + val messageID = smsMessageDatabase.getIDForMessageAtIndex(threadId, messageCount - 1) // The message that was just received + if (messageID < 0) { return } + + val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context) + + // We need to go through and set all messages which are REQUEST_PENDING to NONE + smsMessageDatabase.getAllMessageIDs(threadId) + .filter { messageDatabase.getFriendRequestStatus(it) == LokiMessageFriendRequestStatus.REQUEST_PENDING } + .forEach { + messageDatabase.setFriendRequestStatus(it, LokiMessageFriendRequestStatus.NONE) + } + + // Set the last message to pending + messageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING) + } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index dcbe57652d..1fbb4a480f 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.loki import android.content.Context +import android.os.Handler import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred import nl.komponents.kovenant.functional.bind @@ -134,9 +135,13 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio Log.d("Loki", "Failed to send pairing authorization message to ${pairingAuthorisation.secondaryDevicePublicKey}.") } DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation) - LokiStorageAPI.shared.updateUserDeviceMappings().fail { exception -> - Log.w("Loki", "Failed to update device mapping") - } + // Call function after a short delay + Handler().postDelayed({ + LokiStorageAPI.shared.updateUserDeviceMappings().fail { + Log.w("Loki", "Failed to update device mapping") + } + }, 100) + } fun shouldSendSycMessage(context: Context, address: Address): Boolean { diff --git a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java index ec8357b232..5cd99e5fcb 100644 --- a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java +++ b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java @@ -29,14 +29,14 @@ public class MessageSenderEventListener implements SignalServiceMessageSender.Ev } @Override public void onFriendRequestSending(long messageID, long threadID) { - FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sending, messageID, threadID); + FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageID, threadID); } @Override public void onFriendRequestSent(long messageID, long threadID) { - FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sent, messageID, threadID); + FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sent, messageID, threadID); } @Override public void onFriendRequestSendingFail(long messageID, long threadID) { - FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Failed, messageID, threadID); + FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Failed, messageID, threadID); } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index e6e9fa254c..027dd4fd24 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -60,7 +60,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; -import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import java.io.IOException; @@ -132,7 +131,7 @@ public class MessageSender { // Loki - Set the message's friend request status as soon as it has hit the database if (message.isFriendRequest) { - FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sending, messageId, allocatedThreadId); + FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageId, allocatedThreadId); } sendTextMessage(context, recipient, forceSms, keyExchange, messageId); @@ -168,7 +167,7 @@ public class MessageSender { long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener); // Loki - Set the message's friend request status as soon as it has hit the database if (message.isFriendRequest) { - FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId); + FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId); } sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn()); } catch (Exception e) { @@ -181,7 +180,7 @@ public class MessageSender { long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener); // Loki - Set the message's friend request status as soon as it has hit the database if (message.isFriendRequest) { - FriendRequestHandler.handleFriendRequest(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId); + FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId); } sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn()); } catch (MmsException e) { From 10ec395915f76e600d0458b3d103b1aeaabd4885 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 29 Oct 2019 12:13:22 +1100 Subject: [PATCH 13/64] Correctly update UI when an outgoing friend request is accepted from a secondary device. --- .../conversation/ConversationActivity.java | 20 ++++++++++++-- .../securesms/jobs/PushDecryptJob.java | 4 ++- .../securesms/loki/MultiDeviceUtilities.kt | 26 ++++++++++++------- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 31114f6b80..df45d35688 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -128,6 +128,7 @@ import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.database.Database; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -245,6 +246,7 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -2187,8 +2189,22 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void handleThreadFriendRequestStatusChanged(long threadID) { - if (threadID != this.threadId) { return; } - new Handler(getMainLooper()).post(this::updateInputPanel); + Util.runOnMain(() -> { + boolean shouldUpdateInputPanel = true; + if (threadID != this.threadId) { + Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID); + if (threadRecipient != null && !threadRecipient.isGroupRecipient()) { + // We should update our input if this thread is a part of the other threads device + Set devices = MultiDeviceUtilitiesKt.getAllDevicePublicKeys(threadRecipient.getAddress().serialize(), LokiStorageAPI.Companion.getShared()); + shouldUpdateInputPanel = devices.contains(recipient.getAddress().serialize()); + } else { + shouldUpdateInputPanel = false; + } + } + if (shouldUpdateInputPanel) { + this.updateInputPanel(); + } + }); } private void updateInputPanel() { diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index f3ba69fe73..ba8aab96d4 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -1099,7 +1099,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // it must be a friend request accepted message. Declining a friend request doesn't send a message. lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); // Update the last message if needed - FriendRequestHandler.updateLastFriendRequestMessage(context, threadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + String primaryDevice = MultiDeviceUtilitiesKt.getPrimaryDevicePublicKey(pubKey); + long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false)); + FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); } private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 1fbb4a480f..72811df37c 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -41,15 +41,23 @@ fun getAllDeviceFriendRequestStatus(context: Context, hexEncodedPublicKey: Strin fun getAllDevicePublicKeys(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI, block: (devicePublicKey: String, isFriend: Boolean, friendCount: Int) -> Unit) { val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) - storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).success { items -> - val devices = items.toMutableSet() - if (hexEncodedPublicKey != userHexEncodedPublicKey) { - devices.remove(userHexEncodedPublicKey) - } - val friends = getFriendPublicKeys(context, devices) - for (device in devices) { - block(device, friends.contains(device), friends.count()) - } + val devices = getAllDevicePublicKeys(hexEncodedPublicKey, storageAPI).toMutableSet() + if (hexEncodedPublicKey != userHexEncodedPublicKey) { + devices.remove(userHexEncodedPublicKey) + } + val friends = getFriendPublicKeys(context, devices) + for (device in devices) { + block(device, friends.contains(device), friends.count()) + } +} + +fun getAllDevicePublicKeys(hexEncodedPublicKey: String, storageAPI: LokiStorageAPI): Set { + val future = SettableFuture>() + storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).success { future.set(it) }.fail { future.setException(it) } + return try { + future.get() + } catch (e: Exception) { + setOf() } } From 3298d665e981c534d305cc48f8510a7a4651da37 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 29 Oct 2019 14:03:32 +1100 Subject: [PATCH 14/64] Fix note to self message syncing. Don't send messages if we're sending to ourselves, instead we just send a regular sync message. --- .../securesms/jobs/PushMediaSendJob.java | 2 +- .../securesms/jobs/PushTextSendJob.java | 4 ++-- .../securesms/jobs/TypingSendJob.java | 6 +++--- .../thoughtcrime/securesms/sms/MessageSender.java | 15 +++++++++++++++ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 6e50e3446d..eab4072ddd 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -274,7 +274,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, mediaMessage, syncAccess); - messageSender.sendMessage(messageId, syncMessage, syncAccess); + messageSender.sendMessage(templateMessageId, syncMessage, syncAccess); return syncAccess.isPresent(); } else { LokiSyncMessage syncMessage = null; diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 99dcb6e78b..0585d6e586 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -225,12 +225,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, textSecureMessage, syncAccess); - messageSender.sendMessage(messageId, syncMessage, syncAccess); + messageSender.sendMessage(templateMessageId, syncMessage, syncAccess); return syncAccess.isPresent(); } else { LokiSyncMessage syncMessage = null; if (shouldSendSyncMessage) { - // Set the sync message destination the primary device, this way it will show that we sent a message to the primary device and not a secondary device + // Set the sync message destination to the primary device, this way it will show that we sent a message to the primary device and not a secondary device String primaryDevice = MultiDeviceUtilitiesKt.getPrimaryDevicePublicKey(address.getNumber()); SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); // We also need to use the original message id and not -1 diff --git a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java index d1dc6e53fb..e5379894dd 100644 --- a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -96,9 +97,8 @@ public class TypingSendJob extends BaseJob implements InjectableType { List> unidentifiedAccess = Stream.of(recipients).map(r -> UnidentifiedAccessUtil.getAccessFor(context, r)).toList(); SignalServiceTypingMessage typingMessage = new SignalServiceTypingMessage(typing ? Action.STARTED : Action.STOPPED, System.currentTimeMillis(), groupId); - // Loki - Don't send typing indicators in group chats - if (!recipient.isGroupRecipient()) { - // TODO: Message ID + // Loki - Don't send typing indicators in group chats or to ourselves + if (!recipient.isGroupRecipient() && !MultiDeviceUtilitiesKt.isOneOfOurDevices(context, recipient.getAddress())) { messageSender.sendTyping(0, addresses, unidentifiedAccess, typingMessage); } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 027dd4fd24..58334eeb48 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -269,6 +269,14 @@ public class MessageSender { jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); return; } + + // Note to self + boolean isNoteToSelf = MultiDeviceUtilitiesKt.isOneOfOurDevices(context, recipient.getAddress()); + if (isNoteToSelf) { + jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); + return; + } + boolean[] hasSentSyncMessage = { false }; MultiDeviceUtilitiesKt.getAllDevicePublicKeys(context, recipientPublicKey, storageAPI, (devicePublicKey, isFriend, friendCount) -> { @@ -305,6 +313,13 @@ public class MessageSender { return; } + // Note to self + boolean isNoteToSelf = MultiDeviceUtilitiesKt.isOneOfOurDevices(context, recipient.getAddress()); + if (isNoteToSelf) { + jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); + return; + } + boolean[] hasSentSyncMessage = { false }; MultiDeviceUtilitiesKt.getAllDevicePublicKeys(context, recipientPublicKey, storageAPI, (devicePublicKey, isFriend, friendCount) -> { From 7ff7c36e27b00204f933e858aff71c08e5ab2c6a Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 30 Oct 2019 09:59:11 +1100 Subject: [PATCH 15/64] Cleanup code. --- .../ApplicationPreferencesActivity.java | 4 +- .../components/TypingStatusSender.java | 13 +- .../conversation/ConversationActivity.java | 42 ++--- .../securesms/database/Address.java | 2 +- .../securesms/jobs/PushDecryptJob.java | 36 +--- .../securesms/jobs/PushMediaSendJob.java | 5 +- .../securesms/jobs/PushTextSendJob.java | 5 +- .../securesms/jobs/TypingSendJob.java | 4 +- .../securesms/loki/MultiDeviceUtilities.kt | 173 ++++-------------- .../notifications/MarkReadReceiver.java | 14 +- .../securesms/sms/MessageSender.java | 145 +++++++-------- 11 files changed, 156 insertions(+), 287 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 279db687a3..b7f5b797de 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -44,7 +44,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.loki.DeviceLinkingDialog; import org.thoughtcrime.securesms.loki.DeviceLinkingDialogDelegate; import org.thoughtcrime.securesms.loki.DeviceLinkingView; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.QRCodeDialog; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment; @@ -394,7 +394,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA } @Override public void sendPairingAuthorizedMessage(@NotNull PairingAuthorisation pairingAuthorisation) { - MultiDeviceUtilitiesKt.signAndSendPairingAuthorisationMessage(context, pairingAuthorisation); + MultiDeviceUtilities.signAndSendPairingAuthorisationMessage(context, pairingAuthorisation); } @Override public void handleDeviceLinkAuthorized(@NotNull PairingAuthorisation pairingAuthorisation) {} @Override public void handleDeviceLinkingDialogDismissed() {} diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java index b7526f1065..b813f5c63e 100644 --- a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -9,13 +9,14 @@ import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.jobs.TypingSendJob; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import kotlin.Unit; @@ -91,14 +92,14 @@ public class TypingStatusSender { return; } - MultiDeviceUtilitiesKt.getAllDevicePublicKeys(context, recipient.getAddress().serialize(), storageAPI, (devicePublicKey, isFriend, friendCount) -> { - Recipient device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false); - long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(device); + Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(recipient.getAddress().serialize()); + for (String device : devices) { + Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false); + long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient); if (deviceThreadID > -1) { ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(deviceThreadID, typingStarted)); } - return Unit.INSTANCE; - }); + } } private class StartRunnable implements Runnable { diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index df45d35688..e3f6769fe3 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -38,7 +38,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.os.Vibrator; import android.provider.Browser; import android.provider.ContactsContract; @@ -128,7 +127,6 @@ import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.Database; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -163,7 +161,7 @@ import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate; import org.thoughtcrime.securesms.loki.LokiUserDatabase; import org.thoughtcrime.securesms.loki.MentionCandidateSelectionView; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.MediaSendActivity; import org.thoughtcrime.securesms.mms.AttachmentManager; @@ -228,9 +226,6 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.thoughtcrime.securesms.util.views.Stub; import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.SignalServiceMessageSender; -import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.loki.api.LokiAPI; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus; @@ -246,6 +241,7 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; @@ -2195,7 +2191,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID); if (threadRecipient != null && !threadRecipient.isGroupRecipient()) { // We should update our input if this thread is a part of the other threads device - Set devices = MultiDeviceUtilitiesKt.getAllDevicePublicKeys(threadRecipient.getAddress().serialize(), LokiStorageAPI.Companion.getShared()); + Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(threadRecipient.getAddress().serialize()); shouldUpdateInputPanel = devices.contains(recipient.getAddress().serialize()); } else { shouldUpdateInputPanel = false; @@ -2216,13 +2212,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity inputMethodManager.showSoftInput(inputPanel.composeText, 0); } boolean hasPendingFriendRequest = !recipient.isGroupRecipient() && hasPendingFriendRequestWithAnyLinkedDevice(); - boolean isFriendsWithAnyLinkedDevices = MultiDeviceUtilitiesKt.isFriendsWithAnyLinkedDevice(this, recipient); + boolean isFriendsWithAnyLinkedDevices = MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient); boolean shouldEnableInput = isFriendsWithAnyLinkedDevices || !hasPendingFriendRequest; updateToggleButtonState(); inputPanel.setEnabled(shouldEnableInput); int hintID = shouldEnableInput ? R.string.activity_conversation_default_hint : R.string.activity_conversation_pending_friend_request_hint; inputPanel.setHint(getResources().getString(hintID)); - if (!shouldEnableInput) { + if (shouldEnableInput) { inputPanel.composeText.requestFocus(); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); inputMethodManager.showSoftInput(inputPanel.composeText, 0); @@ -3050,31 +3046,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public boolean hasPendingFriendRequestWithAnyLinkedDevice() { if (recipient.isGroupRecipient()) return false; - SettableFuture future = new SettableFuture<>(); - LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); - MultiDeviceUtilitiesKt.getAllDeviceFriendRequestStatus(this, recipient.getAddress().serialize(), storageAPI).success(map -> { - for (LokiThreadFriendRequestStatus status : map.values()) { - // Break out of the loop as soon as one of the device has a pending friend request - if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { - future.set(true); - break; - } + Map map = MultiDeviceUtilities.getAllDeviceFriendRequestStatuses(this, recipient.getAddress().serialize()); + for (LokiThreadFriendRequestStatus status : map.values()) { + if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { + return true; } - - // If we reached this and we haven't set anything on the future then it means that we don't have a pending request - if (!future.isDone()) { future.set(false); } - return Unit.INSTANCE; - }).fail(e -> { - future.set(false); - return Unit.INSTANCE; - }); - - try { - return future.get(); - } catch (Exception e) { - return false; } + + return false; } // endregion } diff --git a/src/org/thoughtcrime/securesms/database/Address.java b/src/org/thoughtcrime/securesms/database/Address.java index ed20c6fbbb..d063025cfa 100644 --- a/src/org/thoughtcrime/securesms/database/Address.java +++ b/src/org/thoughtcrime/securesms/database/Address.java @@ -61,7 +61,7 @@ public class Address implements Parcelable, Comparable
{ private Address(@NonNull String address, Boolean isPublicChat) { if (address == null) throw new AssertionError(address); - this.address = address; + this.address = address.toLowerCase(); this.isPublicChat = isPublicChat; } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index ba8aab96d4..60c846da20 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -72,7 +72,7 @@ import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.LokiPreKeyBundleDatabase; import org.thoughtcrime.securesms.loki.LokiPreKeyRecordDatabase; import org.thoughtcrime.securesms.loki.LokiThreadDatabase; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; @@ -780,11 +780,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { return; } - // Don't insert friend request if we're already friends with a one of the users other device - if (message.isFriendRequest() && MultiDeviceUtilitiesKt.isFriendsWithAnyLinkedDevice(context, primaryDeviceRecipient)) { - return; - } - Optional insertResult; try { @@ -828,8 +823,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - Recipient recipient = getSyncMessagePrimaryDestination(message); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + Recipient recipient = getSyncMessagePrimaryDestination(message); OutgoingExpirationUpdateMessage expirationUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, message.getTimestamp(), @@ -1099,7 +1094,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // it must be a friend request accepted message. Declining a friend request doesn't send a message. lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); // Update the last message if needed - String primaryDevice = MultiDeviceUtilitiesKt.getPrimaryDevicePublicKey(pubKey); + String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey); long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false)); FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); } @@ -1107,7 +1102,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { if (!envelope.isFriendRequest()) { return; } // This handles the case where another user sends us a regular message without authorisation - boolean shouldBecomeFriends = MultiDeviceUtilitiesKt.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context); + boolean shouldBecomeFriends = MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context); if (shouldBecomeFriends) { // Become friends AND update the message they sent becomeFriendsWithContact(content.getSender()); @@ -1536,26 +1531,15 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } private Recipient getPrimaryDeviceRecipient(String recipient) { - SettableFuture device = new SettableFuture<>(); - - // Get the primary device - LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient).success(primaryDevice -> { + try { + String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient); String publicKey = (primaryDevice != null) ? primaryDevice : recipient; - // If our the public key matches our primary device then we need to forward the message to ourselves (Note to self) + // If the public key matches our primary device then we need to forward the message to ourselves (Note to self) String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) { publicKey = TextSecurePreferences.getLocalNumber(context); } - device.set(publicKey); - return Unit.INSTANCE; - }).fail(exception -> { - device.set(recipient); - return Unit.INSTANCE; - }); - - try { - String primarySender = device.get(); - return Recipient.from(context, Address.fromSerialized(primarySender), false); + return Recipient.from(context, Address.fromSerialized(publicKey), false); } catch (Exception e) { Log.d("Loki", "Failed to get primary device public key for message. " + e.getMessage()); return Recipient.from(context, Address.fromSerialized(recipient), false); @@ -1610,7 +1594,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { return sender.isBlocked(); } else if (content.getSyncMessage().isPresent()) { // We should ignore a sync message if the sender is not one of our devices - boolean isOurDevice = MultiDeviceUtilitiesKt.isOneOfOurDevices(context, sender.getAddress()); + boolean isOurDevice = MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()); if (!isOurDevice) { Log.w(TAG, "Got a sync message from a device that is not ours!."); } return !isOurDevice; } diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index eab4072ddd..9a10d5f797 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; @@ -43,6 +43,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; +import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import java.io.FileNotFoundException; @@ -280,7 +281,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { LokiSyncMessage syncMessage = null; if (shouldSendSyncMessage) { // Set the sync message destination the primary device, this way it will show that we sent a message to the primary device and not a secondary device - String primaryDevice = MultiDeviceUtilitiesKt.getPrimaryDevicePublicKey(address.getNumber()); + String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()); SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); // We also need to use the original message id and not -1 syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 0585d6e586..7c93b82c8b 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.ExpiringMessageManager; @@ -30,6 +30,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; +import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import java.io.IOException; @@ -231,7 +232,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { LokiSyncMessage syncMessage = null; if (shouldSendSyncMessage) { // Set the sync message destination to the primary device, this way it will show that we sent a message to the primary device and not a secondary device - String primaryDevice = MultiDeviceUtilitiesKt.getPrimaryDevicePublicKey(address.getNumber()); + String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()); SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); // We also need to use the original message id and not -1 syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); diff --git a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java index e5379894dd..023afda792 100644 --- a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -98,7 +98,7 @@ public class TypingSendJob extends BaseJob implements InjectableType { SignalServiceTypingMessage typingMessage = new SignalServiceTypingMessage(typing ? Action.STARTED : Action.STOPPED, System.currentTimeMillis(), groupId); // Loki - Don't send typing indicators in group chats or to ourselves - if (!recipient.isGroupRecipient() && !MultiDeviceUtilitiesKt.isOneOfOurDevices(context, recipient.getAddress())) { + if (!recipient.isGroupRecipient() && !MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress())) { messageSender.sendTyping(0, addresses, unidentifiedAccess, typingMessage); } } diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 72811df37c..d3c8b17cad 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -1,11 +1,9 @@ +@file:JvmName("MultiDeviceUtilities") package org.thoughtcrime.securesms.loki import android.content.Context import android.os.Handler import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.deferred -import nl.komponents.kovenant.functional.bind -import nl.komponents.kovenant.functional.map import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address @@ -13,7 +11,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.thoughtcrime.securesms.util.concurrent.SettableFuture import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage @@ -23,85 +20,53 @@ import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.utilities.retryIfNeeded -fun getAllDeviceFriendRequestStatus(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI): Promise, Exception> { +fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Map { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) - return storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> - val map = mutableMapOf() - - for (devicePublicKey in keys) { - val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false) - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device) - val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID) - map.put(devicePublicKey, friendRequestStatus); - } - - map + val keys = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey) + val map = mutableMapOf() + for (devicePublicKey in keys) { + val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false) + val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device) + val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID) + map[devicePublicKey] = friendRequestStatus } + return map } -fun getAllDevicePublicKeys(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI, block: (devicePublicKey: String, isFriend: Boolean, friendCount: Int) -> Unit) { +fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Map { val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) - val devices = getAllDevicePublicKeys(hexEncodedPublicKey, storageAPI).toMutableSet() + val devices = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).toMutableSet() if (hexEncodedPublicKey != userHexEncodedPublicKey) { devices.remove(userHexEncodedPublicKey) } val friends = getFriendPublicKeys(context, devices) + val map = mutableMapOf() for (device in devices) { - block(device, friends.contains(device), friends.count()) + map[device] = friends.contains(device) } + return map } -fun getAllDevicePublicKeys(hexEncodedPublicKey: String, storageAPI: LokiStorageAPI): Set { - val future = SettableFuture>() - storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).success { future.set(it) }.fail { future.setException(it) } - return try { - future.get() - } catch (e: Exception) { - setOf() - } +fun getFriendCount(context: Context, devices: Set): Int { + return getFriendPublicKeys(context, devices).count() } fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Boolean { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val storageAPI = LokiStorageAPI.shared - val future = SettableFuture() - storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey -> - if (primaryDevicePublicKey == null) { - // If the public key doesn't have any other devices then go through regular friend request logic - future.set(false) - return@success - } - // If we are the primary device and the public key is our secondary device then we should become friends - val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) - if (primaryDevicePublicKey == userHexEncodedPublicKey) { - storageAPI.getSecondaryDevicePublicKeys(userHexEncodedPublicKey).success { secondaryDevices -> - future.set(secondaryDevices.contains(publicKey)) - }.fail { - future.set(false) - } - return@success - } - // If we share the same primary device then we should become friends - val ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) - if (ourPrimaryDevice != null && ourPrimaryDevice == primaryDevicePublicKey) { - future.set(true) - return@success - } - // If we are friends with the primary device then we should become friends - val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) - if (threadID < 0) { - future.set(false) - return@success - } - future.set(lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) + + // If the public key doesn't have any other devices then go through regular friend request logic + val primaryDevicePublicKey = storageAPI.getPrimaryDevicePublicKey(publicKey) ?: return false + + // If this is one of our devices then we should become friends + if (isOneOfOurDevices(context, publicKey)) { + return true } - return try { - future.get() - } catch (e: Exception) { - false - } + // If we are friends with the primary device then we should become friends + val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) + val primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) + return primaryDeviceThreadID >= 0 && lokiThreadDatabase.getFriendRequestStatus(primaryDeviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS } fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise { @@ -157,27 +122,12 @@ fun shouldSendSycMessage(context: Context, address: Address): Boolean { return false } - // Don't send sync messages if it's our address - val publicKey = address.serialize() - if (publicKey == TextSecurePreferences.getLocalNumber(context)) { - return false - } + // Don't send sync messages if it's one of our devices + return !isOneOfOurDevices(context, address) +} - val storageAPI = LokiStorageAPI.shared - val future = SettableFuture() - storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey -> - val isOurPrimaryDevice = primaryDevicePublicKey != null && TextSecurePreferences.getMasterHexEncodedPublicKey(context) == publicKey - // Don't send sync message if the primary device is the same as ours - future.set(!isOurPrimaryDevice) - }.fail { - future.set(false) - } - - return try { - future.get() - } catch (e: Exception) { - false - } +fun isOneOfOurDevices(context: Context, publicKey: String): Boolean { + return isOneOfOurDevices(context, Address.fromSerialized(publicKey)) } fun isOneOfOurDevices(context: Context, address: Address): Boolean { @@ -186,61 +136,18 @@ fun isOneOfOurDevices(context: Context, address: Address): Boolean { } val ourPublicKey = TextSecurePreferences.getLocalNumber(context) - val storageAPI = LokiStorageAPI.shared - val future = SettableFuture() - storageAPI.getAllDevicePublicKeys(ourPublicKey).success { - future.set(it.contains(address.serialize())) - }.fail { - future.set(false) - } - - return try { - future.get() - } catch (e: Exception) { - false - } -} - -fun getPrimaryDevicePublicKey(hexEncodedPublicKey: String): String? { - val storageAPI = LokiStorageAPI.shared - val future = SettableFuture() - storageAPI.getPrimaryDevicePublicKey(hexEncodedPublicKey).success { - future.set(it) - }.fail { - future.set(null) - } - - return try { - future.get() - } catch (e: Exception) { - null - } + val devices = LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey) + return devices.contains(address.serialize()) } fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Boolean { if (recipient.isGroupRecipient) return true - val future = SettableFuture() - val storageAPI = LokiStorageAPI.shared - getAllDeviceFriendRequestStatus(context, recipient.address.serialize(), storageAPI).success { map -> - for (status in map.values) { - if (status == LokiThreadFriendRequestStatus.FRIENDS) { - future.set(true) - break - } + val map = getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()) + for (status in map.values) { + if (status == LokiThreadFriendRequestStatus.FRIENDS) { + return true } - - if (!future.isDone) { - future.set(false) - } - }.fail { e -> - future.set(false) } - - try { - return future.get() - } catch (e: Exception) { - return false - } - + return false } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index 9d403bad9a..ff5a1477ee 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; @@ -90,17 +90,17 @@ public class MarkReadReceiver extends BroadcastReceiver { .collect(Collectors.groupingBy(SyncMessageId::getAddress)); for (Address address : addressMap.keySet()) { - LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); - List timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); - MultiDeviceUtilitiesKt.getAllDevicePublicKeys(context, address.serialize(), storageAPI, (devicePublicKey, isFriend, friendCount) -> { + Map devices = MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, address.serialize()); + for (Map.Entry entry : devices.entrySet()) { + String device = entry.getKey(); + boolean isFriend = entry.getValue(); // Loki - This also prevents read receipts from being sent in group chats as they don't maintain a friend request status if (isFriend) { - ApplicationContext.getInstance(context).getJobManager().add(new SendReadReceiptJob(Address.fromSerialized(devicePublicKey), timestamps)); + ApplicationContext.getInstance(context).getJobManager().add(new SendReadReceiptJob(Address.fromSerialized(device), timestamps)); } - return Unit.INSTANCE; - }); + } } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 58334eeb48..660da23c4d 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -44,7 +44,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; @@ -63,6 +63,8 @@ import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import java.io.IOException; +import java.util.Map; +import java.util.Set; import kotlin.Unit; @@ -75,24 +77,20 @@ public class MessageSender { sendBackgroundMessage(context, contactHexEncodedPublicKey); // Go through the other devices and only send background messages if we're friends or we have received friend request - LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); - storageAPI.getAllDevicePublicKeys(contactHexEncodedPublicKey).success(devices -> { - for (String device : devices) { - // Don't send message to the device we already have sent to - if (device.equals(contactHexEncodedPublicKey)) { continue; } - Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false); - long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); - if (threadID < 0) { continue; } - LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); - if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { - sendBackgroundMessage(context, device); - } - - // TODO: Do we want to send a custom FR Message if we're not friends and we haven't received a friend request? + Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(contactHexEncodedPublicKey); + for (String device : devices) { + // Don't send message to the device we already have sent to + if (device.equals(contactHexEncodedPublicKey)) { continue; } + Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false); + long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); + if (threadID < 0) { continue; } + LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); + if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { + sendBackgroundMessage(context, device); } - return Unit.INSTANCE; - }); + // TODO: Do we want to send a custom FR Message if we're not friends and we haven't received a friend request? + } } public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) { @@ -198,20 +196,16 @@ public class MessageSender { final byte[] message, final int ttl) { String ourPublicKey = TextSecurePreferences.getLocalNumber(context); - LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); + Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey); + for (String device : devices) { + // Don't send to ourselves + if (device.equals(ourPublicKey)) { continue; } - storageAPI.getAllDevicePublicKeys(ourPublicKey).success(devices -> { - for (String device : devices) { - // Don't send to ourselves - if (device.equals(ourPublicKey)) { continue; } - - // Create a send job for our device - Address address = Address.fromSerialized(device); - jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl)); - } - return Unit.INSTANCE; - }); + // Create a send job for our device + Address address = Address.fromSerialized(device); + jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl)); + } } public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) { @@ -260,7 +254,6 @@ public class MessageSender { } private static void sendTextPush(Context context, Recipient recipient, long messageId) { - LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); // Just send the message normally if it's a group message @@ -271,39 +264,40 @@ public class MessageSender { } // Note to self - boolean isNoteToSelf = MultiDeviceUtilitiesKt.isOneOfOurDevices(context, recipient.getAddress()); + boolean isNoteToSelf = MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()); if (isNoteToSelf) { jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); return; } - boolean[] hasSentSyncMessage = { false }; + boolean hasSentSyncMessage = false; - MultiDeviceUtilitiesKt.getAllDevicePublicKeys(context, recipientPublicKey, storageAPI, (devicePublicKey, isFriend, friendCount) -> { - Util.runOnMain(() -> { - Address address = Address.fromSerialized(devicePublicKey); - long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; + Map devices = MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey); + int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); + for (Map.Entry entry : devices.entrySet()) { + String devicePublicKey = entry.getKey(); + boolean isFriend = entry.getValue(); - if (isFriend) { - // Send a normal message if the user is friends with the recipient - // We should also send a sync message if we haven't already sent one - boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && MultiDeviceUtilitiesKt.shouldSendSycMessage(context, address); - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); - hasSentSyncMessage[0] = shouldSendSyncMessage; - } else { - // Send friend requests to non friends. If the user is friends with any - // of the devices then send out a default friend request message. - boolean isFriendsWithAny = (friendCount > 0); - String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); - } - }); - return Unit.INSTANCE; - }); + Address address = Address.fromSerialized(devicePublicKey); + long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; + + if (isFriend) { + // Send a normal message if the user is friends with the recipient + // We should also send a sync message if we haven't already sent one + boolean shouldSendSyncMessage = !hasSentSyncMessage && MultiDeviceUtilities.shouldSendSycMessage(context, address); + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); + hasSentSyncMessage = shouldSendSyncMessage; + } else { + // Send friend requests to non friends. If the user is friends with any + // of the devices then send out a default friend request message. + boolean isFriendsWithAny = (friendCount > 0); + String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); + } + } } private static void sendMediaPush(Context context, Recipient recipient, long messageId) { - LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); // Just send the message normally if it's a group message @@ -314,36 +308,37 @@ public class MessageSender { } // Note to self - boolean isNoteToSelf = MultiDeviceUtilitiesKt.isOneOfOurDevices(context, recipient.getAddress()); + boolean isNoteToSelf = MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()); if (isNoteToSelf) { jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); return; } - boolean[] hasSentSyncMessage = { false }; + boolean hasSentSyncMessage = false; - MultiDeviceUtilitiesKt.getAllDevicePublicKeys(context, recipientPublicKey, storageAPI, (devicePublicKey, isFriend, friendCount) -> { - Util.runOnMain(() -> { - Address address = Address.fromSerialized(devicePublicKey); - long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; + Map devices = MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey); + int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); + for (Map.Entry entry : devices.entrySet()) { + String devicePublicKey = entry.getKey(); + boolean isFriend = entry.getValue(); - if (isFriend) { - // Send a normal message if the user is friends with the recipient - // We should also send a sync message if we haven't already sent one - boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && MultiDeviceUtilitiesKt.shouldSendSycMessage(context, address); - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); - hasSentSyncMessage[0] = shouldSendSyncMessage; - } else { - // Send friend requests to non friends. If the user is friends with any - // of the devices then send out a default friend request message. - boolean isFriendsWithAny = friendCount > 0; - String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false); - } - }); - return Unit.INSTANCE; - }); + Address address = Address.fromSerialized(devicePublicKey); + long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; + if (isFriend) { + // Send a normal message if the user is friends with the recipient + // We should also send a sync message if we haven't already sent one + boolean shouldSendSyncMessage = !hasSentSyncMessage && MultiDeviceUtilities.shouldSendSycMessage(context, address); + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); + hasSentSyncMessage = shouldSendSyncMessage; + } else { + // Send friend requests to non friends. If the user is friends with any + // of the devices then send out a default friend request message. + boolean isFriendsWithAny = (friendCount > 0); + String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false); + } + } } private static void sendGroupPush(Context context, Recipient recipient, long messageId, Address filterAddress) { From 359363184a37d6d8bcffbe84f83a61878536be57 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 30 Oct 2019 10:13:19 +1100 Subject: [PATCH 16/64] More cleanup. --- .../securesms/ApplicationContext.java | 20 ++----------------- .../push/MessageSenderEventListener.java | 5 ++++- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 70c76ce3ec..a402ca5a9b 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -147,7 +147,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc private LokiPublicChatAPI lokiPublicChatAPI = null; public SignalCommunicationModule communicationModule; public MixpanelAPI mixpanel; - private BroadcastReceiver syncMessageEventReceiver; private volatile boolean isAppVisible; @@ -200,22 +199,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc if (setUpStorageAPIIfNeeded()) { LokiStorageAPI.Companion.getShared().updateUserDeviceMappings(); } - - // Loki - Event listener - syncMessageEventReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Send the sync message to our devices - long messageID = intent.getLongExtra(LokiMessageSyncEvent.MESSAGE_ID, -1); - long timestamp = intent.getLongExtra(LokiMessageSyncEvent.TIMESTAMP, -1); - byte[] message = intent.getByteArrayExtra(LokiMessageSyncEvent.SYNC_MESSAGE); - int ttl = intent.getIntExtra(LokiMessageSyncEvent.TTL, -1); - if (messageID > 0 && timestamp > 0 && message != null && ttl > 0) { - MessageSender.sendSyncMessageToOurDevices(context, messageID, timestamp, message, ttl); - } - } - }; - LocalBroadcastManager.getInstance(this).registerReceiver(syncMessageEventReceiver, new IntentFilter(LokiMessageSyncEvent.MESSAGE_SYNC_EVENT)); } @Override @@ -243,7 +226,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc @Override public void onTerminate() { stopKovenant(); - LocalBroadcastManager.getInstance(this).unregisterReceiver(syncMessageEventReceiver); super.onTerminate(); } @@ -532,6 +514,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc } public void createDefaultPublicChatsIfNeeded() { + /* List defaultPublicChats = LokiPublicChatAPI.Companion.getDefaultChats(BuildConfig.DEBUG); for (LokiPublicChat publiChat : defaultPublicChats) { long threadID = GroupManager.getThreadId(publiChat.getId(), this); @@ -548,6 +531,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc TextSecurePreferences.setBooleanPreference(this, migrationKey, true); } } + */ } public void createRSSFeedsIfNeeded() { diff --git a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java index 5cd99e5fcb..cca8b0b7b1 100644 --- a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java +++ b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java @@ -5,6 +5,7 @@ import android.content.Context; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.LokiMessageSyncEvent; +import org.thoughtcrime.securesms.sms.MessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -25,7 +26,9 @@ public class MessageSenderEventListener implements SignalServiceMessageSender.Ev @Override public void onSyncEvent(long messageID, long timestamp, byte[] message, int ttl) { - LokiMessageSyncEvent.INSTANCE.broadcastSecurityUpdateEvent(context, messageID, timestamp, message, ttl); + if (messageID > 0 && timestamp > 0 && message != null && ttl > 0) { + MessageSender.sendSyncMessageToOurDevices(context, messageID, timestamp, message, ttl); + } } @Override public void onFriendRequestSending(long messageID, long threadID) { From 15f7e5ab37c990a7231489a4b312597677d4d041 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 30 Oct 2019 10:55:45 +1100 Subject: [PATCH 17/64] Don't send an empty message body when sending a background message. --- src/org/thoughtcrime/securesms/sms/MessageSender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 660da23c4d..91e8bfc8ed 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -97,7 +97,7 @@ public class MessageSender { Util.runOnMain(() -> { SignalServiceMessageSender messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender(); SignalServiceAddress address = new SignalServiceAddress(contactHexEncodedPublicKey); - SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), ""); + SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), null); try { // Try send to the original person messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter From 4072f506b5554b650323fe2a0a06186b7229b34b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 30 Oct 2019 15:53:23 +1100 Subject: [PATCH 18/64] Oops. --- src/org/thoughtcrime/securesms/ApplicationContext.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index a402ca5a9b..45d1ab5832 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -514,7 +514,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc } public void createDefaultPublicChatsIfNeeded() { - /* List defaultPublicChats = LokiPublicChatAPI.Companion.getDefaultChats(BuildConfig.DEBUG); for (LokiPublicChat publiChat : defaultPublicChats) { long threadID = GroupManager.getThreadId(publiChat.getId(), this); @@ -531,7 +530,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc TextSecurePreferences.setBooleanPreference(this, migrationKey, true); } } - */ } public void createRSSFeedsIfNeeded() { From 31ad7a40de0d83f64515dca57bb73d88afda84c5 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 31 Oct 2019 11:36:52 +1100 Subject: [PATCH 19/64] Stop main thread from being blocked. Refactoring. --- .../components/TypingStatusSender.java | 17 +-- .../conversation/ConversationActivity.java | 75 ++++++----- .../securesms/loki/MultiDeviceUtilities.kt | 30 +++-- .../securesms/loki/PushMessageSyncSendJob.kt | 1 + .../securesms/loki/SeedActivity.kt | 1 - .../notifications/MarkReadReceiver.java | 21 +-- .../securesms/sms/MessageSender.java | 127 +++++++----------- .../securesms/util/TextSecurePreferences.java | 2 +- 8 files changed, 138 insertions(+), 136 deletions(-) diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java index b813f5c63e..dd28616d8e 100644 --- a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -91,15 +91,16 @@ public class TypingStatusSender { ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted)); return; } - - Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(recipient.getAddress().serialize()); - for (String device : devices) { - Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false); - long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient); - if (deviceThreadID > -1) { - ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(deviceThreadID, typingStarted)); + LokiStorageAPI.shared.getAllDevicePublicKeysAsync(recipient.getAddress().serialize()).success(devices -> { + for (String device : devices) { + Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false); + long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient); + if (deviceThreadID > -1) { + ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(deviceThreadID, typingStarted)); + } } - } + return Unit.INSTANCE; + }); } private class StartRunnable implements Runnable { diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index e3f6769fe3..2c80d636f7 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -249,7 +249,11 @@ import java.util.concurrent.atomic.AtomicInteger; import kotlin.Unit; import network.loki.messenger.R; +import nl.komponents.kovenant.Kovenant; +import nl.komponents.kovenant.KovenantApi; +import nl.komponents.kovenant.Promise; +import static nl.komponents.kovenant.KovenantApi.task; import static org.thoughtcrime.securesms.TransportOption.Type; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; @@ -2185,44 +2189,50 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void handleThreadFriendRequestStatusChanged(long threadID) { - Util.runOnMain(() -> { - boolean shouldUpdateInputPanel = true; if (threadID != this.threadId) { Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID); if (threadRecipient != null && !threadRecipient.isGroupRecipient()) { - // We should update our input if this thread is a part of the other threads device - Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(threadRecipient.getAddress().serialize()); - shouldUpdateInputPanel = devices.contains(recipient.getAddress().serialize()); - } else { - shouldUpdateInputPanel = false; + LokiStorageAPI.shared.getAllDevicePublicKeysAsync(threadRecipient.getAddress().serialize()).success(devices -> { + // We should update our input if this thread is a part of the other threads device + if (devices.contains(recipient.getAddress().serialize())) { + this.updateInputPanel(); + } + return Unit.INSTANCE; + }); } + return; } - if (shouldUpdateInputPanel) { - this.updateInputPanel(); - } - }); + + this.updateInputPanel(); } private void updateInputPanel() { - if (recipient.isGroupRecipient()) { + if (recipient.isGroupRecipient() || isNoteToSelf()) { + setInputPanelEnabled(true); + return; + } + + task(() -> { + // Run the functions below in a background thread since they are blocking + return MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient) || !hasPendingFriendRequestWithAnyLinkedDevice(); + }).success(shouldEnableInput -> { + setInputPanelEnabled(shouldEnableInput); + return Unit.INSTANCE; + }); + } + + private void setInputPanelEnabled(boolean enabled) { + Util.runOnMain(() -> { updateToggleButtonState(); - inputPanel.setEnabled(true); - inputPanel.composeText.requestFocus(); - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(inputPanel.composeText, 0); - } - boolean hasPendingFriendRequest = !recipient.isGroupRecipient() && hasPendingFriendRequestWithAnyLinkedDevice(); - boolean isFriendsWithAnyLinkedDevices = MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient); - boolean shouldEnableInput = isFriendsWithAnyLinkedDevices || !hasPendingFriendRequest; - updateToggleButtonState(); - inputPanel.setEnabled(shouldEnableInput); - int hintID = shouldEnableInput ? R.string.activity_conversation_default_hint : R.string.activity_conversation_pending_friend_request_hint; - inputPanel.setHint(getResources().getString(hintID)); - if (shouldEnableInput) { - inputPanel.composeText.requestFocus(); - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(inputPanel.composeText, 0); - } + int hintID = enabled ? R.string.activity_conversation_default_hint : R.string.activity_conversation_pending_friend_request_hint; + inputPanel.setHint(getResources().getString(hintID)); + inputPanel.setEnabled(enabled); + if (enabled) { + inputPanel.composeText.requestFocus(); + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(inputPanel.composeText, 0); + } + }); } private void sendMessage() { @@ -2428,7 +2438,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void updateToggleButtonState() { // Don't allow attachments if we're not friends LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId); - if (!recipient.isGroupRecipient() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { + if (!isNoteToSelf() && !recipient.isGroupRecipient() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { buttonToggle.display(sendButton); quickAttachmentToggle.hide(); inlineAttachmentToggle.hide(); @@ -3047,6 +3057,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public boolean hasPendingFriendRequestWithAnyLinkedDevice() { if (recipient.isGroupRecipient()) return false; + // This call will block the thread that is being run on! be careful Map map = MultiDeviceUtilities.getAllDeviceFriendRequestStatuses(this, recipient.getAddress().serialize()); for (LokiThreadFriendRequestStatus status : map.values()) { if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { @@ -3056,5 +3067,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return false; } + + public boolean isNoteToSelf() { + return TextSecurePreferences.getLocalNumber(this).equalsIgnoreCase(recipient.getAddress().serialize()); + } // endregion } diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index d3c8b17cad..ccee4f7c2c 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -4,6 +4,8 @@ package org.thoughtcrime.securesms.loki import android.content.Context import android.os.Handler import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.map +import nl.komponents.kovenant.toFailVoid import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address @@ -20,6 +22,10 @@ import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.utilities.retryIfNeeded +/* + All functions within this class, excluding the ones which return promises, BLOCK the thread! Don't run them on the main thread! + */ + fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Map { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val keys = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey) @@ -33,18 +39,20 @@ fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: Str return map } -fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Map { +fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise, Unit> { val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) - val devices = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).toMutableSet() - if (hexEncodedPublicKey != userHexEncodedPublicKey) { - devices.remove(userHexEncodedPublicKey) - } - val friends = getFriendPublicKeys(context, devices) - val map = mutableMapOf() - for (device in devices) { - map[device] = friends.contains(device) - } - return map + return LokiStorageAPI.shared.getAllDevicePublicKeysAsync(hexEncodedPublicKey).map { keys -> + val devices = keys.toMutableSet() + if (hexEncodedPublicKey != userHexEncodedPublicKey) { + devices.remove(userHexEncodedPublicKey) + } + val friends = getFriendPublicKeys(context, devices) + val friendMap = mutableMapOf() + for (device in devices) { + friendMap[device] = friends.contains(device) + } + friendMap + }.toFailVoid() } fun getFriendCount(context: Context, devices: Set): Int { diff --git a/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt index 9fb7f9d75e..f5848cbb07 100644 --- a/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt @@ -39,6 +39,7 @@ class PushMessageSyncSendJob private constructor( 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(3) .build(), diff --git a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt index 8a537e1de6..1d494a8711 100644 --- a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt @@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.util.Hex import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.curve25519.Curve25519 import org.whispersystems.libsignal.util.KeyHelper -import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.crypto.MnemonicCodec import org.whispersystems.signalservice.loki.utilities.Analytics diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index ff5a1477ee..f92ff642f0 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.service.ExpiringMessageManager; +import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import java.util.LinkedList; @@ -29,6 +30,7 @@ import java.util.List; import java.util.Map; import kotlin.Unit; +import kotlin.contracts.Returns; public class MarkReadReceiver extends BroadcastReceiver { @@ -91,16 +93,17 @@ public class MarkReadReceiver extends BroadcastReceiver { for (Address address : addressMap.keySet()) { List timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); - - Map devices = MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, address.serialize()); - for (Map.Entry entry : devices.entrySet()) { - String device = entry.getKey(); - boolean isFriend = entry.getValue(); - // Loki - This also prevents read receipts from being sent in group chats as they don't maintain a friend request status - if (isFriend) { - ApplicationContext.getInstance(context).getJobManager().add(new SendReadReceiptJob(Address.fromSerialized(device), timestamps)); + MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, address.serialize()).success(devices -> { + for (Map.Entry entry : devices.entrySet()) { + String device = entry.getKey(); + boolean isFriend = entry.getValue(); + // Loki - This also prevents read receipts from being sent in group chats as they don't maintain a friend request status + if (isFriend) { + Util.runOnMain(() -> ApplicationContext.getInstance(context).getJobManager().add(new SendReadReceiptJob(Address.fromSerialized(device), timestamps))); + } } - } + return Unit.INSTANCE; + }); } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 91e8bfc8ed..e15cda3de8 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -72,6 +72,8 @@ public class MessageSender { private static final String TAG = MessageSender.class.getSimpleName(); + private enum MessageType { TEXT, MEDIA } + public static void sendBackgroundMessageToAllDevices(Context context, String contactHexEncodedPublicKey) { // Send the background message to the original pubkey sendBackgroundMessage(context, contactHexEncodedPublicKey); @@ -254,91 +256,64 @@ public class MessageSender { } private static void sendTextPush(Context context, Recipient recipient, long messageId) { - JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - - // Just send the message normally if it's a group message - String recipientPublicKey = recipient.getAddress().serialize(); - if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey)) { - jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); - return; - } - - // Note to self - boolean isNoteToSelf = MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()); - if (isNoteToSelf) { - jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); - return; - } - - boolean hasSentSyncMessage = false; - - Map devices = MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey); - int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); - for (Map.Entry entry : devices.entrySet()) { - String devicePublicKey = entry.getKey(); - boolean isFriend = entry.getValue(); - - Address address = Address.fromSerialized(devicePublicKey); - long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; - - if (isFriend) { - // Send a normal message if the user is friends with the recipient - // We should also send a sync message if we haven't already sent one - boolean shouldSendSyncMessage = !hasSentSyncMessage && MultiDeviceUtilities.shouldSendSycMessage(context, address); - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); - hasSentSyncMessage = shouldSendSyncMessage; - } else { - // Send friend requests to non friends. If the user is friends with any - // of the devices then send out a default friend request message. - boolean isFriendsWithAny = (friendCount > 0); - String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); - } - } + sendMessagePush(context, MessageType.TEXT, recipient, messageId); } private static void sendMediaPush(Context context, Recipient recipient, long messageId) { + sendMessagePush(context, MessageType.MEDIA, recipient, messageId); + } + + private static void sendMessagePush(Context context, MessageType type, Recipient recipient, long messageId) { JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - // Just send the message normally if it's a group message + // Just send the message normally if it's a group message or we're sending to one of our devices + // TODO: Test how badly this will block the thread String recipientPublicKey = recipient.getAddress().serialize(); - if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey)) { - PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false); - return; - } - - // Note to self - boolean isNoteToSelf = MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()); - if (isNoteToSelf) { - jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); - return; - } - - boolean hasSentSyncMessage = false; - - Map devices = MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey); - int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); - for (Map.Entry entry : devices.entrySet()) { - String devicePublicKey = entry.getKey(); - boolean isFriend = entry.getValue(); - - Address address = Address.fromSerialized(devicePublicKey); - long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; - - if (isFriend) { - // Send a normal message if the user is friends with the recipient - // We should also send a sync message if we haven't already sent one - boolean shouldSendSyncMessage = !hasSentSyncMessage && MultiDeviceUtilities.shouldSendSycMessage(context, address); - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); - hasSentSyncMessage = shouldSendSyncMessage; + if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey) || MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress())) { + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false); } else { - // Send friend requests to non friends. If the user is friends with any - // of the devices then send out a default friend request message. - boolean isFriendsWithAny = (friendCount > 0); - String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false); + jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); } + return; } + + boolean[] hasSentSyncMessage = { false }; + MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey).success(devices -> { + int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); + Util.runOnMain(() -> { + for (Map.Entry entry : devices.entrySet()) { + String devicePublicKey = entry.getKey(); + boolean isFriend = entry.getValue(); + + Address address = Address.fromSerialized(devicePublicKey); + long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; + + if (isFriend) { + // Send a normal message if the user is friends with the recipient + // We should also send a sync message if we haven't already sent one + boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && MultiDeviceUtilities.shouldSendSycMessage(context, address); + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); + } else { + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); + } + hasSentSyncMessage[0] = shouldSendSyncMessage; + } else { + // Send friend requests to non friends. If the user is friends with any + // of the devices then send out a default friend request message. + boolean isFriendsWithAny = (friendCount > 0); + String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false); + } else { + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); + } + } + } + }); + return Unit.INSTANCE; + }); } private static void sendGroupPush(Context context, Recipient recipient, long messageId, Address filterAddress) { diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 46499f05f0..bd72a58b59 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -640,7 +640,7 @@ public class TextSecurePreferences { } public static void setLocalNumber(Context context, String localNumber) { - setStringPreference(context, LOCAL_NUMBER_PREF, localNumber); + setStringPreference(context, LOCAL_NUMBER_PREF, localNumber.toLowerCase()); } public static void removeLocalNumber(Context context) { From d7571d237a73ab7be51bc665cf6b71e32aaf1585 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 31 Oct 2019 12:12:20 +1100 Subject: [PATCH 20/64] Skip friend request handling if we received a group message. --- .../thoughtcrime/securesms/jobs/PushDecryptJob.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 60c846da20..e7e893ba11 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -1080,7 +1080,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private void acceptFriendRequestIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) { // If we get anything other than a friend request, we can assume that we have a session with the other user - if (envelope.isFriendRequest()) { return; } + if (envelope.isFriendRequest() || isGroupChatMessage(content)) { return; } becomeFriendsWithContact(content.getSender()); } @@ -1100,7 +1100,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { - if (!envelope.isFriendRequest()) { return; } + if (!envelope.isFriendRequest() || isGroupChatMessage(message)) { return; } // This handles the case where another user sends us a regular message without authorisation boolean shouldBecomeFriends = MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context); if (shouldBecomeFriends) { @@ -1602,6 +1602,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType { return false; } + private boolean isGroupChatMessage(SignalServiceContent content) { + return content.getDataMessage().isPresent() && isGroupChatMessage(content.getDataMessage().get()); + } + + private boolean isGroupChatMessage(SignalServiceDataMessage message) { + return message.isGroupUpdate(); + } + private void resetRecipientToPush(@NonNull Recipient recipient) { if (recipient.isForceSmsSelection()) { DatabaseFactory.getRecipientDatabase(context).setForceSmsSelection(recipient, false); From bdc0ed36ebd53978f9eee37099a8b4b3bd2830bc Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 31 Oct 2019 13:20:07 +1100 Subject: [PATCH 21/64] Process public chat messages in a background thread and not the promise thread. --- src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 3e6685e429..71a577db75 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -25,6 +25,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage +import org.whispersystems.signalservice.loki.utilities.successBackground class LokiPublicChatPoller(private val context: Context, private val group: LokiPublicChat) { private val handler = Handler() @@ -197,7 +198,8 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki finalize() } } - api.getMessages(group.channel, group.server).success { messages -> + api.getMessages(group.channel, group.server).successBackground { messages -> + // Process messages in the background messages.forEach { message -> if (message.hexEncodedPublicKey != userHexEncodedPublicKey) { processIncomingMessage(message) From f6b039ee7756f1a686ec6f08f83bc38ebf006ca1 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 31 Oct 2019 16:09:29 +1100 Subject: [PATCH 22/64] Disabled unused permissions --- AndroidManifest.xml | 10 ++++++++-- .../securesms/ContactSelectionListFragment.java | 2 ++ .../securesms/ConversationListActivity.java | 2 ++ .../securesms/contacts/ContactsCursorLoader.java | 3 +++ .../securesms/database/helpers/ClassicOpenHelper.java | 3 ++- .../securesms/jobs/MultiDeviceContactUpdateJob.java | 2 ++ .../thoughtcrime/securesms/mms/AttachmentManager.java | 2 ++ .../securesms/search/SearchRepository.java | 3 +++ .../securesms/service/WebRtcCallService.java | 2 ++ 9 files changed, 26 insertions(+), 3 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7b88684d51..3b18e8b60b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -11,9 +11,11 @@ + @@ -26,10 +28,12 @@ + + - + diff --git a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java index 18c46d3ae5..c4878b861c 100644 --- a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -178,6 +178,7 @@ public class ContactSelectionListFragment extends Fragment showContactsDescription.setText(R.string.contact_selection_list_fragment__signal_needs_access_to_your_contacts_in_order_to_display_them); showContactsButton.setVisibility(View.VISIBLE); + /* showContactsButton.setOnClickListener(v -> { Permissions.with(this) .request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS) @@ -190,6 +191,7 @@ public class ContactSelectionListFragment extends Fragment }) .execute(); }); + */ } public void setQueryFilter(String filter) { diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index d51d1b3ce9..3858149e94 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -142,6 +142,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit private void initializeSearchListener() { searchAction.setOnClickListener(v -> { + /* Loki - We don't need contact permissions Permissions.with(this) .request(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS) .ifNecessary() @@ -149,6 +150,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit searchAction.getY() + (searchAction.getHeight() / 2))) .withPermanentDenialDialog(getString(R.string.ConversationListActivity_signal_needs_contacts_permission_in_order_to_search_your_contacts_but_it_has_been_permanently_denied)) .execute(); + */ }); searchToolbar.setListener(new SearchToolbar.SearchListener() { diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index 807b229ed4..a95cc0330a 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -187,6 +187,8 @@ public class ContactsCursorLoader extends CursorLoader { ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(getContext()); List cursorList = new ArrayList<>(2); + return cursorList; + /* if (!Permissions.hasAny(getContext(), Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { return cursorList; } @@ -201,6 +203,7 @@ public class ContactsCursorLoader extends CursorLoader { cursorList.add(filterNonPushContacts(contactsDatabase.querySystemContacts(filter))); } return cursorList; + */ } private Cursor getGroupsCursor() { diff --git a/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java index 821befc565..5ad89598cf 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java @@ -1265,7 +1265,7 @@ public class ClassicOpenHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_contact_photo TEXT DEFAULT NULL"); db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_phone_label TEXT DEFAULT NULL"); db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_contact_uri TEXT DEFAULT NULL"); - + /* if (Permissions.hasAny(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { @@ -1295,6 +1295,7 @@ public class ClassicOpenHelper extends SQLiteOpenHelper { } } } + */ } db.setTransactionSuccessful(); diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index a10caa0edf..953e6b623c 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -158,6 +158,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy private void generateFullContactUpdate() throws IOException, UntrustedIdentityException, NetworkException { + /* Loki - Disabled if (!Permissions.hasAny(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { Log.w(TAG, "No contact permissions, skipping multi-device contact update..."); return; @@ -214,6 +215,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy } finally { if (contactDataFile != null) contactDataFile.delete(); } + */ } @Override diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index f7054afa23..4483e20d2a 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -410,6 +410,7 @@ public class AttachmentManager { } public static void selectLocation(Activity activity, int requestCode) { + /* Loki - Enable again once we have location sharing Permissions.with(activity) .request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION) .ifNecessary() @@ -422,6 +423,7 @@ public class AttachmentManager { } }) .execute(); + */ } public static void selectGif(Activity activity, int requestCode, boolean isForMms) { diff --git a/src/org/thoughtcrime/securesms/search/SearchRepository.java b/src/org/thoughtcrime/securesms/search/SearchRepository.java index a88107b211..956b473e70 100644 --- a/src/org/thoughtcrime/securesms/search/SearchRepository.java +++ b/src/org/thoughtcrime/securesms/search/SearchRepository.java @@ -121,6 +121,8 @@ public class SearchRepository { } private CursorList queryContacts(String query) { + return CursorList.emptyList(); + /* Loki - Don't need contact permissions if (!Permissions.hasAny(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { return CursorList.emptyList(); } @@ -130,6 +132,7 @@ public class SearchRepository { MergeCursor contacts = new MergeCursor(new Cursor[]{ textSecureContacts, systemContacts }); return new CursorList<>(contacts, new RecipientModelBuilder(context)); + */ } private CursorList queryConversations(@NonNull String query) { diff --git a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java index 9709834069..257e02f4de 100644 --- a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -380,9 +380,11 @@ public class WebRtcCallService extends Service implements InjectableType, try { boolean isSystemContact = false; + /* if (Permissions.hasAny(WebRtcCallService.this, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { isSystemContact = ContactAccessor.getInstance().isSystemContact(WebRtcCallService.this, recipient.getAddress().serialize()); } + */ boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this); From 789aa244b5594407d904178f7eb1f30cf1c9c12c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 1 Nov 2019 12:15:40 +1100 Subject: [PATCH 23/64] Use promises instead of blocking the thread. --- .../components/TypingStatusSender.java | 2 +- .../conversation/ConversationActivity.java | 28 ++--- .../securesms/jobs/PushDecryptJob.java | 32 +++-- .../securesms/jobs/PushMediaSendJob.java | 3 +- .../securesms/jobs/PushTextSendJob.java | 3 +- .../securesms/jobs/TypingSendJob.java | 6 +- .../securesms/loki/FriendRequestHandler.kt | 37 +++--- .../securesms/loki/MultiDeviceUtilities.kt | 119 +++++++++++------- .../securesms/sms/MessageSender.java | 79 +++++++----- 9 files changed, 182 insertions(+), 127 deletions(-) diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java index dd28616d8e..927b158e42 100644 --- a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -91,7 +91,7 @@ public class TypingStatusSender { ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted)); return; } - LokiStorageAPI.shared.getAllDevicePublicKeysAsync(recipient.getAddress().serialize()).success(devices -> { + LokiStorageAPI.shared.getAllDevicePublicKeys(recipient.getAddress().serialize()).success(devices -> { for (String device : devices) { Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false); long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 2c80d636f7..d9ec0a0154 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -127,6 +127,7 @@ import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.database.Database; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -2192,7 +2193,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (threadID != this.threadId) { Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID); if (threadRecipient != null && !threadRecipient.isGroupRecipient()) { - LokiStorageAPI.shared.getAllDevicePublicKeysAsync(threadRecipient.getAddress().serialize()).success(devices -> { + LokiStorageAPI.shared.getAllDevicePublicKeys(threadRecipient.getAddress().serialize()).success(devices -> { // We should update our input if this thread is a part of the other threads device if (devices.contains(recipient.getAddress().serialize())) { this.updateInputPanel(); @@ -2212,10 +2213,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return; } - task(() -> { - // Run the functions below in a background thread since they are blocking - return MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient) || !hasPendingFriendRequestWithAnyLinkedDevice(); - }).success(shouldEnableInput -> { + // It could take a while before our promise resolves, so we assume the best case + LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId); + boolean isPending = friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED; + setInputPanelEnabled(!isPending); + + // This promise correctly updates the UI for multidevice + MultiDeviceUtilities.shouldEnableUserInput(this, recipient).success(shouldEnableInput -> { setInputPanelEnabled(shouldEnableInput); return Unit.INSTANCE; }); @@ -3054,20 +3058,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity updateInputPanel(); } - public boolean hasPendingFriendRequestWithAnyLinkedDevice() { - if (recipient.isGroupRecipient()) return false; - - // This call will block the thread that is being run on! be careful - Map map = MultiDeviceUtilities.getAllDeviceFriendRequestStatuses(this, recipient.getAddress().serialize()); - for (LokiThreadFriendRequestStatus status : map.values()) { - if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { - return true; - } - } - - return false; - } - public boolean isNoteToSelf() { return TextSecurePreferences.getLocalNumber(this).equalsIgnoreCase(recipient.getAddress().serialize()); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index e7e893ba11..332375246a 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -134,6 +134,7 @@ import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestS import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.security.MessageDigest; import java.security.SecureRandom; @@ -1094,15 +1095,19 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // it must be a friend request accepted message. Declining a friend request doesn't send a message. lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); // Update the last message if needed - String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey); - long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false)); - FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> { + Util.runOnMain(() -> { + long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false)); + FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + }); + return Unit.INSTANCE; + }); } private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { if (!envelope.isFriendRequest() || isGroupChatMessage(message)) { return; } // This handles the case where another user sends us a regular message without authorisation - boolean shouldBecomeFriends = MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context); + boolean shouldBecomeFriends = PromiseUtil.get(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), false); if (shouldBecomeFriends) { // Become friends AND update the message they sent becomeFriendsWithContact(content.getSender()); @@ -1113,9 +1118,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Recipient originalRecipient = getMessageDestination(content, message); Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); - SmsDatabase smsMessageDatabase = DatabaseFactory.getSmsDatabase(context); - MmsDatabase mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context); - LokiMessageDatabase lokiMessageDatabase= DatabaseFactory.getLokiMessageDatabase(context); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient); long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient); @@ -1532,7 +1534,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private Recipient getPrimaryDeviceRecipient(String recipient) { try { - String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient); + String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient).get(); String publicKey = (primaryDevice != null) ? primaryDevice : recipient; // If the public key matches our primary device then we need to forward the message to ourselves (Note to self) String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); @@ -1593,10 +1595,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } else if (content.getCallMessage().isPresent() || content.getTypingMessage().isPresent()) { return sender.isBlocked(); } else if (content.getSyncMessage().isPresent()) { - // We should ignore a sync message if the sender is not one of our devices - boolean isOurDevice = MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()); - if (!isOurDevice) { Log.w(TAG, "Got a sync message from a device that is not ours!."); } - return !isOurDevice; + try { + // We should ignore a sync message if the sender is not one of our devices + boolean isOurDevice = MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()).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; diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 9a10d5f797..408e353d9c 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -45,6 +45,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.FileNotFoundException; import java.io.IOException; @@ -281,7 +282,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { LokiSyncMessage syncMessage = null; if (shouldSendSyncMessage) { // Set the sync message destination the primary device, this way it will show that we sent a message to the primary device and not a secondary device - String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()); + String primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null); SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); // We also need to use the original message id and not -1 syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 7c93b82c8b..bfd4839e36 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -32,6 +32,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.IOException; @@ -232,7 +233,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { LokiSyncMessage syncMessage = null; if (shouldSendSyncMessage) { // Set the sync message destination to the primary device, this way it will show that we sent a message to the primary device and not a secondary device - String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()); + String primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null); SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); // We also need to use the original message id and not -1 syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); diff --git a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java index 023afda792..babe7fe04e 100644 --- a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -20,6 +20,7 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage.Action; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.util.Collections; import java.util.List; @@ -98,7 +99,10 @@ public class TypingSendJob extends BaseJob implements InjectableType { SignalServiceTypingMessage typingMessage = new SignalServiceTypingMessage(typing ? Action.STARTED : Action.STOPPED, System.currentTimeMillis(), groupId); // Loki - Don't send typing indicators in group chats or to ourselves - if (!recipient.isGroupRecipient() && !MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress())) { + if (recipient.isGroupRecipient()) { return; } + + boolean isOurDevice = PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false); + if (!isOurDevice) { messageSender.sendTyping(0, addresses, unidentifiedAccess, typingMessage); } } diff --git a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt index 70a0850f36..732588d2a2 100644 --- a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt +++ b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt @@ -5,6 +5,7 @@ import nl.komponents.kovenant.Promise import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.successUi import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.util.Util import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus @@ -34,8 +35,10 @@ object FriendRequestHandler { if (type == ActionType.Sending) { // We only want to update message status if we aren't friends with another of their devices // This avoids spam in the ui where it would keep telling the user that they sent a friend request on every single message - if (!isFriendsWithAnyLinkedDevice(context, recipient) && friendRequestStatus == LokiMessageFriendRequestStatus.NONE) { - messageDatabase.setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING) + isFriendsWithAnyLinkedDevice(context, recipient).successUi { isFriends -> + if (!isFriends && friendRequestStatus == LokiMessageFriendRequestStatus.NONE) { + messageDatabase.setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING) + } } } else if (friendRequestStatus != LokiMessageFriendRequestStatus.NONE) { // Update the friend request status of the message if we have it @@ -70,23 +73,25 @@ object FriendRequestHandler { // We only want to update the last message status if we're not friends with any of their linked devices // This ensures that we don't spam the UI with accept/decline messages val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return - if (isFriendsWithAnyLinkedDevice(context, recipient)) { return } + isFriendsWithAnyLinkedDevice(context, recipient).successUi { isFriends -> + if (isFriends) { return@successUi } - // Since messages are forwarded to the primary device thread, we need to update it there - val messageCount = smsMessageDatabase.getMessageCountForThread(threadId) - val messageID = smsMessageDatabase.getIDForMessageAtIndex(threadId, messageCount - 1) // The message that was just received - if (messageID < 0) { return } + // Since messages are forwarded to the primary device thread, we need to update it there + val messageCount = smsMessageDatabase.getMessageCountForThread(threadId) + val messageID = smsMessageDatabase.getIDForMessageAtIndex(threadId, messageCount - 1) // The message that was just received + if (messageID < 0) { return@successUi } - val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context) + val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context) - // We need to go through and set all messages which are REQUEST_PENDING to NONE - smsMessageDatabase.getAllMessageIDs(threadId) - .filter { messageDatabase.getFriendRequestStatus(it) == LokiMessageFriendRequestStatus.REQUEST_PENDING } - .forEach { - messageDatabase.setFriendRequestStatus(it, LokiMessageFriendRequestStatus.NONE) - } + // We need to go through and set all messages which are REQUEST_PENDING to NONE + smsMessageDatabase.getAllMessageIDs(threadId) + .filter { messageDatabase.getFriendRequestStatus(it) == LokiMessageFriendRequestStatus.REQUEST_PENDING } + .forEach { + messageDatabase.setFriendRequestStatus(it, LokiMessageFriendRequestStatus.NONE) + } - // Set the last message to pending - messageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING) + // Set the last message to pending + messageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING) + } } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index ccee4f7c2c..f80c89c75b 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -3,9 +3,9 @@ package org.thoughtcrime.securesms.loki import android.content.Context import android.os.Handler -import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.* +import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map -import nl.komponents.kovenant.toFailVoid import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address @@ -20,28 +20,26 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus +import org.whispersystems.signalservice.loki.utilities.recover import org.whispersystems.signalservice.loki.utilities.retryIfNeeded -/* - All functions within this class, excluding the ones which return promises, BLOCK the thread! Don't run them on the main thread! - */ - -fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Map { +fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise, Exception> { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) - val keys = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey) - val map = mutableMapOf() - for (devicePublicKey in keys) { - val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false) - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device) - val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID) - map[devicePublicKey] = friendRequestStatus - } - return map + return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> + val map = mutableMapOf() + for (devicePublicKey in keys) { + val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false) + val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device) + val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID) + map[devicePublicKey] = friendRequestStatus + } + map + }.recover { mutableMapOf() } } fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise, Unit> { val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) - return LokiStorageAPI.shared.getAllDevicePublicKeysAsync(hexEncodedPublicKey).map { keys -> + return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> val devices = keys.toMutableSet() if (hexEncodedPublicKey != userHexEncodedPublicKey) { devices.remove(userHexEncodedPublicKey) @@ -59,22 +57,31 @@ fun getFriendCount(context: Context, devices: Set): Int { return getFriendPublicKeys(context, devices).count() } -fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Boolean { +fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Promise { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val storageAPI = LokiStorageAPI.shared - // If the public key doesn't have any other devices then go through regular friend request logic - val primaryDevicePublicKey = storageAPI.getPrimaryDevicePublicKey(publicKey) ?: return false - - // If this is one of our devices then we should become friends - if (isOneOfOurDevices(context, publicKey)) { - return true + // If this public key is our primary device then we should become friends + if (publicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { + return Promise.of(true) } - // If we are friends with the primary device then we should become friends - val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) - val primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) - return primaryDeviceThreadID >= 0 && lokiThreadDatabase.getFriendRequestStatus(primaryDeviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS + return storageAPI.getPrimaryDevicePublicKey(publicKey).map { primaryDevicePublicKey -> + // If the public key doesn't have any other devices then go through regular friend request logic + if (primaryDevicePublicKey == null) { + return@map false + } + + // If the primary device public key matches our primary device then we should become friends since this is our other device + if (primaryDevicePublicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { + return@map true + } + + // If we are friends with the primary device then we should become friends + val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) + val primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) + primaryDeviceThreadID >= 0 && lokiThreadDatabase.getFriendRequestStatus(primaryDeviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS + } } fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise { @@ -125,37 +132,55 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio } -fun shouldSendSycMessage(context: Context, address: Address): Boolean { +fun shouldSendSycMessage(context: Context, address: Address): Promise { if (address.isGroup || address.isEmail || address.isMmsGroup) { - return false + return Promise.of(false) } // Don't send sync messages if it's one of our devices - return !isOneOfOurDevices(context, address) + return isOneOfOurDevices(context, address).map { !it } } -fun isOneOfOurDevices(context: Context, publicKey: String): Boolean { - return isOneOfOurDevices(context, Address.fromSerialized(publicKey)) -} - -fun isOneOfOurDevices(context: Context, address: Address): Boolean { +fun isOneOfOurDevices(context: Context, address: Address): Promise { if (address.isGroup || address.isEmail || address.isMmsGroup) { - return false + return Promise.of(false) } val ourPublicKey = TextSecurePreferences.getLocalNumber(context) - val devices = LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey) - return devices.contains(address.serialize()) + return LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).map { devices -> + devices.contains(address.serialize()) + } } -fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Boolean { - if (recipient.isGroupRecipient) return true +fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Promise { + if (recipient.isGroupRecipient) { return Promise.of(true) } - val map = getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()) - for (status in map.values) { - if (status == LokiThreadFriendRequestStatus.FRIENDS) { - return true + return getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()).map { map -> + for (status in map.values) { + if (status == LokiThreadFriendRequestStatus.FRIENDS) { + return@map true + } } + false } - return false -} \ No newline at end of file +} + +fun hasPendingFriendRequestWithAnyLinkedDevice(context: Context, recipient: Recipient): Promise { + if (recipient.isGroupRecipient) { return Promise.of(false) } + + return getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()).map { map -> + for (status in map.values) { + if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { + return@map true + } + } + false + } +} + +fun shouldEnableUserInput(context: Context, recipient: Recipient): Promise { + // Input should be enabled if we don't have any pending requests OR we're friends with any linked device + return hasPendingFriendRequestWithAnyLinkedDevice(context, recipient).bind { hasPendingFriendRequest -> + if (!hasPendingFriendRequest) Promise.of(true) else isFriendsWithAnyLinkedDevice(context, recipient) + }.recover { true } +} diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index e15cda3de8..b0f77a6fe3 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -61,12 +61,15 @@ import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.IOException; import java.util.Map; import java.util.Set; import kotlin.Unit; +import nl.komponents.kovenant.Kovenant; +import nl.komponents.kovenant.Promise; public class MessageSender { @@ -79,20 +82,24 @@ public class MessageSender { sendBackgroundMessage(context, contactHexEncodedPublicKey); // Go through the other devices and only send background messages if we're friends or we have received friend request - Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(contactHexEncodedPublicKey); - for (String device : devices) { - // Don't send message to the device we already have sent to - if (device.equals(contactHexEncodedPublicKey)) { continue; } - Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false); - long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); - if (threadID < 0) { continue; } - LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); - if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { - sendBackgroundMessage(context, device); - } + LokiStorageAPI.shared.getAllDevicePublicKeys(contactHexEncodedPublicKey).success(devices -> { + Util.runOnMain(() -> { + for (String device : devices) { + // Don't send message to the device we already have sent to + if (device.equals(contactHexEncodedPublicKey)) { continue; } + Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false); + long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); + if (threadID < 0) { continue; } + LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); + if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { + sendBackgroundMessage(context, device); + } - // TODO: Do we want to send a custom FR Message if we're not friends and we haven't received a friend request? - } + // TODO: Do we want to send a custom FR Message if we're not friends and we haven't received a friend request? + } + }); + return Unit.INSTANCE; + }); } public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) { @@ -199,15 +206,19 @@ public class MessageSender { final int ttl) { String ourPublicKey = TextSecurePreferences.getLocalNumber(context); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey); - for (String device : devices) { - // Don't send to ourselves - if (device.equals(ourPublicKey)) { continue; } + LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).success(devices -> { + Util.runOnMain(() -> { + for (String device : devices) { + // Don't send to ourselves + if (device.equals(ourPublicKey)) { continue; } - // Create a send job for our device - Address address = Address.fromSerialized(device); - jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl)); - } + // Create a send job for our device + Address address = Address.fromSerialized(device); + jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl)); + } + }); + return Unit.INSTANCE; + }); } public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) { @@ -267,9 +278,8 @@ public class MessageSender { JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); // Just send the message normally if it's a group message or we're sending to one of our devices - // TODO: Test how badly this will block the thread String recipientPublicKey = recipient.getAddress().serialize(); - if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey) || MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress())) { + if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey) || PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false)) { if (type == MessageType.MEDIA) { PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false); } else { @@ -292,13 +302,24 @@ public class MessageSender { if (isFriend) { // Send a normal message if the user is friends with the recipient // We should also send a sync message if we haven't already sent one - boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && MultiDeviceUtilities.shouldSendSycMessage(context, address); - if (type == MessageType.MEDIA) { - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); - } else { - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); + Promise promise = Promise.Companion.of(false, Kovenant.INSTANCE.getContext()); + if (!hasSentSyncMessage[0]) { + promise = MultiDeviceUtilities.shouldSendSycMessage(context, address).success(value -> { + hasSentSyncMessage[0] = value; + return Unit.INSTANCE; + }); } - hasSentSyncMessage[0] = shouldSendSyncMessage; + + promise.success(shouldSendSyncMessage -> { + Util.runOnMain(() -> { + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); + } else { + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); + } + }); + return Unit.INSTANCE; + }); } else { // Send friend requests to non friends. If the user is friends with any // of the devices then send out a default friend request message. From c223e087f6c2a9403688edd71b637a646d3384f9 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 1 Nov 2019 15:49:26 +1100 Subject: [PATCH 24/64] Don't block thread when retrying. --- src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt | 2 +- src/org/thoughtcrime/securesms/loki/SeedActivity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index f80c89c75b..7c73b73eac 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -118,7 +118,7 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio return } retryIfNeeded(8) { - sendPairingAuthorisationMessage(context, pairingAuthorisation.secondaryDevicePublicKey, signedPairingAuthorisation).get() + sendPairingAuthorisationMessage(context, pairingAuthorisation.secondaryDevicePublicKey, signedPairingAuthorisation) }.fail { Log.d("Loki", "Failed to send pairing authorization message to ${pairingAuthorisation.secondaryDevicePublicKey}.") } diff --git a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt index 1d494a8711..05d40c1879 100644 --- a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt @@ -203,7 +203,7 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate { application.setUpStorageAPIIfNeeded() DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Slave, this) retryIfNeeded(8) { - sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation).get() + sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation) } } else { startActivity(Intent(this, DisplayNameActivity::class.java)) From b8818cbce42cd7722c8dba1ba4465d24ae43843b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 4 Nov 2019 08:44:19 +1100 Subject: [PATCH 25/64] Fix quotes in public group chats --- .../securesms/jobs/PushDecryptJob.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 332375246a..b42b07ce76 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -769,7 +769,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Optional> sharedContacts = getContacts(message.getSharedContacts()); Optional> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or("")); Optional sticker = getStickerAttachment(message.getSticker()); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(primaryDeviceRecipient.getAddress(), message.getTimestamp(), -1, + + // If message is from group then we need to map it to the correct sender + Address sender = message.isGroupUpdate() ? Address.fromSerialized(content.getSender()) : primaryDeviceRecipient.getAddress(); + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(sender, message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(), quote, sharedContacts, linkPreviews, sticker); @@ -933,7 +936,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { SmsDatabase database = DatabaseFactory.getSmsDatabase(context); String body = message.getBody().isPresent() ? message.getBody().get() : ""; Recipient originalRecipient = getMessageDestination(content, message); - Recipient primaryDeviceRecipient = message.isGroupUpdate() ? originalRecipient : getMessagePrimaryDestination(content, message); + Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) { handleExpirationUpdate(content, message, Optional.absent()); @@ -946,7 +949,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } else { notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice()); - IncomingTextMessage _textMessage = new IncomingTextMessage(primaryDeviceRecipient.getAddress(), + // If message is from group then we need to map it to the correct sender + Address sender = message.isGroupUpdate() ? Address.fromSerialized(content.getSender()) : primaryDeviceRecipient.getAddress(); + IncomingTextMessage _textMessage = new IncomingTextMessage(sender, content.getSenderDevice(), message.getTimestamp(), body, message.getGroupInfo(), @@ -1105,7 +1110,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { - if (!envelope.isFriendRequest() || isGroupChatMessage(message)) { return; } + if (!envelope.isFriendRequest() || message.isGroupUpdate()) { return; } // This handles the case where another user sends us a regular message without authorisation boolean shouldBecomeFriends = PromiseUtil.get(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), false); if (shouldBecomeFriends) { @@ -1611,11 +1616,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } private boolean isGroupChatMessage(SignalServiceContent content) { - return content.getDataMessage().isPresent() && isGroupChatMessage(content.getDataMessage().get()); - } - - private boolean isGroupChatMessage(SignalServiceDataMessage message) { - return message.isGroupUpdate(); + return content.getDataMessage().isPresent() && content.getDataMessage().get().isGroupUpdate(); } private void resetRecipientToPush(@NonNull Recipient recipient) { From ed6ca6a64ae7d7f2bfb58b0c382347b624278040 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 6 Nov 2019 13:40:55 +1100 Subject: [PATCH 26/64] We should automatically become friends with a contact if we are friends with one of their other devices. --- .../securesms/loki/MultiDeviceUtilities.kt | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 7c73b73eac..7ea362e844 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -58,29 +58,24 @@ fun getFriendCount(context: Context, devices: Set): Int { } fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Promise { - val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) - val storageAPI = LokiStorageAPI.shared - // If this public key is our primary device then we should become friends if (publicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { return Promise.of(true) } - return storageAPI.getPrimaryDevicePublicKey(publicKey).map { primaryDevicePublicKey -> + return LokiStorageAPI.shared.getPrimaryDevicePublicKey(publicKey).bind { primaryDevicePublicKey -> // If the public key doesn't have any other devices then go through regular friend request logic if (primaryDevicePublicKey == null) { - return@map false + return@bind Promise.of(false) } // If the primary device public key matches our primary device then we should become friends since this is our other device if (primaryDevicePublicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { - return@map true + return@bind Promise.of(true) } - // If we are friends with the primary device then we should become friends - val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) - val primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) - primaryDeviceThreadID >= 0 && lokiThreadDatabase.getFriendRequestStatus(primaryDeviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS + // If we are friends with any of the other devices then we should become friends + isFriendsWithAnyLinkedDevice(context, Address.fromSerialized(primaryDevicePublicKey)) } } @@ -153,9 +148,13 @@ fun isOneOfOurDevices(context: Context, address: Address): Promise { - if (recipient.isGroupRecipient) { return Promise.of(true) } + return isFriendsWithAnyLinkedDevice(context, recipient.address) +} - return getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()).map { map -> +fun isFriendsWithAnyLinkedDevice(context: Context, address: Address): Promise { + if (!address.isPhone) { return Promise.of(true) } + + return getAllDeviceFriendRequestStatuses(context, address.serialize()).map { map -> for (status in map.values) { if (status == LokiThreadFriendRequestStatus.FRIENDS) { return@map true From 96fa96f5c190901fd6b9c076f62d1aaf19941037 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 6 Nov 2019 16:08:20 +1100 Subject: [PATCH 27/64] Fix sync message sending. --- .../securesms/loki/MultiDeviceUtilities.kt | 9 ------- .../securesms/sms/MessageSender.java | 24 ++++++------------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 7ea362e844..f412564805 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -127,15 +127,6 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio } -fun shouldSendSycMessage(context: Context, address: Address): Promise { - if (address.isGroup || address.isEmail || address.isMmsGroup) { - return Promise.of(false) - } - - // Don't send sync messages if it's one of our devices - return isOneOfOurDevices(context, address).map { !it } -} - fun isOneOfOurDevices(context: Context, address: Address): Promise { if (address.isGroup || address.isEmail || address.isMmsGroup) { return Promise.of(false) diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index b0f77a6fe3..242a7722d8 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -288,6 +288,7 @@ public class MessageSender { return; } + // If we get here then we are sending a message to a device that is not ours boolean[] hasSentSyncMessage = { false }; MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey).success(devices -> { int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); @@ -302,24 +303,13 @@ public class MessageSender { if (isFriend) { // Send a normal message if the user is friends with the recipient // We should also send a sync message if we haven't already sent one - Promise promise = Promise.Companion.of(false, Kovenant.INSTANCE.getContext()); - if (!hasSentSyncMessage[0]) { - promise = MultiDeviceUtilities.shouldSendSycMessage(context, address).success(value -> { - hasSentSyncMessage[0] = value; - return Unit.INSTANCE; - }); + boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && address.isPhone(); + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); + } else { + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); } - - promise.success(shouldSendSyncMessage -> { - Util.runOnMain(() -> { - if (type == MessageType.MEDIA) { - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); - } else { - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); - } - }); - return Unit.INSTANCE; - }); + if (shouldSendSyncMessage) { hasSentSyncMessage[0] = true; } } else { // Send friend requests to non friends. If the user is friends with any // of the devices then send out a default friend request message. From 92580a0dd9bd288b58f331dadf8b6096fe20130b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 4 Nov 2019 11:17:34 +1100 Subject: [PATCH 28/64] Fixed up contact sync sending to use the correct field in protobuf. --- .../jobs/MultiDeviceContactUpdateJob.java | 51 ++++++++++++------- .../securesms/jobs/PushDecryptJob.java | 1 + .../securesms/loki/LokiThreadDatabase.kt | 4 ++ .../securesms/loki/MultiDeviceUtilities.kt | 1 + 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index a10caa0edf..7ae1a13782 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.database.Database; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.dependencies.InjectableType; @@ -38,6 +39,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.util.InvalidNumberException; +import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import java.io.ByteArrayInputStream; import java.io.File; @@ -83,7 +85,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy .addConstraint(NetworkConstraint.KEY) .setQueue("MultiDeviceContactUpdateJob") .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(Parameters.UNLIMITED) + .setMaxAttempts(3) .build(), address, forceSync); @@ -126,6 +128,9 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy private void generateSingleContactUpdate(@NonNull Address address) throws IOException, UntrustedIdentityException, NetworkException { + // Loki - Only sync regular contacts + if (!address.isPhone()) { return; } + File contactDataFile = createTempFile("multidevice-contact-update"); try { @@ -134,16 +139,19 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy Optional identityRecord = DatabaseFactory.getIdentityDatabase(context).getIdentity(address); Optional verifiedMessage = getVerifiedMessage(recipient, identityRecord); - out.write(new DeviceContact(address.toPhoneString(), - Optional.fromNullable(recipient.getName()), - getAvatar(recipient.getContactUri()), - Optional.fromNullable(recipient.getColor().serialize()), - verifiedMessage, - Optional.fromNullable(recipient.getProfileKey()), - recipient.isBlocked(), - recipient.getExpireMessages() > 0 ? - Optional.of(recipient.getExpireMessages()) : - Optional.absent())); + // Loki - Only sync contacts we are friends with + if (getFriendRequestStatus(recipient) == LokiThreadFriendRequestStatus.FRIENDS) { + out.write(new DeviceContact(address.toPhoneString(), + Optional.fromNullable(recipient.getName()), + getAvatar(recipient.getContactUri()), + Optional.fromNullable(recipient.getColor().serialize()), + verifiedMessage, + Optional.fromNullable(recipient.getProfileKey()), + recipient.isBlocked(), + recipient.getExpireMessages() > 0 ? + Optional.of(recipient.getExpireMessages()) : + Optional.absent())); + } out.close(); sendUpdate(messageSender, contactDataFile, false); @@ -158,11 +166,6 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy private void generateFullContactUpdate() throws IOException, UntrustedIdentityException, NetworkException { - if (!Permissions.hasAny(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { - Log.w(TAG, "No contact permissions, skipping multi-device contact update..."); - return; - } - boolean isAppVisible = ApplicationContext.getInstance(context).isAppVisible(); long timeSinceLastSync = System.currentTimeMillis() - TextSecurePreferences.getLastFullContactSyncTime(context); @@ -189,13 +192,16 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy Recipient recipient = Recipient.from(context, address, false); Optional identity = DatabaseFactory.getIdentityDatabase(context).getIdentity(address); Optional verified = getVerifiedMessage(recipient, identity); - Optional name = Optional.fromNullable(contactData.name); + Optional name = Optional.fromNullable(DatabaseFactory.getLokiUserDatabase(context).getDisplayName(address.serialize())); Optional color = Optional.of(recipient.getColor().serialize()); Optional profileKey = Optional.fromNullable(recipient.getProfileKey()); boolean blocked = recipient.isBlocked(); Optional expireTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent(); - out.write(new DeviceContact(address.toPhoneString(), name, getAvatar(contactUri), color, verified, profileKey, blocked, expireTimer)); + // Loki - Only sync contacts we are friends with + if (getFriendRequestStatus(recipient) == LokiThreadFriendRequestStatus.FRIENDS) { + out.write(new DeviceContact(address.toPhoneString(), name, getAvatar(contactUri), color, verified, profileKey, blocked, expireTimer)); + } } if (ProfileKeyUtil.hasProfileKey(context)) { @@ -216,6 +222,11 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy } } + private LokiThreadFriendRequestStatus getFriendRequestStatus(Recipient recipient) { + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); + return DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); + } + @Override public boolean onShouldRetry(@NonNull Exception exception) { if (exception instanceof PushNetworkException) return true; @@ -239,7 +250,6 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy .build(); try { - // TODO: Message ID messageSender.sendMessage(0, SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, complete)), UnidentifiedAccessUtil.getAccessForSync(context)); } catch (IOException ioe) { @@ -319,6 +329,9 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy default: throw new AssertionError("Unknown state: " + identity.get().getVerifiedStatus()); } + // Loki - For now always set to verified + state = VerifiedMessage.VerifiedState.VERIFIED; + return Optional.of(new VerifiedMessage(destination, identityKey, state, System.currentTimeMillis())); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index b42b07ce76..347314f910 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -1060,6 +1060,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(userHexEncodedPublicKey); DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(authorisation); TextSecurePreferences.setMasterHexEncodedPublicKey(context, authorisation.getPrimaryDevicePublicKey()); + TextSecurePreferences.setMultiDevice(context, true); // Send a background message to the primary device MessageSender.sendBackgroundMessage(context, authorisation.getPrimaryDevicePublicKey()); // Propagate the updates to the file server diff --git a/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt b/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt index 4c177bc108..3b66359ea9 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt @@ -41,6 +41,8 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa } fun getFriendRequestStatus(threadID: Long): LokiThreadFriendRequestStatus { + if (threadID < 0) { return LokiThreadFriendRequestStatus.NONE } + val database = databaseHelper.readableDatabase val result = database.get(friendRequestTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor -> cursor.getInt(friendRequestStatus) @@ -53,6 +55,8 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa } override fun setFriendRequestStatus(threadID: Long, friendRequestStatus: LokiThreadFriendRequestStatus) { + if (threadID < 0) { return } + val database = databaseHelper.writableDatabase val contentValues = ContentValues(2) contentValues.put(Companion.threadID, threadID) diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index f412564805..060cdda1a9 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -118,6 +118,7 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio Log.d("Loki", "Failed to send pairing authorization message to ${pairingAuthorisation.secondaryDevicePublicKey}.") } DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation) + TextSecurePreferences.setMultiDevice(context, true) // Call function after a short delay Handler().postDelayed({ LokiStorageAPI.shared.updateUserDeviceMappings().fail { From 7df3393e23596a25f745b9e09d5ef779f41ea1b1 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 4 Nov 2019 11:44:08 +1100 Subject: [PATCH 29/64] Sync contacts upon pairing a secondary device. --- .../securesms/loki/MultiDeviceUtilities.kt | 24 ++++++++++++------- .../securesms/sms/MessageSender.java | 9 +++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 060cdda1a9..580ade8175 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -10,8 +10,10 @@ import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair @@ -112,20 +114,24 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio Log.d("Loki", "Failed to sign pairing authorization.") return } - retryIfNeeded(8) { + DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation) + TextSecurePreferences.setMultiDevice(context, true) + + val sendPromise = retryIfNeeded(8) { sendPairingAuthorisationMessage(context, pairingAuthorisation.secondaryDevicePublicKey, signedPairingAuthorisation) }.fail { Log.d("Loki", "Failed to send pairing authorization message to ${pairingAuthorisation.secondaryDevicePublicKey}.") } - DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation) - TextSecurePreferences.setMultiDevice(context, true) - // Call function after a short delay - Handler().postDelayed({ - LokiStorageAPI.shared.updateUserDeviceMappings().fail { - Log.w("Loki", "Failed to update device mapping") - } - }, 100) + val updatePromise = LokiStorageAPI.shared.updateUserDeviceMappings().fail { + Log.d("Loki", "Failed to update device mapping") + } + + // If both promises complete successfully then we should sync our contacts + all(listOf(sendPromise, updatePromise), cancelOthersOnError = false).success { + Log.d("Loki", "Successfully pairing with a secondary device! Syncing contacts.") + MessageSender.sendContactSyncMessage(context) + } } fun isOneOfOurDevices(context: Context, address: Address): Promise { diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 242a7722d8..3e9a62035a 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobs.MmsSendJob; +import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.PushGroupSendJob; import org.thoughtcrime.securesms.jobs.PushMediaSendJob; import org.thoughtcrime.securesms.jobs.PushTextSendJob; @@ -77,6 +78,14 @@ public class MessageSender { private enum MessageType { TEXT, MEDIA } + public static void sendContactSyncMessage(Context context) { + ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, true)); + } + + public static void sendContactSyncMessage(Context context, Address address) { + ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, address)); + } + public static void sendBackgroundMessageToAllDevices(Context context, String contactHexEncodedPublicKey) { // Send the background message to the original pubkey sendBackgroundMessage(context, contactHexEncodedPublicKey); From 6f2a6f7f94c49f08fe5b39cab4f92911a9155b64 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 4 Nov 2019 13:46:36 +1100 Subject: [PATCH 30/64] Sync contact upon accepting friend request --- .../conversation/ConversationActivity.java | 14 ++++------ .../securesms/jobs/PushDecryptJob.java | 6 +++-- .../securesms/loki/MultiDeviceUtilities.kt | 4 +-- .../securesms/sms/MessageSender.java | 27 ++++++++++++++----- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index d9ec0a0154..c682afcb62 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -127,7 +127,6 @@ import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.Database; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -242,17 +241,12 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import kotlin.Unit; import network.loki.messenger.R; -import nl.komponents.kovenant.Kovenant; -import nl.komponents.kovenant.KovenantApi; -import nl.komponents.kovenant.Promise; import static nl.komponents.kovenant.KovenantApi.task; import static org.thoughtcrime.securesms.TransportOption.Type; @@ -3032,16 +3026,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id); long threadId = originalThreadID < 0 ? this.threadId : originalThreadID; - String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString(); + Address contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress(); + String contactPubKey = contact.toString(); Context context = this; AsyncTask.execute(() -> { try { - MessageSender.sendBackgroundMessageToAllDevices(this, contactID); + MessageSender.sendBackgroundMessageToAllDevices(this, contactPubKey); + MessageSender.syncContact(this, contact); DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS); lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); Util.runOnMain(this::updateInputPanel); } catch (Exception e) { - Log.d("Loki", "Failed to send background message to: " + contactID + "."); + Log.d("Loki", "Failed to send background message to: " + contactPubKey + "."); } }); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 347314f910..071e46251d 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -99,7 +99,6 @@ import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; -import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.whispersystems.libsignal.state.PreKeyBundle; import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.guava.Optional; @@ -147,7 +146,6 @@ import javax.inject.Inject; import kotlin.Unit; import network.loki.messenger.R; -import nl.komponents.kovenant.Promise; public class PushDecryptJob extends BaseJob implements InjectableType { @@ -1100,6 +1098,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // If the thread's friend request status is not `FRIENDS`, 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 + MessageSender.syncContact(context, contactID.getAddress()); // Update the last message if needed LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> { Util.runOnMain(() -> { @@ -1145,6 +1145,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { 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 diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 580ade8175..7f3883e73c 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.loki import android.content.Context -import android.os.Handler import nl.komponents.kovenant.* import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map @@ -10,7 +9,6 @@ import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.sms.MessageSender @@ -130,7 +128,7 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio // If both promises complete successfully then we should sync our contacts all(listOf(sendPromise, updatePromise), cancelOthersOnError = false).success { Log.d("Loki", "Successfully pairing with a secondary device! Syncing contacts.") - MessageSender.sendContactSyncMessage(context) + MessageSender.syncAllContacts(context) } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 3e9a62035a..4b4726f4df 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -18,6 +18,7 @@ package org.thoughtcrime.securesms.sms; import android.content.Context; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; @@ -66,7 +67,6 @@ import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.IOException; import java.util.Map; -import java.util.Set; import kotlin.Unit; import nl.komponents.kovenant.Kovenant; @@ -78,12 +78,21 @@ public class MessageSender { private enum MessageType { TEXT, MEDIA } - public static void sendContactSyncMessage(Context context) { + public static void syncAllContacts(Context context) { ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, true)); } - public static void sendContactSyncMessage(Context context, Address address) { - ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, address)); + /** + * Send a contact sync message to all our devices telling them that we want to sync `contact` + */ + public static void syncContact(Context context, Address contact) { + // Don't bother sending a contact sync message if it's one of our devices that we want to sync across + MultiDeviceUtilities.isOneOfOurDevices(context, contact).success(isOneOfOurDevice -> { + if (!isOneOfOurDevice) { + ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, contact)); + } + return Unit.INSTANCE; + }); } public static void sendBackgroundMessageToAllDevices(Context context, String contactHexEncodedPublicKey) { @@ -100,11 +109,10 @@ public class MessageSender { long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); if (threadID < 0) { continue; } LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); + // TODO: Do we want to send a bg message regardless of FR status? OR do we want to send a custom FR to those we are not friends with if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { sendBackgroundMessage(context, device); } - - // TODO: Do we want to send a custom FR Message if we're not friends and we haven't received a friend request? } }); return Unit.INSTANCE; @@ -112,10 +120,15 @@ public class MessageSender { } public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) { + sendMessageWithBody(context, contactHexEncodedPublicKey, null); + } + + public static void sendMessageWithBody(Context context, String contactHexEncodedPublicKey, @Nullable String messageBody) { Util.runOnMain(() -> { SignalServiceMessageSender messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender(); SignalServiceAddress address = new SignalServiceAddress(contactHexEncodedPublicKey); - SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), null); + String body = (messageBody == null || messageBody.isEmpty()) ? null : messageBody; + SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), body); try { // Try send to the original person messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter From ce265f29fd9e8f45b418c302dd9bd2d726899310 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 4 Nov 2019 16:16:56 +1100 Subject: [PATCH 31/64] Handle received contact sync message --- .../securesms/jobs/PushDecryptJob.java | 45 +++++++++++++++++++ .../securesms/loki/MultiDeviceUtilities.kt | 2 +- .../push/MessageSenderEventListener.java | 2 +- .../securesms/sms/MessageSender.java | 21 ++++++--- 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 071e46251d..d556f69499 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -116,6 +116,9 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage; import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; @@ -135,6 +138,7 @@ import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestSt import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus; import org.whispersystems.signalservice.loki.utilities.PromiseUtil; +import java.io.IOException; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.ArrayList; @@ -352,6 +356,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp()); else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get()); + else if (syncMessage.getContacts().isPresent()) handleSynchronizeContactMessage(syncMessage.getContacts().get()); else Log.w(TAG, "Contains no known sync types..."); } else if (content.getCallMessage().isPresent()) { Log.i(TAG, "Got call message..."); @@ -641,6 +646,46 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } + private void handleSynchronizeContactMessage(@NonNull ContactsMessage contactsMessage) { + if (contactsMessage.getContactsStream().isStream()) { + try { + DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(contactsMessage.getContactsStream().asStream().getInputStream()); + DeviceContact deviceContact = contactsInputStream.read(); + while (deviceContact != null) { + // Check if we have the contact as a friend + Address address = Address.fromSerialized(deviceContact.getNumber()); + if (!address.isPhone()) { continue; } + + /* + If we're not friends with the contact we received or our friend request expired then we should send them a friend request + otherwise if we have received a friend request with from them then we should automatically accept the friend request + */ + Recipient recipient = Recipient.from(context, address, false); + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); + LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); + if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { + MessageSender.sendBackgroundFriendRequest(context, deviceContact.getNumber(), "This is an automated friend request. Still under testing!"); + } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { + // Accept the incoming friend request + becomeFriendsWithContact(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? + + // Read the next contact + deviceContact = contactsInputStream.read(); + } + } catch (IOException e) { + // Exception is thrown when we don't have any more contacts to read from + } catch (Exception e) { + Log.d("Loki", "Failed to sync contact: " + e.getMessage()); + } + } + + } + private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, @NonNull SentTranscriptMessage message) throws StorageFailedException diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 7f3883e73c..5b471a42bb 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -82,7 +82,7 @@ fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Conte fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise { val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() val address = SignalServiceAddress(contactHexEncodedPublicKey) - val message = SignalServiceDataMessage.newBuilder().withBody("").withPairingAuthorisation(authorisation) + val message = SignalServiceDataMessage.newBuilder().withBody(null).withPairingAuthorisation(authorisation) // A REQUEST should always act as a friend request. A GRANT should always be replying back as a normal message. if (authorisation.type == PairingAuthorisation.Type.REQUEST) { val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number) diff --git a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java index cca8b0b7b1..6b492be25e 100644 --- a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java +++ b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java @@ -26,7 +26,7 @@ public class MessageSenderEventListener implements SignalServiceMessageSender.Ev @Override public void onSyncEvent(long messageID, long timestamp, byte[] message, int ttl) { - if (messageID > 0 && timestamp > 0 && message != null && ttl > 0) { + if (messageID >= 0 && timestamp > 0 && message != null && ttl > 0) { MessageSender.sendSyncMessageToOurDevices(context, messageID, timestamp, message, ttl); } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 4b4726f4df..64653eb88d 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -55,6 +55,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.libsignal.state.PreKeyBundle; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -120,18 +121,28 @@ public class MessageSender { } public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) { - sendMessageWithBody(context, contactHexEncodedPublicKey, null); + SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), null); + sendMessage(context, contactHexEncodedPublicKey, message); } - public static void sendMessageWithBody(Context context, String contactHexEncodedPublicKey, @Nullable String messageBody) { + public static void sendBackgroundFriendRequest(Context context, String contactHexEncodedPublicKey, String messageBody) { + PreKeyBundle bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(contactHexEncodedPublicKey); + SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() + .withTimestamp(System.currentTimeMillis()) + .withBody(messageBody) + .asFriendRequest(true) + .withPreKeyBundle(bundle) + .build(); + sendMessage(context, contactHexEncodedPublicKey, message); + } + + private static void sendMessage(Context context, String contactHexEncodedPublicKey, SignalServiceDataMessage message) { Util.runOnMain(() -> { SignalServiceMessageSender messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender(); SignalServiceAddress address = new SignalServiceAddress(contactHexEncodedPublicKey); - String body = (messageBody == null || messageBody.isEmpty()) ? null : messageBody; - SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), body); try { // Try send to the original person - messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter + messageSender.sendMessage(-1, address, Optional.absent(), message); // The message ID doesn't matter } catch (Exception e) { Log.d("Loki", "Failed to send background message to: " + contactHexEncodedPublicKey + "."); } From ddafcf4530e6c8c8a5583571c7a795c4359866f4 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 6 Nov 2019 10:40:49 +1100 Subject: [PATCH 32/64] Fix device linking blocking UI --- .../securesms/ApplicationPreferencesActivity.java | 3 ++- src/org/thoughtcrime/securesms/loki/SeedActivity.kt | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index b7f5b797de..9e55810f50 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import android.os.AsyncTask; import android.os.Build; import android.os.Build.VERSION; import android.os.Bundle; @@ -394,7 +395,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA } @Override public void sendPairingAuthorizedMessage(@NotNull PairingAuthorisation pairingAuthorisation) { - MultiDeviceUtilities.signAndSendPairingAuthorisationMessage(context, pairingAuthorisation); + AsyncTask.execute(() -> MultiDeviceUtilities.signAndSendPairingAuthorisationMessage(context, pairingAuthorisation)); } @Override public void handleDeviceLinkAuthorized(@NotNull PairingAuthorisation pairingAuthorisation) {} @Override public void handleDeviceLinkingDialogDismissed() {} diff --git a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt index 05d40c1879..a185b5fe43 100644 --- a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt @@ -4,6 +4,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.os.AsyncTask import android.os.Bundle import android.view.View import android.view.inputmethod.InputMethodManager @@ -202,8 +203,10 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate { application.setUpP2PAPI() application.setUpStorageAPIIfNeeded() DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Slave, this) - retryIfNeeded(8) { - sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation) + AsyncTask.execute { + retryIfNeeded(8) { + sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation) + } } } else { startActivity(Intent(this, DisplayNameActivity::class.java)) From 9541588383dc578ddd86cd4bf997677dc0a103af Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 6 Nov 2019 11:02:07 +1100 Subject: [PATCH 33/64] Fix up syncing all contacts. We were looking up contact from the phone book which we had no access to. --- .../jobs/MultiDeviceContactUpdateJob.java | 26 ++++++++++++++++--- .../securesms/jobs/PushDecryptJob.java | 12 +++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 7ae1a13782..75d79edbd3 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -46,7 +46,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -183,8 +185,8 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy File contactDataFile = createTempFile("multidevice-contact-update"); try { - DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile)); - Collection contacts = ContactAccessor.getInstance().getContactsWithPush(context); + DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile)); + List contacts = getAllContacts(); for (ContactData contactData : contacts) { Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactData.id)); @@ -192,7 +194,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy Recipient recipient = Recipient.from(context, address, false); Optional identity = DatabaseFactory.getIdentityDatabase(context).getIdentity(address); Optional verified = getVerifiedMessage(recipient, identity); - Optional name = Optional.fromNullable(DatabaseFactory.getLokiUserDatabase(context).getDisplayName(address.serialize())); + Optional name = Optional.fromNullable(contactData.name); Optional color = Optional.of(recipient.getColor().serialize()); Optional profileKey = Optional.fromNullable(recipient.getProfileKey()); boolean blocked = recipient.isBlocked(); @@ -222,6 +224,20 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy } } + private List getAllContacts() { + List
contactAddresses = DatabaseFactory.getRecipientDatabase(context).getRegistered(); + List contacts = new ArrayList<>(contactAddresses.size()); + for (Address address : contactAddresses) { + if (!address.isPhone()) { continue; } + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, address, false)); + String name = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(address.serialize()); + ContactData contactData = new ContactData(threadId, name); + contactData.numbers.add(new ContactAccessor.NumberData("TextSecure", address.serialize())); + contacts.add(contactData); + } + return contacts; + } + private LokiThreadFriendRequestStatus getFriendRequestStatus(Recipient recipient) { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); return DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); @@ -259,6 +275,9 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy } private Optional getAvatar(@Nullable Uri uri) throws IOException { + return Optional.absent(); + + /* Loki - Disabled until we support custom avatars. This will need to be reworked if (uri == null) { return Optional.absent(); } @@ -312,6 +331,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy cursor.close(); } } + */ } private Optional getVerifiedMessage(Recipient recipient, Optional identity) throws InvalidNumberException { diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index d556f69499..df0d474136 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -667,7 +667,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { MessageSender.sendBackgroundFriendRequest(context, deviceContact.getNumber(), "This is an automated friend request. Still under testing!"); } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { // Accept the incoming friend request - becomeFriendsWithContact(deviceContact.getNumber()); + becomeFriendsWithContact(deviceContact.getNumber(), false); } // TODO: Handle blocked - If user is not blocked then we should do the friend request logic otherwise add them to our block list @@ -1131,10 +1131,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private void acceptFriendRequestIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) { // If we get anything other than a friend request, we can assume that we have a session with the other user if (envelope.isFriendRequest() || isGroupChatMessage(content)) { return; } - becomeFriendsWithContact(content.getSender()); + becomeFriendsWithContact(content.getSender(), true); } - private void becomeFriendsWithContact(String pubKey) { + private void becomeFriendsWithContact(String pubKey, boolean syncContact) { LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); Recipient contactID = Recipient.from(context, Address.fromSerialized(pubKey), false); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(contactID); @@ -1144,7 +1144,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // 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 - MessageSender.syncContact(context, contactID.getAddress()); + if (syncContact) { + MessageSender.syncContact(context, contactID.getAddress()); + } // Update the last message if needed LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> { Util.runOnMain(() -> { @@ -1161,7 +1163,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { boolean shouldBecomeFriends = PromiseUtil.get(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), false); if (shouldBecomeFriends) { // Become friends AND update the message they sent - becomeFriendsWithContact(content.getSender()); + becomeFriendsWithContact(content.getSender(), true); // Send them an accept message back MessageSender.sendBackgroundMessage(context, content.getSender()); } else { From 55ecd3cae42075bc48f32c8054f064490b3d85ec Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 8 Nov 2019 10:20:11 +1100 Subject: [PATCH 34/64] Add a job for sending a background message. Send a friend request background message to other linked devices upon accepting a friend request. --- .../securesms/jobs/JobManagerFactories.java | 2 + .../securesms/jobs/PushDecryptJob.java | 4 +- .../securesms/loki/MultiDeviceUtilities.kt | 8 +- .../loki/PushBackgroundMessageSendJob.kt | 98 +++++++++++++++++++ .../securesms/loki/PushMessageSyncSendJob.kt | 2 +- .../securesms/sms/MessageSender.java | 35 +++---- 6 files changed, 121 insertions(+), 28 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 7309716f9c..cfa9aa7beb 100644 --- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; +import org.thoughtcrime.securesms.loki.PushBackgroundMessageSendJob; import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; import java.util.Arrays; @@ -72,6 +73,7 @@ public final class JobManagerFactories { put(TypingSendJob.KEY, new TypingSendJob.Factory()); put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); put(PushMessageSyncSendJob.KEY, new PushMessageSyncSendJob.Factory()); + put(PushBackgroundMessageSendJob.KEY, new PushBackgroundMessageSendJob.Factory()); }}; } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index df0d474136..c1a43646c1 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -652,9 +652,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType { DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(contactsMessage.getContactsStream().asStream().getInputStream()); DeviceContact deviceContact = contactsInputStream.read(); while (deviceContact != null) { - // Check if we have the contact as a friend + // Check if we have the contact as a friend and that we're not trying to sync our own device Address address = Address.fromSerialized(deviceContact.getNumber()); - if (!address.isPhone()) { continue; } + if (!address.isPhone() || address.toPhoneString().equalsIgnoreCase(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 diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 5b471a42bb..fffbeecd80 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki import android.content.Context +import android.os.Handler import nl.komponents.kovenant.* import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map @@ -22,6 +23,8 @@ import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.utilities.recover import org.whispersystems.signalservice.loki.utilities.retryIfNeeded +import java.util.* +import kotlin.concurrent.schedule fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise, Exception> { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) @@ -128,7 +131,10 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio // If both promises complete successfully then we should sync our contacts all(listOf(sendPromise, updatePromise), cancelOthersOnError = false).success { Log.d("Loki", "Successfully pairing with a secondary device! Syncing contacts.") - MessageSender.syncAllContacts(context) + // Send out sync contact after a delay + Timer().schedule(3000) { + MessageSender.syncAllContacts(context) + } } } diff --git a/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt new file mode 100644 index 0000000000..cca9031003 --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt @@ -0,0 +1,98 @@ +package org.thoughtcrime.securesms.loki + +import org.thoughtcrime.securesms.ApplicationContext +import org.thoughtcrime.securesms.database.DatabaseFactory +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.logging.Log +import org.whispersystems.libsignal.util.guava.Optional +import org.whispersystems.signalservice.api.SignalServiceMessageSender +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair +import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage +import org.whispersystems.signalservice.api.push.SignalServiceAddress +import java.io.IOException +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class PushBackgroundMessageSendJob private constructor( + parameters: Parameters, + private val recipient: String, + private val messageBody: String?, + private val friendRequest: Boolean +) : BaseJob(parameters) { + companion object { + const val KEY = "PushBackgroundMessageSendJob" + + private val TAG = PushBackgroundMessageSendJob::class.java.simpleName + + private val KEY_RECIPIENT = "recipient" + private val KEY_MESSAGE_BODY = "message_body" + private val KEY_FRIEND_REQUEST = "asFriendRequest" + } + + constructor(recipient: String): this(recipient, null, false) + constructor(recipient: String, messageBody: String?, friendRequest: Boolean) : this(Parameters.Builder() + .addConstraint(NetworkConstraint.KEY) + .setQueue(KEY) + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(1) + .build(), + recipient, messageBody, friendRequest) + + override fun serialize(): Data { + return Data.Builder() + .putString(KEY_RECIPIENT, recipient) + .putString(KEY_MESSAGE_BODY, messageBody) + .putBoolean(KEY_FRIEND_REQUEST, friendRequest) + .build() + } + + override fun getFactoryKey(): String { + return KEY + } + + public override fun onRun() { + val message = SignalServiceDataMessage.newBuilder() + .withTimestamp(System.currentTimeMillis()) + .withBody(messageBody) + + if (friendRequest) { + val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(recipient) + message.withPreKeyBundle(bundle) + .asFriendRequest(true) + } + + val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() + val address = SignalServiceAddress(recipient) + try { + messageSender.sendMessage(-1, address, Optional.absent(), message.build()) // The message ID doesn't matter + } catch (e: Exception) { + Log.d("Loki", "Failed to send background message to: $recipient.") + throw e + } + } + + 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 { + override fun create(parameters: Parameters, data: Data): PushBackgroundMessageSendJob { + try { + val recipient = data.getString(KEY_RECIPIENT) + val messageBody = if (data.hasString(KEY_MESSAGE_BODY)) data.getString(KEY_MESSAGE_BODY) else null + val friendRequest = data.getBooleanOrDefault(KEY_FRIEND_REQUEST, false) + return PushBackgroundMessageSendJob(parameters, recipient, messageBody, friendRequest) + } catch (e: IOException) { + throw AssertionError(e) + } + } + } +} diff --git a/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt index f5848cbb07..6d451c2bc4 100644 --- a/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt @@ -41,7 +41,7 @@ class PushMessageSyncSendJob private constructor( .addConstraint(NetworkConstraint.KEY) .setQueue(KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(3) + .setMaxAttempts(1) .build(), messageID, recipient, timestamp, message, ttl) diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 64653eb88d..1ec057723e 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -17,6 +17,7 @@ package org.thoughtcrime.securesms.sms; import android.content.Context; +import android.os.AsyncTask; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -47,6 +48,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; +import org.thoughtcrime.securesms.loki.PushBackgroundMessageSendJob; import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; @@ -110,9 +112,10 @@ public class MessageSender { long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); if (threadID < 0) { continue; } LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); - // TODO: Do we want to send a bg message regardless of FR status? OR do we want to send a custom FR to those we are not friends with if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { sendBackgroundMessage(context, device); + } else if (friendRequestStatus == LokiThreadFriendRequestStatus.NONE || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { + sendBackgroundFriendRequest(context, device, "This is a friend request from android! please replace this message in the future"); } } }); @@ -120,34 +123,18 @@ public class MessageSender { }); } + // region Background message + + // We don't call the message sender here directly and instead we just opt to create a specific job for the send + // This is because calling message sender directly would cause the application to freeze in some cases as it was blocking the thread when waiting for a response from the send public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) { - SignalServiceDataMessage message = new SignalServiceDataMessage(System.currentTimeMillis(), null); - sendMessage(context, contactHexEncodedPublicKey, message); + ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(contactHexEncodedPublicKey)); } public static void sendBackgroundFriendRequest(Context context, String contactHexEncodedPublicKey, String messageBody) { - PreKeyBundle bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(contactHexEncodedPublicKey); - SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() - .withTimestamp(System.currentTimeMillis()) - .withBody(messageBody) - .asFriendRequest(true) - .withPreKeyBundle(bundle) - .build(); - sendMessage(context, contactHexEncodedPublicKey, message); - } - - private static void sendMessage(Context context, String contactHexEncodedPublicKey, SignalServiceDataMessage message) { - Util.runOnMain(() -> { - SignalServiceMessageSender messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender(); - SignalServiceAddress address = new SignalServiceAddress(contactHexEncodedPublicKey); - try { - // Try send to the original person - messageSender.sendMessage(-1, address, Optional.absent(), message); // The message ID doesn't matter - } catch (Exception e) { - Log.d("Loki", "Failed to send background message to: " + contactHexEncodedPublicKey + "."); - } - }); + ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(contactHexEncodedPublicKey, messageBody, true)); } + // endregion public static long send(final Context context, final OutgoingTextMessage message, From aea686c856fcc902e547e9d01765c2025872f6c7 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 8 Nov 2019 10:38:17 +1100 Subject: [PATCH 35/64] Only handle friend requests for direct chats. --- .../securesms/jobs/PushDecryptJob.java | 5 +++++ .../securesms/loki/FriendRequestHandler.kt | 22 ++++++++++--------- .../securesms/loki/MultiDeviceUtilities.kt | 5 +++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index c1a43646c1..b7b92a2ced 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -1137,6 +1137,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private void becomeFriendsWithContact(String pubKey, boolean syncContact) { LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); Recipient contactID = Recipient.from(context, Address.fromSerialized(pubKey), false); + if (contactID.isGroupRecipient()) return; + long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(contactID); LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID); if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) { return; } @@ -1172,6 +1174,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); + // Loki - Friend requests only work in direct chats + if (!originalRecipient.getAddress().isPhone()) { return; } + long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient); long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient); LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID); diff --git a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt index 732588d2a2..cc4ef0f273 100644 --- a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt +++ b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt @@ -16,20 +16,20 @@ object FriendRequestHandler { @JvmStatic fun updateFriendRequestState(context: Context, type: ActionType, messageId: Long, threadId: Long) { + if (threadId < 0) return + val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return + if (!recipient.address.isPhone) { return } + // Update thread status - // Note: Do we need to only update these if we're not friends? - if (threadId >= 0) { - val threadFriendStatus = when (type) { - ActionType.Sending -> LokiThreadFriendRequestStatus.REQUEST_SENDING - ActionType.Failed -> LokiThreadFriendRequestStatus.NONE - ActionType.Sent -> LokiThreadFriendRequestStatus.REQUEST_SENT - } - DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, threadFriendStatus) + val threadFriendStatus = when (type) { + ActionType.Sending -> LokiThreadFriendRequestStatus.REQUEST_SENDING + ActionType.Failed -> LokiThreadFriendRequestStatus.NONE + ActionType.Sent -> LokiThreadFriendRequestStatus.REQUEST_SENT } + DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, threadFriendStatus) // Update message status - val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) - if (recipient != null && messageId >= 0) { + if (messageId >= 0) { val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context) val friendRequestStatus = messageDatabase.getFriendRequestStatus(messageId) if (type == ActionType.Sending) { @@ -73,6 +73,8 @@ object FriendRequestHandler { // We only want to update the last message status if we're not friends with any of their linked devices // This ensures that we don't spam the UI with accept/decline messages val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return + if (!recipient.address.isPhone) { return } + isFriendsWithAnyLinkedDevice(context, recipient).successUi { isFriends -> if (isFriends) { return@successUi } diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index fffbeecd80..13e61721ac 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -61,6 +61,11 @@ fun getFriendCount(context: Context, devices: Set): Int { } fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Promise { + // Don't become friends if we're a group + if (!Address.fromSerialized(publicKey).isPhone) { + return Promise.of(false) + } + // If this public key is our primary device then we should become friends if (publicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { return Promise.of(true) From 12639b491fa4098df204296ec4d7e77bd71bf6ea Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Mon, 11 Nov 2019 12:47:43 +1100 Subject: [PATCH 36/64] Sync all contacts with only a specific device. This may fix the issue where the sync message sending queue gets blocked because of sending full contact syncs to all devices. --- .../jobs/MultiDeviceContactUpdateJob.java | 45 ++++++++++++++----- .../securesms/jobs/PushDecryptJob.java | 9 ++++ .../securesms/loki/MultiDeviceUtilities.kt | 8 ++-- .../securesms/sms/MessageSender.java | 4 +- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 75d79edbd3..00425988ec 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -37,6 +37,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; @@ -62,41 +63,57 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy private static final long FULL_SYNC_TIME = TimeUnit.HOURS.toMillis(6); private static final String KEY_ADDRESS = "address"; + private static final String KEY_RECIPIENT = "recipient"; private static final String KEY_FORCE_SYNC = "force_sync"; @Inject SignalServiceMessageSender messageSender; private @Nullable String address; + // The recipient of this sync message. If null then we send to all devices + private @Nullable String recipient; + private boolean forceSync; + /** + * Create a full contact sync job which syncs across to all other devices + */ public MultiDeviceContactUpdateJob(@NonNull Context context) { this(context, false); } + public MultiDeviceContactUpdateJob(@NonNull Context context, boolean forceSync) { this(context, null, forceSync); } - public MultiDeviceContactUpdateJob(@NonNull Context context, boolean forceSync) { - this(context, null, forceSync); + /** + * Create a full contact sync job which only gets sent to `recipient` + */ + public MultiDeviceContactUpdateJob(@NonNull Context context, @Nullable Address recipient, boolean forceSync) { + this(context, recipient, null, forceSync); } + /** + * Create a single contact sync job which syncs across `address` to the all other devices + */ public MultiDeviceContactUpdateJob(@NonNull Context context, @Nullable Address address) { - this(context, address, true); + this(context, null, address, true); } - public MultiDeviceContactUpdateJob(@NonNull Context context, @Nullable Address address, boolean forceSync) { + private MultiDeviceContactUpdateJob(@NonNull Context context, @Nullable Address recipient, @Nullable Address address, boolean forceSync) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setQueue("MultiDeviceContactUpdateJob") .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(3) + .setMaxAttempts(1) .build(), + recipient, address, forceSync); } - private MultiDeviceContactUpdateJob(@NonNull Job.Parameters parameters, @Nullable Address address, boolean forceSync) { + private MultiDeviceContactUpdateJob(@NonNull Job.Parameters parameters, @Nullable Address recipient, @Nullable Address address, boolean forceSync) { super(parameters); this.forceSync = forceSync; + this.recipient = (recipient != null) ? recipient.serialize() : null; if (address != null) this.address = address.serialize(); else this.address = null; @@ -106,6 +123,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy public @NonNull Data serialize() { return new Data.Builder().putString(KEY_ADDRESS, address) .putBoolean(KEY_FORCE_SYNC, forceSync) + .putString(KEY_RECIPIENT, recipient) .build(); } @@ -124,7 +142,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy } if (address == null) generateFullContactUpdate(); - else generateSingleContactUpdate(Address.fromSerialized(address)); + else if (address != TextSecurePreferences.getMasterHexEncodedPublicKey(context)) generateSingleContactUpdate(Address.fromSerialized(address)); } private void generateSingleContactUpdate(@NonNull Address address) @@ -245,7 +263,8 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy @Override public boolean onShouldRetry(@NonNull Exception exception) { - if (exception instanceof PushNetworkException) return true; + // Loki - Disabled because we have our own retrying + // if (exception instanceof PushNetworkException) return true; return false; } @@ -265,9 +284,10 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy .withLength(contactsFile.length()) .build(); + SignalServiceAddress messageRecipient = recipient != null ? new SignalServiceAddress(recipient) : null; + try { - messageSender.sendMessage(0, SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, complete)), - UnidentifiedAccessUtil.getAccessForSync(context)); + messageSender.sendMessage(0, SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, complete)), messageRecipient); } catch (IOException ioe) { throw new NetworkException(ioe); } @@ -375,7 +395,10 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy String serialized = data.getString(KEY_ADDRESS); Address address = serialized != null ? Address.fromSerialized(serialized) : null; - return new MultiDeviceContactUpdateJob(parameters, address, data.getBoolean(KEY_FORCE_SYNC)); + String recipientSerialized = data.getString(KEY_RECIPIENT); + Address recipient = recipientSerialized != null ? Address.fromSerialized(recipientSerialized) : null; + + return new MultiDeviceContactUpdateJob(parameters, recipient, address, data.getBoolean(KEY_FORCE_SYNC)); } } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index b7b92a2ced..4107e58dc0 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -648,6 +648,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private void handleSynchronizeContactMessage(@NonNull ContactsMessage contactsMessage) { if (contactsMessage.getContactsStream().isStream()) { + Log.d("Loki", "Received contact sync message"); try { DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(contactsMessage.getContactsStream().asStream().getInputStream()); DeviceContact deviceContact = contactsInputStream.read(); @@ -665,9 +666,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { MessageSender.sendBackgroundFriendRequest(context, deviceContact.getNumber(), "This is an automated friend request. Still under testing!"); + Log.d("Loki", "Sent friend request to " + deviceContact.getNumber()); } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { // Accept the incoming friend request becomeFriendsWithContact(deviceContact.getNumber(), false); + 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 @@ -679,6 +682,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } catch (IOException e) { // Exception is thrown when we don't have any more contacts to read from + return; } catch (Exception e) { Log.d("Loki", "Failed to sync contact: " + e.getMessage()); } @@ -1113,6 +1117,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) { setDisplayName(envelope.getSource(), content.senderDisplayName.get()); } + + // Contact sync + if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) { + handleSynchronizeContactMessage(content.getSyncMessage().get().getContacts().get()); + } } private void setDisplayName(String hexEncodedPublicKey, String profileName) { diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 13e61721ac..db84dcf440 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -123,10 +123,12 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation) TextSecurePreferences.setMultiDevice(context, true) + val address = Address.fromSerialized(pairingAuthorisation.secondaryDevicePublicKey); + val sendPromise = retryIfNeeded(8) { - sendPairingAuthorisationMessage(context, pairingAuthorisation.secondaryDevicePublicKey, signedPairingAuthorisation) + sendPairingAuthorisationMessage(context, address.serialize(), signedPairingAuthorisation) }.fail { - Log.d("Loki", "Failed to send pairing authorization message to ${pairingAuthorisation.secondaryDevicePublicKey}.") + Log.d("Loki", "Failed to send pairing authorization message to ${address.serialize()}.") } val updatePromise = LokiStorageAPI.shared.updateUserDeviceMappings().fail { @@ -138,7 +140,7 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio Log.d("Loki", "Successfully pairing with a secondary device! Syncing contacts.") // Send out sync contact after a delay Timer().schedule(3000) { - MessageSender.syncAllContacts(context) + MessageSender.syncAllContacts(context, address) } } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 1ec057723e..2ee0a3a5a4 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -81,8 +81,8 @@ public class MessageSender { private enum MessageType { TEXT, MEDIA } - public static void syncAllContacts(Context context) { - ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, true)); + public static void syncAllContacts(Context context, Address recipient) { + ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, recipient, true)); } /** From 039df379ccb5a136a96e21306c5ba2af91be57a5 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 12 Nov 2019 08:33:54 +1100 Subject: [PATCH 37/64] Fix minor UI --- .../securesms/conversation/ConversationActivity.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index c682afcb62..785d086582 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -2213,10 +2213,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity setInputPanelEnabled(!isPending); // This promise correctly updates the UI for multidevice - MultiDeviceUtilities.shouldEnableUserInput(this, recipient).success(shouldEnableInput -> { - setInputPanelEnabled(shouldEnableInput); - return Unit.INSTANCE; - }); + if (friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { + MultiDeviceUtilities.shouldEnableUserInput(this, recipient).success(shouldEnableInput -> { + setInputPanelEnabled(shouldEnableInput); + return Unit.INSTANCE; + }); + } } private void setInputPanelEnabled(boolean enabled) { From 7c4e7f261d84ef3bb5a90119e4572c018b33df3f Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 12 Nov 2019 16:30:28 +1100 Subject: [PATCH 38/64] Fix contact sync message parsing in pairing authorisation. --- .../securesms/jobs/MultiDeviceContactUpdateJob.java | 2 +- src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 00425988ec..7e2e45b9d9 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -142,7 +142,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy } if (address == null) generateFullContactUpdate(); - else if (address != TextSecurePreferences.getMasterHexEncodedPublicKey(context)) generateSingleContactUpdate(Address.fromSerialized(address)); + else if (!address.equalsIgnoreCase(TextSecurePreferences.getMasterHexEncodedPublicKey(context))) generateSingleContactUpdate(Address.fromSerialized(address)); } private void generateSingleContactUpdate(@NonNull Address address) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 4107e58dc0..d27589ed83 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -665,7 +665,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { - MessageSender.sendBackgroundFriendRequest(context, deviceContact.getNumber(), "This is an automated friend request. Still under testing!"); + MessageSender.sendBackgroundFriendRequest(context, deviceContact.getNumber(), "Accept this friend request to enable messages to be synced across devices"); Log.d("Loki", "Sent friend request to " + deviceContact.getNumber()); } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { // Accept the incoming friend request @@ -682,7 +682,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } catch (IOException e) { // Exception is thrown when we don't have any more contacts to read from - return; } catch (Exception e) { Log.d("Loki", "Failed to sync contact: " + e.getMessage()); } @@ -1635,7 +1634,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false); - if (content.getDataMessage().isPresent()) { + if (content.getPairingAuthorisation().isPresent()) { + return false; + } else if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); Recipient conversation = getMessageDestination(content, message); From 5c1ec4c06d72825c017b026ecd91e28fad514c58 Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Wed, 13 Nov 2019 09:47:27 +1100 Subject: [PATCH 39/64] Fix contact sync caused decryption lock to never release. Fix message --- .../securesms/jobs/PushDecryptJob.java | 18 ++++++++---------- .../securesms/sms/MessageSender.java | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index d27589ed83..4f1345f5a0 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -14,6 +14,7 @@ import android.util.Pair; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; +import com.google.android.gms.common.util.IOUtils; import org.signal.libsignal.metadata.InvalidMetadataMessageException; import org.signal.libsignal.metadata.InvalidMetadataVersionException; @@ -139,6 +140,7 @@ import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetSta import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.IOException; +import java.io.InputStream; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.ArrayList; @@ -649,10 +651,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private void handleSynchronizeContactMessage(@NonNull ContactsMessage contactsMessage) { if (contactsMessage.getContactsStream().isStream()) { Log.d("Loki", "Received contact sync message"); + try { - DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(contactsMessage.getContactsStream().asStream().getInputStream()); - DeviceContact deviceContact = contactsInputStream.read(); - while (deviceContact != null) { + InputStream in = contactsMessage.getContactsStream().asStream().getInputStream(); + DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in); + List devices = contactsInputStream.readAll(); + for (DeviceContact deviceContact : devices) { // Check if we have the contact as a friend and that we're not trying to sync our own device Address address = Address.fromSerialized(deviceContact.getNumber()); if (!address.isPhone() || address.toPhoneString().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { continue; } @@ -676,17 +680,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // 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? - - // Read the next contact - deviceContact = contactsInputStream.read(); } - } catch (IOException e) { - // Exception is thrown when we don't have any more contacts to read from } catch (Exception e) { - Log.d("Loki", "Failed to sync contact: " + e.getMessage()); + Log.d("Loki", "Failed to sync contact: " + e); } } - } private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 2ee0a3a5a4..996684cd1a 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -115,7 +115,7 @@ public class MessageSender { if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { sendBackgroundMessage(context, device); } else if (friendRequestStatus == LokiThreadFriendRequestStatus.NONE || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { - sendBackgroundFriendRequest(context, device, "This is a friend request from android! please replace this message in the future"); + sendBackgroundFriendRequest(context, device, "Accept this friend request to enable messages to be synced across devices"); } } }); From 41e0934dd47724af3b0cf3f5c51e843c3b52e265 Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Wed, 13 Nov 2019 12:28:17 +1100 Subject: [PATCH 40/64] Only update thread friend request status on message send if we haven't sent a previous request. Always send self sync messages to our primary device. Cache conversation input enabled so that subsequent calls don't cause it to flicker. --- .../conversation/ConversationActivity.java | 13 ++++++++++- .../securesms/jobs/PushSendJob.java | 3 ++- .../securesms/loki/FriendRequestHandler.kt | 23 +++++++++++-------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 785d086582..1ceca9c158 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -329,6 +329,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected HidingLinearLayout quickAttachmentToggle; protected HidingLinearLayout inlineAttachmentToggle; private InputPanel inputPanel; + private boolean alwaysEnableInputPanel = false; private LinkPreviewViewModel linkPreviewViewModel; private ConversationSearchViewModel searchViewModel; @@ -2202,7 +2203,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void updateInputPanel() { - if (recipient.isGroupRecipient() || isNoteToSelf()) { + /* + alwaysEnableInputPanel caches whether we have enabled the input once. + + This stops the case where the input panel disables and enables rapidly. + - This can occur when we are not friends with the current thread BUT multi-device tells us that we are friends with another one of their devices. + */ + + if (recipient.isGroupRecipient() || isNoteToSelf() || alwaysEnableInputPanel) { setInputPanelEnabled(true); return; } @@ -2212,9 +2220,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity boolean isPending = friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED; setInputPanelEnabled(!isPending); + alwaysEnableInputPanel = friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS; + // This promise correctly updates the UI for multidevice if (friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { MultiDeviceUtilities.shouldEnableUserInput(this, recipient).success(shouldEnableInput -> { + alwaysEnableInputPanel = shouldEnableInput; setInputPanelEnabled(shouldEnableInput); return Unit.INSTANCE; }); diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java index aaedf8f25a..526264ef67 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -303,7 +303,8 @@ public abstract class PushSendJob extends SendJob { } protected SignalServiceSyncMessage buildSelfSendSyncMessage(@NonNull Context context, @NonNull SignalServiceDataMessage message, Optional syncAccess) { - String localNumber = TextSecurePreferences.getLocalNumber(context); + String primary = TextSecurePreferences.getMasterHexEncodedPublicKey(context); + String localNumber = primary != null ? primary : TextSecurePreferences.getLocalNumber(context); SentTranscriptMessage transcript = new SentTranscriptMessage(localNumber, message.getTimestamp(), message, diff --git a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt index cc4ef0f273..a4ca3bd886 100644 --- a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt +++ b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt @@ -1,12 +1,8 @@ package org.thoughtcrime.securesms.loki import android.content.Context -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.successUi import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.util.Util -import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import java.lang.IllegalStateException @@ -20,13 +16,20 @@ object FriendRequestHandler { val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return if (!recipient.address.isPhone) { return } - // Update thread status - val threadFriendStatus = when (type) { - ActionType.Sending -> LokiThreadFriendRequestStatus.REQUEST_SENDING - ActionType.Failed -> LokiThreadFriendRequestStatus.NONE - ActionType.Sent -> LokiThreadFriendRequestStatus.REQUEST_SENT + val currentFriendStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId) + // Update thread status if we haven't sent a friend request before + if (currentFriendStatus != LokiThreadFriendRequestStatus.REQUEST_RECEIVED && + currentFriendStatus != LokiThreadFriendRequestStatus.REQUEST_SENT && + currentFriendStatus != LokiThreadFriendRequestStatus.FRIENDS + ) { + val threadFriendStatus = when (type) { + ActionType.Sending -> LokiThreadFriendRequestStatus.REQUEST_SENDING + ActionType.Failed -> LokiThreadFriendRequestStatus.NONE + ActionType.Sent -> LokiThreadFriendRequestStatus.REQUEST_SENT + } + DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, threadFriendStatus) } - DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, threadFriendStatus) + // Update message status if (messageId >= 0) { From 9c243eed751e1161a623607cccf8ad1219808005 Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Wed, 13 Nov 2019 14:53:42 +1100 Subject: [PATCH 41/64] Correctly redirect typing messages and receipts. Send an accept message back after becoming friends with a contact during contact sync. --- .../jobs/MultiDeviceContactUpdateJob.java | 3 -- .../securesms/jobs/PushDecryptJob.java | 47 +++++++++++++++---- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 7e2e45b9d9..656a5718da 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -369,9 +369,6 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy default: throw new AssertionError("Unknown state: " + identity.get().getVerifiedStatus()); } - // Loki - For now always set to verified - state = VerifiedMessage.VerifiedState.VERIFIED; - return Optional.of(new VerifiedMessage(destination, identityKey, state, System.currentTimeMillis())); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 4f1345f5a0..5635f4332c 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -658,7 +658,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { List devices = contactsInputStream.readAll(); for (DeviceContact deviceContact : devices) { // Check if we have the contact as a friend and that we're not trying to sync our own device - Address address = Address.fromSerialized(deviceContact.getNumber()); + String pubKey = deviceContact.getNumber(); + Address address = Address.fromSerialized(pubKey); if (!address.isPhone() || address.toPhoneString().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { continue; } /* @@ -669,11 +670,13 @@ public class PushDecryptJob extends BaseJob implements InjectableType { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { - MessageSender.sendBackgroundFriendRequest(context, deviceContact.getNumber(), "Accept this friend request to enable messages to be synced across devices"); - Log.d("Loki", "Sent friend request to " + deviceContact.getNumber()); + MessageSender.sendBackgroundFriendRequest(context, pubKey, "Accept this friend request to enable messages to be synced across devices"); + Log.d("Loki", "Sent friend request to " + pubKey); } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { // Accept the incoming friend request - becomeFriendsWithContact(deviceContact.getNumber(), false); + becomeFriendsWithContact(pubKey, false); + // Send them an accept message back + MessageSender.sendBackgroundMessage(context, pubKey); Log.d("Loki", "Became friends with " + deviceContact.getNumber()); } @@ -1386,10 +1389,17 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private void handleDeliveryReceipt(@NonNull SignalServiceContent content, @NonNull SignalServiceReceiptMessage message) { + // Redirect message to primary device conversation + Address sender = Address.fromSerialized(content.getSender()); + if (sender.isPhone()) { + Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender()); + sender = primaryDevice.getAddress(); + } + for (long timestamp : message.getTimestamps()) { Log.i(TAG, String.format("Received encrypted delivery receipt: (XXXXX, %d)", timestamp)); DatabaseFactory.getMmsSmsDatabase(context) - .incrementDeliveryReceiptCount(new SyncMessageId(Address.fromSerialized(content.getSender()), timestamp), System.currentTimeMillis()); + .incrementDeliveryReceiptCount(new SyncMessageId(sender, timestamp), System.currentTimeMillis()); } } @@ -1398,11 +1408,19 @@ public class PushDecryptJob extends BaseJob implements InjectableType { @NonNull SignalServiceReceiptMessage message) { if (TextSecurePreferences.isReadReceiptsEnabled(context)) { + + // Redirect message to primary device conversation + Address sender = Address.fromSerialized(content.getSender()); + if (sender.isPhone()) { + Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender()); + sender = primaryDevice.getAddress(); + } + for (long timestamp : message.getTimestamps()) { Log.i(TAG, String.format("Received encrypted read receipt: (XXXXX, %d)", timestamp)); DatabaseFactory.getMmsSmsDatabase(context) - .incrementReadReceiptCount(new SyncMessageId(Address.fromSerialized(content.getSender()), timestamp), content.getTimestamp()); + .incrementReadReceiptCount(new SyncMessageId(sender, timestamp), content.getTimestamp()); } } } @@ -1424,6 +1442,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); } else { + // See if we need to redirect the message + author = getPrimaryDeviceRecipient(content.getSender()); threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(author); } @@ -1598,10 +1618,17 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } - private Recipient getPrimaryDeviceRecipient(String recipient) { + /** + * Get the primary device recipient of the passed in device. + * + * If the device doesn't have a primary device then it will return the same device. + * If the device is our primary device then it will return our current device. + * Otherwise it will return the primary device. + */ + private Recipient getPrimaryDeviceRecipient(String pubKey) { try { - String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient).get(); - String publicKey = (primaryDevice != null) ? primaryDevice : recipient; + String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).get(); + String publicKey = (primaryDevice != null) ? primaryDevice : pubKey; // If the public key matches our primary device then we need to forward the message to ourselves (Note to self) String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) { @@ -1610,7 +1637,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { return Recipient.from(context, Address.fromSerialized(publicKey), false); } catch (Exception e) { Log.d("Loki", "Failed to get primary device public key for message. " + e.getMessage()); - return Recipient.from(context, Address.fromSerialized(recipient), false); + return Recipient.from(context, Address.fromSerialized(pubKey), false); } } From a90b0e70f56c2d84887ab0b9bb04c2cf338ee736 Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Wed, 13 Nov 2019 16:05:37 +1100 Subject: [PATCH 42/64] Fix conversation input. --- .../conversation/ConversationActivity.java | 35 ++++++++++++------- .../securesms/loki/MultiDeviceUtilities.kt | 7 ---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 1ceca9c158..bf967f8c03 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -329,7 +329,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected HidingLinearLayout quickAttachmentToggle; protected HidingLinearLayout inlineAttachmentToggle; private InputPanel inputPanel; - private boolean alwaysEnableInputPanel = false; private LinkPreviewViewModel linkPreviewViewModel; private ConversationSearchViewModel searchViewModel; @@ -354,6 +353,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private ArrayList mentions = new ArrayList<>(); private String oldText = ""; + // Multi Device + private boolean isFriendsWithAnyDevice = false; @Override protected void onPreCreate() { @@ -2204,13 +2205,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void updateInputPanel() { /* - alwaysEnableInputPanel caches whether we have enabled the input once. + isFriendsWithAnyDevice caches whether we are friends with any of the other users device. This stops the case where the input panel disables and enables rapidly. - This can occur when we are not friends with the current thread BUT multi-device tells us that we are friends with another one of their devices. */ - - if (recipient.isGroupRecipient() || isNoteToSelf() || alwaysEnableInputPanel) { + if (recipient.isGroupRecipient() || isNoteToSelf() || isFriendsWithAnyDevice) { setInputPanelEnabled(true); return; } @@ -2220,13 +2220,23 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity boolean isPending = friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED; setInputPanelEnabled(!isPending); - alwaysEnableInputPanel = friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS; + // We should always have the input panel enabled if we are friends with the current user + isFriendsWithAnyDevice = friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS; - // This promise correctly updates the UI for multidevice - if (friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { - MultiDeviceUtilities.shouldEnableUserInput(this, recipient).success(shouldEnableInput -> { - alwaysEnableInputPanel = shouldEnableInput; - setInputPanelEnabled(shouldEnableInput); + // Multi-device input logic + if (!isFriendsWithAnyDevice) { + // We should enable the input if we don't have any pending friend requests OR we are friends with a linked device + MultiDeviceUtilities.hasPendingFriendRequestWithAnyLinkedDevice(this, recipient).success(hasPendingRequests -> { + if (!hasPendingRequests) { + setInputPanelEnabled(true); + } else { + MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient).success(isFriends -> { + // If we are friend with any of the other devices then we want to make sure the input panel is always enabled for the duration of this conversation + isFriendsWithAnyDevice = isFriends; + setInputPanelEnabled(isFriends); + return Unit.INSTANCE; + }); + } return Unit.INSTANCE; }); } @@ -2447,9 +2457,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void updateToggleButtonState() { - // Don't allow attachments if we're not friends - LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId); - if (!isNoteToSelf() && !recipient.isGroupRecipient() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { + // Don't allow attachments if we're not friends with any device + if (!isNoteToSelf() && !recipient.isGroupRecipient() && !isFriendsWithAnyDevice) { buttonToggle.display(sendButton); quickAttachmentToggle.hide(); inlineAttachmentToggle.hide(); diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index db84dcf440..86da0c8082 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -185,10 +185,3 @@ fun hasPendingFriendRequestWithAnyLinkedDevice(context: Context, recipient: Reci false } } - -fun shouldEnableUserInput(context: Context, recipient: Recipient): Promise { - // Input should be enabled if we don't have any pending requests OR we're friends with any linked device - return hasPendingFriendRequestWithAnyLinkedDevice(context, recipient).bind { hasPendingFriendRequest -> - if (!hasPendingFriendRequest) Promise.of(true) else isFriendsWithAnyLinkedDevice(context, recipient) - }.recover { true } -} From 44ccc66ec2a9c8b060b85ddab7cb7a8d2a45c213 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 14 Nov 2019 10:44:55 +1100 Subject: [PATCH 43/64] Fix multi-device media messages. This fixes the issue of the same attachments being uploaded multiple times per linked device. Now we only upload the attachments once and then we send the media message. --- .../securesms/jobs/AttachmentUploadJob.java | 18 ++++--- .../securesms/jobs/PushMediaSendJob.java | 50 +++++++++++-------- .../securesms/sms/MessageSender.java | 20 ++++++-- 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java index 9a091150ce..dce85f83e6 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java @@ -27,6 +27,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import java.io.IOException; import java.io.InputStream; @@ -84,14 +85,17 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType { if (databaseAttachment == null) { throw new IllegalStateException("Cannot find the specified attachment."); } + + // Only upload attachment if necessary + if (databaseAttachment.getUrl().isEmpty()) { + MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); + Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment); + SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment); + SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker(), new SignalServiceAddress(destination.serialize())); + Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get(); - MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); - Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment); - SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment); - SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker(), new SignalServiceAddress(destination.serialize())); - Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get(); - - database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment); + database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment); + } } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 408e353d9c..108bcab936 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -49,6 +49,8 @@ import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -94,37 +96,29 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { this.shouldSendSyncMessage = shouldSendSyncMessage; } - @WorkerThread public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination, boolean shouldSendSyncMessage) { - enqueue(context, jobManager, messageId, messageId, destination, shouldSendSyncMessage); + enqueue(context, jobManager, messageId, messageId, destination, false, null, shouldSendSyncMessage); } - @WorkerThread - public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination, boolean shouldSendSyncMessage) { - enqueue(context, jobManager, templateMessageId, messageId, destination, false, null, shouldSendSyncMessage); - } - - @WorkerThread public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination, Boolean isFriendRequest, @Nullable String customFriendRequestMessage, boolean shouldSendSyncMessage) { + enqueue(context, jobManager, Collections.singletonList(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage, shouldSendSyncMessage))); + } + + @WorkerThread + public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, List jobs) { + if (jobs.size() == 0) { return; } + PushMediaSendJob first = jobs.get(0); + long messageId = first.templateMessageId; try { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - OutgoingMediaMessage message = database.getOutgoingMessage(messageId); - List attachments = new LinkedList<>(); - - attachments.addAll(message.getAttachments()); - attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList()); - attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList()); - - List attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId(), destination)).toList(); + List attachmentJobs = getAttachmentUploadJobs(context, messageId, first.destination); if (attachmentJobs.isEmpty()) { - jobManager.add(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage, shouldSendSyncMessage)); + for (PushMediaSendJob job : jobs) { jobManager.add(job); } } else { jobManager.startChain(attachmentJobs) - .then(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage, shouldSendSyncMessage)) - .enqueue(); + .then((List)(List)jobs) + .enqueue(); } - } catch (NoSuchMessageException | MmsException e) { Log.w(TAG, "Failed to enqueue message.", e); DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId); @@ -132,6 +126,20 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { } } + public static List getAttachmentUploadJobs(@NonNull Context context, long messageId, @NonNull Address destination) + throws NoSuchMessageException, MmsException + { + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + OutgoingMediaMessage message = database.getOutgoingMessage(messageId); + List attachments = new LinkedList<>(); + + attachments.addAll(message.getAttachments()); + attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList()); + attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList()); + + return Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId(), destination)).toList(); + } + @Override public @NonNull Data serialize() { Data.Builder builder = new Data.Builder() diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 996684cd1a..76b8685aaa 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; +import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobs.MmsSendJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; @@ -69,6 +70,8 @@ import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestSt import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import kotlin.Unit; @@ -313,6 +316,7 @@ public class MessageSender { MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey).success(devices -> { int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); Util.runOnMain(() -> { + ArrayList jobs = new ArrayList<>(); for (Map.Entry entry : devices.entrySet()) { String devicePublicKey = entry.getKey(); boolean isFriend = entry.getValue(); @@ -325,9 +329,9 @@ public class MessageSender { // We should also send a sync message if we haven't already sent one boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && address.isPhone(); if (type == MessageType.MEDIA) { - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); + jobs.add(new PushMediaSendJob(messageId, messageIDToUse, address, false, null, shouldSendSyncMessage)); } else { - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); + jobs.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); } if (shouldSendSyncMessage) { hasSentSyncMessage[0] = true; } } else { @@ -336,12 +340,20 @@ public class MessageSender { boolean isFriendsWithAny = (friendCount > 0); String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; if (type == MessageType.MEDIA) { - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false); + jobs.add(new PushMediaSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); } else { - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); + jobs.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); } } } + + // Start the send + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, (List)(List)jobs); + } else { + // Schedule text send jobs + jobManager.startChain(jobs).enqueue(); + } }); return Unit.INSTANCE; }); From d1289ec7bd200c62363bddb26d4ffd2c2f770062 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 14 Nov 2019 12:00:46 +1100 Subject: [PATCH 44/64] Refactoring. --- .../securesms/ApplicationContext.java | 2 -- .../conversation/ConversationActivity.java | 19 +++++----------- .../jobs/MultiDeviceContactUpdateJob.java | 2 +- .../securesms/jobs/PushDecryptJob.java | 2 +- .../securesms/loki/FriendRequestHandler.kt | 1 - .../securesms/loki/LokiMessageSyncEvent.kt | 22 ------------------- .../securesms/loki/MultiDeviceUtilities.kt | 5 +++-- .../loki/PushBackgroundMessageSendJob.kt | 4 ---- .../push/MessageSenderEventListener.java | 1 - .../securesms/util/TextSecurePreferences.java | 2 +- 10 files changed, 12 insertions(+), 48 deletions(-) delete mode 100644 src/org/thoughtcrime/securesms/loki/LokiMessageSyncEvent.kt diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 45d1ab5832..1a7af8b058 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -66,7 +66,6 @@ import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.loki.BackgroundPollWorker; import org.thoughtcrime.securesms.loki.LokiAPIDatabase; -import org.thoughtcrime.securesms.loki.LokiMessageSyncEvent; import org.thoughtcrime.securesms.loki.LokiPublicChatManager; import org.thoughtcrime.securesms.loki.LokiRSSFeedPoller; import org.thoughtcrime.securesms.loki.LokiUserDatabase; @@ -82,7 +81,6 @@ import org.thoughtcrime.securesms.service.LocalBackupListener; import org.thoughtcrime.securesms.service.RotateSenderCertificateListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; -import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper; import org.webrtc.PeerConnectionFactory; diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index bf967f8c03..6e761f90bc 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -3050,18 +3050,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Address contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress(); String contactPubKey = contact.toString(); - Context context = this; - AsyncTask.execute(() -> { - try { - MessageSender.sendBackgroundMessageToAllDevices(this, contactPubKey); - MessageSender.syncContact(this, contact); - DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS); - lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); - Util.runOnMain(this::updateInputPanel); - } catch (Exception e) { - Log.d("Loki", "Failed to send background message to: " + contactPubKey + "."); - } - }); + DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS); + lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + MessageSender.sendBackgroundMessageToAllDevices(this, contactPubKey); + MessageSender.syncContact(this, contact); + updateInputPanel(); } @Override @@ -3077,7 +3070,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } public boolean isNoteToSelf() { - return TextSecurePreferences.getLocalNumber(this).equalsIgnoreCase(recipient.getAddress().serialize()); + return TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize()); } // endregion } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 656a5718da..6479ef0b64 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -142,7 +142,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy } if (address == null) generateFullContactUpdate(); - else if (!address.equalsIgnoreCase(TextSecurePreferences.getMasterHexEncodedPublicKey(context))) generateSingleContactUpdate(Address.fromSerialized(address)); + else if (!address.equals(TextSecurePreferences.getMasterHexEncodedPublicKey(context))) generateSingleContactUpdate(Address.fromSerialized(address)); } private void generateSingleContactUpdate(@NonNull Address address) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 5635f4332c..f72f9cf1a5 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -660,7 +660,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Check if we have the contact as a friend and that we're not trying to sync our own device String pubKey = deviceContact.getNumber(); Address address = Address.fromSerialized(pubKey); - if (!address.isPhone() || address.toPhoneString().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { continue; } + 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 diff --git a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt index a4ca3bd886..bb58f3dd88 100644 --- a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt +++ b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt @@ -5,7 +5,6 @@ import nl.komponents.kovenant.ui.successUi import org.thoughtcrime.securesms.database.DatabaseFactory import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus -import java.lang.IllegalStateException object FriendRequestHandler { enum class ActionType { Sending, Sent, Failed } diff --git a/src/org/thoughtcrime/securesms/loki/LokiMessageSyncEvent.kt b/src/org/thoughtcrime/securesms/loki/LokiMessageSyncEvent.kt deleted file mode 100644 index 426154a3cd..0000000000 --- a/src/org/thoughtcrime/securesms/loki/LokiMessageSyncEvent.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.thoughtcrime.securesms.loki - -import android.content.Context -import android.content.Intent -import android.support.v4.content.LocalBroadcastManager - -object LokiMessageSyncEvent { - const val MESSAGE_SYNC_EVENT = "com.loki-network.messenger.MESSAGE_SYNC_EVENT" - const val MESSAGE_ID = "message_id" - const val TIMESTAMP = "timestamp" - const val SYNC_MESSAGE = "sync_message" - const val TTL = "ttl" - - fun broadcastSecurityUpdateEvent(context: Context, messageID: Long, timestamp: Long, message: ByteArray, ttl: Int) { - val intent = Intent(MESSAGE_SYNC_EVENT) - intent.putExtra(MESSAGE_ID, messageID) - intent.putExtra(TIMESTAMP, timestamp) - intent.putExtra(SYNC_MESSAGE, message) - intent.putExtra(TTL, ttl) - LocalBroadcastManager.getInstance(context).sendBroadcast(intent) - } -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 86da0c8082..257336de05 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -2,10 +2,11 @@ package org.thoughtcrime.securesms.loki import android.content.Context -import android.os.Handler -import nl.komponents.kovenant.* +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.all import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map +import nl.komponents.kovenant.toFailVoid import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address diff --git a/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt index cca9031003..7533609185 100644 --- a/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt @@ -2,21 +2,17 @@ package org.thoughtcrime.securesms.loki import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseFactory -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.logging.Log import org.whispersystems.libsignal.util.guava.Optional -import org.whispersystems.signalservice.api.SignalServiceMessageSender import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair -import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage import org.whispersystems.signalservice.api.push.SignalServiceAddress import java.io.IOException import java.util.concurrent.TimeUnit -import javax.inject.Inject class PushBackgroundMessageSendJob private constructor( parameters: Parameters, diff --git a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java index 6b492be25e..5cc2d886c1 100644 --- a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java +++ b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java @@ -4,7 +4,6 @@ import android.content.Context; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.loki.FriendRequestHandler; -import org.thoughtcrime.securesms.loki.LokiMessageSyncEvent; import org.thoughtcrime.securesms.sms.MessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.push.SignalServiceAddress; diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index bd72a58b59..8041fe10f4 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -1183,7 +1183,7 @@ public class TextSecurePreferences { } public static void setMasterHexEncodedPublicKey(Context context, String masterHexEncodedPublicKey) { - setStringPreference(context, "master_hex_encoded_public_key", masterHexEncodedPublicKey); + setStringPreference(context, "master_hex_encoded_public_key", masterHexEncodedPublicKey.toLowerCase()); } // endregion } From 4837c520e384217ea43c465b0a18dfa0f68a9ac3 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 14 Nov 2019 13:14:09 +1100 Subject: [PATCH 45/64] multiple deletion frontend --- .../conversation/ConversationFragment.java | 136 ++++++++++++------ 1 file changed, 92 insertions(+), 44 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 87b03e5118..93d360b532 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -55,6 +55,8 @@ import android.widget.Toast; import android.widget.ViewSwitcher; import com.annimon.stream.Stream; +import com.annimon.stream.operator.LongArray; +import com.google.common.primitives.Longs; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.MessageDetailsActivity; @@ -113,6 +115,7 @@ import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; import kotlin.Unit; @@ -414,7 +417,8 @@ public class ConversationFragment extends Fragment menu.findItem(R.id.menu_context_reply).setVisible(isPublicChat && selectedMessageCount == 1); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext()); boolean userCanModerate = isPublicChat && LokiPublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer()); - boolean isDeleteOptionVisible = isPublicChat && selectedMessageCount == 1 && (isSentByUser || userCanModerate); +// boolean isDeleteOptionVisible = isPublicChat && selectedMessageCount == 1 && (isSentByUser || userCanModerate); + boolean isDeleteOptionVisible = isPublicChat && (isSentByUser || userCanModerate); menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible); } else { menu.findItem(R.id.menu_context_copy_public_key).setVisible(false); @@ -520,53 +524,97 @@ public class ConversationFragment extends Fragment { @Override protected Void doInBackground(MessageRecord... messageRecords) { + ArrayList serverIDs = new ArrayList<>(); + ArrayList ignoredMessages = new ArrayList<>(); + ArrayList failedMessages = new ArrayList<>(); + boolean isSentByUser = true; + LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getLokiPublicChatAPI(); for (MessageRecord messageRecord : messageRecords) { - boolean isThreadDeleted; - - if (publicChat != null) { - final SettableFuture[] future = { new SettableFuture() }; - - LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getLokiPublicChatAPI(); - boolean isSentByUser = messageRecord.isOutgoing(); - Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id); - - if (publicChatAPI != null && serverID != null) { - publicChatAPI - .deleteMessage(serverID, publicChat.getChannel(), publicChat.getServer(), isSentByUser) - .success(l -> { - @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture) future[0]; - f.set(Unit.INSTANCE); - return Unit.INSTANCE; - }).fail(e -> { - @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture) future[0]; - f.setException(e); - return Unit.INSTANCE; - }); - } else { - @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture) future[0]; - f.setException(new Exception("Message server ID is null.")); - } - - try { - @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture)future[0]; - f.get(); - } catch (Exception exception) { - Log.d("Loki", "Couldn't delete message due to error: " + exception.toString() + "."); - return null; - } + isSentByUser = isSentByUser && messageRecord.isOutgoing(); + Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id); + if (serverID != null) { + serverIDs.add(serverID); } - - if (messageRecord.isMms()) { - isThreadDeleted = DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId()); - } else { - isThreadDeleted = DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); - } - - if (isThreadDeleted) { - threadId = -1; - listener.setThreadId(threadId); + else { + ignoredMessages.add(messageRecord.getId()); } } + long[] serverIDsArray = new long[serverIDs.size()]; + for (int i=0; i < serverIDs.size(); i++) { + serverIDsArray[i] = serverIDs.get(i); + } + if (publicChat != null && publicChatAPI != null) { + publicChatAPI + .deleteMessages(serverIDsArray, publicChat.getChannel(), publicChat.getServer(), isSentByUser) + .success(l -> { + List longList = Longs.asList(l); + for (MessageRecord messageRecord : messageRecords) { + Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id); + if (longList.contains(serverID)) { + if (messageRecord.isMms()) { + DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId()); + } else { + DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); + } + } + else if (!ignoredMessages.contains(serverID)) { + failedMessages.add(messageRecord.getId()); + Log.d("Loki", "Failed to delete message: " + messageRecord.getId() + "."); + } + } + return null; + }). fail(e -> { + Log.d("Loki", "Couldn't delete message due to error: " + e.toString() + "."); + return null; + }); + } +// for (MessageRecord messageRecord : messageRecords) { +// boolean isThreadDeleted; +// +// if (publicChat != null) { +// final SettableFuture[] future = { new SettableFuture() }; +// +// LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getLokiPublicChatAPI(); +// boolean isSentByUser = messageRecord.isOutgoing(); +// Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id); +// +// if (publicChatAPI != null && serverID != null) { +// publicChatAPI +// .deleteMessage(serverID, publicChat.getChannel(), publicChat.getServer(), isSentByUser) +// .success(l -> { +// @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture) future[0]; +// f.set(Unit.INSTANCE); +// return Unit.INSTANCE; +// }).fail(e -> { +// @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture) future[0]; +// f.setException(e); +// return Unit.INSTANCE; +// }); +// } else { +// @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture) future[0]; +// f.setException(new Exception("Message server ID is null.")); +// } +// +// try { +// @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture)future[0]; +// f.get(); +// } catch (Exception exception) { +// Log.d("Loki", "Couldn't delete message due to error: " + exception.toString() + "."); +// return null; +// } +// } +// +// if (messageRecord.isMms()) { +// isThreadDeleted = DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId()); +// } else { +// isThreadDeleted = DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); +// } +// +// if (isThreadDeleted) { +// threadId = -1; +// listener.setThreadId(threadId); +// } +// } return null; } From 9ed5ebab6d9cc694546ef99bbc602ce1959b3b20 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 15 Nov 2019 09:27:14 +1100 Subject: [PATCH 46/64] Update ConversationFragement.java with the change of server. Use List instead of Array. Delete the converting codes. --- .../securesms/conversation/ConversationFragment.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 93d360b532..10b82b8ba2 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -539,18 +539,13 @@ public class ConversationFragment extends Fragment ignoredMessages.add(messageRecord.getId()); } } - long[] serverIDsArray = new long[serverIDs.size()]; - for (int i=0; i < serverIDs.size(); i++) { - serverIDsArray[i] = serverIDs.get(i); - } if (publicChat != null && publicChatAPI != null) { publicChatAPI - .deleteMessages(serverIDsArray, publicChat.getChannel(), publicChat.getServer(), isSentByUser) + .deleteMessages(serverIDs, publicChat.getChannel(), publicChat.getServer(), isSentByUser) .success(l -> { - List longList = Longs.asList(l); for (MessageRecord messageRecord : messageRecords) { Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id); - if (longList.contains(serverID)) { + if (l.contains(serverID)) { if (messageRecord.isMms()) { DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId()); } else { From 55ea9869a8fde47bab5104d8714af0acfa6dbf84 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 14 Nov 2019 16:29:55 +1100 Subject: [PATCH 47/64] Hook up http cache to LokiDotNetAPI. --- .../securesms/ApplicationContext.java | 10 +++++----- .../securesms/jobs/PushMediaSendJob.java | 6 ++++-- .../securesms/jobs/PushTextSendJob.java | 16 ++++++++++------ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 1a7af8b058..e12ea412fb 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -20,17 +20,13 @@ import android.annotation.SuppressLint; import android.arch.lifecycle.DefaultLifecycleObserver; import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.ProcessLifecycleOwner; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.database.ContentObserver; import android.os.AsyncTask; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.multidex.MultiDexApplication; -import android.support.v4.content.LocalBroadcastManager; import com.crashlytics.android.Crashlytics; import com.google.android.gms.security.ProviderInstaller; @@ -91,6 +87,7 @@ import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol; +import org.whispersystems.signalservice.loki.api.LokiDotNetAPI; import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI; import org.whispersystems.signalservice.loki.api.LokiLongPoller; @@ -113,6 +110,7 @@ import io.fabric.sdk.android.Fabric; import kotlin.Unit; import kotlin.jvm.functions.Function1; import network.loki.messenger.BuildConfig; +import okhttp3.Cache; import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant; import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant; @@ -128,6 +126,7 @@ import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant; public class ApplicationContext extends MultiDexApplication implements DependencyInjector, DefaultLifecycleObserver, LokiP2PAPIDelegate { private static final String TAG = ApplicationContext.class.getSimpleName(); + private final static int OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB private ExpiringMessageManager expiringMessageManager; private TypingStatusRepository typingStatusRepository; @@ -192,7 +191,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc }; // Loki - Set up public chat manager lokiPublicChatManager = new LokiPublicChatManager(this); - + // Loki - Set the cache + LokiDotNetAPI.setCache(new Cache(this.getCacheDir(), OK_HTTP_CACHE_SIZE)); // Loki - Update device mappings if (setUpStorageAPIIfNeeded()) { LokiStorageAPI.Companion.getShared().updateUserDeviceMappings(); diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 108bcab936..ae133a0394 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -227,8 +227,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { } } catch (UntrustedIdentityException uie) { warn(TAG, "Failure", uie); - database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey()); - database.markAsSentFailed(messageId); + if (messageId >= 0) { + database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey()); + database.markAsSentFailed(messageId); + } } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index bfd4839e36..a8a084a447 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -159,14 +159,18 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { } catch (InsecureFallbackApprovalException e) { warn(TAG, "Failure", e); - database.markAsPendingInsecureSmsFallback(record.getId()); - MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId()); - ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(false)); + if (messageId >= 0) { + database.markAsPendingInsecureSmsFallback(record.getId()); + MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId()); + ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(false)); + } } catch (UntrustedIdentityException e) { warn(TAG, "Failure", e); - database.addMismatchedIdentity(record.getId(), Address.fromSerialized(e.getE164Number()), e.getIdentityKey()); - database.markAsSentFailed(record.getId()); - database.markAsPush(record.getId()); + if (messageId >= 0) { + database.addMismatchedIdentity(record.getId(), Address.fromSerialized(e.getE164Number()), e.getIdentityKey()); + database.markAsSentFailed(record.getId()); + database.markAsPush(record.getId()); + } } } From 848cab8677845ccd1a6437aec11334d554b9e190 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 15 Nov 2019 09:58:13 +1100 Subject: [PATCH 48/64] Handle incoming messages from our other devices in public chats. --- .../securesms/loki/LokiPublicChatPoller.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 71a577db75..810353a614 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -25,6 +25,8 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage +import org.whispersystems.signalservice.loki.api.LokiStorageAPI +import org.whispersystems.signalservice.loki.utilities.get import org.whispersystems.signalservice.loki.utilities.successBackground class LokiPublicChatPoller(private val context: Context, private val group: LokiPublicChat) { @@ -199,12 +201,15 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki } } api.getMessages(group.channel, group.server).successBackground { messages -> - // Process messages in the background - messages.forEach { message -> - if (message.hexEncodedPublicKey != userHexEncodedPublicKey) { - processIncomingMessage(message) - } else { - processOutgoingMessage(message) + if (messages.isNotEmpty()) { + val ourDevices = LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).get(setOf()) + // Process messages in the background + messages.forEach { message -> + if (ourDevices.contains(message.hexEncodedPublicKey)) { + processOutgoingMessage(message) + } else { + processIncomingMessage(message) + } } } }.fail { From 463aaf0fb871944a541714db1a8275365c1e2c5e Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 15 Nov 2019 11:37:36 +1100 Subject: [PATCH 49/64] Fix processing of outgoing attachment messages in public chats. Before we were directly inserting messages into the database but that wasn't working because attachments never got downloaded. This fixes it so we forcefully go through signals pipeline via self sync messages. --- .../securesms/jobs/PushDecryptJob.java | 7 +- .../securesms/loki/LokiPublicChatPoller.kt | 135 +++++++----------- 2 files changed, 54 insertions(+), 88 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index f72f9cf1a5..0c2afbff5e 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -891,7 +891,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { return threadId; } - private long handleSynchronizeSentMediaMessage(@NonNull SentTranscriptMessage message) + public long handleSynchronizeSentMediaMessage(@NonNull SentTranscriptMessage message) throws MmsException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); @@ -927,6 +927,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { try { long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, null); + if (message.messageServerID >= 0) { DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageId, message.messageServerID); } if (recipients.getAddress().isGroup()) { GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); @@ -1222,7 +1223,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } - private long handleSynchronizeSentTextMessage(@NonNull SentTranscriptMessage message) + public long handleSynchronizeSentTextMessage(@NonNull SentTranscriptMessage message) throws MmsException { @@ -1245,6 +1246,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage); messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null); + if (message.messageServerID >= 0) { DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageId, message.messageServerID); } + database = DatabaseFactory.getMmsDatabase(context); GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 810353a614..cbaa33cf65 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -4,23 +4,15 @@ import android.content.Context import android.os.Handler import android.util.Log import org.thoughtcrime.securesms.crypto.IdentityKeyUtil -import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.jobs.PushDecryptJob -import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository -import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil -import org.thoughtcrime.securesms.mms.OutgoingMediaMessage -import org.thoughtcrime.securesms.mms.QuoteModel -import org.thoughtcrime.securesms.recipients.Recipient -import org.thoughtcrime.securesms.util.GroupUtil import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.thoughtcrime.securesms.util.Util import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer import org.whispersystems.signalservice.api.messages.SignalServiceContent import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage import org.whispersystems.signalservice.api.messages.SignalServiceGroup +import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI @@ -28,6 +20,7 @@ import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.utilities.get import org.whispersystems.signalservice.loki.utilities.successBackground +import java.util.* class LokiPublicChatPoller(private val context: Context, private val group: LokiPublicChat) { private val handler = Handler() @@ -97,18 +90,17 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki // endregion // region Polling - private fun pollForNewMessages() { - fun processIncomingMessage(message: LokiPublicChatMessage) { - val id = group.id.toByteArray() - val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null) - val quote = if (message.quote != null) { - SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteeHexEncodedPublicKey), message.quote!!.quotedMessageBody, listOf()) - } else { - null - } - val attachments = message.attachments.mapNotNull { attachment -> - if (attachment.kind != LokiPublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null } - SignalServiceAttachmentPointer( + private fun getDataMessage(message: LokiPublicChatMessage): SignalServiceDataMessage { + val id = group.id.toByteArray() + val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null) + val quote = if (message.quote != null) { + SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteeHexEncodedPublicKey), message.quote!!.quotedMessageBody, listOf()) + } else { + null + } + val attachments = message.attachments.mapNotNull { attachment -> + if (attachment.kind != LokiPublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null } + SignalServiceAttachmentPointer( attachment.serverID, attachment.contentType, ByteArray(0), @@ -120,86 +112,57 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki false, Optional.fromNullable(attachment.caption), attachment.url) - } - val linkPreview = message.attachments.firstOrNull { it.kind == LokiPublicChatMessage.Attachment.Kind.LinkPreview } - val signalLinkPreviews = mutableListOf() - if (linkPreview != null) { - val attachment = SignalServiceAttachmentPointer( - linkPreview.serverID, - linkPreview.contentType, - ByteArray(0), - Optional.of(linkPreview.size), - Optional.absent(), - linkPreview.width, linkPreview.height, - Optional.absent(), - Optional.of(linkPreview.fileName), - false, - Optional.fromNullable(linkPreview.caption), - linkPreview.url) - signalLinkPreviews.add(SignalServiceDataMessage.Preview(linkPreview.linkPreviewURL!!, linkPreview.linkPreviewTitle!!, Optional.of(attachment))) - } - val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body - val serviceDataMessage = SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, false, 0, false, null, false, quote, null, signalLinkPreviews, null) + } + val linkPreview = message.attachments.firstOrNull { it.kind == LokiPublicChatMessage.Attachment.Kind.LinkPreview } + val signalLinkPreviews = mutableListOf() + if (linkPreview != null) { + val attachment = SignalServiceAttachmentPointer( + linkPreview.serverID, + linkPreview.contentType, + ByteArray(0), + Optional.of(linkPreview.size), + Optional.absent(), + linkPreview.width, linkPreview.height, + Optional.absent(), + Optional.of(linkPreview.fileName), + false, + Optional.fromNullable(linkPreview.caption), + linkPreview.url) + signalLinkPreviews.add(SignalServiceDataMessage.Preview(linkPreview.linkPreviewURL!!, linkPreview.linkPreviewTitle!!, Optional.of(attachment))) + } + val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body + return SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, false, 0, false, null, false, quote, null, signalLinkPreviews, null) + } + + private fun pollForNewMessages() { + fun processIncomingMessage(message: LokiPublicChatMessage) { + val serviceDataMessage = getDataMessage(message) val serviceContent = SignalServiceContent(serviceDataMessage, message.hexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false) val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})" DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName) - if (quote != null || attachments.count() > 0 || linkPreview != null) { + if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) } else { PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) } } + fun processOutgoingMessage(message: LokiPublicChatMessage) { val messageServerID = message.serverID ?: return - val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context) - val isDuplicate = lokiMessageDatabase.getMessageID(messageServerID) != null + val isDuplicate = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID) != null if (isDuplicate) { return } if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { return } - val id = group.id.toByteArray() - val mmsDatabase = DatabaseFactory.getMmsDatabase(context) - val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(id, false)), false) - val quote: QuoteModel? - if (message.quote != null) { - quote = QuoteModel(message.quote!!.quotedMessageTimestamp, Address.fromSerialized(message.quote!!.quoteeHexEncodedPublicKey), message.quote!!.quotedMessageBody, false, listOf()) + val localNumber = TextSecurePreferences.getLocalNumber(context) + val dataMessage = getDataMessage(message) + val transcript = SentTranscriptMessage(localNumber, dataMessage.timestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(localNumber, false)) + transcript.messageServerID = messageServerID + if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) { + PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript) } else { - quote = null - } - // TODO: Handle attachments correctly for our previous messages - val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body - val signalMessage = OutgoingMediaMessage(recipient, body, listOf(), message.timestamp, 0, 0, - ThreadDatabase.DistributionTypes.DEFAULT, quote, listOf(), listOf(), listOf(), listOf()) - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient) - fun finalize() { - val messageID = mmsDatabase.insertMessageOutbox(signalMessage, threadID, false, null) - mmsDatabase.markAsSent(messageID, true) - mmsDatabase.markUnidentified(messageID, false) - lokiMessageDatabase.setServerID(messageID, messageServerID) - } - val urls = LinkPreviewUtil.findWhitelistedUrls(message.body) - val urlCount = urls.size - if (urlCount != 0) { - val lpr = LinkPreviewRepository(context) - var count = 0 - urls.forEach { url -> - lpr.getLinkPreview(context, url.url) { lp -> - Util.runOnMain { - count += 1 - if (lp.isPresent) { signalMessage.linkPreviews.add(lp.get()) } - if (count == urlCount) { - try { - finalize() - } catch (e: Exception) { - // TODO: Handle - } - - } - } - } - } - } else { - finalize() + PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) } } + api.getMessages(group.channel, group.server).successBackground { messages -> if (messages.isNotEmpty()) { val ourDevices = LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).get(setOf()) From ed2dfdbf683f4f09d06630f65bdfe221a5dba070 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 15 Nov 2019 15:47:46 +1100 Subject: [PATCH 50/64] remove the commented out code --- .../conversation/ConversationFragment.java | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 10b82b8ba2..a5e58b4a8e 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -563,53 +563,6 @@ public class ConversationFragment extends Fragment return null; }); } -// for (MessageRecord messageRecord : messageRecords) { -// boolean isThreadDeleted; -// -// if (publicChat != null) { -// final SettableFuture[] future = { new SettableFuture() }; -// -// LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getLokiPublicChatAPI(); -// boolean isSentByUser = messageRecord.isOutgoing(); -// Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id); -// -// if (publicChatAPI != null && serverID != null) { -// publicChatAPI -// .deleteMessage(serverID, publicChat.getChannel(), publicChat.getServer(), isSentByUser) -// .success(l -> { -// @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture) future[0]; -// f.set(Unit.INSTANCE); -// return Unit.INSTANCE; -// }).fail(e -> { -// @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture) future[0]; -// f.setException(e); -// return Unit.INSTANCE; -// }); -// } else { -// @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture) future[0]; -// f.setException(new Exception("Message server ID is null.")); -// } -// -// try { -// @SuppressWarnings("unchecked") SettableFuture f = (SettableFuture)future[0]; -// f.get(); -// } catch (Exception exception) { -// Log.d("Loki", "Couldn't delete message due to error: " + exception.toString() + "."); -// return null; -// } -// } -// -// if (messageRecord.isMms()) { -// isThreadDeleted = DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId()); -// } else { -// isThreadDeleted = DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); -// } -// -// if (isThreadDeleted) { -// threadId = -1; -// listener.setThreadId(threadId); -// } -// } return null; } From 30df89ea588c22b60c1aec9bb230ca5795986b17 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 15 Nov 2019 16:24:58 +1100 Subject: [PATCH 51/64] Fix critical issues --- res/values/strings.xml | 2 +- .../thoughtcrime/securesms/ApplicationContext.java | 2 +- .../securesms/loki/FriendRequestHandler.kt | 1 - .../securesms/loki/LokiPublicChatPoller.kt | 2 -- .../securesms/loki/NewConversationActivity.kt | 2 +- .../securesms/loki/PushBackgroundMessageSendJob.kt | 8 ++++---- .../securesms/loki/PushMessageSyncSendJob.kt | 12 ++++++------ 7 files changed, 13 insertions(+), 16 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index eb564c207e..307c000c62 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1572,7 +1572,7 @@ Looks like you don\'t have any conversations yet. Get started by messaging a friend. - This is a secondary device + Secondary device Copied to clipboard Share Public Key Show QR Code diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index e12ea412fb..f1c3c872db 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -126,7 +126,7 @@ import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant; public class ApplicationContext extends MultiDexApplication implements DependencyInjector, DefaultLifecycleObserver, LokiP2PAPIDelegate { private static final String TAG = ApplicationContext.class.getSimpleName(); - private final static int OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB + private final static int OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10 MB private ExpiringMessageManager expiringMessageManager; private TypingStatusRepository typingStatusRepository; diff --git a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt index bb58f3dd88..050f9a1bb6 100644 --- a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt +++ b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt @@ -29,7 +29,6 @@ object FriendRequestHandler { DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, threadFriendStatus) } - // Update message status if (messageId >= 0) { val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index cbaa33cf65..28bca2fea1 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -146,7 +146,6 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) } } - fun processOutgoingMessage(message: LokiPublicChatMessage) { val messageServerID = message.serverID ?: return val isDuplicate = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID) != null @@ -162,7 +161,6 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) } } - api.getMessages(group.channel, group.server).successBackground { messages -> if (messages.isNotEmpty()) { val ourDevices = LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).get(setOf()) diff --git a/src/org/thoughtcrime/securesms/loki/NewConversationActivity.kt b/src/org/thoughtcrime/securesms/loki/NewConversationActivity.kt index 16be98fd73..9ad53944a5 100644 --- a/src/org/thoughtcrime/securesms/loki/NewConversationActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/NewConversationActivity.kt @@ -68,7 +68,7 @@ class NewConversationActivity : PassphraseRequiredActionBarActivity(), ScanListe fun startNewConversationIfPossible(hexEncodedPublicKey: String) { if (!PublicKeyValidation.isValid(hexEncodedPublicKey)) { return Toast.makeText(this, R.string.fragment_new_conversation_invalid_public_key_message, Toast.LENGTH_SHORT).show() } val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) - // If we try to contact our master then redirect to note to self + // If we try to contact our master device then redirect to note to self val contactPublicKey = if (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == hexEncodedPublicKey) userHexEncodedPublicKey else hexEncodedPublicKey val contact = Recipient.from(this, Address.fromSerialized(contactPublicKey), true) val intent = Intent(this, ConversationActivity::class.java) diff --git a/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt index 7533609185..0c7e055ff1 100644 --- a/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt @@ -15,10 +15,10 @@ import java.io.IOException import java.util.concurrent.TimeUnit class PushBackgroundMessageSendJob private constructor( - parameters: Parameters, - private val recipient: String, - private val messageBody: String?, - private val friendRequest: Boolean + parameters: Parameters, + private val recipient: String, + private val messageBody: String?, + private val friendRequest: Boolean ) : BaseJob(parameters) { companion object { const val KEY = "PushBackgroundMessageSendJob" diff --git a/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt index 6d451c2bc4..f18e941fda 100644 --- a/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt @@ -14,12 +14,12 @@ 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 + 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 { From d0a828985a7da7881ad4e7dc2940f6bc38c19b50 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 18 Nov 2019 10:03:59 +1100 Subject: [PATCH 52/64] Fix placeholder messages showing in the wrong conversation. --- src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 0c2afbff5e..25b59eb42e 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -1580,8 +1580,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } private Optional insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) { + Recipient primaryDevice = getPrimaryDeviceRecipient(sender); SmsDatabase database = DatabaseFactory.getSmsDatabase(context); - IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromSerialized(sender), + IncomingTextMessage textMessage = new IncomingTextMessage(primaryDevice.getAddress(), senderDevice, timestamp, "", Optional.absent(), 0, false); From 573aabdd529c9df03bf2ca110aa3b6a0d5a55c13 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 11:31:19 +1100 Subject: [PATCH 53/64] Fix copy --- .../thoughtcrime/securesms/jobs/PushDecryptJob.java | 2 +- .../thoughtcrime/securesms/sms/MessageSender.java | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 0c2afbff5e..ae5b62a039 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -670,7 +670,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { - MessageSender.sendBackgroundFriendRequest(context, pubKey, "Accept this friend request to enable messages to be synced across devices"); + MessageSender.sendBackgroundFriendRequest(context, pubKey, "Please accept to enable messages to be synced across devices"); Log.d("Loki", "Sent friend request to " + pubKey); } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { // Accept the incoming friend request diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 76b8685aaa..de240c81e4 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -17,9 +17,7 @@ package org.thoughtcrime.securesms.sms; import android.content.Context; -import android.os.AsyncTask; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; @@ -58,13 +56,9 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.libsignal.state.PreKeyBundle; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.SignalServiceMessageSender; -import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.push.ContactTokenDetails; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.utilities.PromiseUtil; @@ -75,8 +69,6 @@ import java.util.List; import java.util.Map; import kotlin.Unit; -import nl.komponents.kovenant.Kovenant; -import nl.komponents.kovenant.Promise; public class MessageSender { @@ -118,7 +110,7 @@ public class MessageSender { if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { sendBackgroundMessage(context, device); } else if (friendRequestStatus == LokiThreadFriendRequestStatus.NONE || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { - sendBackgroundFriendRequest(context, device, "Accept this friend request to enable messages to be synced across devices"); + sendBackgroundFriendRequest(context, device, "Please accept to enable messages to be synced across devices"); } } }); @@ -338,7 +330,7 @@ public class MessageSender { // Send friend requests to non friends. If the user is friends with any // of the devices then send out a default friend request message. boolean isFriendsWithAny = (friendCount > 0); - String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; + String defaultFriendRequestMessage = isFriendsWithAny ? "Please accept to enable messages to be synced across devices" : null; if (type == MessageType.MEDIA) { jobs.add(new PushMediaSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); } else { From 457faae5a50e5c610e09f6db7c56de72980a23d7 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 18 Nov 2019 11:08:23 +1100 Subject: [PATCH 54/64] Added public chat server profile name batch polling. --- .../securesms/loki/LokiPublicChatPoller.kt | 81 ++++++++++++++++--- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 28bca2fea1..47825577df 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -28,6 +28,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki // region Convenience private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) + private var displayNameUpdatees = setOf() private val api: LokiPublicChatAPI get() = { @@ -62,6 +63,13 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki handler.postDelayed(this, pollForModeratorsInterval) } } + + private val pollForDisplayNamesTask = object : Runnable { + override fun run() { + pollForDisplayNames() + handler.postDelayed(this, pollForDisplayNamesInterval) + } + } // endregion // region Settings @@ -69,6 +77,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki private val pollForNewMessagesInterval: Long = 4 * 1000 private val pollForDeletedMessagesInterval: Long = 20 * 1000 private val pollForModeratorsInterval: Long = 10 * 60 * 1000 + private val pollForDisplayNamesInterval: Long = 1 * 60 * 1000 } // endregion @@ -78,6 +87,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki pollForNewMessagesTask.run() pollForDeletedMessagesTask.run() pollForModeratorsTask.run() + pollForDisplayNamesTask.run() hasStarted = true } @@ -85,6 +95,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki handler.removeCallbacks(pollForNewMessagesTask) handler.removeCallbacks(pollForDeletedMessagesTask) handler.removeCallbacks(pollForModeratorsTask) + handler.removeCallbacks(pollForDisplayNamesTask) hasStarted = false } // endregion @@ -136,10 +147,15 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki private fun pollForNewMessages() { fun processIncomingMessage(message: LokiPublicChatMessage) { + // If the sender of the current message is not a secondary device, we need to set the display name in the database + val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(message.hexEncodedPublicKey).get() + if (primaryDevice == null) { + val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})" + DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName) + } + val senderPublicKey = primaryDevice ?: message.hexEncodedPublicKey val serviceDataMessage = getDataMessage(message) - val serviceContent = SignalServiceContent(serviceDataMessage, message.hexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false) - val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})" - DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName) + val serviceContent = SignalServiceContent(serviceDataMessage, senderPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false) if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) } else { @@ -161,23 +177,70 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) } } + api.getMessages(group.channel, group.server).successBackground { messages -> if (messages.isNotEmpty()) { val ourDevices = LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).get(setOf()) - // Process messages in the background - messages.forEach { message -> - if (ourDevices.contains(message.hexEncodedPublicKey)) { - processOutgoingMessage(message) - } else { - processIncomingMessage(message) + val uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet() + val devicesToUpdate = uniqueDevices.filter { !ourDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } + + fun proceed() { + // Get the set of primary device pubKeys FROM the secondary devices in uniqueDevices + val newDisplayNameUpdatees = uniqueDevices.mapNotNull { + // This will return null if current device is primary + // So if it's non-null then we know the device is a secondary device + val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get() + primaryDevice + }.toSet() + + // Fetch the display names of the primary devices + displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees) + + // Process messages in the background + messages.forEach { message -> + if (ourDevices.contains(message.hexEncodedPublicKey)) { + processOutgoingMessage(message) + } else { + processIncomingMessage(message) + } } } + + // We need to fetch device mappings for all the devices we don't have + if (devicesToUpdate.isEmpty()) { + proceed() + } else { + // Fetch the device mappings first + try { + LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).get() + } finally { + proceed() + } + } + } }.fail { Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.") } } + private fun pollForDisplayNames() { + if (displayNameUpdatees.isEmpty()) { return } + + val devices = displayNameUpdatees + displayNameUpdatees = setOf() + + api.getDisplayNames(devices, group.server).successBackground { mapping -> + for (pair in mapping.entries) { + val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" + DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName) + } + }.fail { + // Retry next time + displayNameUpdatees = displayNameUpdatees.union(devices) + } + } + private fun pollForDeletedMessages() { api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs -> val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context) From 8eedff81eb382c22c5cd9cedd7e21d907534bf0f Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 18 Nov 2019 11:55:16 +1100 Subject: [PATCH 55/64] Async everything! --- .../securesms/loki/LokiPublicChatPoller.kt | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 47825577df..230a618b14 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -1,8 +1,12 @@ package org.thoughtcrime.securesms.loki import android.content.Context +import android.os.AsyncTask import android.os.Handler import android.util.Log +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.bind +import nl.komponents.kovenant.then import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.jobs.PushDecryptJob @@ -178,46 +182,42 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki } } - api.getMessages(group.channel, group.server).successBackground { messages -> + var ourDevices = setOf() + var uniqueDevices = setOf() + LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).bind { devices -> + ourDevices = devices + api.getMessages(group.channel, group.server) + }.bind { messages -> if (messages.isNotEmpty()) { - val ourDevices = LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).get(setOf()) - val uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet() - val devicesToUpdate = uniqueDevices.filter { !ourDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } - - fun proceed() { - // Get the set of primary device pubKeys FROM the secondary devices in uniqueDevices - val newDisplayNameUpdatees = uniqueDevices.mapNotNull { - // This will return null if current device is primary - // So if it's non-null then we know the device is a secondary device - val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get() - primaryDevice - }.toSet() - - // Fetch the display names of the primary devices - displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees) - - // Process messages in the background - messages.forEach { message -> - if (ourDevices.contains(message.hexEncodedPublicKey)) { - processOutgoingMessage(message) - } else { - processIncomingMessage(message) - } - } - } - // We need to fetch device mappings for all the devices we don't have - if (devicesToUpdate.isEmpty()) { - proceed() - } else { - // Fetch the device mappings first - try { - LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).get() - } finally { - proceed() + uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet() + val devicesToUpdate = uniqueDevices.filter { !ourDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } + if (devicesToUpdate.isNotEmpty()) { + return@bind LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).then { messages } + } + } + Promise.of(messages) + }.successBackground { + // Get the set of primary device pubKeys FROM the secondary devices in uniqueDevices + val newDisplayNameUpdatees = uniqueDevices.mapNotNull { + // This will return null if current device is primary + // So if it's non-null then we know the device is a secondary device + val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get() + primaryDevice + }.toSet() + + // Fetch the display names of the primary devices + displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees) + }.success { messages -> + // Process messages in the background + messages.forEach { message -> + AsyncTask.execute { + if (ourDevices.contains(message.hexEncodedPublicKey)) { + processOutgoingMessage(message) + } else { + processIncomingMessage(message) } } - } }.fail { Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.") From edbffe72114f7363d4d69df5db3af5395d15203a Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 18 Nov 2019 12:43:26 +1100 Subject: [PATCH 56/64] Disable linking devices once we hit a cap. --- .../ApplicationPreferencesActivity.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 9e55810f50..77dbd53d0b 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -36,12 +36,15 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.ContextCompat; import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v4.util.ArraySet; import android.support.v7.app.AlertDialog; import android.support.v7.preference.Preference; import android.widget.Toast; import org.jetbrains.annotations.NotNull; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; +import org.thoughtcrime.securesms.database.Database; +import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.loki.DeviceLinkingDialog; import org.thoughtcrime.securesms.loki.DeviceLinkingDialogDelegate; import org.thoughtcrime.securesms.loki.DeviceLinkingView; @@ -56,12 +59,14 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.api.PairingAuthorisation; import org.whispersystems.signalservice.loki.crypto.MnemonicCodec; import org.whispersystems.signalservice.loki.utilities.Analytics; import org.whispersystems.signalservice.loki.utilities.SerializationKt; import java.io.File; +import java.util.Set; import network.loki.messenger.R; @@ -192,10 +197,20 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_QR_CODE)); Preference linkDevicePreference = this.findPreference(PREFERENCE_CATEGORY_LINK_DEVICE); - // Hide if this is a slave device - linkDevicePreference.setVisible(isMasterDevice); linkDevicePreference.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_LINK_DEVICE)); + // Disable if we hit the cap of 1 linked device + if (isMasterDevice) { + Context context = getContext(); + String ourNumber = TextSecurePreferences.getLocalNumber(context); + boolean shouldEnableDeviceLinking = DatabaseFactory.getLokiAPIDatabase(context).getPairingAuthorisations(ourNumber).size() <= 1; + linkDevicePreference.setEnabled(shouldEnableDeviceLinking); + linkDevicePreference.getIcon().setAlpha(shouldEnableDeviceLinking ? 255 : 124); + } else { + // Hide if this is a slave device + linkDevicePreference.setVisible(false); + } + Preference seedPreference = this.findPreference(PREFERENCE_CATEGORY_SEED); // Hide if this is a slave device seedPreference.setVisible(isMasterDevice); From 35ee1c139157b62a86716e73385aa49bf66e8727 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 18 Nov 2019 13:02:19 +1100 Subject: [PATCH 57/64] Fix avatar images not being updated correctly. --- .../securesms/components/AvatarImageView.java | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/org/thoughtcrime/securesms/components/AvatarImageView.java b/src/org/thoughtcrime/securesms/components/AvatarImageView.java index 6eae6b42aa..53ce56c737 100644 --- a/src/org/thoughtcrime/securesms/components/AvatarImageView.java +++ b/src/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -104,38 +104,22 @@ public class AvatarImageView extends AppCompatImageView { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); - if (w == 0 || h == 0 || recipient == null) { return; } - - Drawable image; - Context context = this.getContext(); - if (recipient.isGroupRecipient()) { - - - String name = Optional.fromNullable(recipient.getName()).or(Optional.fromNullable(TextSecurePreferences.getProfileName(context))).or(""); - MaterialColor fallbackColor = recipient.getColor(); - - if (fallbackColor == ContactColors.UNKNOWN_COLOR && !TextUtils.isEmpty(name)) { - fallbackColor = ContactColors.generateFor(name); - } - - image = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(context, fallbackColor.toAvatarColor(context)); - } else { - // Default to primary device image - String ourPublicKey = TextSecurePreferences.getLocalNumber(context); - String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); - String recipientAddress = recipient.getAddress().serialize(); - String profileAddress = (ourPrimaryDevice != null && ourPublicKey.equals(recipientAddress)) ? ourPrimaryDevice : recipientAddress; - image = new JazzIdenticonDrawable(w, h, profileAddress.toLowerCase()); - } - setImageDrawable(image); + updateImage(w, h); } public void update(String hexEncodedPublicKey) { - this.recipient = Recipient.from(getContext(), Address.fromSerialized(hexEncodedPublicKey), false); + Address address = Address.fromSerialized(hexEncodedPublicKey); + if (!address.equals(recipient.getAddress())) { + this.recipient = Recipient.from(getContext(), address, false); + updateImage(); + } } public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) { - this.recipient = recipient; + if (this.recipient == null || !this.recipient.equals(recipient)) { + this.recipient = recipient; + updateImage(); + } /* if (recipient != null) { requestManager.load(recipient.getContactPhoto()) @@ -170,4 +154,32 @@ public class AvatarImageView extends AppCompatImageView { } } + private void updateImage() { updateImage(getWidth(), getHeight()); } + private void updateImage(int w, int h) { + if (w == 0 || h == 0 || recipient == null) { return; } + + Drawable image; + Context context = this.getContext(); + if (recipient.isGroupRecipient()) { + + + String name = Optional.fromNullable(recipient.getName()).or(Optional.fromNullable(TextSecurePreferences.getProfileName(context))).or(""); + MaterialColor fallbackColor = recipient.getColor(); + + if (fallbackColor == ContactColors.UNKNOWN_COLOR && !TextUtils.isEmpty(name)) { + fallbackColor = ContactColors.generateFor(name); + } + + image = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(context, fallbackColor.toAvatarColor(context)); + } else { + // Default to primary device image + String ourPublicKey = TextSecurePreferences.getLocalNumber(context); + String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); + String recipientAddress = recipient.getAddress().serialize(); + String profileAddress = (ourPrimaryDevice != null && ourPublicKey.equals(recipientAddress)) ? ourPrimaryDevice : recipientAddress; + image = new JazzIdenticonDrawable(w, h, profileAddress.toLowerCase()); + } + setImageDrawable(image); + } + } From 638d693e11c3b60254e3ab4228894afa251798bb Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 13:12:57 +1100 Subject: [PATCH 58/64] Clean --- .../securesms/loki/LokiPublicChatPoller.kt | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 230a618b14..9db5c22bdb 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -22,7 +22,6 @@ import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage import org.whispersystems.signalservice.loki.api.LokiStorageAPI -import org.whispersystems.signalservice.loki.utilities.get import org.whispersystems.signalservice.loki.utilities.successBackground import java.util.* @@ -81,7 +80,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki private val pollForNewMessagesInterval: Long = 4 * 1000 private val pollForDeletedMessagesInterval: Long = 20 * 1000 private val pollForModeratorsInterval: Long = 10 * 60 * 1000 - private val pollForDisplayNamesInterval: Long = 1 * 60 * 1000 + private val pollForDisplayNamesInterval: Long = 60 * 1000 } // endregion @@ -181,17 +180,16 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) } } - - var ourDevices = setOf() + var userDevices = setOf() var uniqueDevices = setOf() LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).bind { devices -> - ourDevices = devices + userDevices = devices api.getMessages(group.channel, group.server) }.bind { messages -> if (messages.isNotEmpty()) { // We need to fetch device mappings for all the devices we don't have uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet() - val devicesToUpdate = uniqueDevices.filter { !ourDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } + val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } if (devicesToUpdate.isNotEmpty()) { return@bind LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).then { messages } } @@ -205,14 +203,13 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get() primaryDevice }.toSet() - // Fetch the display names of the primary devices displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees) }.success { messages -> // Process messages in the background messages.forEach { message -> AsyncTask.execute { - if (ourDevices.contains(message.hexEncodedPublicKey)) { + if (userDevices.contains(message.hexEncodedPublicKey)) { processOutgoingMessage(message) } else { processIncomingMessage(message) @@ -226,18 +223,15 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki private fun pollForDisplayNames() { if (displayNameUpdatees.isEmpty()) { return } - - val devices = displayNameUpdatees + val hexEncodedPublicKeys = displayNameUpdatees displayNameUpdatees = setOf() - - api.getDisplayNames(devices, group.server).successBackground { mapping -> + api.getDisplayNames(hexEncodedPublicKeys, group.server).successBackground { mapping -> for (pair in mapping.entries) { val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName) } }.fail { - // Retry next time - displayNameUpdatees = displayNameUpdatees.union(devices) + displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys) } } From 5da4892d9d16faae2a04a35201b5b7dd8c7f3725 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 13:19:25 +1100 Subject: [PATCH 59/64] Ensure UI updates happen on the main thread --- .../securesms/loki/LokiPublicChatPoller.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 9db5c22bdb..16a50d67df 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.jobs.PushDecryptJob import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.thoughtcrime.securesms.util.Util import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer import org.whispersystems.signalservice.api.messages.SignalServiceContent @@ -159,10 +160,12 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki val senderPublicKey = primaryDevice ?: message.hexEncodedPublicKey val serviceDataMessage = getDataMessage(message) val serviceContent = SignalServiceContent(serviceDataMessage, senderPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false) - if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { - PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) - } else { - PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) + Util.runOnMain { + if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { + PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) + } else { + PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) + } } } fun processOutgoingMessage(message: LokiPublicChatMessage) { @@ -174,10 +177,12 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki val dataMessage = getDataMessage(message) val transcript = SentTranscriptMessage(localNumber, dataMessage.timestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(localNumber, false)) transcript.messageServerID = messageServerID - if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) { - PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript) - } else { - PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) + Util.runOnMain { + if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) { + PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript) + } else { + PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) + } } } var userDevices = setOf() From e785eab28b1202da767ee3979a686ee96bb09427 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 13:21:52 +1100 Subject: [PATCH 60/64] Clean --- .../securesms/ApplicationPreferencesActivity.java | 12 ++++-------- .../securesms/components/AvatarImageView.java | 4 ++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 77dbd53d0b..f9309eaf83 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -36,14 +36,12 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.ContextCompat; import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v4.util.ArraySet; import android.support.v7.app.AlertDialog; import android.support.v7.preference.Preference; import android.widget.Toast; import org.jetbrains.annotations.NotNull; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.database.Database; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.loki.DeviceLinkingDialog; import org.thoughtcrime.securesms.loki.DeviceLinkingDialogDelegate; @@ -59,14 +57,12 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.api.PairingAuthorisation; import org.whispersystems.signalservice.loki.crypto.MnemonicCodec; import org.whispersystems.signalservice.loki.utilities.Analytics; import org.whispersystems.signalservice.loki.utilities.SerializationKt; import java.io.File; -import java.util.Set; import network.loki.messenger.R; @@ -202,10 +198,10 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA // Disable if we hit the cap of 1 linked device if (isMasterDevice) { Context context = getContext(); - String ourNumber = TextSecurePreferences.getLocalNumber(context); - boolean shouldEnableDeviceLinking = DatabaseFactory.getLokiAPIDatabase(context).getPairingAuthorisations(ourNumber).size() <= 1; - linkDevicePreference.setEnabled(shouldEnableDeviceLinking); - linkDevicePreference.getIcon().setAlpha(shouldEnableDeviceLinking ? 255 : 124); + String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context); + boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(context).getPairingAuthorisations(userHexEncodedPublicKey).size() <= 1; + linkDevicePreference.setEnabled(isDeviceLinkingEnabled); + linkDevicePreference.getIcon().setAlpha(isDeviceLinkingEnabled ? 255 : 124); } else { // Hide if this is a slave device linkDevicePreference.setVisible(false); diff --git a/src/org/thoughtcrime/securesms/components/AvatarImageView.java b/src/org/thoughtcrime/securesms/components/AvatarImageView.java index 53ce56c737..28598f4700 100644 --- a/src/org/thoughtcrime/securesms/components/AvatarImageView.java +++ b/src/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -155,14 +155,14 @@ public class AvatarImageView extends AppCompatImageView { } private void updateImage() { updateImage(getWidth(), getHeight()); } + private void updateImage(int w, int h) { if (w == 0 || h == 0 || recipient == null) { return; } Drawable image; Context context = this.getContext(); + if (recipient.isGroupRecipient()) { - - String name = Optional.fromNullable(recipient.getName()).or(Optional.fromNullable(TextSecurePreferences.getProfileName(context))).or(""); MaterialColor fallbackColor = recipient.getColor(); From 780ee73511d33d7d224c3fc45ceb99ef48e16aaa Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 13:53:02 +1100 Subject: [PATCH 61/64] Update version number --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 1fa07e86db..a8df70cc40 100644 --- a/build.gradle +++ b/build.gradle @@ -195,8 +195,8 @@ dependencies { } } -def canonicalVersionCode = 18 -def canonicalVersionName = "1.4.1" +def canonicalVersionCode = 19 +def canonicalVersionName = "1.5.0" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 3e09946d25cf72d5d24a2cc851f6c59125c89cc4 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 15:00:55 +1100 Subject: [PATCH 62/64] Clean --- .../conversation/ConversationFragment.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java index a5e58b4a8e..46338150f5 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -55,8 +55,6 @@ import android.widget.Toast; import android.widget.ViewSwitcher; import com.annimon.stream.Stream; -import com.annimon.stream.operator.LongArray; -import com.google.common.primitives.Longs; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.MessageDetailsActivity; @@ -103,7 +101,6 @@ import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture; import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI; @@ -115,10 +112,8 @@ import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Locale; -import java.util.Objects; import java.util.Set; -import kotlin.Unit; import network.loki.messenger.R; @SuppressLint("StaticFieldLeak") @@ -412,13 +407,15 @@ public class ConversationFragment extends Fragment LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); boolean isPublicChat = publicChat != null; int selectedMessageCount = messageRecords.size(); - boolean isSentByUser = ((MessageRecord)messageRecords.toArray()[0]).isOutgoing(); - menu.findItem(R.id.menu_context_copy_public_key).setVisible(isPublicChat && selectedMessageCount == 1 && !isSentByUser); + boolean areAllSentByUser = true; + for (MessageRecord message : messageRecords) { + if (!message.isOutgoing()) { areAllSentByUser = false; } + } + menu.findItem(R.id.menu_context_copy_public_key).setVisible(isPublicChat && selectedMessageCount == 1 && !areAllSentByUser); menu.findItem(R.id.menu_context_reply).setVisible(isPublicChat && selectedMessageCount == 1); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext()); boolean userCanModerate = isPublicChat && LokiPublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer()); -// boolean isDeleteOptionVisible = isPublicChat && selectedMessageCount == 1 && (isSentByUser || userCanModerate); - boolean isDeleteOptionVisible = isPublicChat && (isSentByUser || userCanModerate); + boolean isDeleteOptionVisible = isPublicChat && (areAllSentByUser || userCanModerate); menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible); } else { menu.findItem(R.id.menu_context_copy_public_key).setVisible(false); @@ -534,8 +531,7 @@ public class ConversationFragment extends Fragment Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id); if (serverID != null) { serverIDs.add(serverID); - } - else { + } else { ignoredMessages.add(messageRecord.getId()); } } @@ -551,8 +547,7 @@ public class ConversationFragment extends Fragment } else { DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); } - } - else if (!ignoredMessages.contains(serverID)) { + } else if (!ignoredMessages.contains(serverID)) { failedMessages.add(messageRecord.getId()); Log.d("Loki", "Failed to delete message: " + messageRecord.getId() + "."); } @@ -563,7 +558,6 @@ public class ConversationFragment extends Fragment return null; }); } - return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, messageRecords.toArray(new MessageRecord[messageRecords.size()])); From 045e4be2f0419d68949f3821c1e469ac42162d21 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 15:10:40 +1100 Subject: [PATCH 63/64] Partially undo threading changes --- .../securesms/loki/LokiPublicChatPoller.kt | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 16a50d67df..d2cd777e63 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.loki import android.content.Context -import android.os.AsyncTask import android.os.Handler import android.util.Log import nl.komponents.kovenant.Promise @@ -11,7 +10,6 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.jobs.PushDecryptJob import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.thoughtcrime.securesms.util.Util import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer import org.whispersystems.signalservice.api.messages.SignalServiceContent @@ -160,12 +158,10 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki val senderPublicKey = primaryDevice ?: message.hexEncodedPublicKey val serviceDataMessage = getDataMessage(message) val serviceContent = SignalServiceContent(serviceDataMessage, senderPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false) - Util.runOnMain { - if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { - PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) - } else { - PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) - } + if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { + PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) + } else { + PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) } } fun processOutgoingMessage(message: LokiPublicChatMessage) { @@ -177,12 +173,10 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki val dataMessage = getDataMessage(message) val transcript = SentTranscriptMessage(localNumber, dataMessage.timestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(localNumber, false)) transcript.messageServerID = messageServerID - Util.runOnMain { - if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) { - PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript) - } else { - PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) - } + if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) { + PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript) + } else { + PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) } } var userDevices = setOf() @@ -213,12 +207,10 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki }.success { messages -> // Process messages in the background messages.forEach { message -> - AsyncTask.execute { - if (userDevices.contains(message.hexEncodedPublicKey)) { - processOutgoingMessage(message) - } else { - processIncomingMessage(message) - } + if (userDevices.contains(message.hexEncodedPublicKey)) { + processOutgoingMessage(message) + } else { + processIncomingMessage(message) } } }.fail { From e17002c97905d9a4bb3a8e96e42ac1c2cfd49e48 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 15:10:56 +1100 Subject: [PATCH 64/64] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a8df70cc40..7d9eb8bef6 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ dependencies { } } -def canonicalVersionCode = 19 +def canonicalVersionCode = 20 def canonicalVersionName = "1.5.0" def postFixSize = 10