mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-26 20:55:10 +00:00
Implement simple avatar color picking algorithm to align with iOS.
This commit is contained in:
committed by
Cody Henthorne
parent
bf7aaddbf9
commit
290c107698
@@ -16,7 +16,7 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.ringrtc.CallLinkState.Restrictions
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks
|
||||
import org.thoughtcrime.securesms.calls.links.UpdateCallLinkRepository
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
@@ -29,7 +29,7 @@ class CreateCallLinkViewModel(
|
||||
private val mutationRepository: UpdateCallLinkRepository = UpdateCallLinkRepository()
|
||||
) : ViewModel() {
|
||||
private val credentials = CallLinkCredentials.generate()
|
||||
private val avatarColor = AvatarColor.random()
|
||||
private val avatarColor = AvatarColorHash.forCallLink(credentials.linkKeyBytes)
|
||||
private val _callLink: MutableState<CallLinkTable.CallLink> = mutableStateOf(
|
||||
CallLinkTable.CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
|
||||
@@ -98,7 +98,7 @@ public enum AvatarColor {
|
||||
}
|
||||
|
||||
/** Colors that can be assigned via {@link #random()}. */
|
||||
private static final AvatarColor[] RANDOM_OPTIONS = new AvatarColor[] {
|
||||
static final AvatarColor[] RANDOM_OPTIONS = new AvatarColor[] {
|
||||
A100,
|
||||
A110,
|
||||
A120,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.colors
|
||||
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
|
||||
/**
|
||||
* Stolen from iOS. Utilizes a simple hash to map different characteristics to an avatar color index.
|
||||
*/
|
||||
object AvatarColorHash {
|
||||
|
||||
/**
|
||||
* Utilize Uppercase UUID of ServiceId.
|
||||
*
|
||||
* Uppercase is necessary here because iOS utilizes uppercase UUIDs by default.
|
||||
*/
|
||||
fun forAddress(serviceId: String?, e164: String?): AvatarColor {
|
||||
if (!serviceId.isNullOrEmpty()) {
|
||||
return forSeed(serviceId.toString().uppercase())
|
||||
}
|
||||
|
||||
if (!e164.isNullOrEmpty()) {
|
||||
return forSeed(e164)
|
||||
}
|
||||
|
||||
return AvatarColor.A100
|
||||
}
|
||||
|
||||
fun forGroupId(group: GroupId): AvatarColor {
|
||||
return forData(group.decodedId)
|
||||
}
|
||||
|
||||
fun forSeed(seed: String): AvatarColor {
|
||||
return forData(seed.toByteArray())
|
||||
}
|
||||
|
||||
fun forCallLink(rootKey: ByteArray): AvatarColor {
|
||||
return forIndex(rootKey.first().toInt())
|
||||
}
|
||||
|
||||
private fun forData(data: ByteArray): AvatarColor {
|
||||
var hash = 0
|
||||
for (value in data) {
|
||||
hash = hash.rotateLeft(3) xor value.toInt()
|
||||
}
|
||||
|
||||
return forIndex(hash)
|
||||
}
|
||||
|
||||
private fun forIndex(index: Int): AvatarColor {
|
||||
return AvatarColor.RANDOM_OPTIONS[(index.toUInt() % AvatarColor.RANDOM_OPTIONS.size.toUInt()).toInt()]
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import org.signal.core.util.optionalInt
|
||||
import org.signal.core.util.optionalLong
|
||||
import org.signal.core.util.optionalString
|
||||
import org.signal.core.util.or
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.readToSet
|
||||
import org.signal.core.util.readToSingleBoolean
|
||||
@@ -48,6 +49,7 @@ import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.color.MaterialColor
|
||||
import org.thoughtcrime.securesms.color.MaterialColor.UnknownColorException
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors.Companion.forChatColor
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors.Id.Companion.forLongValue
|
||||
@@ -602,7 +604,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
} else {
|
||||
val values = ContentValues().apply {
|
||||
put(GROUP_ID, groupId.toString())
|
||||
put(AVATAR_COLOR, AvatarColor.random().serialize())
|
||||
put(AVATAR_COLOR, AvatarColorHash.forGroupId(groupId).serialize())
|
||||
}
|
||||
|
||||
val id = writableDatabase.insert(TABLE_NAME, null, values)
|
||||
@@ -3855,12 +3857,13 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
private fun buildContentValuesForNewUser(e164: String?, pni: PNI?, aci: ACI?): ContentValues {
|
||||
check(e164 != null || pni != null || aci != null) { "Must provide some sort of identifier!" }
|
||||
|
||||
val serviceId = (aci ?: pni)?.toString()
|
||||
val values = contentValuesOf(
|
||||
PHONE to e164,
|
||||
SERVICE_ID to (aci ?: pni)?.toString(),
|
||||
SERVICE_ID to serviceId,
|
||||
PNI_COLUMN to pni?.toString(),
|
||||
STORAGE_SERVICE_ID to Base64.encodeBytes(StorageSyncHelper.generateKey()),
|
||||
AVATAR_COLOR to AvatarColor.random().serialize()
|
||||
AVATAR_COLOR to AvatarColorHash.forAddress(serviceId, e164).serialize()
|
||||
)
|
||||
|
||||
if (pni != null || aci != null) {
|
||||
@@ -3917,14 +3920,16 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
|
||||
if (isInsert) {
|
||||
put(AVATAR_COLOR, AvatarColor.random().serialize())
|
||||
put(AVATAR_COLOR, AvatarColorHash.forAddress(contact.serviceId.toString(), contact.number.orNull()).serialize())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getValuesForStorageGroupV1(groupV1: SignalGroupV1Record, isInsert: Boolean): ContentValues {
|
||||
return ContentValues().apply {
|
||||
put(GROUP_ID, GroupId.v1orThrow(groupV1.groupId).toString())
|
||||
val groupId = GroupId.v1orThrow(groupV1.groupId)
|
||||
|
||||
put(GROUP_ID, groupId.toString())
|
||||
put(GROUP_TYPE, GroupType.SIGNAL_V1.id)
|
||||
put(PROFILE_SHARING, if (groupV1.isProfileSharingEnabled) "1" else "0")
|
||||
put(BLOCKED, if (groupV1.isBlocked) "1" else "0")
|
||||
@@ -3938,14 +3943,16 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
|
||||
if (isInsert) {
|
||||
put(AVATAR_COLOR, AvatarColor.random().serialize())
|
||||
put(AVATAR_COLOR, AvatarColorHash.forGroupId(groupId).serialize())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getValuesForStorageGroupV2(groupV2: SignalGroupV2Record, isInsert: Boolean): ContentValues {
|
||||
return ContentValues().apply {
|
||||
put(GROUP_ID, GroupId.v2(groupV2.masterKeyOrThrow).toString())
|
||||
val groupId = GroupId.v2(groupV2.masterKeyOrThrow)
|
||||
|
||||
put(GROUP_ID, groupId.toString())
|
||||
put(GROUP_TYPE, GroupType.SIGNAL_V2.id)
|
||||
put(PROFILE_SHARING, if (groupV2.isProfileSharingEnabled) "1" else "0")
|
||||
put(BLOCKED, if (groupV2.isBlocked) "1" else "0")
|
||||
@@ -3960,7 +3967,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
|
||||
if (isInsert) {
|
||||
put(AVATAR_COLOR, AvatarColor.random().serialize())
|
||||
put(AVATAR_COLOR, AvatarColorHash.forGroupId(groupId).serialize())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.attachments.TombstoneAttachment
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
||||
import org.thoughtcrime.securesms.contactshare.Contact
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
@@ -1206,7 +1206,7 @@ object SyncMessageProcessor {
|
||||
revoked = false,
|
||||
expiration = Instant.MIN
|
||||
),
|
||||
avatarColor = AvatarColor.random()
|
||||
avatarColor = AvatarColorHash.forCallLink(callLinkRootKey.keyBytes)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks;
|
||||
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash;
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||
@@ -358,7 +358,7 @@ public class CommunicationActions {
|
||||
null
|
||||
),
|
||||
new SignalCallLinkState("", CallLinkState.Restrictions.UNKNOWN, false, Instant.MIN),
|
||||
AvatarColor.random()
|
||||
AvatarColorHash.INSTANCE.forCallLink(rootKey.getKeyBytes())
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user