Merge remote-tracking branch 'upstream/dev' into notification_control

This commit is contained in:
Harris 2021-07-26 09:38:11 +10:00
commit 285d5a6c22
9 changed files with 68 additions and 25 deletions

View File

@ -143,8 +143,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.2' testImplementation 'org.robolectric:shadows-multidex:4.2'
} }
def canonicalVersionCode = 206 def canonicalVersionCode = 207
def canonicalVersionName = "1.11.5" def canonicalVersionName = "1.11.6"
def postFixSize = 10 def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1, def abiPostFix = ['armeabi-v7a' : 1,

View File

@ -281,6 +281,15 @@ public class MmsDatabase extends MessagingDatabase {
} }
} }
public void updateSentTimestamp(long messageId, long newTimestamp, long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + DATE_SENT + " = ? " +
"WHERE " + ID + " = ?",
new String[] {newTimestamp + "", messageId + ""});
notifyConversationListeners(threadId);
notifyConversationListListeners();
}
public long getThreadIdForMessage(long id) { public long getThreadIdForMessage(long id) {
String sql = "SELECT " + THREAD_ID + " FROM " + TABLE_NAME + " WHERE " + ID + " = ?"; String sql = "SELECT " + THREAD_ID + " FROM " + TABLE_NAME + " WHERE " + ID + " = ?";
String[] sqlArgs = new String[] {id+""}; String[] sqlArgs = new String[] {id+""};

View File

@ -20,8 +20,6 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair; import android.util.Pair;
@ -320,6 +318,15 @@ public class SmsDatabase extends MessagingDatabase {
return results; return results;
} }
public void updateSentTimestamp(long messageId, long newTimestamp, long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + DATE_SENT + " = ? " +
"WHERE " + ID + " = ?",
new String[] {newTimestamp + "", messageId + ""});
notifyConversationListeners(threadId);
notifyConversationListListeners();
}
public Pair<Long, Long> updateBundleMessageBody(long messageId, String body) { public Pair<Long, Long> updateBundleMessageBody(long messageId, String body) {
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT; long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT;
return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type); return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type);

View File

@ -27,12 +27,11 @@ import org.session.libsignal.messages.SignalServiceGroup
import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.KeyHelper
import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.guava.Optional
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.util.SessionMetaProtocol import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.util.SessionMetaProtocol
class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol {
@ -284,6 +283,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return database.getMessageFor(timestamp, address)?.getId() return database.getMessageFor(timestamp, address)?.getId()
} }
override fun updateSentTimestamp(
messageID: Long,
isMms: Boolean,
openGroupSentTimestamp: Long,
threadId: Long
) {
if (isMms) {
val mmsDb = DatabaseFactory.getMmsDatabase(context)
mmsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId)
} else {
val smsDb = DatabaseFactory.getSmsDatabase(context)
smsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId)
}
}
override fun markAsSent(timestamp: Long, author: String) { override fun markAsSent(timestamp: Long, author: String) {
val database = DatabaseFactory.getMmsSmsDatabase(context) val database = DatabaseFactory.getMmsSmsDatabase(context)
val messageRecord = database.getMessageFor(timestamp, author) ?: return val messageRecord = database.getMessageFor(timestamp, author) ?: return

View File

@ -88,6 +88,7 @@ interface StorageProtocol {
fun persistAttachments(messageID: Long, attachments: List<Attachment>): List<Long> fun persistAttachments(messageID: Long, attachments: List<Attachment>): List<Long>
fun getAttachmentsForMessage(messageID: Long): List<DatabaseAttachment> fun getAttachmentsForMessage(messageID: Long): List<DatabaseAttachment>
fun getMessageIdInDatabase(timestamp: Long, author: String): Long? // TODO: This is a weird name fun getMessageIdInDatabase(timestamp: Long, author: String): Long? // TODO: This is a weird name
fun updateSentTimestamp(messageID: Long, isMms: Boolean, openGroupSentTimestamp: Long, threadId: Long)
fun markAsSending(timestamp: Long, author: String) fun markAsSending(timestamp: Long, author: String)
fun markAsSent(timestamp: Long, author: String) fun markAsSent(timestamp: Long, author: String)
fun markUnidentified(timestamp: Long, author: String) fun markUnidentified(timestamp: Long, author: String)

View File

@ -95,7 +95,7 @@ object OpenGroupAPIV2 {
return RequestBody.create(MediaType.get("application/json"), parametersAsJSON) return RequestBody.create(MediaType.get("application/json"), parametersAsJSON)
} }
private fun send(request: Request, isJsonRequired: Boolean = true): Promise<Map<*, *>, Exception> { private fun send(request: Request): Promise<Map<*, *>, Exception> {
val url = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.InvalidURL) val url = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.InvalidURL)
val urlBuilder = HttpUrl.Builder() val urlBuilder = HttpUrl.Builder()
.scheme(url.scheme()) .scheme(url.scheme())
@ -127,7 +127,7 @@ object OpenGroupAPIV2 {
if (request.useOnionRouting) { if (request.useOnionRouting) {
val publicKey = MessagingModuleConfiguration.shared.storage.getOpenGroupPublicKey(request.server) val publicKey = MessagingModuleConfiguration.shared.storage.getOpenGroupPublicKey(request.server)
?: return Promise.ofFail(Error.NoPublicKey) ?: return Promise.ofFail(Error.NoPublicKey)
return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey, isJSONRequired = isJsonRequired).fail { e -> return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey).fail { e ->
// A 401 means that we didn't provide a (valid) auth token for a route that required one. We use this as an // A 401 means that we didn't provide a (valid) auth token for a route that required one. We use this as an
// indication that the token we're using has expired. Note that a 403 has a different meaning; it means that // indication that the token we're using has expired. Note that a 403 has a different meaning; it means that
// we provided a valid token but it doesn't have a high enough permission level for the route in question. // we provided a valid token but it doesn't have a high enough permission level for the route in question.

View File

@ -13,20 +13,19 @@ import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.messages.visible.* import org.session.libsession.messaging.messages.visible.*
import org.session.libsession.messaging.open_groups.* import org.session.libsession.messaging.open_groups.*
import org.session.libsession.utilities.Address
import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.snode.RawResponsePromise import org.session.libsession.snode.RawResponsePromise
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeModule
import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.SnodeMessage
import org.session.libsession.snode.SnodeModule
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.SSKEnvironment
import org.session.libsignal.crypto.PushTransportDetails import org.session.libsignal.crypto.PushTransportDetails
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.hexEncodedPublicKey
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import java.lang.IllegalStateException import org.session.libsignal.utilities.hexEncodedPublicKey
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote
@ -146,7 +145,8 @@ object MessageSender {
} }
val base64EncodedData = Base64.encodeBytes(wrappedMessage) val base64EncodedData = Base64.encodeBytes(wrappedMessage)
// Send the result // Send the result
val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, message.sentTimestamp!!) val timestamp = message.sentTimestamp!! + SnodeAPI.clockOffset
val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, timestamp)
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
SnodeModule.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!) SnodeModule.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!)
} }
@ -234,7 +234,7 @@ object MessageSender {
) )
OpenGroupAPIV2.send(openGroupMessage,room,server).success { OpenGroupAPIV2.send(openGroupMessage,room,server).success {
message.openGroupServerMessageID = it.serverID message.openGroupServerMessageID = it.serverID
handleSuccessfulMessageSend(message, destination) handleSuccessfulMessageSend(message, destination, openGroupSentTimestamp = it.sentTimestamp)
deferred.resolve(Unit) deferred.resolve(Unit)
}.fail { }.fail {
handleFailure(it) handleFailure(it)
@ -248,12 +248,17 @@ object MessageSender {
} }
// Result Handling // Result Handling
fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false) { fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) {
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey()!! val userPublicKey = storage.getUserPublicKey()!!
val messageID = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey) ?: return val messageID = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey) ?: return
// Ignore future self-sends // Ignore future self-sends
storage.addReceivedMessageTimestamp(message.sentTimestamp!!) storage.addReceivedMessageTimestamp(message.sentTimestamp!!)
if (openGroupSentTimestamp != -1L && message is VisibleMessage) {
storage.addReceivedMessageTimestamp(openGroupSentTimestamp)
storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, message.threadID!!)
message.sentTimestamp = openGroupSentTimestamp
}
// Track the open group server message ID // Track the open group server message ID
if (message.openGroupServerMessageID != null && destination is Destination.OpenGroupV2) { if (message.openGroupServerMessageID != null && destination is Destination.OpenGroupV2) {
val encoded = GroupUtil.getEncodedOpenGroupID("${destination.server}.${destination.room}".toByteArray()) val encoded = GroupUtil.getEncodedOpenGroupID("${destination.server}.${destination.room}".toByteArray())

View File

@ -20,6 +20,8 @@ import org.session.libsignal.crypto.getRandomElementOrNull
import org.session.libsignal.utilities.Broadcaster import org.session.libsignal.utilities.Broadcaster
import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.HTTP
import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.database.LokiAPIDatabaseProtocol
import java.util.*
import kotlin.math.abs
private typealias Path = List<Snode> private typealias Path = List<Snode>
@ -306,7 +308,7 @@ object OnionRequestAPI {
/** /**
* Sends an onion request to `destination`. Builds new paths as needed. * Sends an onion request to `destination`. Builds new paths as needed.
*/ */
private fun sendOnionRequest(destination: Destination, payload: Map<*, *>, isJSONRequired: Boolean = true): Promise<Map<*, *>, Exception> { private fun sendOnionRequest(destination: Destination, payload: Map<*, *>): Promise<Map<*, *>, Exception> {
val deferred = deferred<Map<*, *>, Exception>() val deferred = deferred<Map<*, *>, Exception>()
lateinit var guardSnode: Snode lateinit var guardSnode: Snode
buildOnionForDestination(payload, destination).success { result -> buildOnionForDestination(payload, destination).success { result ->
@ -347,11 +349,12 @@ object OnionRequestAPI {
body = json["body"] as Map<*, *> body = json["body"] as Map<*, *>
} else { } else {
val bodyAsString = json["body"] as String val bodyAsString = json["body"] as String
if (!isJSONRequired) {
body = mapOf( "result" to bodyAsString )
} else {
body = JsonUtil.fromJson(bodyAsString, Map::class.java) body = JsonUtil.fromJson(bodyAsString, Map::class.java)
} }
if (body["t"] != null) {
val timestamp = body["t"] as Long
val offset = timestamp - Date().time
SnodeAPI.clockOffset = offset
} }
if (statusCode != 200) { if (statusCode != 200) {
val exception = HTTPRequestFailedAtDestinationException(statusCode, body, destination.description) val exception = HTTPRequestFailedAtDestinationException(statusCode, body, destination.description)
@ -455,7 +458,7 @@ object OnionRequestAPI {
* *
* `publicKey` is the hex encoded public key of the user the call is associated with. This is needed for swarm cache maintenance. * `publicKey` is the hex encoded public key of the user the call is associated with. This is needed for swarm cache maintenance.
*/ */
fun sendOnionRequest(request: Request, server: String, x25519PublicKey: String, target: String = "/loki/v3/lsrpc", isJSONRequired: Boolean = true): Promise<Map<*, *>, Exception> { fun sendOnionRequest(request: Request, server: String, x25519PublicKey: String, target: String = "/loki/v3/lsrpc"): Promise<Map<*, *>, Exception> {
val headers = request.getHeadersForOnionRequest() val headers = request.getHeadersForOnionRequest()
val url = request.url() val url = request.url()
val urlAsString = url.toString() val urlAsString = url.toString()
@ -472,7 +475,7 @@ object OnionRequestAPI {
"headers" to headers "headers" to headers
) )
val destination = Destination.Server(host, target, x25519PublicKey, url.scheme(), url.port()) val destination = Destination.Server(host, target, x25519PublicKey, url.scheme(), url.port())
return sendOnionRequest(destination, payload, isJSONRequired).recover { exception -> return sendOnionRequest(destination, payload).recover { exception ->
Log.d("Loki", "Couldn't reach server: $urlAsString due to error: $exception.") Log.d("Loki", "Couldn't reach server: $urlAsString due to error: $exception.")
throw exception throw exception
} }

View File

@ -28,7 +28,6 @@ import kotlin.Pair
object SnodeAPI { object SnodeAPI {
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
private val database: LokiAPIDatabaseProtocol private val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage get() = SnodeModule.shared.storage
private val broadcaster: Broadcaster private val broadcaster: Broadcaster
@ -38,6 +37,11 @@ object SnodeAPI {
internal var snodePool: Set<Snode> internal var snodePool: Set<Snode>
get() = database.getSnodePool() get() = database.getSnodePool()
set(newValue) { database.setSnodePool(newValue) } set(newValue) { database.setSnodePool(newValue) }
/**
* The offset between the user's clock and the Service Node's clock. Used in cases where the
* user's clock is incorrect.
*/
internal var clockOffset = 0L
// Settings // Settings
private val maxRetryCount = 6 private val maxRetryCount = 6