mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-20 17:57:29 +00:00
Add support for avatar colors in storage service.
This commit is contained in:
committed by
Michelle Tang
parent
93d18c1763
commit
83611414cc
@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.migrations.ApplyUnknownFieldsToSelfMigrationJo
|
||||
import org.thoughtcrime.securesms.migrations.AttachmentCleanupMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.AttachmentHashBackfillMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.AttributesMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.AvatarColorStorageServiceMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.AvatarIdRemovalMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.AvatarMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.BackfillDigestsForDuplicatesMigrationJob;
|
||||
@@ -271,6 +272,7 @@ public final class JobManagerFactories {
|
||||
put(AttachmentCleanupMigrationJob.KEY, new AttachmentCleanupMigrationJob.Factory());
|
||||
put(AttachmentHashBackfillMigrationJob.KEY, new AttachmentHashBackfillMigrationJob.Factory());
|
||||
put(AttributesMigrationJob.KEY, new AttributesMigrationJob.Factory());
|
||||
put(AvatarColorStorageServiceMigrationJob.KEY, new AvatarColorStorageServiceMigrationJob.Factory());
|
||||
put(AvatarIdRemovalMigrationJob.KEY, new AvatarIdRemovalMigrationJob.Factory());
|
||||
put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory());
|
||||
put(BackfillDigestsMigrationJob.KEY, new BackfillDigestsMigrationJob.Factory());
|
||||
|
||||
@@ -107,9 +107,7 @@ import java.util.stream.Collectors
|
||||
* == Syncing a new field on an existing record ==
|
||||
*
|
||||
* - Add the field the the respective proto
|
||||
* - Update the respective model (i.e. [SignalContactRecord])
|
||||
* - Add getters
|
||||
* - Update the builder
|
||||
* - Update [StorageSyncModels]
|
||||
* - Update the respective record processor (i.e [ContactRecordProcessor]). You need to make sure that you're:
|
||||
* - Merging the attributes, likely preferring remote
|
||||
* - Adding to doParamsMatch()
|
||||
|
||||
@@ -173,9 +173,10 @@ public class ApplicationMigrations {
|
||||
static final int FTS_TRIGGER_FIX = 129;
|
||||
static final int THREAD_TABLE_PINNED_MIGRATION = 130;
|
||||
static final int GROUP_DECLINE_INVITE_FIX = 131;
|
||||
static final int AVATAR_COLOR_MIGRATION_JOB = 132;
|
||||
}
|
||||
|
||||
public static final int CURRENT_VERSION = 131;
|
||||
public static final int CURRENT_VERSION = 132;
|
||||
|
||||
/**
|
||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||
@@ -798,6 +799,10 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.GROUP_DECLINE_INVITE_FIX, new DatabaseMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.AVATAR_COLOR_MIGRATION_JOB) {
|
||||
jobs.put(Version.AVATAR_COLOR_MIGRATION_JOB, new AvatarColorStorageServiceMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.thoughtcrime.securesms.migrations
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.Companion.ID
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.Companion.STORAGE_SERVICE_ID
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.Companion.TABLE_NAME
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.Companion.TYPE
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
|
||||
/**
|
||||
* A job that marks all contacts and groups as needing to be synced, so that we'll update the
|
||||
* storage records with the new avatar color field.
|
||||
*/
|
||||
internal class AvatarColorStorageServiceMigrationJob(
|
||||
parameters: Parameters = Parameters.Builder().build()
|
||||
) : MigrationJob(parameters) {
|
||||
|
||||
companion object {
|
||||
val TAG = Log.tag(AvatarColorStorageServiceMigrationJob::class.java)
|
||||
const val KEY = "AvatarColorStorageServiceMigrationJob"
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun isUiBlocking(): Boolean = false
|
||||
|
||||
override fun performMigration() {
|
||||
if (!Recipient.isSelfSet) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!SignalStore.account.isRegistered) {
|
||||
return
|
||||
}
|
||||
|
||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||
SignalDatabase.recipients.markAllContactsAndGroupsAsNeedsSync()
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
|
||||
override fun shouldRetry(e: Exception): Boolean = false
|
||||
|
||||
private fun RecipientTable.markAllContactsAndGroupsAsNeedsSync() {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.select(ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$STORAGE_SERVICE_ID NOT NULL AND $TYPE IN (${RecipientTable.RecipientType.INDIVIDUAL.id}, ${RecipientTable.RecipientType.GV2.id})")
|
||||
.run()
|
||||
.use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
rotateStorageId(RecipientId.from(cursor.requireLong(ID)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Factory : Job.Factory<AvatarColorStorageServiceMigrationJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): AvatarColorStorageServiceMigrationJob {
|
||||
return AvatarColorStorageServiceMigrationJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,6 +231,7 @@ class ContactRecordProcessor(
|
||||
nickname = remote.proto.nickname
|
||||
pniSignatureVerified = remote.proto.pniSignatureVerified || local.proto.pniSignatureVerified
|
||||
note = remote.proto.note.nullIfBlank() ?: ""
|
||||
avatarColor = local.proto.avatarColor
|
||||
}.build().toSignalContactRecord(StorageId.forContact(keyGenerator.generate()))
|
||||
|
||||
val matchesRemote = doParamsMatch(remote, merged)
|
||||
|
||||
@@ -58,6 +58,7 @@ class GroupV2RecordProcessor(private val recipientTable: RecipientTable, private
|
||||
dontNotifyForMentionsIfMuted = remote.proto.dontNotifyForMentionsIfMuted
|
||||
hideStory = remote.proto.hideStory
|
||||
storySendMode = remote.proto.storySendMode
|
||||
avatarColor = local.proto.avatarColor
|
||||
}.build().toSignalGroupV2Record(StorageId.forGroupV2(keyGenerator.generate()))
|
||||
|
||||
val matchesRemote = doParamsMatch(remote, merged)
|
||||
|
||||
@@ -162,6 +162,7 @@ object StorageSyncHelper {
|
||||
storyViewReceiptsEnabled = storyViewReceiptsState
|
||||
hasSeenGroupStoryEducationSheet = SignalStore.story.userHasSeenGroupStoryEducationSheet
|
||||
hasCompletedUsernameOnboarding = SignalStore.uiHints.hasCompletedUsernameOnboarding()
|
||||
avatarColor = StorageSyncModels.localToRemoteAvatarColor(self.avatarColor)
|
||||
username = SignalStore.account.username ?: ""
|
||||
usernameLink = SignalStore.account.usernameLink?.let { linkComponents ->
|
||||
AccountRecord.UsernameLink(
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.signal.core.util.isNotEmpty
|
||||
import org.signal.core.util.isNullOrEmpty
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.database.GroupTable.ShowAsStoryState
|
||||
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
@@ -41,6 +42,7 @@ import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.Id
|
||||
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record
|
||||
import java.util.Currency
|
||||
import kotlin.math.max
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AvatarColor as RemoteAvatarColor
|
||||
|
||||
object StorageSyncModels {
|
||||
|
||||
@@ -182,6 +184,7 @@ object StorageSyncModels {
|
||||
pniSignatureVerified = recipient.syncExtras.pniSignatureVerified
|
||||
nickname = recipient.nickname.takeUnless { it.isEmpty }?.let { ContactRecord.Name(given = it.givenName, family = it.familyName) }
|
||||
note = recipient.note ?: ""
|
||||
avatarColor = localToRemoteAvatarColor(recipient.avatarColor)
|
||||
}.build().toSignalContactRecord(StorageId.forContact(rawStorageId))
|
||||
}
|
||||
|
||||
@@ -218,6 +221,7 @@ object StorageSyncModels {
|
||||
mutedUntilTimestamp = recipient.muteUntil
|
||||
dontNotifyForMentionsIfMuted = recipient.mentionSetting == RecipientTable.MentionSetting.ALWAYS_NOTIFY
|
||||
hideStory = recipient.extras != null && recipient.extras.hideStory()
|
||||
avatarColor = localToRemoteAvatarColor(recipient.avatarColor)
|
||||
storySendMode = when (groups.getShowAsStoryState(groupId)) {
|
||||
ShowAsStoryState.ALWAYS -> GroupV2Record.StorySendMode.ENABLED
|
||||
ShowAsStoryState.NEVER -> GroupV2Record.StorySendMode.DISABLED
|
||||
@@ -341,4 +345,23 @@ object StorageSyncModels {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun localToRemoteAvatarColor(avatarColor: AvatarColor): RemoteAvatarColor {
|
||||
return when (avatarColor) {
|
||||
AvatarColor.A100 -> RemoteAvatarColor.A100
|
||||
AvatarColor.A110 -> RemoteAvatarColor.A110
|
||||
AvatarColor.A120 -> RemoteAvatarColor.A120
|
||||
AvatarColor.A130 -> RemoteAvatarColor.A130
|
||||
AvatarColor.A140 -> RemoteAvatarColor.A140
|
||||
AvatarColor.A150 -> RemoteAvatarColor.A150
|
||||
AvatarColor.A160 -> RemoteAvatarColor.A160
|
||||
AvatarColor.A170 -> RemoteAvatarColor.A170
|
||||
AvatarColor.A180 -> RemoteAvatarColor.A180
|
||||
AvatarColor.A190 -> RemoteAvatarColor.A190
|
||||
AvatarColor.A200 -> RemoteAvatarColor.A200
|
||||
AvatarColor.A210 -> RemoteAvatarColor.A210
|
||||
AvatarColor.UNKNOWN -> RemoteAvatarColor.A100
|
||||
AvatarColor.ON_SURFACE_VARIANT -> RemoteAvatarColor.A100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,31 @@ message StorageRecord {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If unset - computed as the value of the first byte of SHA-256(msg=CONTACT_ID)
|
||||
// modulo the count of colors. Once set the avatar color for a recipient is
|
||||
// never recomputed or changed.
|
||||
//
|
||||
// `CONTACT_ID` is the first available identifier from the list:
|
||||
// - ServiceIdToBinary(ACI)
|
||||
// - E164
|
||||
// - ServiceIdToBinary(PNI)
|
||||
// - Group Id
|
||||
enum AvatarColor {
|
||||
A100 = 0;
|
||||
A110 = 1;
|
||||
A120 = 2;
|
||||
A130 = 3;
|
||||
A140 = 4;
|
||||
A150 = 5;
|
||||
A160 = 6;
|
||||
A170 = 7;
|
||||
A180 = 8;
|
||||
A190 = 9;
|
||||
A200 = 10;
|
||||
A210 = 11;
|
||||
}
|
||||
|
||||
message ContactRecord {
|
||||
enum IdentityState {
|
||||
DEFAULT = 0;
|
||||
@@ -110,7 +135,8 @@ message ContactRecord {
|
||||
bool pniSignatureVerified = 21;
|
||||
Name nickname = 22;
|
||||
string note = 23;
|
||||
// NEXT ID: 24
|
||||
optional AvatarColor avatarColor = 24;
|
||||
// Next ID: 25
|
||||
}
|
||||
|
||||
message GroupV1Record {
|
||||
@@ -139,6 +165,7 @@ message GroupV2Record {
|
||||
bool hideStory = 8;
|
||||
reserved /* storySendEnabled */ 9;
|
||||
StorySendMode storySendMode = 10;
|
||||
optional AvatarColor avatarColor = 11;
|
||||
}
|
||||
|
||||
message Payments {
|
||||
@@ -237,6 +264,7 @@ message AccountRecord {
|
||||
optional bool hasBackup = 39; // Set to true after backups are enabled and one is uploaded.
|
||||
optional uint64 backupTier = 40; // See zkgroup for integer particular values
|
||||
IAPSubscriberData backupSubscriberData = 41;
|
||||
optional AvatarColor avatarColor = 42;
|
||||
}
|
||||
|
||||
message StoryDistributionListRecord {
|
||||
|
||||
Reference in New Issue
Block a user