mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Convert RecipientId to Kotlin.
This commit is contained in:
committed by
Jeffrey Starke
parent
49c5fead39
commit
d49ef1dd7d
@@ -1,238 +0,0 @@
|
||||
package org.thoughtcrime.securesms.recipients;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.DatabaseId;
|
||||
import org.signal.core.util.LongSerializer;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.util.DelimiterUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class RecipientId implements Parcelable, Comparable<RecipientId>, DatabaseId {
|
||||
|
||||
private static final String TAG = "RecipientId";
|
||||
private static final long UNKNOWN_ID = -1;
|
||||
private static final char DELIMITER = ',';
|
||||
|
||||
public static final RecipientId UNKNOWN = RecipientId.from(UNKNOWN_ID);
|
||||
public static final LongSerializer<RecipientId> SERIALIZER = new Serializer();
|
||||
|
||||
private final long id;
|
||||
|
||||
public static RecipientId from(long id) {
|
||||
if (id == 0) {
|
||||
throw new InvalidLongRecipientIdError();
|
||||
}
|
||||
|
||||
return new RecipientId(id);
|
||||
}
|
||||
|
||||
public static RecipientId from(@NonNull String id) {
|
||||
try {
|
||||
return RecipientId.from(Long.parseLong(id));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidStringRecipientIdError();
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable RecipientId fromNullable(@Nullable String id) {
|
||||
return id != null ? from(id) : null;
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public static @NonNull RecipientId from(@NonNull SignalServiceAddress address) {
|
||||
return from(address.getServiceId(), address.getNumber().orElse(null));
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public static @NonNull RecipientId from(@NonNull ServiceId serviceId) {
|
||||
return from(serviceId, null);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public static @NonNull RecipientId fromE164(@NonNull String identifier) {
|
||||
return from(null, identifier);
|
||||
}
|
||||
|
||||
public static @NonNull RecipientId from(@NonNull GroupId groupId) {
|
||||
RecipientId recipientId = RecipientIdCache.INSTANCE.get(groupId);
|
||||
if (recipientId == null) {
|
||||
Log.d(TAG, "RecipientId cache miss for " + groupId);
|
||||
recipientId = SignalDatabase.recipients().getOrInsertFromPossiblyMigratedGroupId(groupId);
|
||||
if (groupId.isV2()) {
|
||||
RecipientIdCache.INSTANCE.put(groupId, recipientId);
|
||||
}
|
||||
}
|
||||
return recipientId;
|
||||
}
|
||||
/**
|
||||
* Used for when you have a string that could be either a UUID or an e164. This was primarily
|
||||
* created for interacting with protocol stores.
|
||||
* @param identifier A UUID or e164
|
||||
*/
|
||||
@AnyThread
|
||||
public static @NonNull RecipientId fromSidOrE164(@NonNull String identifier) {
|
||||
ServiceId serviceId = ServiceId.parseOrNull(identifier);
|
||||
if (serviceId != null) {
|
||||
return from(serviceId);
|
||||
} else {
|
||||
return from(null, identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@SuppressLint("WrongThread")
|
||||
private static @NonNull RecipientId from(@Nullable ServiceId serviceId, @Nullable String e164) {
|
||||
if (serviceId != null && serviceId.isUnknown()) {
|
||||
return RecipientId.UNKNOWN;
|
||||
}
|
||||
|
||||
RecipientId recipientId = RecipientIdCache.INSTANCE.get(serviceId, e164);
|
||||
|
||||
if (recipientId == null) {
|
||||
recipientId = SignalDatabase.recipients().getAndPossiblyMerge(serviceId, e164);
|
||||
RecipientIdCache.INSTANCE.put(recipientId, e164, serviceId);
|
||||
}
|
||||
|
||||
return recipientId;
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public static void clearCache() {
|
||||
RecipientIdCache.INSTANCE.clear();
|
||||
}
|
||||
|
||||
private RecipientId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
private RecipientId(Parcel in) {
|
||||
id = in.readLong();
|
||||
}
|
||||
|
||||
public static @NonNull String toSerializedList(@NonNull Collection<RecipientId> ids) {
|
||||
return Util.join(Stream.of(ids).map(RecipientId::serialize).toList(), String.valueOf(DELIMITER));
|
||||
}
|
||||
|
||||
public static List<RecipientId> fromSerializedList(@NonNull String serialized) {
|
||||
String[] stringIds = DelimiterUtil.split(serialized, DELIMITER);
|
||||
List<RecipientId> out = new ArrayList<>(stringIds.length);
|
||||
|
||||
for (String stringId : stringIds) {
|
||||
RecipientId id = RecipientId.from(Long.parseLong(stringId));
|
||||
out.add(id);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static boolean serializedListContains(@NonNull String serialized, @NonNull RecipientId recipientId) {
|
||||
return Pattern.compile("\\b" + recipientId.serialize() + "\\b")
|
||||
.matcher(serialized)
|
||||
.find();
|
||||
}
|
||||
|
||||
public boolean isUnknown() {
|
||||
return id == UNKNOWN_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String serialize() {
|
||||
return String.valueOf(id);
|
||||
}
|
||||
|
||||
public long toLong() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public @NonNull String toQueueKey() {
|
||||
return toQueueKey(false);
|
||||
}
|
||||
|
||||
public @NonNull String toQueueKey(boolean forMedia) {
|
||||
return "RecipientId::" + id + (forMedia ? "::MEDIA" : "");
|
||||
}
|
||||
|
||||
public @NonNull String toScheduledSendQueueKey() {
|
||||
return "RecipientId::" + id + "::SCHEDULED";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "RecipientId::" + id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
RecipientId that = (RecipientId) o;
|
||||
|
||||
return id == that.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) (id ^ (id >>> 32));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RecipientId o) {
|
||||
return Long.compare(this.id, o.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(id);
|
||||
}
|
||||
|
||||
public static final Creator<RecipientId> CREATOR = new Creator<RecipientId>() {
|
||||
@Override
|
||||
public RecipientId createFromParcel(Parcel in) {
|
||||
return new RecipientId(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipientId[] newArray(int size) {
|
||||
return new RecipientId[size];
|
||||
}
|
||||
};
|
||||
|
||||
private static class InvalidLongRecipientIdError extends AssertionError {}
|
||||
private static class InvalidStringRecipientIdError extends AssertionError {}
|
||||
|
||||
private static class Serializer implements LongSerializer<RecipientId> {
|
||||
@Override
|
||||
public Long serialize(RecipientId data) {
|
||||
return data.toLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull RecipientId deserialize(Long data) {
|
||||
return RecipientId.from(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.recipients
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.AnyThread
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import org.signal.core.util.DatabaseId
|
||||
import org.signal.core.util.LongSerializer
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@Parcelize
|
||||
@Serializable
|
||||
class RecipientId private constructor(private val id: Long) : Parcelable, Comparable<RecipientId>, DatabaseId {
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = Log.tag(RecipientId::class)
|
||||
private const val UNKNOWN_ID = -1L
|
||||
private const val DELIMITER = ','
|
||||
|
||||
@JvmField
|
||||
val UNKNOWN = from(UNKNOWN_ID)
|
||||
|
||||
@JvmField
|
||||
val SERIALIZER: LongSerializer<RecipientId> = Serializer()
|
||||
|
||||
@JvmStatic
|
||||
fun from(id: Long): RecipientId {
|
||||
if (id == 0L) {
|
||||
throw InvalidLongRecipientIdError()
|
||||
}
|
||||
|
||||
return RecipientId(id)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun from(id: String): RecipientId {
|
||||
try {
|
||||
return from(id.toLong())
|
||||
} catch (_: NumberFormatException) {
|
||||
throw InvalidStringRecipientIdError()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun fromNullable(id: String?): RecipientId? = id?.let { from(it) }
|
||||
|
||||
@JvmStatic
|
||||
fun from(address: SignalServiceAddress): RecipientId = from(address.serviceId, address.number.orNull())
|
||||
|
||||
@JvmStatic
|
||||
fun from(serviceId: ServiceId): RecipientId = from(serviceId, null)
|
||||
|
||||
@JvmStatic
|
||||
fun fromE164(identifier: String): RecipientId = from(null, identifier)
|
||||
|
||||
@JvmStatic
|
||||
fun from(groupId: GroupId): RecipientId {
|
||||
var recipientId = RecipientIdCache.INSTANCE.get(groupId)
|
||||
if (recipientId == null) {
|
||||
Log.d(TAG, "RecipientId cache miss for $groupId")
|
||||
recipientId = SignalDatabase.recipients.getOrInsertFromPossiblyMigratedGroupId(groupId)
|
||||
if (groupId.isV2) {
|
||||
RecipientIdCache.INSTANCE.put(groupId, recipientId)
|
||||
}
|
||||
}
|
||||
|
||||
return recipientId
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for when you have a string that could be either a UUID or an e164. This was primarily
|
||||
* created for interacting with protocol stores.
|
||||
* @param identifier A UUID or e164
|
||||
*/
|
||||
@JvmStatic
|
||||
@AnyThread
|
||||
fun fromSidOrE164(identifier: String): RecipientId {
|
||||
val serviceId = ServiceId.parseOrNull(identifier)
|
||||
return if (serviceId != null) {
|
||||
from(serviceId)
|
||||
} else {
|
||||
from(null, identifier)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@AnyThread
|
||||
@SuppressLint("WrongThread")
|
||||
fun from(serviceId: ServiceId?, e164: String?): RecipientId {
|
||||
if (serviceId != null && serviceId.isUnknown) {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
var recipientId = RecipientIdCache.INSTANCE.get(serviceId, e164)
|
||||
if (recipientId == null) {
|
||||
recipientId = SignalDatabase.recipients.getAndPossiblyMerge(serviceId, e164)
|
||||
RecipientIdCache.INSTANCE.put(recipientId, e164, serviceId)
|
||||
}
|
||||
|
||||
return recipientId
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@AnyThread
|
||||
fun clearCache() {
|
||||
RecipientIdCache.INSTANCE.clear()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun toSerializedList(ids: Collection<RecipientId>): String {
|
||||
return ids.joinToString(DELIMITER.toString()) { it.serialize() }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun fromSerializedList(serialized: String): List<RecipientId> {
|
||||
return serialized.split(DELIMITER).filter { it.isNotEmpty() }.map { from(it) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun serializedListContains(serialized: String, recipientId: RecipientId): Boolean {
|
||||
return Pattern.compile("\\b${recipientId.serialize()}\\b")
|
||||
.matcher(serialized)
|
||||
.find()
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(): String = id.toString()
|
||||
|
||||
override fun toString(): String = "RecipientId::$id"
|
||||
|
||||
fun toLong(): Long = id
|
||||
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
val isUnknown: Boolean = id == UNKNOWN_ID
|
||||
|
||||
@JvmOverloads
|
||||
fun toQueueKey(forMedia: Boolean = false): String {
|
||||
return "RecipientId::$id${if (forMedia) "::MEDIA" else ""}"
|
||||
}
|
||||
|
||||
fun toScheduledSendQueueKey(): String = "RecipientId::$id::SCHEDULED"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as RecipientId
|
||||
|
||||
return id == other.id
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id.hashCode()
|
||||
}
|
||||
|
||||
override fun compareTo(other: RecipientId): Int {
|
||||
return id.compareTo(other.id)
|
||||
}
|
||||
|
||||
private class InvalidLongRecipientIdError : AssertionError()
|
||||
private class InvalidStringRecipientIdError : AssertionError()
|
||||
|
||||
private class Serializer : LongSerializer<RecipientId> {
|
||||
override fun serialize(data: RecipientId): Long = data.toLong()
|
||||
override fun deserialize(input: Long): RecipientId = from(input)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
/**
|
||||
* Kotlinx Serialization serializer for [RecipientId] objects.
|
||||
*/
|
||||
class RecipientIdSerializer : KSerializer<RecipientId> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("RecipientId", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: RecipientId) {
|
||||
encoder.encodeString(value.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): RecipientId {
|
||||
return RecipientId.from(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import androidx.test.core.app.ApplicationProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
@@ -50,7 +49,7 @@ class GroupsV2UpdateMessageProducerTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(RecipientId::class)
|
||||
mockkObject(RecipientId.Companion)
|
||||
val aliceId = RecipientId.from(1)
|
||||
val bobId = RecipientId.from(2)
|
||||
every { RecipientId.from(alice) } returns aliceId
|
||||
|
||||
Reference in New Issue
Block a user