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.
This commit is contained in:
Greyson Parrelli 2022-09-21 09:02:10 -04:00 committed by Cody Henthorne
parent bc7b0b40b0
commit 80a6e0f781
11 changed files with 142 additions and 2 deletions

View file

@ -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()
}
)
}
}

View file

@ -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);

View file

@ -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();

View file

@ -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;
}

View file

@ -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)
}

View file

@ -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<InsertResult> insertMessageInbox(IncomingTextMessage message, long type) {
boolean tryToCollapseJoinRequestEvents = false;

View file

@ -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) {

View file

@ -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() {

View file

@ -261,4 +261,8 @@ message MessageExportState {
repeated string startedAttachments = 4;
repeated string completedAttachments = 5;
Progress progress = 6;
}
message ThreadMergeEvent {
string previousE164 = 1;
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M4.137,14C4.334,14 4.539,13.933 4.721,13.814L5.115,13.554C6.169,12.854 7.723,11.277 7.989,10.489H8.011C8.269,11.27 9.823,12.854 10.885,13.554L11.279,13.814C11.461,13.933 11.666,14 11.856,14C12.242,14 12.5,13.74 12.5,13.375C12.5,13.152 12.379,12.929 12.174,12.802L11.575,12.423C10.521,11.739 8.663,10.013 8.663,8.19V5.401H9.74C10.271,5.401 10.415,5.036 10.119,4.627L8.428,2.254C8.186,1.919 7.822,1.912 7.572,2.254L5.873,4.619C5.578,5.036 5.714,5.401 6.26,5.401H7.337V8.19C7.337,10.013 5.471,11.731 4.425,12.423L3.826,12.802C3.614,12.929 3.5,13.159 3.5,13.383C3.5,13.717 3.75,14 4.137,14Z"
android:fillColor="#000000"/>
</vector>

View file

@ -1345,6 +1345,10 @@
<string name="MessageRecord_s_changed_their_phone_number">%1$s changed their phone number.</string>
<!-- Update item message shown in the release channel when someone is already a sustainer so we ask them if they want to boost. -->
<string name="MessageRecord_like_this_new_feature_help_support_signal_with_a_one_time_donation">Like this new feature? Help support Signal with a one-time donation.</string>
<!-- Update item message shown when we merge two threads together -->
<string name="MessageRecord_your_message_history_with_s_and_their_number_s_has_been_merged">Your message history with %1$s and their number %2$s has been merged.</string>
<!-- Update item message shown when we merge two threads together and we don't know the phone number of the other thread -->
<string name="MessageRecord_your_message_history_with_s_and_another_chat_has_been_merged">Your message history with %1$s and another chat that belonged to them has been merged.</string>
<!-- Group Calling update messages -->
<string name="MessageRecord_s_started_a_group_call_s">%1$s started a group call · %2$s</string>