Merge remote-tracking branch 'upstream/dev' into libsession-integration

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
#	app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt
#	app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt
#	app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
#	app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt
#	app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java
#	app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt
#	app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt
#	app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt
#	libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt
#	libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt
This commit is contained in:
Morgan Pretty
2023-06-06 15:44:24 +10:00
86 changed files with 413 additions and 12141 deletions

View File

@@ -40,6 +40,7 @@ interface StorageProtocol {
fun getUserPublicKey(): String?
fun getUserX25519KeyPair(): ECKeyPair
fun getUserProfile(): Profile
fun setProfileAvatar(recipient: Recipient, profileAvatar: String?)
fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?)
fun clearUserPic()
// Signal

View File

@@ -125,6 +125,7 @@ class JobQueue : JobDelegate {
is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob, is ConfigurationSyncJob -> {
txQueue.send(job)
}
is RetrieveProfileAvatarJob,
is AttachmentDownloadJob -> {
mediaQueue.send(job)
}
@@ -224,6 +225,7 @@ class JobQueue : JobDelegate {
GroupAvatarDownloadJob.KEY,
BackgroundGroupAddJob.KEY,
OpenGroupDeleteJob.KEY,
RetrieveProfileAvatarJob.KEY,
ConfigurationSyncJob.KEY,
)
allJobTypes.forEach { type ->

View File

@@ -0,0 +1,113 @@
package org.session.libsession.messaging.jobs
import android.text.TextUtils
import org.session.libsession.avatars.AvatarHelper
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.utilities.DownloadUtilities.downloadFile
import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfileAvatarId
import org.session.libsession.utilities.Util.copy
import org.session.libsession.utilities.Util.equals
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfilePictureURL
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.streams.ProfileCipherInputStream
import org.session.libsignal.utilities.Log
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.security.SecureRandom
class RetrieveProfileAvatarJob(private val profileAvatar: String?, private val recipientAddress: Address): Job {
override var delegate: JobDelegate? = null
override var id: String? = null
override var failureCount: Int = 0
override val maxFailureCount: Int = 0
companion object {
val TAG = RetrieveProfileAvatarJob::class.simpleName
val KEY: String = "RetrieveProfileAvatarJob"
// Keys used for database storage
private const val PROFILE_AVATAR_KEY = "profileAvatar"
private const val RECEIPIENT_ADDRESS_KEY = "recipient"
}
override suspend fun execute(dispatcherName: String) {
val context = MessagingModuleConfiguration.shared.context
val storage = MessagingModuleConfiguration.shared.storage
val recipient = Recipient.from(context, recipientAddress, true)
val profileKey = recipient.resolve().profileKey
if (profileKey == null || (profileKey.size != 32 && profileKey.size != 16)) {
Log.w(TAG, "Recipient profile key is gone!")
return
}
// Commit '78d1e9d' (fix: open group threads and avatar downloads) had this commented out so
// it's now limited to just the current user case
if (
recipient.isLocalNumber &&
AvatarHelper.avatarFileExists(context, recipient.resolve().address) &&
equals(profileAvatar, recipient.resolve().profileAvatar)
) {
Log.w(TAG, "Already retrieved profile avatar: $profileAvatar")
return
}
if (profileAvatar.isNullOrEmpty()) {
Log.w(TAG, "Removing profile avatar for: " + recipient.address.serialize())
if (recipient.isLocalNumber) {
setProfileAvatarId(context, SecureRandom().nextInt())
setProfilePictureURL(context, null)
}
AvatarHelper.delete(context, recipient.address)
storage.setProfileAvatar(recipient, null)
return
}
val downloadDestination = File.createTempFile("avatar", ".jpg", context.cacheDir)
try {
downloadFile(downloadDestination, profileAvatar)
val avatarStream: InputStream = ProfileCipherInputStream(FileInputStream(downloadDestination), profileKey)
val decryptDestination = File.createTempFile("avatar", ".jpg", context.cacheDir)
copy(avatarStream, FileOutputStream(decryptDestination))
decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.address))
if (recipient.isLocalNumber) {
setProfileAvatarId(context, SecureRandom().nextInt())
setProfilePictureURL(context, profileAvatar)
}
storage.setProfileAvatar(recipient, profileAvatar)
} catch (e: Exception) {
Log.e("Loki", "Failed to download profile avatar", e)
} finally {
downloadDestination.delete()
}
}
override fun serialize(): Data {
return Data.Builder()
.putString(PROFILE_AVATAR_KEY, profileAvatar)
.putString(RECEIPIENT_ADDRESS_KEY, recipientAddress.serialize())
.build()
}
override fun getFactoryKey(): String {
return KEY
}
class Factory: Job.Factory<RetrieveProfileAvatarJob> {
override fun create(data: Data): RetrieveProfileAvatarJob {
val profileAvatar = if (data.hasString(PROFILE_AVATAR_KEY)) { data.getString(PROFILE_AVATAR_KEY) } else { null }
val recipientAddress = Address.fromSerialized(data.getString(RECEIPIENT_ADDRESS_KEY))
return RetrieveProfileAvatarJob(profileAvatar, recipientAddress)
}
}
}

View File

@@ -52,20 +52,10 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }),
null, null, LinkedList(admins.map { Address.fromSerialized(it) }), SnodeAPI.nowWithOffset)
storage.setProfileSharing(Address.fromSerialized(groupID), true)
// Send a closed group update message to all members individually
val closedGroupUpdateKind = ClosedGroupControlMessage.Kind.New(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), name, encryptionKeyPair, membersAsData, adminsAsData, 0)
val sentTime = SnodeAPI.nowWithOffset
val ourPubKey = storage.getUserPublicKey()
for (member in members) {
val closedGroupControlMessage = ClosedGroupControlMessage(closedGroupUpdateKind)
closedGroupControlMessage.sentTimestamp = sentTime
try {
sendNonDurably(closedGroupControlMessage, Address.fromSerialized(member), member == ourPubKey).get()
} catch (e: Exception) {
deferred.reject(e)
return@queue
}
}
// Add the group to the user's set of public keys to poll for
storage.addClosedGroupPublicKey(groupPublicKey)
@@ -73,6 +63,30 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, sentTime)
// Create the thread
storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
// Notify the user
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTime)
val ourPubKey = storage.getUserPublicKey()
for (member in members) {
val closedGroupControlMessage = ClosedGroupControlMessage(closedGroupUpdateKind)
closedGroupControlMessage.sentTimestamp = sentTime
try {
sendNonDurably(closedGroupControlMessage, Address.fromSerialized(member), member == ourPubKey).get()
} catch (e: Exception) {
// We failed to properly create the group so delete it's associated data (in the past
// we didn't create this data until the messages successfully sent but this resulted
// in race conditions due to the `NEW` message sent to our own swarm)
storage.removeClosedGroupPublicKey(groupPublicKey)
storage.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
storage.deleteConversation(threadID)
deferred.reject(e)
return@queue
}
}
// Add the group to the config now that it was successfully created
storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), sentTime, encryptionKeyPair)
// Notify the PN server
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)

View File

@@ -6,6 +6,7 @@ import org.session.libsession.avatars.AvatarHelper
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage

View File

@@ -79,7 +79,12 @@ class ClosedGroupPollerV2 {
// reasonable fake time interval to use instead.
val storage = MessagingModuleConfiguration.shared.storage
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
val threadID = storage.getThreadId(groupID) ?: return
val threadID = storage.getThreadId(groupID)
if (threadID == null) {
Log.d("Loki", "Stopping group poller due to missing thread for closed group: $groupPublicKey.")
stopPolling(groupPublicKey)
return
}
val lastUpdated = storage.getLastUpdated(threadID)
val timeSinceLastMessage = if (lastUpdated != -1L) Date().time - lastUpdated else 5 * 60 * 1000
val minPollInterval = Companion.minPollInterval