mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-03 11:22:21 +00:00
Implement the new protocol
This commit is contained in:
@@ -47,11 +47,11 @@ object LokiPushNotificationManager {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun register(token: String, hexEncodedPublicKey: String, context: Context?, force: Boolean) {
|
||||
fun register(token: String, publicKey: String, context: Context?, force: Boolean) {
|
||||
val oldToken = TextSecurePreferences.getFCMToken(context)
|
||||
val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context)
|
||||
if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return }
|
||||
val parameters = mapOf( "token" to token, "pubKey" to hexEncodedPublicKey )
|
||||
val parameters = mapOf( "token" to token, "pubKey" to publicKey )
|
||||
val url = "$server/register"
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body).build()
|
||||
|
||||
@@ -165,7 +165,7 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
|
||||
}
|
||||
val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.publicKey
|
||||
val serviceDataMessage = getDataMessage(message)
|
||||
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false, false)
|
||||
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false)
|
||||
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
|
||||
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
||||
} else {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
|
||||
class EphemeralMessage private constructor(val data: Map<*, *>) {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun create(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey ))
|
||||
|
||||
@JvmStatic
|
||||
fun createDeviceUnlinkingRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "unpairingRequest" to true ))
|
||||
|
||||
@JvmStatic
|
||||
fun createSessionRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "friendRequest" to true, "sessionRequest" to true ))
|
||||
|
||||
internal fun parse(serialized: String): EphemeralMessage {
|
||||
val data = JsonUtil.fromJson(serialized, Map::class.java) ?: throw IllegalArgumentException("Couldn't parse string to JSON")
|
||||
return EphemeralMessage(data)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> get(key: String, defaultValue: T): T {
|
||||
return data[key] as? T ?: defaultValue
|
||||
}
|
||||
|
||||
fun serialize(): String {
|
||||
return JsonUtil.toJson(data)
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters)
|
||||
|
||||
constructor() : this(Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setQueue("MultiDeviceOpenGroupUpdateJob")
|
||||
.setQueue(KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build())
|
||||
|
||||
@@ -30,16 +30,16 @@ object MultiDeviceProtocol {
|
||||
enum class MessageType { Text, Media }
|
||||
|
||||
@JvmStatic
|
||||
fun sendTextPush(context: Context, recipient: Recipient, messageID: Long, isEndSession: Boolean) {
|
||||
sendMessagePush(context, recipient, messageID, MessageType.Text, isEndSession)
|
||||
fun sendTextPush(context: Context, recipient: Recipient, messageID: Long) {
|
||||
sendMessagePush(context, recipient, messageID, MessageType.Text)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun sendMediaPush(context: Context, recipient: Recipient, messageID: Long) {
|
||||
sendMessagePush(context, recipient, messageID, MessageType.Media, false)
|
||||
sendMessagePush(context, recipient, messageID, MessageType.Media)
|
||||
}
|
||||
|
||||
private fun sendMessagePush(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType, isEndSession: Boolean) {
|
||||
private fun sendMessagePush(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType) {
|
||||
val jobManager = ApplicationContext.getInstance(context).jobManager
|
||||
val isMultiDeviceRequired = !recipient.address.isOpenGroup
|
||||
if (!isMultiDeviceRequired) {
|
||||
@@ -162,6 +162,8 @@ object MultiDeviceProtocol {
|
||||
}
|
||||
val isValid = isValidDeviceLinkMessage(context, deviceLink)
|
||||
if (!isValid) { return }
|
||||
// The line below isn't actually necessary because this is called after PushDecryptJob
|
||||
// calls handlePreKeyBundleMessageIfNeeded, but it also doesn't hurt.
|
||||
SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(context, content)
|
||||
linkingSession.processLinkingAuthorization(deviceLink)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.jobmanager.Data
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.BaseJob
|
||||
import org.thoughtcrime.securesms.logging.Log
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class PushEphemeralMessageSendJob private constructor(parameters: Parameters, private val message: EphemeralMessage) : BaseJob(parameters) {
|
||||
|
||||
companion object {
|
||||
private const val KEY_MESSAGE = "message"
|
||||
const val KEY = "PushBackgroundMessageSendJob"
|
||||
}
|
||||
|
||||
constructor(message: EphemeralMessage) : this(Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setQueue(KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(1)
|
||||
.build(),
|
||||
message)
|
||||
|
||||
override fun serialize(): Data {
|
||||
return Data.Builder()
|
||||
.putString(KEY_MESSAGE, message.serialize())
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String { return KEY }
|
||||
|
||||
public override fun onRun() {
|
||||
val recipient = message.get<String?>("recipient", null) ?: throw IllegalStateException()
|
||||
val dataMessage = SignalServiceDataMessage.newBuilder().withTimestamp(System.currentTimeMillis())
|
||||
// Attach a pre key bundle if needed
|
||||
if (message.get("friendRequest", false)) {
|
||||
val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(recipient)
|
||||
dataMessage.withPreKeyBundle(bundle)
|
||||
}
|
||||
// Set flags if needed (these are mutually exclusive)
|
||||
when {
|
||||
message.get("unpairingRequest", false) -> dataMessage.asDeviceUnlinkingRequest(true)
|
||||
message.get("sessionRequest", false) -> dataMessage.asSessionRequest(true)
|
||||
}
|
||||
// Send the message
|
||||
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
||||
val address = SignalServiceAddress(recipient)
|
||||
try {
|
||||
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(recipient), false))
|
||||
messageSender.sendMessage(0, address, udAccess, dataMessage.build()) // The message ID doesn't matter
|
||||
} catch (e: Exception) {
|
||||
Log.d("Loki", "Failed to send background message to: $recipient due to error: $e.")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onShouldRetry(e: Exception): Boolean {
|
||||
// Disable since we have our own retrying
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCanceled() { }
|
||||
|
||||
class Factory : Job.Factory<PushEphemeralMessageSendJob> {
|
||||
|
||||
override fun create(parameters: Parameters, data: Data): PushEphemeralMessageSendJob {
|
||||
try {
|
||||
val messageJSON = data.getString(KEY_MESSAGE)
|
||||
return PushEphemeralMessageSendJob(parameters, EphemeralMessage.parse(messageJSON))
|
||||
} catch (e: IOException) {
|
||||
throw AssertionError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.jobmanager.Data
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.BaseJob
|
||||
import org.thoughtcrime.securesms.logging.Log
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.loki.protocol.meta.TTLUtilities
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class PushSessionRequestMessageSendJob private constructor(parameters: Parameters, private val publicKey: String) : BaseJob(parameters) {
|
||||
|
||||
companion object {
|
||||
const val KEY = "PushSessionRequestMessageSendJob"
|
||||
}
|
||||
|
||||
constructor(publicKey: String) : this(Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setQueue(KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(1)
|
||||
.build(),
|
||||
publicKey)
|
||||
|
||||
override fun serialize(): Data {
|
||||
return Data.Builder().putString("publicKey", publicKey).build()
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String { return KEY }
|
||||
|
||||
public override fun onRun() {
|
||||
// Prepare
|
||||
val contentMessage = SignalServiceProtos.Content.newBuilder()
|
||||
// Attach the pre key bundle message
|
||||
val preKeyBundleMessage = SignalServiceProtos.PreKeyBundleMessage.newBuilder()
|
||||
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(publicKey) ?: return
|
||||
preKeyBundleMessage.identityKey = ByteString.copyFrom(preKeyBundle.identityKey.serialize())
|
||||
preKeyBundleMessage.deviceId = preKeyBundle.deviceId
|
||||
preKeyBundleMessage.preKeyId = preKeyBundle.preKeyId
|
||||
preKeyBundleMessage.signedKeyId = preKeyBundle.signedPreKeyId
|
||||
preKeyBundleMessage.preKey = ByteString.copyFrom(preKeyBundle.preKey.serialize())
|
||||
preKeyBundleMessage.signedKey = ByteString.copyFrom(preKeyBundle.signedPreKey.serialize())
|
||||
preKeyBundleMessage.signature = ByteString.copyFrom(preKeyBundle.signedPreKeySignature)
|
||||
contentMessage.preKeyBundleMessage = preKeyBundleMessage.build()
|
||||
// Attach the null message
|
||||
val nullMessage = SignalServiceProtos.NullMessage.newBuilder()
|
||||
val sr = SecureRandom()
|
||||
val paddingSize = sr.nextInt(512)
|
||||
val padding = ByteArray(paddingSize)
|
||||
sr.nextBytes(padding)
|
||||
nullMessage.padding = ByteString.copyFrom(padding)
|
||||
contentMessage.nullMessage = nullMessage.build()
|
||||
// Send the result
|
||||
val serializedContentMessage = contentMessage.build().toByteArray()
|
||||
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
||||
val address = SignalServiceAddress(publicKey)
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
|
||||
val ttl = TTLUtilities.getTTL(TTLUtilities.MessageType.SessionRequest)
|
||||
try {
|
||||
messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess,
|
||||
Date().time, serializedContentMessage, false, ttl, false,
|
||||
true, false, false)
|
||||
} catch (e: Exception) {
|
||||
Log.d("Loki", "Failed to send session request to: $publicKey due to error: $e.")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onShouldRetry(e: Exception): Boolean {
|
||||
// Disable since we have our own retrying
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCanceled() { }
|
||||
|
||||
class Factory : Job.Factory<PushSessionRequestMessageSendJob> {
|
||||
|
||||
override fun create(parameters: Parameters, data: Data): PushSessionRequestMessageSendJob {
|
||||
try {
|
||||
val publicKey = data.getString("publicKey")
|
||||
return PushSessionRequestMessageSendJob(parameters, publicKey)
|
||||
} catch (e: IOException) {
|
||||
throw AssertionError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob
|
||||
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.libsignal.loki.SessionResetStatus
|
||||
@@ -27,8 +29,8 @@ object SessionManagementProtocol {
|
||||
val smsDB = DatabaseFactory.getSmsDatabase(context)
|
||||
val devices = lokiThreadDB.getSessionRestoreDevices(threadID)
|
||||
for (device in devices) {
|
||||
val sessionRequest = EphemeralMessage.createSessionRequest(recipient.address.serialize())
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
|
||||
val endSessionMessage = OutgoingEndSessionMessage(OutgoingTextMessage(recipient, "TERMINATE", 0, -1))
|
||||
MessageSender.send(context, endSessionMessage, threadID, false, null)
|
||||
}
|
||||
val infoMessage = OutgoingTextMessage(recipient, "", 0, 0)
|
||||
val infoMessageID = smsDB.insertMessageOutbox(threadID, infoMessage, false, System.currentTimeMillis(), null)
|
||||
@@ -53,37 +55,24 @@ object SessionManagementProtocol {
|
||||
|
||||
@JvmStatic
|
||||
fun handlePreKeyBundleMessageIfNeeded(context: Context, content: SignalServiceContent) {
|
||||
val recipient = recipient(context, content.sender)
|
||||
if (recipient.isGroupRecipient) { return }
|
||||
val preKeyBundleMessage = content.lokiServiceMessage.orNull()?.preKeyBundleMessage ?: return
|
||||
val publicKey = content.sender
|
||||
val recipient = recipient(context, publicKey)
|
||||
if (recipient.isGroupRecipient) { return } // Should never occur
|
||||
val preKeyBundleMessage = content.preKeyBundleMessage.orNull() ?: return
|
||||
val registrationID = TextSecurePreferences.getLocalRegistrationId(context) // TODO: It seems wrong to use the local registration ID for this?
|
||||
val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context)
|
||||
Log.d("Loki", "Received a pre key bundle from: " + content.sender.toString() + ".")
|
||||
if (content.dataMessage.isPresent && content.dataMessage.get().isSessionRequest) {
|
||||
val sessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
|
||||
if (sessionRequestTimestamp != null && content.timestamp < sessionRequestTimestamp) {
|
||||
// We sent or processed a session request after this one was sent
|
||||
Log.d("Loki", "Ignoring session request from: ${content.sender}.")
|
||||
return
|
||||
}
|
||||
}
|
||||
val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
|
||||
lokiPreKeyBundleDatabase.setPreKeyBundle(content.sender, preKeyBundle)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleSessionRequestIfNeeded(context: Context, content: SignalServiceContent): Boolean {
|
||||
if (!content.dataMessage.isPresent || !content.dataMessage.get().isSessionRequest) { return false }
|
||||
Log.d("Loki", "Received a pre key bundle from: $publicKey.")
|
||||
val sessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
|
||||
if (sessionRequestTimestamp != null && content.timestamp < sessionRequestTimestamp) {
|
||||
// We sent or processed a session request after this one was sent
|
||||
Log.d("Loki", "Ignoring session request from: ${content.sender}.")
|
||||
return false
|
||||
return
|
||||
}
|
||||
val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
|
||||
lokiPreKeyBundleDatabase.setPreKeyBundle(content.sender, preKeyBundle)
|
||||
DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestTimestamp(content.sender, Date().time)
|
||||
val ephemeralMessage = EphemeralMessage.create(content.sender)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
return true
|
||||
val job = PushNullMessageSendJob(content.sender)
|
||||
ApplicationContext.getInstance(context).jobManager.add(job)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -95,8 +84,8 @@ object SessionManagementProtocol {
|
||||
sessionStore.archiveAllSessions(content.sender)
|
||||
lokiThreadDB.setSessionResetStatus(content.sender, SessionResetStatus.REQUEST_RECEIVED)
|
||||
Log.d("Loki", "Sending an ephemeral message back to: ${content.sender}.")
|
||||
val ephemeralMessage = EphemeralMessage.create(content.sender)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
val job = PushNullMessageSendJob(content.sender)
|
||||
ApplicationContext.getInstance(context).jobManager.add(job)
|
||||
SecurityEvent.broadcastSecurityUpdateEvent(context)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ class SessionResetImplementation(private val context: Context) : SessionResetPro
|
||||
|
||||
override fun onNewSessionAdopted(publicKey: String, oldSessionResetStatus: SessionResetStatus) {
|
||||
if (oldSessionResetStatus == SessionResetStatus.IN_PROGRESS) {
|
||||
val ephemeralMessage = EphemeralMessage.create(publicKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
val job = PushNullMessageSendJob(publicKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(job)
|
||||
}
|
||||
// TODO: Show session reset succeed message
|
||||
}
|
||||
|
||||
@@ -94,10 +94,8 @@ object SyncMessagesProtocol {
|
||||
val contactPublicKeys = contactsInputStream.readAll().map { it.number }
|
||||
for (contactPublicKey in contactPublicKeys) {
|
||||
if (contactPublicKey == userPublicKey || !PublicKeyValidation.isValid(contactPublicKey)) { return }
|
||||
val recipient = recipient(context, contactPublicKey)
|
||||
val applicationContext = context.applicationContext as ApplicationContext
|
||||
applicationContext.sendSessionRequestIfNeeded(contactPublicKey)
|
||||
// TODO: Make the thread visible
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class LokiRSSFeedPoller(private val context: Context, private val feed: LokiRSSF
|
||||
val id = feed.id.toByteArray()
|
||||
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.RSS_FEED, null, null, null, null)
|
||||
val x2 = SignalServiceDataMessage(timestamp, x1, null, body)
|
||||
val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false, false, false)
|
||||
val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false, false)
|
||||
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.absent())
|
||||
}
|
||||
}.fail { exception ->
|
||||
|
||||
@@ -28,22 +28,22 @@ object MentionUtilities {
|
||||
val mentions = mutableListOf<Tuple2<Range<Int>, String>>()
|
||||
var startIndex = 0
|
||||
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
if (matcher.find(startIndex)) {
|
||||
while (true) {
|
||||
val hexEncodedPublicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
||||
val userDisplayName: String? = if (hexEncodedPublicKey.toLowerCase() == userHexEncodedPublicKey.toLowerCase()) {
|
||||
val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
||||
val userDisplayName: String? = if (publicKey.toLowerCase() == userPublicKey.toLowerCase()) {
|
||||
TextSecurePreferences.getProfileName(context)
|
||||
} else if (publicChat != null) {
|
||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
|
||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey)
|
||||
} else {
|
||||
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
|
||||
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey)
|
||||
}
|
||||
if (userDisplayName != null) {
|
||||
text = text.subSequence(0, matcher.start()).toString() + "@" + userDisplayName + text.subSequence(matcher.end(), text.length)
|
||||
val endIndex = matcher.start() + 1 + userDisplayName.length
|
||||
startIndex = endIndex
|
||||
mentions.add(Tuple2(Range.create(matcher.start(), endIndex), hexEncodedPublicKey))
|
||||
mentions.add(Tuple2(Range.create(matcher.start(), endIndex), publicKey))
|
||||
} else {
|
||||
startIndex = matcher.end()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user