mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-21 12:38:27 +00:00
Refactor MessageSender
This commit is contained in:
parent
819f414446
commit
eb5b8886d4
@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint
|
|||||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
|
||||||
import org.thoughtcrime.securesms.loki.PushBackgroundMessageSendJob;
|
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
|
||||||
import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob;
|
import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -72,7 +72,7 @@ public final class JobManagerFactories {
|
|||||||
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
||||||
put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
|
put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
|
||||||
put(PushMessageSyncSendJob.KEY, new PushMessageSyncSendJob.Factory());
|
put(PushMessageSyncSendJob.KEY, new PushMessageSyncSendJob.Factory());
|
||||||
put(PushBackgroundMessageSendJob.KEY, new PushBackgroundMessageSendJob.Factory());
|
put(PushEphemeralMessageSendJob.KEY, new PushEphemeralMessageSendJob.Factory());
|
||||||
put(MultiDeviceOpenGroupUpdateJob.KEY, new MultiDeviceOpenGroupUpdateJob.Factory());
|
put(MultiDeviceOpenGroupUpdateJob.KEY, new MultiDeviceOpenGroupUpdateJob.Factory());
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -1,135 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki
|
|
||||||
|
|
||||||
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 org.whispersystems.signalservice.internal.util.JsonUtil
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
data class BackgroundMessage private constructor(val data: Map<String, Any>) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun create(recipient: String) = BackgroundMessage(mapOf("recipient" to recipient))
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun createFriendRequest(recipient: String, messageBody: String) = BackgroundMessage(mapOf( "recipient" to recipient, "body" to messageBody, "friendRequest" to true ))
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun createUnpairingRequest(recipient: String) = BackgroundMessage(mapOf( "recipient" to recipient, "unpairingRequest" to true ))
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun createSessionRestore(recipient: String) = BackgroundMessage(mapOf( "recipient" to recipient, "friendRequest" to true, "sessionRestore" to true ))
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun createSessionRequest(recipient: String) = BackgroundMessage(mapOf("recipient" to recipient, "friendRequest" to true, "sessionRequest" to true))
|
|
||||||
|
|
||||||
internal fun parse(serialized: String): BackgroundMessage {
|
|
||||||
val data = JsonUtil.fromJson(serialized, Map::class.java) as? Map<String, Any> ?: throw AssertionError("JSON parsing failed")
|
|
||||||
return BackgroundMessage(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> get(key: String, defaultValue: T): T {
|
|
||||||
return data[key] as? T ?: defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun serialize(): String {
|
|
||||||
return JsonUtil.toJson(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PushBackgroundMessageSendJob private constructor(
|
|
||||||
parameters: Parameters,
|
|
||||||
private val message: BackgroundMessage
|
|
||||||
) : BaseJob(parameters) {
|
|
||||||
companion object {
|
|
||||||
const val KEY = "PushBackgroundMessageSendJob"
|
|
||||||
|
|
||||||
private val TAG = PushBackgroundMessageSendJob::class.java.simpleName
|
|
||||||
|
|
||||||
private val KEY_MESSAGE = "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(message: BackgroundMessage) : 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())
|
|
||||||
.withBody(message.get<String?>("body", null))
|
|
||||||
|
|
||||||
if (message.get("friendRequest", false)) {
|
|
||||||
val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(recipient)
|
|
||||||
dataMessage.withPreKeyBundle(bundle)
|
|
||||||
.asFriendRequest(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.get("unpairingRequest", false)) {
|
|
||||||
dataMessage.asUnlinkingRequest(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.get("sessionRestore", false)) {
|
|
||||||
dataMessage.asSessionRestorationRequest(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.get("sessionRequest", false)) {
|
|
||||||
dataMessage.asSessionRequest(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
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(-1, address, udAccess, dataMessage.build()) // The message ID doesn't matter
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d("Loki", "Failed to send background message to: ${recipient}.")
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun onShouldRetry(e: Exception): Boolean {
|
|
||||||
// Loki - Disable since we have our own retrying when sending messages
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCanceled() {}
|
|
||||||
|
|
||||||
class Factory : Job.Factory<PushBackgroundMessageSendJob> {
|
|
||||||
override fun create(parameters: Parameters, data: Data): PushBackgroundMessageSendJob {
|
|
||||||
try {
|
|
||||||
val messageJSON = data.getString(KEY_MESSAGE)
|
|
||||||
return PushBackgroundMessageSendJob(parameters, BackgroundMessage.parse(messageJSON))
|
|
||||||
} catch (e: IOException) {
|
|
||||||
throw AssertionError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
@ -79,9 +80,13 @@ object ClosedGroupsProtocol {
|
|||||||
for (device in allDevices) {
|
for (device in allDevices) {
|
||||||
val address = SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID)
|
val address = SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID)
|
||||||
val hasSession = TextSecureSessionStore(context).containsSession(address)
|
val hasSession = TextSecureSessionStore(context).containsSession(address)
|
||||||
if (!hasSession) {
|
if (!hasSession) { sendSessionRequest(context, device) }
|
||||||
MessageSender.sendBackgroundSessionRequest(context, device)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun sendSessionRequest(context: Context, publicKey: String) {
|
||||||
|
val sessionRequest = EphemeralMessage.createSessionRequest(publicKey)
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
|
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||||
|
|
||||||
|
data class EphemeralMessage private constructor(val data: Map<*, *>) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun create(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey ))
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun createUnlinkingRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "unpairingRequest" to true ))
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun createSessionRestorationRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "friendRequest" to true, "sessionRestore" 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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||||
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||||
|
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||||
|
|
||||||
|
object FriendRequestProtocol {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun shouldUpdateFriendRequestStatusFromOutgoingTextMessage(context: Context, message: OutgoingTextMessage): Boolean {
|
||||||
|
// The order of these checks matters
|
||||||
|
if (message.recipient.isGroupRecipient) { return false }
|
||||||
|
if (message.recipient.address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
|
||||||
|
// TODO: Return false if the message is a device linking request
|
||||||
|
// TODO: Return false if the message is a session request
|
||||||
|
return message.isFriendRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun shouldUpdateFriendRequestStatusFromOutgoingMediaMessage(context: Context, message: OutgoingMediaMessage): Boolean {
|
||||||
|
// The order of these checks matters
|
||||||
|
if (message.recipient.isGroupRecipient) { return false }
|
||||||
|
if (message.recipient.address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
|
||||||
|
// TODO: Return false if the message is a device linking request
|
||||||
|
// TODO: Return false if the message is a session request
|
||||||
|
return message.isFriendRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setFriendRequestStatusToSendingIfNeeded(context: Context, messageID: Long, threadID: Long) {
|
||||||
|
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
||||||
|
val messageFRStatus = messageDB.getFriendRequestStatus(messageID)
|
||||||
|
if (messageFRStatus == LokiMessageFriendRequestStatus.NONE || messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_EXPIRED) {
|
||||||
|
messageDB.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_SENDING)
|
||||||
|
}
|
||||||
|
val threadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||||
|
val threadFRStatus = threadDB.getFriendRequestStatus(threadID)
|
||||||
|
if (threadFRStatus == LokiThreadFriendRequestStatus.NONE || threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
||||||
|
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_SENDING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,33 +2,30 @@ package org.thoughtcrime.securesms.loki.protocol
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender
|
|
||||||
import org.whispersystems.libsignal.loki.LokiSessionResetProtocol
|
import org.whispersystems.libsignal.loki.LokiSessionResetProtocol
|
||||||
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
||||||
import org.whispersystems.libsignal.protocol.PreKeySignalMessage
|
import org.whispersystems.libsignal.protocol.PreKeySignalMessage
|
||||||
|
|
||||||
class LokiSessionResetImplementation(private val context: Context) : LokiSessionResetProtocol {
|
class LokiSessionResetImplementation(private val context: Context) : LokiSessionResetProtocol {
|
||||||
|
|
||||||
override fun getSessionResetStatus(hexEncodedPublicKey: String): LokiSessionResetStatus {
|
override fun getSessionResetStatus(hexEncodedPublicKey: String): LokiSessionResetStatus {
|
||||||
return DatabaseFactory.getLokiThreadDatabase(context).getSessionResetStatus(hexEncodedPublicKey)
|
return DatabaseFactory.getLokiThreadDatabase(context).getSessionResetStatus(hexEncodedPublicKey)
|
||||||
}
|
|
||||||
|
|
||||||
override fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: LokiSessionResetStatus) {
|
|
||||||
return DatabaseFactory.getLokiThreadDatabase(context).setSessionResetStatus(hexEncodedPublicKey, sessionResetStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewSessionAdopted(hexEncodedPublicKey: String, oldSessionResetStatus: LokiSessionResetStatus) {
|
|
||||||
if (oldSessionResetStatus == LokiSessionResetStatus.IN_PROGRESS) {
|
|
||||||
// Send a message back to the contact to finalise session reset
|
|
||||||
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Show session reset succeed message
|
override fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: LokiSessionResetStatus) {
|
||||||
}
|
return DatabaseFactory.getLokiThreadDatabase(context).setSessionResetStatus(hexEncodedPublicKey, sessionResetStatus)
|
||||||
|
}
|
||||||
|
|
||||||
override fun validatePreKeySignalMessage(sender: String, message: PreKeySignalMessage) {
|
override fun onNewSessionAdopted(hexEncodedPublicKey: String, oldSessionResetStatus: LokiSessionResetStatus) {
|
||||||
val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getPreKeyRecord(sender)
|
if (oldSessionResetStatus == LokiSessionResetStatus.IN_PROGRESS) {
|
||||||
check(preKeyRecord != null) { "Received a background message from a user without an associated pre key record." }
|
SessionMetaProtocol.sendEphemeralMessage(context, hexEncodedPublicKey)
|
||||||
check(preKeyRecord.id == (message.preKeyId ?: -1)) { "Received a background message from an unknown source." }
|
}
|
||||||
}
|
// TODO: Show session reset succeed message
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun validatePreKeySignalMessage(sender: String, message: PreKeySignalMessage) {
|
||||||
|
val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getPreKeyRecord(sender)
|
||||||
|
check(preKeyRecord != null) { "Received a background message from a user without an associated pre key record." }
|
||||||
|
check(preKeyRecord.id == (message.preKeyId ?: -1)) { "Received a background message from an unknown source." }
|
||||||
|
}
|
||||||
}
|
}
|
@ -18,61 +18,59 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters) : BaseJob(parameters), InjectableType {
|
class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters) : BaseJob(parameters), InjectableType {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KEY = "MultiDeviceOpenGroupUpdateJob"
|
const val KEY = "MultiDeviceOpenGroupUpdateJob"
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var messageSender: SignalServiceMessageSender
|
|
||||||
|
|
||||||
constructor() : this(Parameters.Builder()
|
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
|
||||||
.setQueue("MultiDeviceOpenGroupUpdateJob")
|
|
||||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
|
||||||
.setMaxAttempts(Parameters.UNLIMITED)
|
|
||||||
.build())
|
|
||||||
|
|
||||||
override fun getFactoryKey(): String { return KEY
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(): Data { return Data.EMPTY }
|
|
||||||
|
|
||||||
@Throws(Exception::class)
|
|
||||||
public override fun onRun() {
|
|
||||||
if (!TextSecurePreferences.isMultiDevice(context)) {
|
|
||||||
Log.d("Loki", "Not multi device; aborting...")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val openGroups = mutableListOf<LokiPublicChat>()
|
@Inject
|
||||||
DatabaseFactory.getGroupDatabase(context).groups.use { reader ->
|
lateinit var messageSender: SignalServiceMessageSender
|
||||||
while (true) {
|
|
||||||
val record = reader.next ?: return@use
|
|
||||||
if (!record.isOpenGroup) { continue; }
|
|
||||||
|
|
||||||
val threadID = GroupManager.getThreadIDFromGroupID(record.encodedId, context)
|
constructor() : this(Parameters.Builder()
|
||||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
if (openGroup != null) { openGroups.add(openGroup) }
|
.setQueue("MultiDeviceOpenGroupUpdateJob")
|
||||||
}
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
|
.build())
|
||||||
|
|
||||||
|
override fun getFactoryKey(): String { return KEY }
|
||||||
|
|
||||||
|
override fun serialize(): Data { return Data.EMPTY }
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
public override fun onRun() {
|
||||||
|
if (!TextSecurePreferences.isMultiDevice(context)) {
|
||||||
|
Log.d("Loki", "Not multi device; aborting...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Gather open groups
|
||||||
|
val openGroups = mutableListOf<LokiPublicChat>()
|
||||||
|
DatabaseFactory.getGroupDatabase(context).groups.use { reader ->
|
||||||
|
while (true) {
|
||||||
|
val record = reader.next ?: return@use
|
||||||
|
if (!record.isOpenGroup) { continue; }
|
||||||
|
val threadID = GroupManager.getThreadIDFromGroupID(record.encodedId, context)
|
||||||
|
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||||
|
if (openGroup != null) { openGroups.add(openGroup) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Send the message
|
||||||
|
if (openGroups.size > 0) {
|
||||||
|
messageSender.sendMessage(SignalServiceSyncMessage.forOpenGroups(openGroups), UnidentifiedAccessUtil.getAccessForSync(context))
|
||||||
|
} else {
|
||||||
|
Log.d("Loki", "No open groups to sync.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openGroups.size > 0) {
|
public override fun onShouldRetry(exception: Exception): Boolean {
|
||||||
messageSender.sendMessage(SignalServiceSyncMessage.forOpenGroups(openGroups), UnidentifiedAccessUtil.getAccessForSync(context))
|
return false
|
||||||
} else {
|
|
||||||
Log.d("Loki", "No open groups to sync.")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public override fun onShouldRetry(exception: Exception): Boolean {
|
override fun onCanceled() { }
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCanceled() { }
|
class Factory : Job.Factory<MultiDeviceOpenGroupUpdateJob> {
|
||||||
|
|
||||||
class Factory : Job.Factory<MultiDeviceOpenGroupUpdateJob> {
|
override fun create(parameters: Parameters, data: Data): MultiDeviceOpenGroupUpdateJob {
|
||||||
|
return MultiDeviceOpenGroupUpdateJob(parameters)
|
||||||
override fun create(parameters: Parameters, data: Data): MultiDeviceOpenGroupUpdateJob {
|
}
|
||||||
return MultiDeviceOpenGroupUpdateJob(parameters)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
|
object MultiDeviceProtocol {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun sendUnlinkingRequest(context: Context, publicKey: String) {
|
||||||
|
val unlinkingRequest = EphemeralMessage.createUnlinkingRequest(publicKey)
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(unlinkingRequest))
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun sendTextPush(context: Context, recipient: Recipient, messageID: Long) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun sendMediaPush(context: Context, recipient: Recipient, messageID: Long) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// private static void sendMessagePush(Context context, MessageType type, Recipient recipient, long messageId) {
|
||||||
|
// JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||||
|
//
|
||||||
|
// // Just send the message normally if it's a group message or we're sending to one of our devices
|
||||||
|
// String recipientHexEncodedPublicKey = recipient.getAddress().serialize();
|
||||||
|
// if (GeneralUtilitiesKt.isPublicChat(context, recipientHexEncodedPublicKey) || PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false)) {
|
||||||
|
// if (type == MessageType.MEDIA) {
|
||||||
|
// PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false);
|
||||||
|
// } else {
|
||||||
|
// jobManager.add(new PushTextSendJob(messageId, recipient.getAddress()));
|
||||||
|
// }
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // If we get here then we are sending a message to a device that is not ours
|
||||||
|
// boolean[] hasSentSyncMessage = { false };
|
||||||
|
// MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientHexEncodedPublicKey).success(devices -> {
|
||||||
|
// int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet());
|
||||||
|
// Util.runOnMain(() -> {
|
||||||
|
// ArrayList<Job> jobs = new ArrayList<>();
|
||||||
|
// for (Map.Entry<String, Boolean> entry : devices.entrySet()) {
|
||||||
|
// String deviceHexEncodedPublicKey = entry.getKey();
|
||||||
|
// boolean isFriend = entry.getValue();
|
||||||
|
//
|
||||||
|
// Address address = Address.fromSerialized(deviceHexEncodedPublicKey);
|
||||||
|
// long messageIDToUse = recipientHexEncodedPublicKey.equals(deviceHexEncodedPublicKey) ? messageId : -1L;
|
||||||
|
//
|
||||||
|
// if (isFriend) {
|
||||||
|
// // Send a normal message if the user is friends with the recipient
|
||||||
|
// // We should also send a sync message if we haven't already sent one
|
||||||
|
// boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && address.isPhone();
|
||||||
|
// if (type == MessageType.MEDIA) {
|
||||||
|
// jobs.add(new PushMediaSendJob(messageId, messageIDToUse, address, false, null, shouldSendSyncMessage));
|
||||||
|
// } else {
|
||||||
|
// jobs.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage));
|
||||||
|
// }
|
||||||
|
// if (shouldSendSyncMessage) { hasSentSyncMessage[0] = true; }
|
||||||
|
// } else {
|
||||||
|
// // Send friend requests to non-friends. If the user is friends with any
|
||||||
|
// // of the devices then send out a default friend request message.
|
||||||
|
// boolean isFriendsWithAny = (friendCount > 0);
|
||||||
|
// String defaultFriendRequestMessage = isFriendsWithAny ? "Please accept to enable messages to be synced across devices" : null;
|
||||||
|
// if (type == MessageType.MEDIA) {
|
||||||
|
// jobs.add(new PushMediaSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false));
|
||||||
|
// } else {
|
||||||
|
// jobs.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Start the send
|
||||||
|
// if (type == MessageType.MEDIA) {
|
||||||
|
// PushMediaSendJob.enqueue(context, jobManager, (List<PushMediaSendJob>)(List)jobs);
|
||||||
|
// } else {
|
||||||
|
// // Schedule text send jobs
|
||||||
|
// jobManager.startChain(jobs).enqueue();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// return Unit.INSTANCE;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
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 {
|
||||||
|
|
||||||
|
const val KEY = "PushBackgroundMessageSendJob"
|
||||||
|
|
||||||
|
private val KEY_MESSAGE = "message"
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
.withBody(message.get<String?>("body", null))
|
||||||
|
// Attach a pre key bundle if needed
|
||||||
|
if (message.get("friendRequest", false)) {
|
||||||
|
val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(recipient)
|
||||||
|
dataMessage.withPreKeyBundle(bundle).asFriendRequest(true)
|
||||||
|
}
|
||||||
|
// Set flags if needed
|
||||||
|
if (message.get("unpairingRequest", false)) {
|
||||||
|
dataMessage.asUnlinkingRequest(true)
|
||||||
|
}
|
||||||
|
if (message.get("sessionRestore", false)) {
|
||||||
|
dataMessage.asSessionRestorationRequest(true)
|
||||||
|
}
|
||||||
|
if (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(-1, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,4 +22,10 @@ object SessionManagementProtocol {
|
|||||||
ApplicationContext.getInstance(context).jobManager.add(CleanPreKeysJob())
|
ApplicationContext.getInstance(context).jobManager.add(CleanPreKeysJob())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun sendSessionRestorationRequest(context: Context, publicKey: String) {
|
||||||
|
val sessionRestorationRequest = EphemeralMessage.createSessionRestorationRequest(publicKey)
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRestorationRequest))
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
@ -8,6 +9,12 @@ import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendReque
|
|||||||
|
|
||||||
object SessionMetaProtocol {
|
object SessionMetaProtocol {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun sendEphemeralMessage(context: Context, publicKey: String) {
|
||||||
|
val ephemeralMessage = EphemeralMessage.create(publicKey)
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be invoked for the recipient's master device.
|
* Should be invoked for the recipient's master device.
|
||||||
*/
|
*/
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData
|
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData
|
||||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData
|
import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob
|
||||||
|
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||||
@ -14,8 +17,8 @@ import java.util.*
|
|||||||
object SyncMessagesProtocol {
|
object SyncMessagesProtocol {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun shouldSyncReadReceipt(address: Address): Boolean {
|
fun syncAllContacts(context: Context) {
|
||||||
return !address.isGroup
|
ApplicationContext.getInstance(context).jobManager.add(MultiDeviceContactUpdateJob(context, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -41,4 +44,19 @@ object SyncMessagesProtocol {
|
|||||||
val isFriend = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
val isFriend = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
||||||
return isFriend
|
return isFriend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun syncAllClosedGroups(context: Context) {
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(MultiDeviceGroupUpdateJob())
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun syncAllOpenGroups(context: Context) {
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(MultiDeviceOpenGroupUpdateJob())
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun shouldSyncReadReceipt(address: Address): Boolean {
|
||||||
|
return !address.isGroup
|
||||||
|
}
|
||||||
}
|
}
|
@ -33,127 +33,29 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
|
|||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||||
import org.thoughtcrime.securesms.jobs.MmsSendJob;
|
import org.thoughtcrime.securesms.jobs.MmsSendJob;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
|
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.BackgroundMessage;
|
import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.FriendRequestHandler;
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
|
|
||||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
|
||||||
import org.thoughtcrime.securesms.loki.PushBackgroundMessageSendJob;
|
|
||||||
import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob;
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
|
|
||||||
import org.thoughtcrime.securesms.mms.MmsException;
|
import org.thoughtcrime.securesms.mms.MmsException;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.push.ContactTokenDetails;
|
import org.whispersystems.signalservice.api.push.ContactTokenDetails;
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.LokiDeviceLinkUtilities;
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus;
|
|
||||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import kotlin.Unit;
|
|
||||||
|
|
||||||
public class MessageSender {
|
public class MessageSender {
|
||||||
|
|
||||||
private static final String TAG = MessageSender.class.getSimpleName();
|
private static final String TAG = MessageSender.class.getSimpleName();
|
||||||
|
|
||||||
private enum MessageType { TEXT, MEDIA }
|
|
||||||
|
|
||||||
public static void syncAllContacts(Context context, Address recipient) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, recipient, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void syncAllGroups(Context context) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceGroupUpdateJob());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void syncAllOpenGroups(Context context) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceOpenGroupUpdateJob());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a contact sync message to all our devices telling them that we want to sync `contact`
|
|
||||||
*/
|
|
||||||
public static void syncContact(Context context, Address contact) {
|
|
||||||
// Don't bother sending a contact sync message if it's one of our devices that we want to sync across
|
|
||||||
MultiDeviceUtilities.isOneOfOurDevices(context, contact).success(isOneOfOurDevice -> {
|
|
||||||
if (!isOneOfOurDevice) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, contact));
|
|
||||||
}
|
|
||||||
return Unit.INSTANCE;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendBackgroundMessageToAllDevices(Context context, String contactHexEncodedPublicKey) {
|
|
||||||
// Send the background message to the original pubkey
|
|
||||||
sendBackgroundMessage(context, contactHexEncodedPublicKey);
|
|
||||||
|
|
||||||
// Go through the other devices and only send background messages if we're friends or we have received friend request
|
|
||||||
LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(contactHexEncodedPublicKey).success(devices -> {
|
|
||||||
Util.runOnMain(() -> {
|
|
||||||
for (String device : devices) {
|
|
||||||
// Don't send message to the device we already have sent to
|
|
||||||
if (device.equals(contactHexEncodedPublicKey)) { continue; }
|
|
||||||
Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false);
|
|
||||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient);
|
|
||||||
if (threadID < 0) { continue; }
|
|
||||||
LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
|
|
||||||
if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
|
|
||||||
sendBackgroundMessage(context, device);
|
|
||||||
} else if (friendRequestStatus == LokiThreadFriendRequestStatus.NONE || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
|
||||||
sendBackgroundFriendRequest(context, device, "Please accept to enable messages to be synced across devices");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Unit.INSTANCE;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Background message
|
|
||||||
|
|
||||||
// We don't call the message sender here directly and instead we just opt to create a specific job for the send
|
|
||||||
// This is because calling message sender directly would cause the application to freeze in some cases as it was blocking the thread when waiting for a response from the send
|
|
||||||
public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.create(contactHexEncodedPublicKey)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendBackgroundFriendRequest(Context context, String contactHexEncodedPublicKey, String messageBody) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createFriendRequest(contactHexEncodedPublicKey, messageBody)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendUnpairRequest(Context context, String contactHexEncodedPublicKey) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createUnpairingRequest(contactHexEncodedPublicKey)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendRestoreSessionMessage(Context context, String contactHexEncodedPublicKey) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createSessionRestore(contactHexEncodedPublicKey)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendBackgroundSessionRequest(Context context, String contactHexEncodedPublicKey) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createSessionRequest(contactHexEncodedPublicKey)));
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
public static long send(final Context context,
|
public static long send(final Context context,
|
||||||
final OutgoingTextMessage message,
|
final OutgoingTextMessage message,
|
||||||
final long threadId,
|
final long threadId,
|
||||||
@ -174,9 +76,9 @@ public class MessageSender {
|
|||||||
|
|
||||||
long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener);
|
long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener);
|
||||||
|
|
||||||
// Loki - Set the message's friend request status as soon as it has hit the database
|
// Loki - Set the message's friend request status as soon as it hits the database
|
||||||
if (message.isFriendRequest) {
|
if (FriendRequestProtocol.shouldUpdateFriendRequestStatusFromOutgoingTextMessage(context, message)) {
|
||||||
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageId, allocatedThreadId);
|
FriendRequestProtocol.setFriendRequestStatusToSendingIfNeeded(context, messageId, allocatedThreadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
|
sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
|
||||||
@ -190,73 +92,32 @@ public class MessageSender {
|
|||||||
final boolean forceSms,
|
final boolean forceSms,
|
||||||
final SmsDatabase.InsertListener insertListener)
|
final SmsDatabase.InsertListener insertListener)
|
||||||
{
|
{
|
||||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
try {
|
||||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
|
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||||
|
|
||||||
long allocatedThreadId;
|
long allocatedThreadId;
|
||||||
|
|
||||||
if (threadId == -1) {
|
if (threadId == -1) {
|
||||||
allocatedThreadId = threadDatabase.getThreadIdFor(message.getRecipient(), message.getDistributionType());
|
allocatedThreadId = threadDatabase.getThreadIdFor(message.getRecipient(), message.getDistributionType());
|
||||||
} else {
|
} else {
|
||||||
allocatedThreadId = threadId;
|
allocatedThreadId = threadId;
|
||||||
}
|
|
||||||
|
|
||||||
Recipient recipient = message.getRecipient();
|
|
||||||
|
|
||||||
// Loki - Turn into a GIF message if possible
|
|
||||||
if (message.getLinkPreviews().isEmpty() && message.getAttachments().isEmpty() && LinkPreviewUtil.isWhitelistedMediaUrl(message.getBody())) {
|
|
||||||
new LinkPreviewRepository(context).fetchGIF(context, message.getBody(), attachmentOrNull -> Util.runOnMain(() -> {
|
|
||||||
Attachment attachment = attachmentOrNull.orNull();
|
|
||||||
try {
|
|
||||||
if (attachment != null) { message.getAttachments().add(attachment); }
|
|
||||||
long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
|
|
||||||
// Loki - Set the message's friend request status as soon as it has hit the database
|
|
||||||
if (message.isFriendRequest && !recipient.getAddress().isGroup() && !message.isGroup()) {
|
|
||||||
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId);
|
|
||||||
}
|
|
||||||
sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn());
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
// TODO: Handle
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
|
|
||||||
// Loki - Set the message's friend request status as soon as it has hit the database
|
|
||||||
if (message.isFriendRequest && !recipient.getAddress().isGroup() && !message.isGroup()) {
|
|
||||||
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageID, allocatedThreadId);
|
|
||||||
}
|
|
||||||
sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn());
|
|
||||||
} catch (MmsException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return threadId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Recipient recipient = message.getRecipient();
|
||||||
|
long messageId = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
|
||||||
|
|
||||||
|
// Loki - Set the message's friend request status as soon as it hits the database
|
||||||
|
if (FriendRequestProtocol.shouldUpdateFriendRequestStatusFromOutgoingMediaMessage(context, message)) {
|
||||||
|
FriendRequestProtocol.setFriendRequestStatusToSendingIfNeeded(context, messageId, allocatedThreadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMediaMessage(context, recipient, forceSms, messageId, message.getExpiresIn());
|
||||||
|
return allocatedThreadId;
|
||||||
|
} catch (MmsException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return threadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return allocatedThreadId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendSyncMessageToOurDevices(final Context context,
|
|
||||||
final long messageID,
|
|
||||||
final long timestamp,
|
|
||||||
final byte[] message,
|
|
||||||
final int ttl) {
|
|
||||||
String ourPublicKey = TextSecurePreferences.getLocalNumber(context);
|
|
||||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
|
||||||
LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(ourPublicKey).success(devices -> {
|
|
||||||
Util.runOnMain(() -> {
|
|
||||||
for (String device : devices) {
|
|
||||||
// Don't send to ourselves
|
|
||||||
if (device.equals(ourPublicKey)) { continue; }
|
|
||||||
|
|
||||||
// Create a send job for our device
|
|
||||||
Address address = Address.fromSerialized(device);
|
|
||||||
jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Unit.INSTANCE;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) {
|
public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) {
|
||||||
@ -301,73 +162,11 @@ public class MessageSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void sendTextPush(Context context, Recipient recipient, long messageId) {
|
private static void sendTextPush(Context context, Recipient recipient, long messageId) {
|
||||||
sendMessagePush(context, MessageType.TEXT, recipient, messageId);
|
MultiDeviceProtocol.sendTextPush(context, recipient, messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendMediaPush(Context context, Recipient recipient, long messageId) {
|
private static void sendMediaPush(Context context, Recipient recipient, long messageId) {
|
||||||
sendMessagePush(context, MessageType.MEDIA, recipient, messageId);
|
MultiDeviceProtocol.sendMediaPush(context, recipient, messageId);
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendMessagePush(Context context, MessageType type, Recipient recipient, long messageId) {
|
|
||||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
|
||||||
|
|
||||||
// Just send the message normally if it's a group message or we're sending to one of our devices
|
|
||||||
String recipientHexEncodedPublicKey = recipient.getAddress().serialize();
|
|
||||||
if (GeneralUtilitiesKt.isPublicChat(context, recipientHexEncodedPublicKey) || PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false)) {
|
|
||||||
if (type == MessageType.MEDIA) {
|
|
||||||
PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false);
|
|
||||||
} else {
|
|
||||||
jobManager.add(new PushTextSendJob(messageId, recipient.getAddress()));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here then we are sending a message to a device that is not ours
|
|
||||||
boolean[] hasSentSyncMessage = { false };
|
|
||||||
MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientHexEncodedPublicKey).success(devices -> {
|
|
||||||
int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet());
|
|
||||||
Util.runOnMain(() -> {
|
|
||||||
ArrayList<Job> jobs = new ArrayList<>();
|
|
||||||
for (Map.Entry<String, Boolean> entry : devices.entrySet()) {
|
|
||||||
String deviceHexEncodedPublicKey = entry.getKey();
|
|
||||||
boolean isFriend = entry.getValue();
|
|
||||||
|
|
||||||
Address address = Address.fromSerialized(deviceHexEncodedPublicKey);
|
|
||||||
long messageIDToUse = recipientHexEncodedPublicKey.equals(deviceHexEncodedPublicKey) ? messageId : -1L;
|
|
||||||
|
|
||||||
if (isFriend) {
|
|
||||||
// Send a normal message if the user is friends with the recipient
|
|
||||||
// We should also send a sync message if we haven't already sent one
|
|
||||||
boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && address.isPhone();
|
|
||||||
if (type == MessageType.MEDIA) {
|
|
||||||
jobs.add(new PushMediaSendJob(messageId, messageIDToUse, address, false, null, shouldSendSyncMessage));
|
|
||||||
} else {
|
|
||||||
jobs.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage));
|
|
||||||
}
|
|
||||||
if (shouldSendSyncMessage) { hasSentSyncMessage[0] = true; }
|
|
||||||
} else {
|
|
||||||
// Send friend requests to non-friends. If the user is friends with any
|
|
||||||
// of the devices then send out a default friend request message.
|
|
||||||
boolean isFriendsWithAny = (friendCount > 0);
|
|
||||||
String defaultFriendRequestMessage = isFriendsWithAny ? "Please accept to enable messages to be synced across devices" : null;
|
|
||||||
if (type == MessageType.MEDIA) {
|
|
||||||
jobs.add(new PushMediaSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false));
|
|
||||||
} else {
|
|
||||||
jobs.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the send
|
|
||||||
if (type == MessageType.MEDIA) {
|
|
||||||
PushMediaSendJob.enqueue(context, jobManager, (List<PushMediaSendJob>)(List)jobs);
|
|
||||||
} else {
|
|
||||||
// Schedule text send jobs
|
|
||||||
jobManager.startChain(jobs).enqueue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Unit.INSTANCE;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendGroupPush(Context context, Recipient recipient, long messageId, Address filterAddress) {
|
private static void sendGroupPush(Context context, Recipient recipient, long messageId, Address filterAddress) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user