From 80a6e0f7810a90191f5fbce5a57ffc156431e802 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 21 Sep 2022 09:02:10 -0400 Subject: [PATCH] Show a chat event when two threads are merged. * Add internal button to split contacts for debugging. * Show a chat event when two threads are merged. --- .../InternalConversationSettingsFragment.kt | 44 +++++++++++++++++++ .../securesms/database/MessageDatabase.java | 2 + .../securesms/database/MmsDatabase.java | 6 +++ .../securesms/database/MmsSmsColumns.java | 5 +++ .../securesms/database/RecipientDatabase.kt | 28 ++++++++++++ .../securesms/database/SmsDatabase.java | 18 ++++++++ .../securesms/database/ThreadDatabase.java | 3 +- .../database/model/MessageRecord.java | 21 ++++++++- app/src/main/proto/Database.proto | 4 ++ .../main/res/drawable/ic_thread_merge_16.xml | 9 ++++ app/src/main/res/values/strings.xml | 4 ++ 11 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/ic_thread_merge_16.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt index 08639ca0da..6caa16f27f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt @@ -22,8 +22,10 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientForeverObserver import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage import org.thoughtcrime.securesms.subscription.Subscriber import org.thoughtcrime.securesms.util.Base64 +import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.SpanUtil import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter @@ -213,6 +215,48 @@ class InternalConversationSettingsFragment : DSLSettingsFragment( } ) } + + sectionHeaderPref(DSLSettingsText.from("PNP")) + + clickPref( + title = DSLSettingsText.from("Split contact"), + summary = DSLSettingsText.from("Splits this contact into two recipients and two threads so that you can test merging them together. This will remain the 'primary' recipient."), + onClick = { + MaterialAlertDialogBuilder(requireContext()) + .setTitle("Are you sure?") + .setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() } + .setPositiveButton(android.R.string.ok) { _, _ -> + if (!recipient.hasE164()) { + Toast.makeText(context, "Recipient doesn't have an E164! Can't split.", Toast.LENGTH_SHORT).show() + return@setPositiveButton + } + + SignalDatabase.recipients.debugClearE164AndPni(recipient.id) + + val splitRecipientId: RecipientId = if (FeatureFlags.phoneNumberPrivacy()) { + SignalDatabase.recipients.getAndPossiblyMergePnpVerified(recipient.pni.orElse(null), recipient.pni.orElse(null), recipient.requireE164()) + } else { + SignalDatabase.recipients.getAndPossiblyMerge(recipient.pni.orElse(null), recipient.requireE164()) + } + val splitRecipient: Recipient = Recipient.resolved(splitRecipientId) + val splitThreadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(splitRecipient) + + val messageId: Long = SignalDatabase.sms.insertMessageOutbox( + splitThreadId, + OutgoingEncryptedMessage(splitRecipient, "Test Message ${System.currentTimeMillis()}", 0), + false, + System.currentTimeMillis(), + null + ) + SignalDatabase.sms.markAsSent(messageId, true) + + SignalDatabase.threads.update(splitThreadId, true) + + Toast.makeText(context, "Done! We split the E164/PNI from this contact into $splitRecipientId", Toast.LENGTH_SHORT).show() + } + .show() + } + ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index bd88654eac..fb33b27fea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.StoryResult; import org.thoughtcrime.securesms.database.model.StoryViewState; import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState; +import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent; import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.insights.InsightsConstants; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; @@ -175,6 +176,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract void insertGroupV1MigrationEvents(@NonNull RecipientId recipientId, long threadId, @NonNull GroupMigrationMembershipChange membershipChange); public abstract void insertNumberChangeMessages(@NonNull Recipient recipient); public abstract void insertBoostRequestMessage(@NonNull RecipientId recipientId, long threadId); + public abstract void insertThreadMergeEvent(@NonNull RecipientId recipientId, long threadId, @NonNull ThreadMergeEvent event); public abstract boolean deleteMessage(long messageId); abstract void deleteThread(long threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 3a97b10589..ae07e5d18f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.database.model.StoryViewState; import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList; import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge; import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState; +import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.jobs.TrimThreadJob; @@ -573,6 +574,11 @@ public class MmsDatabase extends MessageDatabase { throw new UnsupportedOperationException(); } + @Override + public void insertThreadMergeEvent(@NonNull RecipientId recipientId, long threadId, @NonNull ThreadMergeEvent event) { + throw new UnsupportedOperationException(); + } + @Override public void endTransaction(SQLiteDatabase database) { database.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 885ba5b625..3623af4f0a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -81,6 +81,7 @@ public interface MmsSmsColumns { protected static final long BAD_DECRYPT_TYPE = 13; protected static final long CHANGE_NUMBER_TYPE = 14; protected static final long BOOST_REQUEST_TYPE = 15; + protected static final long THREAD_MERGE_TYPE = 16; protected static final long BASE_INBOX_TYPE = 20; protected static final long BASE_OUTBOX_TYPE = 21; @@ -222,6 +223,10 @@ public interface MmsSmsColumns { return (type & BASE_TYPE_MASK) == BAD_DECRYPT_TYPE; } + public static boolean isThreadMergeType(long type) { + return (type & BASE_TYPE_MASK) == THREAD_MERGE_TYPE; + } + public static boolean isSecureType(long type) { return (type & SECURE_MESSAGE_BIT) != 0; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt index e015490936..67f910f25f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt @@ -70,6 +70,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime import org.thoughtcrime.securesms.database.model.databaseprotos.ExpiringProfileKeyCredentialColumnData import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras +import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.groups.BadGroupIdException @@ -3570,6 +3571,17 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) : db.update(MmsDatabase.TABLE_NAME, values, MmsDatabase.THREAD_ID + " = ?", SqlUtil.buildArgs(threadMerge.previousThreadId)) } + // Thread Merge event + if (threadMerge.neededMerge) { + val mergeEvent: ThreadMergeEvent.Builder = ThreadMergeEvent.newBuilder() + + if (secondaryRecord.e164 != null) { + mergeEvent.previousE164 = secondaryRecord.e164 + } + + SignalDatabase.sms.insertThreadMergeEvent(primaryRecord.id, threadMerge.threadId, mergeEvent.build()) + } + // MSL messageLog.remapRecipient(secondaryId, primaryId) @@ -3823,6 +3835,22 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) : RecipientId.clearCache() } + /** + * Should only be used for debugging! Clears the E164 and PNI from a recipient. + */ + fun debugClearE164AndPni(recipientId: RecipientId) { + writableDatabase + .update(TABLE_NAME) + .values( + PHONE to null, + PNI_COLUMN to null + ) + .where(ID_WHERE, recipientId) + .run() + + Recipient.live(recipientId).refresh() + } + fun getRecord(context: Context, cursor: Cursor): RecipientRecord { return getRecord(context, cursor, ID) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 7518cf24af..babe25c461 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.database.model.StoryViewState; import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails; import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState; import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails; +import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.jobs.TrimThreadJob; @@ -1121,6 +1122,23 @@ public class SmsDatabase extends MessageDatabase { getWritableDatabase().insert(TABLE_NAME, null, values); } + @Override + public void insertThreadMergeEvent(@NonNull RecipientId recipientId, long threadId, @NonNull ThreadMergeEvent event) { + ContentValues values = new ContentValues(); + values.put(RECIPIENT_ID, recipientId.serialize()); + values.put(ADDRESS_DEVICE_ID, 1); + values.put(DATE_RECEIVED, System.currentTimeMillis()); + values.put(DATE_SENT, System.currentTimeMillis()); + values.put(READ, 1); + values.put(TYPE, Types.THREAD_MERGE_TYPE); + values.put(THREAD_ID, threadId); + values.put(BODY, Base64.encodeBytes(event.toByteArray())); + + getWritableDatabase().insert(TABLE_NAME, null, values); + + ApplicationDependencies.getDatabaseObserver().notifyConversationListeners(threadId); + } + @Override public Optional insertMessageInbox(IncomingTextMessage message, long type) { boolean tryToCollapseJoinRequestEvents = false; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 69cf64c442..1f8add0771 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -1621,7 +1621,8 @@ public class ThreadDatabase extends Database { MmsSmsColumns.Types.isGroupV1MigrationEvent(type) || MmsSmsColumns.Types.isChangeNumber(type) || MmsSmsColumns.Types.isBoostRequest(type) || - MmsSmsColumns.Types.isGroupV2LeaveOnly(type); + MmsSmsColumns.Types.isGroupV2LeaveOnly(type) || + MmsSmsColumns.Types.isThreadMergeType(type); } public Reader readerFor(Cursor cursor) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index e88dfaaa72..870bb43d05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -32,6 +32,7 @@ import androidx.core.content.ContextCompat; import com.annimon.stream.Stream; import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; import org.signal.core.util.StringUtil; import org.signal.core.util.logging.Log; @@ -48,10 +49,12 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList; import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context; import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails; import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails; +import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent; import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.emoji.JumboEmoji; import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -221,6 +224,18 @@ public abstract class MessageRecord extends DisplayRecord { return staticUpdateDescription(context.getString(R.string.MessageRecord_chat_session_refreshed), R.drawable.ic_refresh_16); } else if (isBadDecryptType()) { return fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_a_message_from_s_couldnt_be_delivered, r.getDisplayName(context)), R.drawable.ic_error_outline_14); + } else if (isThreadMergeEventType()) { + try { + ThreadMergeEvent event = ThreadMergeEvent.parseFrom(Base64.decodeOrThrow(getBody())); + + if (event.getPreviousE164().isEmpty()) { + return fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_your_message_history_with_s_and_another_chat_has_been_merged, r.getDisplayName(context)), R.drawable.ic_thread_merge_16); + } else { + return fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_your_message_history_with_s_and_their_number_s_has_been_merged, r.getDisplayName(context), PhoneNumberFormatter.prettyPrint(event.getPreviousE164())), R.drawable.ic_thread_merge_16); + } + } catch (InvalidProtocolBufferException e) { + throw new AssertionError(e); + } } return null; @@ -523,6 +538,10 @@ public abstract class MessageRecord extends DisplayRecord { return MmsSmsColumns.Types.isBadDecryptType(type); } + public boolean isThreadMergeEventType() { + return MmsSmsColumns.Types.isThreadMergeType(type); + } + public boolean isInvalidVersionKeyExchange() { return SmsDatabase.Types.isInvalidVersionKeyExchange(type); } @@ -543,7 +562,7 @@ public abstract class MessageRecord extends DisplayRecord { return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() || isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isProfileChange() || isGroupV1MigrationEvent() || isChatSessionRefresh() || isBadDecryptType() || - isChangeNumber() || isBoostRequest(); + isChangeNumber() || isBoostRequest() || isThreadMergeEventType(); } public boolean isMediaPending() { diff --git a/app/src/main/proto/Database.proto b/app/src/main/proto/Database.proto index 92698e7106..daf0caa0c2 100644 --- a/app/src/main/proto/Database.proto +++ b/app/src/main/proto/Database.proto @@ -261,4 +261,8 @@ message MessageExportState { repeated string startedAttachments = 4; repeated string completedAttachments = 5; Progress progress = 6; +} + +message ThreadMergeEvent { + string previousE164 = 1; } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_thread_merge_16.xml b/app/src/main/res/drawable/ic_thread_merge_16.xml new file mode 100644 index 0000000000..d49b59945c --- /dev/null +++ b/app/src/main/res/drawable/ic_thread_merge_16.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9c58bcd39b..8b11bfa5b3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1345,6 +1345,10 @@ %1$s changed their phone number. Like this new feature? Help support Signal with a one-time donation. + + Your message history with %1$s and their number %2$s has been merged. + + Your message history with %1$s and another chat that belonged to them has been merged. %1$s started a group call ยท %2$s