diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index 430afc6eff..049e5819ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -996,8 +996,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat fun insertProfileNameChangeMessages(recipient: Recipient, newProfileName: String, previousProfileName: String) { writableDatabase.withinTransaction { db -> val groupRecords = groups.getGroupsContainingMember(recipient.id, false) - val profileChangeDetails = ProfileChangeDetails(profileNameChange = ProfileChangeDetails.StringChange(previous = previousProfileName, newValue = newProfileName)) - .encode() + + val extras = MessageExtras( + profileChangeDetails = ProfileChangeDetails(profileNameChange = ProfileChangeDetails.StringChange(previous = previousProfileName, newValue = newProfileName)) + ) val threadIdsToUpdate = mutableListOf().apply { add(threads.getThreadIdFor(recipient.id)) @@ -1020,7 +1022,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat READ to 1, TYPE to MessageTypes.PROFILE_CHANGE_TYPE, THREAD_ID to threadId, - BODY to Base64.encodeWithPadding(profileChangeDetails) + MESSAGE_EXTRAS to extras.encode() ) db.insert(TABLE_NAME, null, values) notifyConversationListeners(threadId) @@ -1029,6 +1031,33 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat } } + fun insertLearnedProfileNameChangeMessage(recipient: Recipient, previousDisplayName: String) { + val threadId: Long? = SignalDatabase.threads.getThreadIdFor(recipient.id) + + if (threadId != null) { + val extras = MessageExtras( + profileChangeDetails = ProfileChangeDetails(learnedProfileName = ProfileChangeDetails.StringChange(previous = previousDisplayName)) + ) + + writableDatabase + .insertInto(TABLE_NAME) + .values( + FROM_RECIPIENT_ID to recipient.id.serialize(), + FROM_DEVICE_ID to 1, + TO_RECIPIENT_ID to Recipient.self().id.serialize(), + DATE_RECEIVED to System.currentTimeMillis(), + DATE_SENT to System.currentTimeMillis(), + READ to 1, + TYPE to MessageTypes.PROFILE_CHANGE_TYPE, + THREAD_ID to threadId, + MESSAGE_EXTRAS to extras.encode() + ) + .run() + + notifyConversationListeners(threadId) + } + } + fun insertGroupV1MigrationEvents(recipientId: RecipientId, threadId: Long, membershipChange: GroupMigrationMembershipChange) { insertGroupV1MigrationNotification(recipientId, threadId) if (!membershipChange.isEmpty) { 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 b1f5bce8ad..9df1ba869a 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 @@ -227,7 +227,7 @@ public abstract class MessageRecord extends DisplayRecord { if (isOutgoing()) return fromRecipient(getToRecipient(), r -> context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified, r.getDisplayName(context)), R.drawable.ic_update_info_16); else return fromRecipient(getFromRecipient(), r -> context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified_from_another_device, r.getDisplayName(context)), R.drawable.ic_update_info_16); } else if (isProfileChange()) { - return staticUpdateDescription(getProfileChangeDescription(context), R.drawable.ic_update_profile_16); + return getProfileChangeDescription(context); } else if (isChangeNumber()) { return fromRecipient(getFromRecipient(), r -> context.getString(R.string.MessageRecord_s_changed_their_phone_number, r.getDisplayName(context)), R.drawable.ic_phone_16); } else if (isBoostRequest()) { @@ -299,7 +299,7 @@ public abstract class MessageRecord extends DisplayRecord { return selfCreatedGroup(change); } - @Nullable public MessageExtras getMessageExtras() { + public @Nullable MessageExtras getMessageExtras() { return messageExtras; } @@ -432,27 +432,41 @@ public abstract class MessageRecord extends DisplayRecord { return UpdateDescription.staticDescription(string, iconResource, lightTint, darkTint); } - private @NonNull String getProfileChangeDescription(@NonNull Context context) { - try { - byte[] decoded = Base64.decode(getBody()); - ProfileChangeDetails profileChangeDetails = ProfileChangeDetails.ADAPTER.decode(decoded); + private @NonNull UpdateDescription getProfileChangeDescription(@NonNull Context context) { + ProfileChangeDetails profileChangeDetails = null; + MessageExtras extras = getMessageExtras(); + if (extras != null) { + profileChangeDetails = extras.profileChangeDetails; + } else { + try { + byte[] decoded = Base64.decode(getBody()); + profileChangeDetails = ProfileChangeDetails.ADAPTER.decode(decoded); + } catch (IOException e) { + Log.w(TAG, "Profile name change details could not be read", e); + } + } + + if (profileChangeDetails != null) { if (profileChangeDetails.profileNameChange != null) { String displayName = getFromRecipient().getDisplayName(context); String newName = StringUtil.isolateBidi(ProfileName.fromSerialized(profileChangeDetails.profileNameChange.newValue).toString()); String previousName = StringUtil.isolateBidi(ProfileName.fromSerialized(profileChangeDetails.profileNameChange.previous).toString()); + String updateMessage; if (getFromRecipient().isSystemContact()) { - return context.getString(R.string.MessageRecord_changed_their_profile_name_from_to, displayName, previousName, newName); + updateMessage = context.getString(R.string.MessageRecord_changed_their_profile_name_from_to, displayName, previousName, newName); } else { - return context.getString(R.string.MessageRecord_changed_their_profile_name_to, previousName, newName); + updateMessage = context.getString(R.string.MessageRecord_changed_their_profile_name_to, previousName, newName); } + + return staticUpdateDescription(updateMessage, R.drawable.ic_update_profile_16); + } else if (profileChangeDetails.learnedProfileName != null) { + return staticUpdateDescription(context.getString(R.string.MessageRecord_started_this_chat, profileChangeDetails.learnedProfileName.previous), R.drawable.symbol_thread_16); } - } catch (IOException e) { - Log.w(TAG, "Profile name change details could not be read", e); } - return context.getString(R.string.MessageRecord_changed_their_profile, getFromRecipient().getDisplayName(context)); + return staticUpdateDescription(context.getString(R.string.MessageRecord_changed_their_profile, getFromRecipient().getDisplayName(context)), R.drawable.ic_update_profile_16); } private UpdateDescription getGroupMigrationEventDescription(@NonNull Context context) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.kt index 8de40d2c0b..fbf0182e76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.kt @@ -54,6 +54,12 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val constructor(recipientIds: Set) : this( Parameters.Builder() .addConstraint(NetworkConstraint.KEY) + .apply { + if (recipientIds.size < 5) { + setQueue(recipientIds.map { it.toLong() }.sorted().joinToString(separator = "_", prefix = QUEUE_PREFIX)) + setMaxInstancesForQueue(2) + } + } .setMaxAttempts(3) .build(), recipientIds.toMutableSet() @@ -364,6 +370,16 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val val remoteProfileName = ProfileName.fromSerialized(plaintextProfileName) val localProfileName = recipient.profileName + if (localProfileName.isEmpty && + !recipient.isSystemContact && + recipient.isProfileSharing && + !recipient.isGroup && + !recipient.isSelf + ) { + Log.i(TAG, "Learned profile name for first time, insert event") + SignalDatabase.messages.insertLearnedProfileNameChangeMessage(recipient, recipient.getDisplayName(context)) + } + if (remoteProfileName != localProfileName) { Log.i(TAG, "Profile name updated. Writing new value.") SignalDatabase.recipients.setProfileName(recipient.id, remoteProfileName) @@ -490,6 +506,7 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val private val TAG = Log.tag(RetrieveProfileJob::class.java) private const val KEY_RECIPIENTS = "recipients" private const val DEDUPE_KEY_RETRIEVE_AVATAR = KEY + "_RETRIEVE_PROFILE_AVATAR" + private const val QUEUE_PREFIX = "RetrieveProfileJob_" /** * Submits the necessary job to refresh the profile of the requested recipient. Works for any diff --git a/app/src/main/protowire/Database.proto b/app/src/main/protowire/Database.proto index 7d7b23edd8..f5ffc95987 100644 --- a/app/src/main/protowire/Database.proto +++ b/app/src/main/protowire/Database.proto @@ -73,6 +73,7 @@ message ProfileChangeDetails { } StringChange profileNameChange = 1; + StringChange learnedProfileName = 2; } message BodyRangeList { @@ -379,6 +380,7 @@ message MessageExtras { oneof extra { GV2UpdateDescription gv2UpdateDescription = 1; signalservice.GroupContext gv1Context = 2; + ProfileChangeDetails profileChangeDetails = 3; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index db2bcfc9a6..4ae4d09760 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1386,6 +1386,8 @@ %1$s changed their profile name to %2$s. %1$s changed their profile name from %2$s to %3$s. %1$s changed their profile. + + You started this chat with %1$s. You created the group.