From 21e529c6c9f9cad6fcda970ea0e28996d81136dc Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 8 Oct 2019 13:23:03 +1100 Subject: [PATCH] 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) }