Sync release note channel settings with storage service.

This commit is contained in:
Cody Henthorne
2026-05-14 16:24:28 -04:00
committed by jeffrey-signal
parent 9f608337f1
commit 6eea4ba937
8 changed files with 108 additions and 8 deletions
@@ -4028,6 +4028,11 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
fun rotateStorageId(recipientId: RecipientId, logFailure: Boolean = false) {
val selfId = Recipient.self().id
if (recipientId != selfId && recipientId == SignalStore.releaseChannel.releaseChannelRecipientId) {
// Release channel info is stored on the account record (self)
rotateStorageId(selfId)
}
val values = ContentValues(1).apply {
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(StorageSyncHelper.generateKey()))
}
@@ -1622,6 +1622,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
Log.w(TAG, "Failed to parse serviceId!")
null
}
} else if (pinned.releaseNotes != null) {
SignalStore.releaseChannel.releaseChannelRecipientId?.let { Recipient.resolved(it) }
} else if (pinned.legacyGroupId != null) {
try {
Recipient.externalGroupExact(GroupId.v1(pinned.legacyGroupId!!.toByteArray()))
@@ -1657,6 +1659,10 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
notifyConversationListListeners()
}
fun applyStorageSyncReleaseChannelUpdate(recipientId: RecipientId, archived: Boolean, forcedUnread: Boolean) {
applyStorageSyncUpdate(recipientId, archived, forcedUnread, isGroup = false)
}
private fun applyStorageSyncUpdate(recipientId: RecipientId, archived: Boolean, forcedUnread: Boolean, isGroup: Boolean) {
val values = ContentValues()
values.put(ARCHIVED, if (archived) 1 else 0)
@@ -199,9 +199,10 @@ public class ApplicationMigrations {
static final int COLLAPSED_EVENTS = 155;
static final int COLLAPSED_EVENTS_2 = 156;
static final int KEY_TRANSPARENCY = 157;
static final int RELEASE_NOTES_CHAT_SYNC = 158;
}
public static final int CURRENT_VERSION = 157;
public static final int CURRENT_VERSION = 158;
/**
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
@@ -924,6 +925,10 @@ public class ApplicationMigrations {
jobs.put(Version.KEY_TRANSPARENCY, new ResetKeyTransparencyMigrationJob());
}
if (lastSeenVersion < Version.RELEASE_NOTES_CHAT_SYNC) {
jobs.put(Version.RELEASE_NOTES_CHAT_SYNC, new AccountRecordMigrationJob());
}
return jobs;
}
@@ -140,6 +140,10 @@ class AccountRecordProcessor(
backupTier = local.proto.backupTier ?: remote.proto.backupTier
automaticKeyVerificationDisabled = remote.proto.automaticKeyVerificationDisabled
hasSeenAdminDeleteEducationDialog = remote.proto.hasSeenAdminDeleteEducationDialog
releaseNotesChatArchived = remote.proto.releaseNotesChatArchived
releaseNotesChatMutedUntilTimestamp = remote.proto.releaseNotesChatMutedUntilTimestamp
releaseNotesChatBlocked = remote.proto.releaseNotesChatBlocked
releaseNotesChatMarkedUnread = remote.proto.releaseNotesChatMarkedUnread
safeSetPayments(payments?.enabled == true, payments?.entropy?.toByteArray())
safeSetSubscriber(donationSubscriberId, donationSubscriberCurrencyCode)
@@ -139,6 +139,8 @@ object StorageSyncHelper {
val storageId = selfRecord?.storageId ?: self.storageId
val releaseChannelRecord: RecipientRecord? = SignalStore.releaseChannel.releaseChannelRecipientId?.let { SignalDatabase.recipients.getRecordForSync(it) }
val accountRecord = SignalAccountRecord.newBuilder(selfRecord?.syncExtras?.storageProto).apply {
profileKey = self.profileKey?.toByteString() ?: ByteString.EMPTY
givenName = self.profileName.givenName
@@ -197,6 +199,13 @@ object StorageSyncHelper {
safeSetPayments(SignalStore.payments.mobileCoinPaymentsEnabled(), Optional.ofNullable(SignalStore.payments.paymentsEntropy).map { obj: Entropy -> obj.bytes }.orElse(null))
automaticKeyVerificationDisabled = !SignalStore.settings.automaticVerificationEnabled
hasSeenAdminDeleteEducationDialog = SignalStore.uiHints.hasSeenAdminDeleteEducationDialog()
if (releaseChannelRecord != null) {
releaseNotesChatArchived = releaseChannelRecord.syncExtras.isArchived == true
releaseNotesChatMutedUntilTimestamp = releaseChannelRecord.muteUntil
releaseNotesChatBlocked = releaseChannelRecord.isBlocked == true
releaseNotesChatMarkedUnread = releaseChannelRecord.syncExtras.isForcedUnread == true
}
}
return accountRecord.toSignalAccountRecord(StorageId.forAccount(storageId)).toSignalStorageRecord()
@@ -308,6 +317,13 @@ object StorageSyncHelper {
SignalStore.misc.usernameQrCodeColorScheme = StorageSyncModels.remoteToLocalUsernameColor(update.new.proto.usernameLink!!.color)
}
SignalStore.releaseChannel.releaseChannelRecipientId?.let { releaseChannelId ->
SignalDatabase.recipients.setBlocked(releaseChannelId, update.new.proto.releaseNotesChatBlocked)
SignalDatabase.recipients.setMuted(releaseChannelId, update.new.proto.releaseNotesChatMutedUntilTimestamp)
SignalDatabase.threads.applyStorageSyncReleaseChannelUpdate(releaseChannelId, update.new.proto.releaseNotesChatArchived, update.new.proto.releaseNotesChatMarkedUnread)
Recipient.live(releaseChannelId).refresh()
}
if (update.new.proto.notificationProfileManualOverride != null) {
if (update.new.proto.notificationProfileManualOverride!!.enabled != null) {
Log.i(TAG, "Found a remote enabled notification override")
@@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.groups.BadGroupIdException
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@@ -123,13 +124,17 @@ object StorageSyncModels {
@JvmStatic
fun localToRemotePinnedConversations(records: List<RecipientRecord>): List<AccountRecord.PinnedConversation> {
val releaseChannelId = SignalStore.releaseChannel.releaseChannelRecipientId
return records
.filter { it.recipientType == RecipientType.GV1 || it.recipientType == RecipientType.GV2 || it.registered == RecipientTable.RegisteredState.REGISTERED }
.map { localToRemotePinnedConversation(it) }
.filter { it.recipientType == RecipientType.GV1 || it.recipientType == RecipientType.GV2 || it.registered == RecipientTable.RegisteredState.REGISTERED || it.id == releaseChannelId }
.map { localToRemotePinnedConversation(it, releaseChannelId) }
}
@JvmStatic
private fun localToRemotePinnedConversation(settings: RecipientRecord): AccountRecord.PinnedConversation {
private fun localToRemotePinnedConversation(settings: RecipientRecord, releaseChannelId: RecipientId?): AccountRecord.PinnedConversation {
if (settings.id == releaseChannelId) {
return AccountRecord.PinnedConversation(releaseNotes = AccountRecord.PinnedConversation.ReleaseNotes())
}
return when (settings.recipientType) {
RecipientType.INDIVIDUAL -> {
AccountRecord.PinnedConversation(