Re-order files
parent
0a5d12cbc3
commit
32ae74c442
@ -1,108 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
|
||||
import android.content.Context
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
|
||||
object FriendRequestHandler {
|
||||
enum class ActionType { Sending, Sent, Failed }
|
||||
|
||||
@JvmStatic
|
||||
fun updateFriendRequestState(context: Context, type: ActionType, messageId: Long, threadId: Long) {
|
||||
if (threadId < 0) return
|
||||
val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return
|
||||
if (!recipient.address.isPhone) { return }
|
||||
|
||||
val currentFriendStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId)
|
||||
// Update thread status if we haven't sent a friend request before
|
||||
if (currentFriendStatus != LokiThreadFriendRequestStatus.REQUEST_RECEIVED &&
|
||||
currentFriendStatus != LokiThreadFriendRequestStatus.REQUEST_SENT &&
|
||||
currentFriendStatus != LokiThreadFriendRequestStatus.FRIENDS
|
||||
) {
|
||||
val threadFriendStatus = when (type) {
|
||||
ActionType.Sending -> LokiThreadFriendRequestStatus.REQUEST_SENDING
|
||||
ActionType.Failed -> LokiThreadFriendRequestStatus.NONE
|
||||
ActionType.Sent -> LokiThreadFriendRequestStatus.REQUEST_SENT
|
||||
}
|
||||
val database = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
database.setFriendRequestStatus(threadId, threadFriendStatus)
|
||||
// If we sent a friend request then we need to hide the session restore prompt
|
||||
if (type == ActionType.Sent) {
|
||||
database.removeAllSessionRestoreDevices(threadId)
|
||||
}
|
||||
}
|
||||
|
||||
// Update message status
|
||||
if (messageId >= 0) {
|
||||
val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val friendRequestStatus = messageDatabase.getFriendRequestStatus(messageId)
|
||||
if (type == ActionType.Sending) {
|
||||
// We only want to update message status if we aren't friends with another of their devices
|
||||
// This avoids spam in the ui where it would keep telling the user that they sent a friend request on every single message
|
||||
isFriendsWithAnyLinkedDevice(context, recipient).successUi { isFriends ->
|
||||
if (!isFriends && friendRequestStatus == LokiMessageFriendRequestStatus.NONE) {
|
||||
messageDatabase.setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING)
|
||||
}
|
||||
}
|
||||
} else if (friendRequestStatus != LokiMessageFriendRequestStatus.NONE) {
|
||||
// Update the friend request status of the message if we have it
|
||||
val messageFriendRequestStatus = when (type) {
|
||||
ActionType.Failed -> LokiMessageFriendRequestStatus.REQUEST_FAILED
|
||||
ActionType.Sent -> LokiMessageFriendRequestStatus.REQUEST_PENDING
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
messageDatabase.setFriendRequestStatus(messageId, messageFriendRequestStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun updateLastFriendRequestMessage(context: Context, threadId: Long, status: LokiMessageFriendRequestStatus) {
|
||||
if (threadId < 0) { return }
|
||||
val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return
|
||||
if (!recipient.address.isPhone || recipient.address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return }
|
||||
|
||||
val messages = DatabaseFactory.getSmsDatabase(context).getAllMessageIDs(threadId)
|
||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val lastMessage = messages.find {
|
||||
val friendRequestStatus = lokiMessageDatabase.getFriendRequestStatus(it)
|
||||
friendRequestStatus == LokiMessageFriendRequestStatus.REQUEST_PENDING
|
||||
} ?: return
|
||||
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessage, status)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun receivedIncomingFriendRequestMessage(context: Context, threadId: Long) {
|
||||
val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context)
|
||||
|
||||
// We only want to update the last message status if we're not friends with any of their linked devices
|
||||
// This ensures that we don't spam the UI with accept/decline messages
|
||||
val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return
|
||||
if (!recipient.address.isPhone || recipient.address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return }
|
||||
|
||||
isFriendsWithAnyLinkedDevice(context, recipient).successUi { isFriends ->
|
||||
if (isFriends) { return@successUi }
|
||||
|
||||
// Since messages are forwarded to the primary device thread, we need to update it there
|
||||
val messageCount = smsMessageDatabase.getMessageCountForThread(threadId)
|
||||
val messageID = smsMessageDatabase.getIDForMessageAtIndex(threadId, messageCount - 1) // The message that was just received
|
||||
if (messageID < 0) { return@successUi }
|
||||
|
||||
val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
|
||||
// We need to go through and set all messages which are REQUEST_PENDING to NONE
|
||||
smsMessageDatabase.getAllMessageIDs(threadId)
|
||||
.filter { messageDatabase.getFriendRequestStatus(it) == LokiMessageFriendRequestStatus.REQUEST_PENDING }
|
||||
.forEach {
|
||||
messageDatabase.setFriendRequestStatus(it, LokiMessageFriendRequestStatus.NONE)
|
||||
}
|
||||
|
||||
// Set the last message to pending
|
||||
messageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.support.annotation.ColorRes
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun Resources.getColorWithID(@ColorRes id: Int, theme: Resources.Theme?): Int {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
getColor(id, theme)
|
||||
} else {
|
||||
@Suppress("DEPRECATION") getColor(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun toPx(dp: Int, resources: Resources): Int {
|
||||
val scale = resources.displayMetrics.density
|
||||
return (dp * scale).roundToInt()
|
||||
}
|
||||
|
||||
fun isPublicChat(context: Context, recipient: String): Boolean {
|
||||
return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().values.map { it.server }.contains(recipient)
|
||||
}
|
||||
|
||||
fun getFriendPublicKeys(context: Context, devicePublicKeys: Set<String>): Set<String> {
|
||||
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
return devicePublicKeys.mapNotNull { device ->
|
||||
val address = Address.fromSerialized(device)
|
||||
val recipient = Recipient.from(context, address, false)
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient)
|
||||
if (threadID < 0) { return@mapNotNull null }
|
||||
val friendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID)
|
||||
if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) device else null
|
||||
}.toSet()
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
@file:JvmName("MultiDeviceUtilities")
|
||||
package org.thoughtcrime.securesms.loki
|
||||
|
||||
import android.content.Context
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import nl.komponents.kovenant.toFailVoid
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.logging.Log
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.LokiDeviceLinkUtilities
|
||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.utilities.recover
|
||||
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
|
||||
|
||||
fun checkIsRevokedSlaveDevice(context: Context) {
|
||||
val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return
|
||||
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
LokiFileServerAPI.shared.getDeviceLinks(masterHexEncodedPublicKey, true).bind { deviceLinks ->
|
||||
val deviceLink = deviceLinks.find { it.masterHexEncodedPublicKey == masterHexEncodedPublicKey && it.slaveHexEncodedPublicKey == hexEncodedPublicKey }
|
||||
if (deviceLink != null) throw Error("Device hasn't been revoked.")
|
||||
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(hexEncodedPublicKey)
|
||||
LokiFileServerAPI.shared.setDeviceLinks(setOf())
|
||||
}.successUi {
|
||||
TextSecurePreferences.setNeedsIsRevokedSlaveDeviceCheck(context, false)
|
||||
ApplicationContext.getInstance(context).clearData()
|
||||
}.fail { error ->
|
||||
TextSecurePreferences.setNeedsIsRevokedSlaveDeviceCheck(context, true)
|
||||
Log.d("Loki", "Revocation check failed due to error: ${error.message ?: error}.")
|
||||
}
|
||||
}
|
||||
|
||||
fun updateDeviceLinksOnServer(context: Context) {
|
||||
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val deviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getDeviceLinks(hexEncodedPublicKey)
|
||||
LokiFileServerAPI.shared.setDeviceLinks(deviceLinks)
|
||||
}
|
||||
|
||||
fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise<Map<String, LokiThreadFriendRequestStatus>, Exception> {
|
||||
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
return LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(hexEncodedPublicKey).map { keys ->
|
||||
val map = mutableMapOf<String, LokiThreadFriendRequestStatus>()
|
||||
for (devicePublicKey in keys) {
|
||||
val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false)
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device)
|
||||
val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID)
|
||||
map[devicePublicKey] = friendRequestStatus
|
||||
}
|
||||
map
|
||||
}.recover { mutableMapOf() }
|
||||
}
|
||||
|
||||
fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise<Map<String, Boolean>, Unit> {
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
return LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(hexEncodedPublicKey).map { keys ->
|
||||
val devices = keys.toMutableSet()
|
||||
if (hexEncodedPublicKey != userHexEncodedPublicKey) {
|
||||
devices.remove(userHexEncodedPublicKey)
|
||||
}
|
||||
val friends = getFriendPublicKeys(context, devices)
|
||||
val friendMap = mutableMapOf<String, Boolean>()
|
||||
for (device in devices) {
|
||||
friendMap[device] = friends.contains(device)
|
||||
}
|
||||
friendMap
|
||||
}.toFailVoid()
|
||||
}
|
||||
|
||||
fun getFriendCount(context: Context, devices: Set<String>): Int {
|
||||
return getFriendPublicKeys(context, devices).count()
|
||||
}
|
||||
|
||||
fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Promise<Boolean, Exception> {
|
||||
// Don't become friends if we're a group
|
||||
if (!Address.fromSerialized(publicKey).isPhone) {
|
||||
return Promise.of(false)
|
||||
}
|
||||
|
||||
// If this public key is our primary device then we should become friends
|
||||
if (publicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) {
|
||||
return Promise.of(true)
|
||||
}
|
||||
|
||||
return LokiDeviceLinkUtilities.getMasterHexEncodedPublicKey(publicKey).bind { primaryDevicePublicKey ->
|
||||
// If the public key doesn't have any other devices then go through regular friend request logic
|
||||
if (primaryDevicePublicKey == null) {
|
||||
return@bind Promise.of(false)
|
||||
}
|
||||
|
||||
// If the primary device public key matches our primary device then we should become friends since this is our other device
|
||||
if (primaryDevicePublicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) {
|
||||
return@bind Promise.of(true)
|
||||
}
|
||||
|
||||
// If we are friends with any of the other devices then we should become friends
|
||||
isFriendsWithAnyLinkedDevice(context, Address.fromSerialized(primaryDevicePublicKey))
|
||||
}
|
||||
}
|
||||
|
||||
fun sendDeviceLinkMessage(context: Context, hexEncodedPublicKey: String, deviceLink: DeviceLink): Promise<Unit, Exception> {
|
||||
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
||||
val address = SignalServiceAddress(hexEncodedPublicKey)
|
||||
val message = SignalServiceDataMessage.newBuilder().withDeviceLink(deviceLink)
|
||||
// A REQUEST should always act as a friend request. An AUTHORIZATION should always be a normal message.
|
||||
if (deviceLink.type == DeviceLink.Type.REQUEST) {
|
||||
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
|
||||
message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
|
||||
} else {
|
||||
// Send over our profile key so that our linked device can get our profile picture
|
||||
message.withProfileKey(ProfileKeyUtil.getProfileKey(context))
|
||||
}
|
||||
return try {
|
||||
Log.d("Loki", "Sending device link message to: $hexEncodedPublicKey.")
|
||||
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false))
|
||||
val result = messageSender.sendMessage(0, address, udAccess, message.build())
|
||||
if (result.success == null) {
|
||||
val exception = when {
|
||||
result.isNetworkFailure -> "Failed to send device link message due to a network error."
|
||||
else -> "Failed to send device link message."
|
||||
}
|
||||
throw Exception(exception)
|
||||
}
|
||||
Promise.ofSuccess(Unit)
|
||||
} catch (e: Exception) {
|
||||
Log.d("Loki", "Failed to send device link message to: $hexEncodedPublicKey due to error: $e.")
|
||||
Promise.ofFail(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun signAndSendDeviceLinkMessage(context: Context, deviceLink: DeviceLink): Promise<Unit, Exception> {
|
||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||
val signedDeviceLink = deviceLink.sign(DeviceLink.Type.AUTHORIZATION, userPrivateKey)
|
||||
if (signedDeviceLink == null || signedDeviceLink.type != DeviceLink.Type.AUTHORIZATION) {
|
||||
return Promise.ofFail(Exception("Failed to sign device link."))
|
||||
}
|
||||
return retryIfNeeded(8) {
|
||||
sendDeviceLinkMessage(context, deviceLink.slaveHexEncodedPublicKey, signedDeviceLink)
|
||||
}
|
||||
}
|
||||
|
||||
fun isOneOfOurDevices(context: Context, address: Address): Promise<Boolean, Exception> {
|
||||
if (address.isGroup || address.isEmail || address.isMmsGroup) {
|
||||
return Promise.of(false)
|
||||
}
|
||||
|
||||
val ourPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
return LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(ourPublicKey).map { devices ->
|
||||
devices.contains(address.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Promise<Boolean, Exception> {
|
||||
return isFriendsWithAnyLinkedDevice(context, recipient.address)
|
||||
}
|
||||
|
||||
fun isFriendsWithAnyLinkedDevice(context: Context, address: Address): Promise<Boolean, Exception> {
|
||||
if (!address.isPhone) { return Promise.of(true) }
|
||||
|
||||
return getAllDeviceFriendRequestStatuses(context, address.serialize()).map { map ->
|
||||
for (status in map.values) {
|
||||
if (status == LokiThreadFriendRequestStatus.FRIENDS) {
|
||||
return@map true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun hasPendingFriendRequestWithAnyLinkedDevice(context: Context, recipient: Recipient): Promise<Boolean, Exception> {
|
||||
if (recipient.isGroupRecipient) { return Promise.of(false) }
|
||||
|
||||
return getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()).map { map ->
|
||||
for (status in map.values) {
|
||||
if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
|
||||
return@map true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
package org.thoughtcrime.securesms.loki.database
|
||||
|
||||
interface LokiThreadDatabaseDelegate {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
package org.thoughtcrime.securesms.loki.todo
|
||||
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.Drawable
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
package org.thoughtcrime.securesms.loki.todo
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
package org.thoughtcrime.securesms.loki.todo
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
package org.thoughtcrime.securesms.loki.todo
|
||||
|
||||
class RNG(hash: Long) {
|
||||
private var seed: Long
|
@ -0,0 +1,25 @@
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.support.annotation.ColorRes
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun Resources.getColorWithID(@ColorRes id: Int, theme: Resources.Theme?): Int {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
getColor(id, theme)
|
||||
} else {
|
||||
@Suppress("DEPRECATION") getColor(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun toPx(dp: Int, resources: Resources): Int {
|
||||
val scale = resources.displayMetrics.density
|
||||
return (dp * scale).roundToInt()
|
||||
}
|
||||
|
||||
fun isPublicChat(context: Context, recipient: String): Boolean {
|
||||
return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().values.map { it.server }.contains(recipient)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.api
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
Loading…
Reference in New Issue