diff --git a/.drone.jsonnet b/.drone.jsonnet index 06a452cd54..9793621ada 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -37,6 +37,7 @@ local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https:/ image: docker_base + 'android', pull: 'always', environment: { ANDROID_HOME: '/usr/lib/android-sdk' }, + mem_limit: "8g", commands: [ 'apt-get update --allow-releaseinfo-change', 'apt-get install -y ninja-build openjdk-21-jdk', @@ -70,7 +71,10 @@ local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https:/ type: 'docker', name: 'Debug APK Build', platform: { arch: 'amd64' }, - trigger: { event: { exclude: [ 'pull_request' ] } }, + trigger: { + event: ['push'], + branch: ['master', 'dev', 'release/*'] + }, steps: [ version_info, clone_submodules, @@ -78,6 +82,7 @@ local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https:/ name: 'Build and upload', image: docker_base + 'android', pull: 'always', + mem_limit: "8g", environment: { SSH_KEY: { from_secret: 'SSH_KEY' }, ANDROID_HOME: '/usr/lib/android-sdk' }, commands: [ 'apt-get update --allow-releaseinfo-change', diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5f78effdc1..ec158c1186 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -37,7 +37,7 @@ jobs: with: submodules: 'recursive' - name: Set up JDK 21 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '21' diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 87e07e5f4b..2cc005925d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,8 +24,8 @@ configurations.configureEach { exclude(module = "commons-logging") } -val canonicalVersionCode = 414 -val canonicalVersionName = "1.25.2" +val canonicalVersionCode = 415 +val canonicalVersionName = "1.26.0" val postFixSize = 10 val abiPostFix = mapOf( @@ -44,17 +44,19 @@ val getGitHash = providers .asText .map { it.trim() } +kotlin { + compilerOptions { + jvmToolchain(21) + } +} + android { namespace = "network.loki.messenger" useLibrary("org.apache.http.legacy") compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = "17" + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } packaging { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b0ab99f3c8..8bc385fce8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,6 +53,7 @@ + @@ -279,7 +280,6 @@ @@ -382,7 +382,7 @@ android:exported="false" /> diff --git a/app/src/main/java/org/session/libsession/database/CallDataProvider.kt b/app/src/main/java/org/session/libsession/database/CallDataProvider.kt deleted file mode 100644 index 8ca27b50db..0000000000 --- a/app/src/main/java/org/session/libsession/database/CallDataProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.session.libsession.database - -interface CallDataProvider { - // answer/offer for call by UUID - // recipient info for call by UUID - -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 0d7df53089..77e2a36685 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -11,7 +11,6 @@ import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.Message -import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.GroupUpdated import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.visible.Attachment @@ -37,6 +36,7 @@ import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.ReactionRecord import network.loki.messenger.libsession_util.util.Contact as LibSessionContact import network.loki.messenger.libsession_util.util.GroupMember as LibSessionGroupMember @@ -116,7 +116,7 @@ interface StorageProtocol { fun removeReceivedMessageTimestamps(timestamps: Set) fun getAttachmentsForMessage(mmsMessageId: Long): List fun getMessageBy(timestamp: Long, author: String): MessageRecord? - fun updateSentTimestamp(messageId: MessageId, openGroupSentTimestamp: Long, threadId: Long) + fun updateSentTimestamp(messageId: MessageId, newTimestamp: Long) fun markAsResyncing(messageId: MessageId) fun markAsSyncing(messageId: MessageId) fun markAsSending(messageId: MessageId) @@ -159,16 +159,17 @@ interface StorageProtocol { // Closed Groups fun getMembers(groupPublicKey: String): List fun getClosedGroupDisplayInfo(groupAccountId: String): GroupDisplayInfo? - fun insertGroupInfoChange(message: GroupUpdated, closedGroup: AccountId): Long? - fun insertGroupInfoLeaving(closedGroup: AccountId): Long? - fun insertGroupInfoErrorQuit(closedGroup: AccountId): Long? + fun insertGroupInfoChange(message: GroupUpdated, closedGroup: AccountId) + fun insertGroupInfoLeaving(closedGroup: AccountId) + fun insertGroupInfoErrorQuit(closedGroup: AccountId) fun insertGroupInviteControlMessage( sentTimestamp: Long, senderPublicKey: String, senderName: String?, closedGroup: AccountId, groupName: String - ): Long? + ) + fun updateGroupInfoChange(messageId: Long, newType: UpdateMessageData.Kind) fun deleteGroupInfoMessages(groupId: AccountId, kind: Class) @@ -190,6 +191,7 @@ interface StorageProtocol { fun trimThread(threadID: Long, threadLimit: Int) fun trimThreadBefore(threadID: Long, timestamp: Long) fun getMessageCount(threadID: Long): Long + fun getTotalPinned(): Int fun setPinned(threadID: Long, isPinned: Boolean) fun isPinned(threadID: Long): Boolean fun deleteConversation(threadID: Long) @@ -208,7 +210,6 @@ interface StorageProtocol { fun getRecipientSettings(address: Address): RecipientSettings? fun syncLibSessionContacts(contacts: List, timestamp: Long?) fun hasAutoDownloadFlagBeenSet(recipient: Recipient): Boolean - fun addContacts(contacts: List) fun shouldAutoDownloadAttachments(recipient: Recipient): Boolean fun setAutoDownloadAttachments(recipient: Recipient, shouldAutoDownloadAttachments: Boolean) @@ -258,17 +259,21 @@ interface StorageProtocol { /** * Add reaction to a specific message. This is preferable to the timestamp lookup. */ - fun addReaction(messageId: MessageId, reaction: Reaction, messageSender: String, notifyUnread: Boolean) + fun addReaction(messageId: MessageId, reaction: Reaction, messageSender: String) + + /** + * Add reactions into the database. If [replaceAll] is true, + * it will remove all existing reactions that belongs to the same message(s). + */ + fun addReactions(reactions: Map>, replaceAll: Boolean, notifyUnread: Boolean) fun removeReaction(emoji: String, messageTimestamp: Long, threadId: Long, author: String, notifyUnread: Boolean) fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long) fun deleteReactions(messageId: MessageId) fun deleteReactions(messageIds: List, mms: Boolean) fun setBlocked(recipients: Iterable, isBlocked: Boolean, fromConfigUpdate: Boolean = false) - fun setRecipientHash(recipient: Recipient, recipientHash: String?) fun blockedContacts(): List fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration? fun setExpirationConfiguration(config: ExpirationConfiguration) - fun getExpiringMessages(messageIds: List = emptyList()): List> fun updateDisappearingState( messageSender: String, threadID: Long, diff --git a/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt b/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt index e78af8da8b..42d26e1de4 100644 --- a/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt +++ b/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt @@ -12,6 +12,7 @@ import org.session.libsession.utilities.Device import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.Toaster import org.session.libsession.utilities.UsernameUtils +import org.thoughtcrime.securesms.pro.ProStatusManager class MessagingModuleConfiguration( val context: Context, @@ -25,7 +26,8 @@ class MessagingModuleConfiguration( val clock: SnodeClock, val preferences: TextSecurePreferences, val deprecationManager: LegacyGroupDeprecationManager, - val usernameUtils: UsernameUtils + val usernameUtils: UsernameUtils, + val proStatusManager: ProStatusManager ) { companion object { diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt index 864b2ada6e..054ef51c56 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt @@ -6,27 +6,24 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import network.loki.messenger.libsession_util.ConfigBase -import network.loki.messenger.libsession_util.util.BlindKeyAPI import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message.Companion.senderOrSync import org.session.libsession.messaging.messages.control.CallMessage -import org.session.libsession.messaging.messages.control.LegacyGroupControlMessage -import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.DataExtractionNotification import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.control.ReadReceipt -import org.session.libsession.messaging.messages.control.SharedConfigurationMessage import org.session.libsession.messaging.messages.control.TypingIndicator import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.ParsedMessage import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.MessageReceiver +import org.session.libsession.messaging.sending_receiving.VisibleMessageHandlerContext +import org.session.libsession.messaging.sending_receiving.constructReactionRecords import org.session.libsession.messaging.sending_receiving.handle -import org.session.libsession.messaging.sending_receiving.handleOpenGroupReactions import org.session.libsession.messaging.sending_receiving.handleUnsendRequest import org.session.libsession.messaging.sending_receiving.handleVisibleMessage import org.session.libsession.messaging.utilities.Data @@ -34,10 +31,9 @@ import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.UserConfigType import org.session.libsignal.protos.UtilProtos import org.session.libsignal.utilities.AccountId -import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.database.model.ReactionRecord import kotlin.math.max data class MessageReceiveParameters( @@ -83,12 +79,9 @@ class BatchMessageReceiveJob( if (message is VisibleMessage) return true else { // message is control message otherwise return when(message) { - is SharedConfigurationMessage -> false - is LegacyGroupControlMessage -> false // message.kind is ClosedGroupControlMessage.Kind.New && !message.isSenderSelf is DataExtractionNotification -> false is MessageRequestResponse -> false is ExpirationTimerUpdate -> false - is ConfigurationMessage -> false is TypingIndicator -> false is UnsendRequest -> false is ReadReceipt -> false @@ -175,33 +168,37 @@ class BatchMessageReceiveJob( } // iterate over threads and persist them (persistence is the longest constant in the batch process operation) - fun processMessages(threadId: Long, messages: List) { + suspend fun processMessages(threadId: Long, messages: List) { // The LinkedHashMap should preserve insertion order val messageIds = linkedMapOf>() val myLastSeen = storage.getLastSeen(threadId) - var newLastSeen = myLastSeen.takeUnless { it == -1L } ?: 0 + var updatedLastSeen = myLastSeen.takeUnless { it == -1L } ?: 0 + val handlerContext = VisibleMessageHandlerContext( + module = MessagingModuleConfiguration.shared, + threadId = threadId, + openGroupID = openGroupID, + ) + + val communityReactions = mutableMapOf>() + messages.forEach { (parameters, message, proto) -> try { when (message) { is VisibleMessage -> { val isUserBlindedSender = - message.sender == serverPublicKey?.let { - BlindKeyAPI.blind15KeyPairOrNull( - ed25519SecretKey = storage.getUserED25519KeyPair()!! - .secretKey.data, - serverPubKey = Hex.fromStringCondensed(it), - ) - }?.let { - AccountId(IdPrefix.BLINDED, it.pubKey.data).hexString - } + message.sender == handlerContext.userBlindedKey + if (message.sender == localUserPublicKey || isUserBlindedSender) { // use sent timestamp here since that is technically the last one we have - newLastSeen = max(newLastSeen, message.sentTimestamp!!) + updatedLastSeen = max(updatedLastSeen, message.sentTimestamp!!) } - val messageId = MessageReceiver.handleVisibleMessage(message, proto, openGroupID, - threadId, + val messageId = MessageReceiver.handleVisibleMessage( + message = message, + proto = proto, + context = handlerContext, runThreadUpdate = false, - runProfileUpdate = true) + runProfileUpdate = true + ) if (messageId != null && message.reaction == null) { messageIds[messageId] = Pair( @@ -209,11 +206,13 @@ class BatchMessageReceiveJob( message.hasMention ) } + parameters.openGroupMessageServerID?.let { - MessageReceiver.handleOpenGroupReactions( - threadId, - it, - parameters.reactions + constructReactionRecords( + openGroupMessageServerID = it, + context = handlerContext, + reactions = parameters.reactions, + out = communityReactions ) } } @@ -248,13 +247,20 @@ class BatchMessageReceiveJob( // increment unreads, notify, and update thread // last seen will be the current last seen if not changed (re-computes the read counts for thread record) // might have been updated from a different thread at this point - val currentLastSeen = storage.getLastSeen(threadId).let { if (it == -1L) 0 else it } - newLastSeen = max(newLastSeen, currentLastSeen) - if (newLastSeen > 0 || currentLastSeen == 0L) { - storage.markConversationAsRead(threadId, newLastSeen, force = true) + val storedLastSeen = storage.getLastSeen(threadId).let { if (it == -1L) 0 else it } + updatedLastSeen = max(updatedLastSeen, storedLastSeen) + // Only call markConversationAsRead() when lastSeen actually advanced (we sent a message). + // For incoming-only batches (like reactions), skip this to preserve REACTIONS_UNREAD flags + // so the notification system can detect them. Thread updates happen separately below. + if (updatedLastSeen > 0 || storedLastSeen == 0L) { + storage.markConversationAsRead(threadId, updatedLastSeen, force = true) } storage.updateThread(threadId, true) SSKEnvironment.shared.notificationManager.updateNotification(context, threadId) + + if (communityReactions.isNotEmpty()) { + storage.addReactions(communityReactions, replaceAll = true, notifyUnread = false) + } } coroutineScope { diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/GroupLeavingJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/GroupLeavingJob.kt deleted file mode 100644 index c2bb27ecf8..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/jobs/GroupLeavingJob.kt +++ /dev/null @@ -1,103 +0,0 @@ -package org.session.libsession.messaging.jobs - -import kotlinx.coroutines.channels.SendChannel -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.messages.control.LegacyGroupControlMessage -import org.session.libsession.messaging.sending_receiving.MessageReceiver -import org.session.libsession.messaging.sending_receiving.MessageSender -import org.session.libsession.messaging.sending_receiving.disableLocalGroupAndUnsubscribe -import org.session.libsession.messaging.utilities.Data -import org.session.libsession.snode.SnodeAPI -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Log - -@Deprecated("This job is only applicable for legacy group. For new group, call GroupManagerV2.leaveGroup directly.") -class GroupLeavingJob( - val groupPublicKey: String, - // Channel to send the result of the job to. This field won't be persisted - private val completeChannel: SendChannel>?, - val deleteThread: Boolean): 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 = GroupLeavingJob::class.simpleName - val KEY: String = "GroupLeavingJob" - - // Keys used for database storage - private val GROUP_PUBLIC_KEY_KEY = "group_public_key" - private val DELETE_THREAD_KEY = "delete_thread" - } - - override suspend fun execute(dispatcherName: String) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: return handlePermanentFailure(dispatcherName, MessageSender.Error.NoThread) - val updatedMembers = group.members.map { it.toString() }.toSet() - userPublicKey - val admins = group.admins.map { it.toString() } - val name = group.title - // Send the update to the group - val closedGroupControlMessage = LegacyGroupControlMessage(LegacyGroupControlMessage.Kind.MemberLeft()) - val sentTime = SnodeAPI.nowWithOffset - closedGroupControlMessage.sentTimestamp = sentTime - storage.setActive(groupID, false) - - MessageSender.sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID), false).success { - // Notify the user - completeChannel?.trySend(Result.success(Unit)) - - // Remove the group private key and unsubscribe from PNs - MessageReceiver.disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey, deleteThread) - handleSuccess(dispatcherName) - }.fail { - storage.setActive(groupID, true) - - // Notify the user - completeChannel?.trySend(Result.failure(it)) - handleFailure(dispatcherName, it) - } - } - - private fun handleSuccess(dispatcherName: String) { - Log.w(TAG, "Group left successfully.") - delegate?.handleJobSucceeded(this, dispatcherName) - } - - private fun handlePermanentFailure(dispatcherName: String, e: Exception) { - delegate?.handleJobFailedPermanently(this, dispatcherName, e) - } - - private fun handleFailure(dispatcherName: String, e: Exception) { - delegate?.handleJobFailed(this, dispatcherName, e) - } - - override fun serialize(): Data { - return Data.Builder() - .putString(GROUP_PUBLIC_KEY_KEY, groupPublicKey) - .putBoolean(DELETE_THREAD_KEY, deleteThread) - .build() - } - - override fun getFactoryKey(): String { - return KEY - } - - class Factory : Job.Factory { - - override fun create(data: Data): GroupLeavingJob { - return GroupLeavingJob( - groupPublicKey = data.getString(GROUP_PUBLIC_KEY_KEY), - completeChannel = null, - deleteThread = data.getBoolean(DELETE_THREAD_KEY) - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/app/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index a1f5712411..188f7cfaed 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -1,7 +1,6 @@ package org.session.libsession.messaging.jobs import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED @@ -9,7 +8,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsignal.utilities.Log -import java.lang.RuntimeException import java.util.Timer import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger @@ -127,7 +125,6 @@ class JobQueue : JobDelegate { is InviteContactsJob, is NotifyPNServerJob, is AttachmentUploadJob, - is GroupLeavingJob, is MessageSendJob -> { txQueue.send(job) } @@ -237,7 +234,6 @@ class JobQueue : JobDelegate { BackgroundGroupAddJob.KEY, OpenGroupDeleteJob.KEY, RetrieveProfileAvatarJob.KEY, - GroupLeavingJob.KEY, InviteContactsJob.KEY, ) allJobTypes.forEach { type -> diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarWork.kt b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarWork.kt new file mode 100644 index 0000000000..8a18ac68c2 --- /dev/null +++ b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarWork.kt @@ -0,0 +1,134 @@ +package org.session.libsession.messaging.jobs + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CancellationException +import org.session.libsession.avatars.AvatarHelper +import org.session.libsession.database.StorageProtocol +import org.session.libsession.utilities.AESGCM +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.DownloadUtilities.downloadFromFileServer +import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfileAvatarId +import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfilePictureURL +import org.session.libsession.utilities.Util +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.exceptions.NonRetryableException +import org.session.libsignal.utilities.HTTP +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.Util.SECURE_RANDOM +import java.io.FileOutputStream + +@HiltWorker +class RetrieveProfileAvatarWork @AssistedInject constructor( + @Assisted private val appContext: Context, + @Assisted parameters: WorkerParameters, + private val storage: StorageProtocol, +) : CoroutineWorker(appContext, parameters) { + override suspend fun doWork(): Result { + val recipientAddress = requireNotNull( + inputData.getString(DATA_ADDRESS)?.let(Address::fromSerialized) + ) { "Recipient address is required" } + + val profileAvatarUrl = requireNotNull(inputData.getString(DATA_URL)) { + "Profile avatar URL is required" + } + + val profileAvatarKey = requireNotNull(inputData.getByteArray(DATA_KEY)) { + "Profile avatar key is required" + } + + require(profileAvatarKey.size == 16 || profileAvatarKey.size == 32) { + "Profile avatar key must be either 16 or 32 bytes long" + } + + val recipient by lazy { Recipient.from(appContext, recipientAddress, false) } + + // 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(appContext, recipientAddress) && + Util.equals(profileAvatarUrl, recipient.resolve().profileAvatar) + ) { + Log.w(TAG, "Already retrieved profile avatar: $profileAvatarUrl") + return Result.success() + } + + try { + val downloaded = downloadFromFileServer(profileAvatarUrl) + val decrypted = AESGCM.decrypt( + downloaded.data, + offset = downloaded.offset, + len = downloaded.len, + symmetricKey = profileAvatarKey + ) + + FileOutputStream(AvatarHelper.getAvatarFile(appContext, recipient.address)).use { out -> + out.write(decrypted) + } + + if (recipient.isLocalNumber) { + setProfileAvatarId(appContext, SECURE_RANDOM.nextInt()) + setProfilePictureURL(appContext, profileAvatarUrl) + } + + storage.setProfilePicture(recipient, profileAvatarUrl, profileAvatarKey) + return Result.success() + } + catch (e: NonRetryableException) { + Log.e("Loki", "Failed to download profile avatar from non-retryable error", e) + return Result.failure() + } + catch (e: CancellationException) { + throw e + } + catch (e: Exception) { + if(e is HTTP.HTTPRequestFailedException && e.statusCode == 404){ + Log.e(TAG, "Failed to download profile avatar from non-retryable error", e) + return Result.failure() + } else { + Log.e(TAG, "Failed to download profile avatar", e) + return Result.retry() + } + } + } + + companion object { + private const val DATA_ADDRESS = "address" + private const val DATA_URL = "url" + private const val DATA_KEY = "key" + + private const val TAG = "RetrieveProfileAvatarWork" + + fun schedule( + context: Context, + recipientAddress: Address, + profileAvatarUrl: String, + profileAvatarKey: ByteArray, + ) { + val uniqueWorkName = "retrieve-avatar-$recipientAddress" + + val data = Data.Builder() + .putString(DATA_ADDRESS, recipientAddress.toString()) + .putString(DATA_URL, profileAvatarUrl) + .putByteArray(DATA_KEY, profileAvatarKey) + .build() + + val workRequest = OneTimeWorkRequestBuilder() + .setInputData(data) + .build() + + WorkManager.getInstance(context) + .beginUniqueWork(uniqueWorkName, ExistingWorkPolicy.REPLACE, workRequest) + .enqueue() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt deleted file mode 100644 index 062b30f23b..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt +++ /dev/null @@ -1,190 +0,0 @@ -package org.session.libsession.messaging.messages.control - -import com.google.protobuf.ByteString -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.messages.copyExpiration -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.ProfileKeyUtil -import org.session.libsignal.crypto.ecc.DjbECPrivateKey -import org.session.libsignal.crypto.ecc.DjbECPublicKey -import org.session.libsignal.crypto.ecc.ECKeyPair -import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.removingIdPrefixIfNeeded -import org.session.libsignal.utilities.toHexString - -class ConfigurationMessage(var closedGroups: List, var openGroups: List, var contacts: List, - var displayName: String, var profilePicture: String?, var profileKey: ByteArray) : ControlMessage() { - - override val isSelfSendValid: Boolean = true - - override fun shouldDiscardIfBlocked(): Boolean = true - - class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List) { - val isValid: Boolean get() = members.isNotEmpty() && admins.isNotEmpty() - - internal constructor() : this("", "", null, listOf(), listOf()) - - override fun toString(): String { - return name - } - - companion object { - - fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.ClosedGroup): ClosedGroup? { - if (!proto.hasPublicKey() || !proto.hasName() || !proto.hasEncryptionKeyPair()) return null - val publicKey = proto.publicKey.toByteArray().toHexString() - val name = proto.name - val encryptionKeyPairAsProto = proto.encryptionKeyPair - val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray().removingIdPrefixIfNeeded()), - DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) - val members = proto.membersList.map { it.toByteArray().toHexString() } - val admins = proto.adminsList.map { it.toByteArray().toHexString() } - return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins) - } - } - - fun toProto(): SignalServiceProtos.ConfigurationMessage.ClosedGroup? { - val result = SignalServiceProtos.ConfigurationMessage.ClosedGroup.newBuilder() - result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey)) - result.name = name - val encryptionKeyPairAsProto = SignalServiceProtos.KeyPair.newBuilder() - encryptionKeyPairAsProto.publicKey = ByteString.copyFrom(encryptionKeyPair!!.publicKey.serialize().removingIdPrefixIfNeeded()) - encryptionKeyPairAsProto.privateKey = ByteString.copyFrom(encryptionKeyPair!!.privateKey.serialize()) - result.encryptionKeyPair = encryptionKeyPairAsProto.build() - result.addAllMembers(members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) - result.addAllAdmins(admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) - return result.build() - } - } - - class Contact(var publicKey: String, var name: String, var profilePicture: String?, var profileKey: ByteArray?, var isApproved: Boolean?, var isBlocked: Boolean?, var didApproveMe: Boolean?) { - - internal constructor() : this("", "", null, null, null, null, null) - - companion object { - - fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.Contact): Contact? { - if (!proto.hasName()) return null - val publicKey = proto.publicKey.toByteArray().toHexString() - val name = proto.name - val profilePicture = if (proto.hasProfilePicture()) proto.profilePicture else null - val profileKey = if (proto.hasProfileKey()) proto.profileKey.toByteArray() else null - val isApproved = if (proto.hasIsApproved()) proto.isApproved else null - val isBlocked = if (proto.hasIsBlocked()) proto.isBlocked else null - val didApproveMe = if (proto.hasDidApproveMe()) proto.didApproveMe else null - return Contact(publicKey, name, profilePicture, profileKey, isApproved, isBlocked, didApproveMe) - } - } - - fun toProto(): SignalServiceProtos.ConfigurationMessage.Contact? { - val result = SignalServiceProtos.ConfigurationMessage.Contact.newBuilder() - result.name = this.name - try { - result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey)) - } catch (e: Exception) { - return null - } - val profilePicture = profilePicture - if (!profilePicture.isNullOrEmpty()) { - result.profilePicture = profilePicture - } - val profileKey = profileKey - if (profileKey != null) { - result.profileKey = ByteString.copyFrom(profileKey) - } - val isApproved = isApproved - if (isApproved != null) { - result.isApproved = isApproved - } - val isBlocked = isBlocked - if (isBlocked != null) { - result.isBlocked = isBlocked - } - val didApproveMe = didApproveMe - if (didApproveMe != null) { - result.didApproveMe = didApproveMe - } - return result.build() - } - } - - companion object { - - fun getCurrent(displayName: String, profilePicture: String?, contacts: List): ConfigurationMessage? { - val closedGroups = mutableListOf() - val openGroups = mutableListOf() - val sharedConfig = MessagingModuleConfiguration.shared - val storage = sharedConfig.storage - val context = sharedConfig.context - val profileKey = ProfileKeyUtil.getProfileKey(context) - val groups = storage.getAllGroups(includeInactive = false) - for (group in groups) { - if (group.isLegacyGroup && group.isActive) { - if (!group.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue - val groupPublicKey = GroupUtil.doubleDecodeGroupID(group.encodedId).toHexString() - val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: continue - val closedGroup = ClosedGroup( - groupPublicKey, - group.title, - encryptionKeyPair, - group.members.map { it.toString() }, - group.admins.map { it.toString() } - ) - closedGroups.add(closedGroup) - } - if (group.isCommunity) { - val threadID = storage.getThreadId(group.encodedId) ?: continue - val openGroup = storage.getOpenGroup(threadID) - val shareUrl = openGroup?.joinURL ?: continue - openGroups.add(shareUrl) - } - } - return ConfigurationMessage(closedGroups, openGroups, contacts, displayName, profilePicture, profileKey) - } - - fun fromProto(proto: SignalServiceProtos.Content): ConfigurationMessage? { - if (!proto.hasConfigurationMessage()) return null - val configurationProto = proto.configurationMessage - val closedGroups = configurationProto.closedGroupsList.mapNotNull { ClosedGroup.fromProto(it) } - val openGroups = configurationProto.openGroupsList - val displayName = configurationProto.displayName - val profilePicture = configurationProto.profilePicture - val profileKey = configurationProto.profileKey - val contacts = configurationProto.contactsList.mapNotNull { Contact.fromProto(it) } - return ConfigurationMessage(closedGroups, openGroups, contacts, displayName, profilePicture, profileKey.toByteArray()) - .copyExpiration(proto) - } - } - - internal constructor(): this(listOf(), listOf(), listOf(), "", null, byteArrayOf()) - - override fun toProto(): SignalServiceProtos.Content? { - val configurationProto = SignalServiceProtos.ConfigurationMessage.newBuilder() - configurationProto.addAllClosedGroups(closedGroups.mapNotNull { it.toProto() }) - configurationProto.addAllOpenGroups(openGroups) - configurationProto.addAllContacts(this.contacts.mapNotNull { it.toProto() }) - configurationProto.displayName = displayName - val profilePicture = profilePicture - if (!profilePicture.isNullOrEmpty()) { - configurationProto.profilePicture = profilePicture - } - configurationProto.profileKey = ByteString.copyFrom(profileKey) - val contentProto = SignalServiceProtos.Content.newBuilder() - contentProto.configurationMessage = configurationProto.build() - return contentProto.build() - } - - override fun toString(): String { - return """ - ConfigurationMessage( - closedGroups: ${(closedGroups)}, - openGroups: ${(openGroups)}, - displayName: $displayName, - profilePicture: $profilePicture, - profileKey: $profileKey - ) - """.trimIndent() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/LegacyGroupControlMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/LegacyGroupControlMessage.kt deleted file mode 100644 index afcb10e8b1..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/LegacyGroupControlMessage.kt +++ /dev/null @@ -1,192 +0,0 @@ -package org.session.libsession.messaging.messages.control - -import com.google.protobuf.ByteString -import org.session.libsignal.crypto.ecc.DjbECPrivateKey -import org.session.libsignal.crypto.ecc.DjbECPublicKey -import org.session.libsignal.crypto.ecc.ECKeyPair -import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.protos.SignalServiceProtos.DataMessage -import org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -import org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED -import org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -import org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -import org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -import org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.NEW -import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.removingIdPrefixIfNeeded -import org.session.libsignal.utilities.toHexString - -class LegacyGroupControlMessage() : ControlMessage() { - var kind: Kind? = null - var groupID: String? = null - - override val defaultTtl: Long get() { - return when (kind) { - is Kind.EncryptionKeyPair -> 14 * 24 * 60 * 60 * 1000 - else -> 14 * 24 * 60 * 60 * 1000 - } - } - - override fun shouldDiscardIfBlocked(): Boolean = kind !is Kind.EncryptionKeyPair - - override val isSelfSendValid: Boolean = true - - override fun isValid(): Boolean { - val kind = kind - if (!super.isValid() || kind == null) return false - return when (kind) { - is Kind.New -> { - !kind.publicKey.isEmpty && kind.name.isNotEmpty() && kind.encryptionKeyPair?.publicKey != null - && kind.encryptionKeyPair?.privateKey != null && kind.members.isNotEmpty() && kind.admins.isNotEmpty() - && kind.expirationTimer >= 0 - } - is Kind.EncryptionKeyPair -> true - is Kind.NameChange -> kind.name.isNotEmpty() - is Kind.MembersAdded -> kind.members.isNotEmpty() - is Kind.MembersRemoved -> kind.members.isNotEmpty() - is Kind.MemberLeft -> true - } - } - - sealed class Kind { - class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List, var expirationTimer: Int) : Kind() { - internal constructor() : this(ByteString.EMPTY, "", null, listOf(), listOf(), 0) - } - /** An encryption key pair encrypted for each member individually. - * - * **Note:** `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group). - */ - class EncryptionKeyPair(var publicKey: ByteString?, var wrappers: Collection) : Kind() { - internal constructor() : this(null, listOf()) - } - class NameChange(var name: String) : Kind() { - internal constructor() : this("") - } - class MembersAdded(var members: List) : Kind() { - internal constructor() : this(listOf()) - } - class MembersRemoved(var members: List) : Kind() { - internal constructor() : this(listOf()) - } - class MemberLeft() : Kind() - - val description: String = - when (this) { - is New -> "new" - is EncryptionKeyPair -> "encryptionKeyPair" - is NameChange -> "nameChange" - is MembersAdded -> "membersAdded" - is MembersRemoved -> "membersRemoved" - is MemberLeft -> "memberLeft" - } - } - - companion object { - const val TAG = "ClosedGroupControlMessage" - - fun fromProto(proto: SignalServiceProtos.Content): LegacyGroupControlMessage? = - proto.takeIf { it.hasDataMessage() }?.dataMessage - ?.takeIf { it.hasClosedGroupControlMessage() }?.closedGroupControlMessage - ?.run { - when (type) { - NEW -> takeIf { it.hasPublicKey() && it.hasEncryptionKeyPair() && it.hasName() }?.let { - ECKeyPair( - DjbECPublicKey(encryptionKeyPair.publicKey.toByteArray()), - DjbECPrivateKey(encryptionKeyPair.privateKey.toByteArray()) - ).let { Kind.New(publicKey, name, it, membersList, adminsList, expirationTimer) } - } - ENCRYPTION_KEY_PAIR -> Kind.EncryptionKeyPair(publicKey, wrappersList.mapNotNull(KeyPairWrapper::fromProto)) - NAME_CHANGE -> takeIf { it.hasName() }?.let { Kind.NameChange(name) } - MEMBERS_ADDED -> Kind.MembersAdded(membersList) - MEMBERS_REMOVED -> Kind.MembersRemoved(membersList) - MEMBER_LEFT -> Kind.MemberLeft() - else -> null - }?.let(::LegacyGroupControlMessage) - } - } - - internal constructor(kind: Kind?, groupID: String? = null) : this() { - this.kind = kind - this.groupID = groupID - } - - override fun toProto(): SignalServiceProtos.Content? { - val kind = kind - if (kind == null) { - Log.w(TAG, "Couldn't construct closed group control message proto from: $this.") - return null - } - try { - val closedGroupControlMessage: DataMessage.ClosedGroupControlMessage.Builder = DataMessage.ClosedGroupControlMessage.newBuilder() - when (kind) { - is Kind.New -> { - closedGroupControlMessage.type = NEW - closedGroupControlMessage.publicKey = kind.publicKey - closedGroupControlMessage.name = kind.name - closedGroupControlMessage.encryptionKeyPair = SignalServiceProtos.KeyPair.newBuilder().also { - it.publicKey = ByteString.copyFrom(kind.encryptionKeyPair!!.publicKey.serialize().removingIdPrefixIfNeeded()) - it.privateKey = ByteString.copyFrom(kind.encryptionKeyPair!!.privateKey.serialize()) - }.build() - closedGroupControlMessage.addAllMembers(kind.members) - closedGroupControlMessage.addAllAdmins(kind.admins) - closedGroupControlMessage.expirationTimer = kind.expirationTimer - } - is Kind.EncryptionKeyPair -> { - closedGroupControlMessage.type = ENCRYPTION_KEY_PAIR - closedGroupControlMessage.publicKey = kind.publicKey ?: ByteString.EMPTY - closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() }) - } - is Kind.NameChange -> { - closedGroupControlMessage.type = NAME_CHANGE - closedGroupControlMessage.name = kind.name - } - is Kind.MembersAdded -> { - closedGroupControlMessage.type = MEMBERS_ADDED - closedGroupControlMessage.addAllMembers(kind.members) - } - is Kind.MembersRemoved -> { - closedGroupControlMessage.type = MEMBERS_REMOVED - closedGroupControlMessage.addAllMembers(kind.members) - } - is Kind.MemberLeft -> { - closedGroupControlMessage.type = MEMBER_LEFT - } - } - return SignalServiceProtos.Content.newBuilder().apply { - dataMessage = DataMessage.newBuilder().also { - it.closedGroupControlMessage = closedGroupControlMessage.build() - }.build() - }.build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct closed group control message proto from: $this.") - return null - } - } - - class KeyPairWrapper(val publicKey: String?, val encryptedKeyPair: ByteString?) { - - val isValid: Boolean = run { - this.publicKey != null && this.encryptedKeyPair != null - } - - companion object { - - fun fromProto(proto: DataMessage.ClosedGroupControlMessage.KeyPairWrapper): KeyPairWrapper { - return KeyPairWrapper(proto.publicKey.toByteArray().toHexString(), proto.encryptedKeyPair) - } - } - - fun toProto(): DataMessage.ClosedGroupControlMessage.KeyPairWrapper? { - val result = DataMessage.ClosedGroupControlMessage.KeyPairWrapper.newBuilder() - result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey ?: return null)) - result.encryptedKeyPair = encryptedKeyPair ?: return null - return try { - result.build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct key pair wrapper proto from: $this") - return null - } - } - } -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/SharedConfigurationMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/SharedConfigurationMessage.kt deleted file mode 100644 index 948203ef99..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/SharedConfigurationMessage.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.session.libsession.messaging.messages.control - -import com.google.protobuf.ByteString -import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage - -class SharedConfigurationMessage(val kind: SharedConfigMessage.Kind, val data: ByteArray, val seqNo: Long): ControlMessage() { - - override val ttl: Long = 30 * 24 * 60 * 60 * 1000L - override val isSelfSendValid: Boolean = true - override fun shouldDiscardIfBlocked(): Boolean = true // should only be called with our own user which shouldn't be blocked... - - companion object { - fun fromProto(proto: SignalServiceProtos.Content): SharedConfigurationMessage? = - proto.takeIf { it.hasSharedConfigMessage() }?.sharedConfigMessage - ?.takeIf { it.hasKind() && it.hasData() } - ?.run { SharedConfigurationMessage(kind, data.toByteArray(), seqno) } - } - - override fun isValid(): Boolean { - if (!super.isValid()) return false - return data.isNotEmpty() && seqNo >= 0 - } - - override fun toProto(): SignalServiceProtos.Content? { - val sharedConfigurationMessage = SharedConfigMessage.newBuilder() - .setKind(kind) - .setSeqno(seqNo) - .setData(ByteString.copyFrom(data)) - .build() - return SignalServiceProtos.Content.newBuilder() - .setSharedConfigMessage(sharedConfigurationMessage) - .build() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index d0789a67e9..c35c6c7c38 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -1,5 +1,6 @@ package org.session.libsession.messaging.messages.signal; +import org.jspecify.annotations.Nullable; import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment; @@ -13,6 +14,7 @@ import org.session.libsignal.messages.SignalServiceGroup; import org.session.libsignal.utilities.Hex; import org.session.libsignal.utilities.guava.Optional; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import java.util.Collections; import java.util.LinkedList; @@ -28,9 +30,10 @@ public class IncomingMediaMessage { private final int subscriptionId; private final long expiresIn; private final long expireStartedAt; - private final boolean expirationUpdate; private final boolean messageRequestResponse; private final boolean hasMention; + @Nullable + private final MessageContent messageContent; private final DataExtractionNotificationInfoMessage dataExtractionNotification; private final QuoteModel quote; @@ -44,17 +47,18 @@ public IncomingMediaMessage(Address from, int subscriptionId, long expiresIn, long expireStartedAt, - boolean expirationUpdate, boolean messageRequestResponse, boolean hasMention, Optional body, Optional group, Optional> attachments, + @Nullable MessageContent messageContent, Optional quote, Optional> sharedContacts, Optional> linkPreviews, Optional dataExtractionNotification) { + this.messageContent = messageContent; this.push = true; this.from = from; this.sentTimeMillis = sentTimeMillis; @@ -62,7 +66,6 @@ public IncomingMediaMessage(Address from, this.subscriptionId = subscriptionId; this.expiresIn = expiresIn; this.expireStartedAt = expireStartedAt; - this.expirationUpdate = expirationUpdate; this.dataExtractionNotification = dataExtractionNotification.orNull(); this.quote = quote.orNull(); this.messageRequestResponse = messageRequestResponse; @@ -96,8 +99,8 @@ public static IncomingMediaMessage from(VisibleMessage message, Optional> linkPreviews) { return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, expireStartedAt, - false, false, message.getHasMention(), Optional.fromNullable(message.getText()), - group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent()); + false, message.getHasMention(), Optional.fromNullable(message.getText()), + group, Optional.fromNullable(attachments), null, quote, Optional.absent(), linkPreviews, Optional.absent()); } public int getSubscriptionId() { @@ -120,12 +123,12 @@ public Address getGroupId() { return groupId; } - public boolean isPushMessage() { - return push; + public @Nullable MessageContent getMessageContent() { + return messageContent; } - public boolean isExpirationUpdate() { - return expirationUpdate; + public boolean isPushMessage() { + return push; } public long getSentTimeMillis() { diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java deleted file mode 100644 index b42d6d1bde..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.utilities.recipients.Recipient; - -import java.util.Collections; -import java.util.LinkedList; - -public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage { - - private final String groupId; - - public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn, long expireStartedAt, String groupId) { - super(recipient, "", new LinkedList(), sentTimeMillis, - DistributionTypes.CONVERSATION, expiresIn, expireStartedAt, null, Collections.emptyList(), - Collections.emptyList()); - this.groupId = groupId; - } - - @Override - public boolean isExpirationUpdate() { - return true; - } - - @Override - public boolean isGroup() { - return groupId != null; - } - - public String getGroupId() { - return groupId; - } - -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java index 86db70f435..8c30df5866 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java @@ -9,6 +9,7 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import java.util.LinkedList; import java.util.List; @@ -28,12 +29,13 @@ public OutgoingGroupMediaMessage(@NonNull Recipient recipient, boolean updateMessage, @Nullable QuoteModel quote, @NonNull List contacts, - @NonNull List previews) + @NonNull List previews, + @Nullable MessageContent messageContent) { super(recipient, body, new LinkedList() {{if (avatar != null) add(avatar);}}, sentTime, - DistributionTypes.CONVERSATION, expireIn, expireStartedAt, quote, contacts, previews); + DistributionTypes.CONVERSATION, expireIn, expireStartedAt, quote, contacts, previews, messageContent); this.groupID = groupId; this.isUpdateMessage = updateMessage; diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java index 85dc0cc6a2..0655855df1 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java @@ -12,11 +12,19 @@ import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import java.util.Collections; import java.util.LinkedList; import java.util.List; +/** + * Represents an outgoing mms message. Note this class is only used for saving messages + * into the database. We will still use {@link org.session.libsession.messaging.messages.Message} + * as a model when sending the message to the network. + *
+ * See {@link OutgoingTextMessage} for the sms table counterpart. + */ public class OutgoingMediaMessage { private final Recipient recipient; @@ -28,6 +36,8 @@ public class OutgoingMediaMessage { private final long expiresIn; private final long expireStartedAt; private final QuoteModel outgoingQuote; + @Nullable + private final MessageContent messageContent; private final List networkFailures = new LinkedList<>(); private final List identityKeyMismatches = new LinkedList<>(); @@ -42,7 +52,8 @@ public OutgoingMediaMessage(Recipient recipient, String message, @NonNull List contacts, @NonNull List linkPreviews, @NonNull List networkFailures, - @NonNull List identityKeyMismatches) + @NonNull List identityKeyMismatches, + @Nullable MessageContent messageContent) { this.recipient = recipient; this.body = message; @@ -53,6 +64,7 @@ public OutgoingMediaMessage(Recipient recipient, String message, this.expiresIn = expiresIn; this.expireStartedAt = expireStartedAt; this.outgoingQuote = outgoingQuote; + this.messageContent = messageContent; this.contacts.addAll(contacts); this.linkPreviews.addAll(linkPreviews); @@ -70,6 +82,7 @@ public OutgoingMediaMessage(OutgoingMediaMessage that) { this.expiresIn = that.expiresIn; this.expireStartedAt = that.expireStartedAt; this.outgoingQuote = that.outgoingQuote; + this.messageContent = that.messageContent; this.identityKeyMismatches.addAll(that.identityKeyMismatches); this.networkFailures.addAll(that.networkFailures); @@ -91,7 +104,12 @@ public static OutgoingMediaMessage from(VisibleMessage message, } return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1, expiresInMillis, expireStartedAt, DistributionTypes.DEFAULT, outgoingQuote, - Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList()); + Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList(), null); + } + + @Nullable + public MessageContent getMessageContent() { + return messageContent; } public Recipient getRecipient() { @@ -114,8 +132,6 @@ public boolean isGroup() { return false; } - public boolean isExpirationUpdate() { return false; } - public long getSentTimeMillis() { return sentTimeMillis; } diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java index e93c3c5986..daff026ca4 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java @@ -8,6 +8,7 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import java.util.Collections; import java.util.List; @@ -22,9 +23,10 @@ public OutgoingSecureMediaMessage(Recipient recipient, String body, long expireStartedAt, @Nullable QuoteModel quote, @NonNull List contacts, - @NonNull List previews) + @NonNull List previews, + @Nullable MessageContent messageContent) { - super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, expireStartedAt, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList()); + super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, expireStartedAt, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList(), messageContent); } public OutgoingSecureMediaMessage(OutgoingMediaMessage base) { diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 6af160e831..2c12ff6a83 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -4,14 +4,11 @@ import network.loki.messenger.libsession_util.util.BlindKeyAPI import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.CallMessage -import org.session.libsession.messaging.messages.control.LegacyGroupControlMessage -import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.DataExtractionNotification import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.GroupUpdated import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.control.ReadReceipt -import org.session.libsession.messaging.messages.control.SharedConfigurationMessage import org.session.libsession.messaging.messages.control.TypingIndicator import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.VisibleMessage @@ -161,14 +158,11 @@ object MessageReceiver { // Parse the message val message: Message = ReadReceipt.fromProto(proto) ?: TypingIndicator.fromProto(proto) ?: - LegacyGroupControlMessage.fromProto(proto) ?: DataExtractionNotification.fromProto(proto) ?: ExpirationTimerUpdate.fromProto(proto, closedGroupSessionId != null) ?: - ConfigurationMessage.fromProto(proto) ?: UnsendRequest.fromProto(proto) ?: MessageRequestResponse.fromProto(proto) ?: CallMessage.fromProto(proto) ?: - SharedConfigurationMessage.fromProto(proto) ?: GroupUpdated.fromProto(proto) ?: VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage // Don't process the envelope any further if the sender is blocked @@ -211,17 +205,8 @@ object MessageReceiver { if (groupPublicKey != null && groupPublicKey !in (currentClosedGroups ?: emptySet()) && IdPrefix.fromValue(groupPublicKey) != IdPrefix.GROUP) { throw Error.NoGroupThread } - if ((message is LegacyGroupControlMessage && message.kind is LegacyGroupControlMessage.Kind.New) || message is SharedConfigurationMessage) { - // Allow duplicates in this case to avoid the following situation: - // • The app performed a background poll or received a push notification - // • This method was invoked and the received message timestamps table was updated - // • Processing wasn't finished - // • The user doesn't see the new closed group - // also allow shared configuration messages to be duplicates since we track hashes separately use seqno for conflict resolution - } else { - if (storage.isDuplicateMessage(envelope.timestamp)) { throw Error.DuplicateMessage } - storage.addReceivedMessageTimestamp(envelope.timestamp) - } + if (storage.isDuplicateMessage(envelope.timestamp)) { throw Error.DuplicateMessage } + storage.addReceivedMessageTimestamp(envelope.timestamp) // Return return Pair(message, proto) } diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index ed6b425069..865fb4d785 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -6,8 +6,8 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.Namespace import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.ExpiryMode @@ -19,12 +19,9 @@ import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.applyExpiryMode -import org.session.libsession.messaging.messages.control.LegacyGroupControlMessage -import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.GroupUpdated import org.session.libsession.messaging.messages.control.MessageRequestResponse -import org.session.libsession.messaging.messages.control.SharedConfigurationMessage import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.LinkPreview import org.session.libsession.messaging.messages.visible.Quote @@ -39,7 +36,6 @@ import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.SnodeModule import org.session.libsession.snode.utilities.asyncPromise import org.session.libsession.utilities.Address -import org.session.libsession.utilities.Device import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.SSKEnvironment import org.session.libsignal.crypto.PushTransportDetails @@ -89,15 +85,6 @@ object MessageSender { } } - fun buildConfigMessageToSnode(destinationPubKey: String, message: SharedConfigurationMessage): SnodeMessage { - return SnodeMessage( - destinationPubKey, - Base64.encodeBytes(message.data), - ttl = message.ttl, - SnodeAPI.nowWithOffset - ) - } - // One-on-One Chats & Closed Groups @Throws(Exception::class) fun buildWrappedMessageToSnode(destination: Destination, message: Message, isSyncMessage: Boolean): SnodeMessage { @@ -129,15 +116,9 @@ object MessageSender { // • a configuration message // • a sync message // • a closed group control message of type `new` - var isNewClosedGroupControlMessage = false - if (message is LegacyGroupControlMessage && message.kind is LegacyGroupControlMessage.Kind.New) isNewClosedGroupControlMessage = - true if (isSelfSend - && message !is ConfigurationMessage && !isSyncMessage - && !isNewClosedGroupControlMessage && message !is UnsendRequest - && message !is SharedConfigurationMessage ) { throw Error.InvalidMessage } @@ -296,13 +277,12 @@ object MessageSender { isSyncMessage: Boolean ): Long? { // For ClosedGroupControlMessage or GroupUpdateMemberLeftMessage, the expiration timer doesn't apply - if (message is LegacyGroupControlMessage || ( - message is GroupUpdated && ( - message.inner.hasMemberLeftMessage() || - message.inner.hasInviteMessage() || - message.inner.hasInviteResponse() || - message.inner.hasDeleteMemberContent() || - message.inner.hasPromoteMessage()))) { + if (message is GroupUpdated && ( + message.inner.hasMemberLeftMessage() || + message.inner.hasInviteMessage() || + message.inner.hasInviteResponse() || + message.inner.hasDeleteMemberContent() || + message.inner.hasPromoteMessage())) { return null } @@ -439,7 +419,6 @@ object MessageSender { // Result Handling fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { - val threadId by lazy { requireNotNull(message.threadID) { "threadID for the message is null" } } val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! // Ignore future self-sends @@ -447,7 +426,6 @@ object MessageSender { message.id?.let { messageId -> if (openGroupSentTimestamp != -1L && message is VisibleMessage) { storage.addReceivedMessageTimestamp(openGroupSentTimestamp) - storage.updateSentTimestamp(messageId, openGroupSentTimestamp, threadId) message.sentTimestamp = openGroupSentTimestamp } @@ -493,8 +471,11 @@ object MessageSender { // Mark the message as sent. storage.markAsSent(messageId) + // Update the message sent timestamp + storage.updateSentTimestamp(messageId, message.sentTimestamp!!) + // Start the disappearing messages timer if needed - SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(message, startDisappearAfterRead = true) + SSKEnvironment.shared.messageExpirationManager.onMessageSent(message) } ?: run { storage.updateReactionIfNeeded(message, message.sender?:userPublicKey, openGroupSentTimestamp) } @@ -589,22 +570,4 @@ object MessageSender { val destination = Destination.from(address) return sendNonDurably(message, destination, isSyncMessage) } - - // Closed groups - fun createClosedGroup(device: Device, name: String, members: Collection): Promise { - return create(device, name, members) - } - - fun explicitNameChange(groupPublicKey: String, newName: String) { - return setName(groupPublicKey, newName) - } - - fun explicitAddMembers(groupPublicKey: String, membersToAdd: List) { - return addMembers(groupPublicKey, membersToAdd) - } - - fun explicitRemoveMembers(groupPublicKey: String, membersToRemove: List) { - return removeMembers(groupPublicKey, membersToRemove) - } - } \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt deleted file mode 100644 index 2422c44be0..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ /dev/null @@ -1,333 +0,0 @@ -@file:Suppress("NAME_SHADOWING") - -package org.session.libsession.messaging.sending_receiving - -import com.google.protobuf.ByteString -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.Channel -import network.loki.messenger.libsession_util.Curve25519 -import nl.komponents.kovenant.Promise -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.jobs.GroupLeavingJob -import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.messages.control.LegacyGroupControlMessage -import org.session.libsession.messaging.sending_receiving.MessageSender.Error -import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 -import org.session.libsession.snode.SnodeAPI -import org.session.libsession.snode.utilities.asyncPromise -import org.session.libsession.snode.utilities.await -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.Address.Companion.fromSerialized -import org.session.libsession.utilities.Device -import org.session.libsession.utilities.GroupUtil -import org.session.libsignal.crypto.ecc.DjbECPrivateKey -import org.session.libsignal.crypto.ecc.DjbECPublicKey -import org.session.libsignal.crypto.ecc.ECKeyPair -import org.session.libsignal.messages.SignalServiceGroup -import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.guava.Optional -import org.session.libsignal.utilities.removingIdPrefixIfNeeded -import org.session.libsignal.utilities.toHexString -import java.util.LinkedList -import java.util.concurrent.ConcurrentHashMap - -const val groupSizeLimit = 100 - -val pendingKeyPairs = ConcurrentHashMap>() - -fun MessageSender.create( - device: Device, - name: String, - members: Collection -): Promise { - return GlobalScope.asyncPromise { - // Prepare - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = storage.getUserPublicKey()!! - val membersAsData = members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) } - // Generate the group's public key - val groupPublicKey = Curve25519.generateKeyPair().pubKey.data.toHexString() // Includes the "05" prefix - // Generate the key pair that'll be used for encryption and decryption - val encryptionKeyPair = Curve25519.generateKeyPair().let { k -> - ECKeyPair( - DjbECPublicKey(k.pubKey.data), - DjbECPrivateKey(k.secretKey.data), - ) - } - // Create the group - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val admins = setOf( userPublicKey ) - val adminsAsData = admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) } - storage.createGroup(groupID, name, LinkedList(members.map { 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 = LegacyGroupControlMessage.Kind.New(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), name, encryptionKeyPair, membersAsData, adminsAsData, 0) - val sentTime = SnodeAPI.nowWithOffset - - // Add the group to the user's set of public keys to poll for - storage.addClosedGroupPublicKey(groupPublicKey) - // Store the encryption key pair - storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, sentTime) - // Create the thread - storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - - // Notify the user - val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - - // ACL Note: Commenting out this line prevents the timestamp of room creation being added to a new closed group, - // which in turn allows us to show the `groupNoMessages` control message text. - //storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTime) - - val ourPubKey = storage.getUserPublicKey() - for (member in members) { - val closedGroupControlMessage = LegacyGroupControlMessage(closedGroupUpdateKind, groupID) - closedGroupControlMessage.sentTimestamp = sentTime - try { - sendNonDurably(closedGroupControlMessage, fromSerialized(member), member == ourPubKey) - .await() - } 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) - throw e - } - } - - // Add the group to the config now that it was successfully created - storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), sentTime, encryptionKeyPair, 0) - // Notify the PN server - PushRegistryV1.register(device = device, publicKey = userPublicKey) - groupID - } -} - -fun MessageSender.setName(groupPublicKey: String, newName: String) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Can't change name for nonexistent closed group.") - throw Error.NoThread - } - val members = group.members.map { it.toString() }.toSet() - val admins = group.admins.map { it.toString() } - // Send the update to the group - val kind = LegacyGroupControlMessage.Kind.NameChange(newName) - val sentTime = SnodeAPI.nowWithOffset - val closedGroupControlMessage = LegacyGroupControlMessage(kind, groupID) - closedGroupControlMessage.sentTimestamp = sentTime - send(closedGroupControlMessage, Address.fromSerialized(groupID)) - // Update the group - storage.updateTitle(groupID, newName) - // Notify the user - val infoType = SignalServiceGroup.Type.NAME_CHANGE - val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID, sentTime) -} - -fun MessageSender.addMembers(groupPublicKey: String, membersToAdd: List) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Can't add members to nonexistent closed group.") - throw Error.NoThread - } - val threadId = storage.getOrCreateThreadIdFor(fromSerialized(groupID)) - val expireTimer = storage.getExpirationConfiguration(threadId)?.expiryMode?.expirySeconds ?: 0 - if (membersToAdd.isEmpty()) { - Log.d("Loki", "Invalid closed group update.") - throw Error.InvalidClosedGroupUpdate - } - val updatedMembers = group.members.map { it.toString() }.toSet() + membersToAdd - // Save the new group members - storage.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) }) - val membersAsData = updatedMembers.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) } - val newMembersAsData = membersToAdd.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) } - val admins = group.admins.map { it.toString() } - val adminsAsData = admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) } - val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: run { - Log.d("Loki", "Couldn't get encryption key pair for closed group.") - throw Error.NoKeyPair - } - val name = group.title - // Send the update to the group - val memberUpdateKind = LegacyGroupControlMessage.Kind.MembersAdded(newMembersAsData) - val sentTime = SnodeAPI.nowWithOffset - val closedGroupControlMessage = LegacyGroupControlMessage(memberUpdateKind, groupID) - closedGroupControlMessage.sentTimestamp = sentTime - send(closedGroupControlMessage, Address.fromSerialized(groupID)) - // Send closed group update messages to any new members individually - for (member in membersToAdd) { - val closedGroupNewKind = LegacyGroupControlMessage.Kind.New( - ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), - name, - encryptionKeyPair, - membersAsData, - adminsAsData, - expireTimer.toInt() - ) - val closedGroupControlMessage = LegacyGroupControlMessage(closedGroupNewKind, groupID) - // It's important that the sent timestamp of this message is greater than the sent timestamp - // of the `MembersAdded` message above. The reason is that upon receiving this `New` message, - // the recipient will update the closed group formation timestamp and ignore any closed group - // updates from before that timestamp. By setting the timestamp of the message below to a value - // greater than that of the `MembersAdded` message, we ensure that newly added members ignore - // the `MembersAdded` message. - closedGroupControlMessage.sentTimestamp = SnodeAPI.nowWithOffset - send(closedGroupControlMessage, Address.fromSerialized(member)) - } - // Notify the user - val infoType = SignalServiceGroup.Type.MEMBER_ADDED - val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, infoType, name, membersToAdd, admins, threadID, sentTime) -} - -fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = storage.getUserPublicKey()!! - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Can't remove members from nonexistent closed group.") - throw Error.NoThread - } - if (membersToRemove.isEmpty() || membersToRemove.contains(userPublicKey)) { - Log.d("Loki", "Invalid closed group update.") - throw Error.InvalidClosedGroupUpdate - } - val admins = group.admins.map { it.toString() } - if (!admins.contains(userPublicKey)) { - Log.d("Loki", "Only an admin can remove members from a group.") - throw Error.InvalidClosedGroupUpdate - } - val updatedMembers = group.members.map { it.toString() }.toSet() - membersToRemove - if (membersToRemove.any { it in admins } && updatedMembers.isNotEmpty()) { - Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.") - throw Error.InvalidClosedGroupUpdate - } - // Save the new group members - storage.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) }) - // Update the zombie list - val oldZombies = storage.getZombieMembers(groupID) - storage.setZombieMembers(groupID, oldZombies.minus(membersToRemove).map { Address.fromSerialized(it) }) - val removeMembersAsData = membersToRemove.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) } - val name = group.title - // Send the update to the group - val memberUpdateKind = LegacyGroupControlMessage.Kind.MembersRemoved(removeMembersAsData) - val sentTime = SnodeAPI.nowWithOffset - val closedGroupControlMessage = LegacyGroupControlMessage(memberUpdateKind, groupID) - closedGroupControlMessage.sentTimestamp = sentTime - send(closedGroupControlMessage, Address.fromSerialized(groupID)) - // Send the new encryption key pair to the remaining group members. - // At this stage we know the user is admin, no need to test. - generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers) - // Notify the user - // We don't display zombie members in the notification as users have already been notified when those members left - val notificationMembers = membersToRemove.minus(oldZombies) - if (notificationMembers.isNotEmpty()) { - // No notification to display when only zombies have been removed - val infoType = SignalServiceGroup.Type.MEMBER_REMOVED - val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, infoType, name, notificationMembers, admins, threadID, sentTime) - } -} - -suspend fun MessageSender.leave(groupPublicKey: String, deleteThread: Boolean = false) { - val channel = Channel>() - val job = GroupLeavingJob(groupPublicKey, completeChannel = channel, deleteThread) - JobQueue.shared.add(job) - - channel.receive().getOrThrow() -} - -fun MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey: String, targetMembers: Collection) { - // Prepare - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = storage.getUserPublicKey()!! - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Can't update nonexistent closed group.") - throw Error.NoThread - } - if (!group.admins.map { it.toString() }.contains(userPublicKey)) { - Log.d("Loki", "Can't distribute new encryption key pair as non-admin.") - throw Error.InvalidClosedGroupUpdate - } - // Generate the new encryption key pair - val newKeyPair = Curve25519.generateKeyPair().let { - ECKeyPair( - DjbECPublicKey(it.pubKey.data), - DjbECPrivateKey(it.secretKey.data), - ) - } - // Replace call will not succeed if no value already set - pendingKeyPairs.putIfAbsent(groupPublicKey,Optional.absent()) - do { - // Make sure we set the pending key pair or wait until it is not null - } while (!pendingKeyPairs.replace(groupPublicKey,Optional.absent(),Optional.fromNullable(newKeyPair))) - // Distribute it - sendEncryptionKeyPair(groupPublicKey, newKeyPair, targetMembers)?.success { - // Store it * after * having sent out the message to the group - storage.addClosedGroupEncryptionKeyPair(newKeyPair, groupPublicKey, SnodeAPI.nowWithOffset) - pendingKeyPairs[groupPublicKey] = Optional.absent() - } -} - -fun MessageSender.sendEncryptionKeyPair(groupPublicKey: String, newKeyPair: ECKeyPair, targetMembers: Collection, targetUser: String? = null, force: Boolean = true): Promise? { - val destination = targetUser ?: GroupUtil.doubleEncodeGroupID(groupPublicKey) - val proto = SignalServiceProtos.KeyPair.newBuilder() - proto.publicKey = ByteString.copyFrom(newKeyPair.publicKey.serialize().removingIdPrefixIfNeeded()) - proto.privateKey = ByteString.copyFrom(newKeyPair.privateKey.serialize()) - val plaintext = proto.build().toByteArray() - val wrappers = targetMembers.map { publicKey -> - val ciphertext = MessageEncrypter.encrypt(plaintext, publicKey) - LegacyGroupControlMessage.KeyPairWrapper(publicKey, ByteString.copyFrom(ciphertext)) - } - val kind = LegacyGroupControlMessage.Kind.EncryptionKeyPair(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), wrappers) - val sentTime = SnodeAPI.nowWithOffset - val closedGroupControlMessage = LegacyGroupControlMessage(kind, null) - closedGroupControlMessage.sentTimestamp = sentTime - return if (force) { - val isSync = MessagingModuleConfiguration.shared.storage.getUserPublicKey() == destination - MessageSender.sendNonDurably(closedGroupControlMessage, Address.fromSerialized(destination), isSyncMessage = isSync) - } else { - MessageSender.send(closedGroupControlMessage, Address.fromSerialized(destination)) - null - } -} - -fun MessageSender.sendLatestEncryptionKeyPair(publicKey: String, groupPublicKey: String) { - val storage = MessagingModuleConfiguration.shared.storage - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Can't send encryption key pair for nonexistent closed group.") - throw Error.NoThread - } - val members = group.members.map { it.toString() } - if (!members.contains(publicKey)) { - Log.d("Loki", "Refusing to send latest encryption key pair to non-member.") - return - } - // Get the latest encryption key pair - val encryptionKeyPair = pendingKeyPairs[groupPublicKey]?.orNull() - ?: storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return - // Send it - val proto = SignalServiceProtos.KeyPair.newBuilder() - proto.publicKey = ByteString.copyFrom(encryptionKeyPair.publicKey.serialize().removingIdPrefixIfNeeded()) - proto.privateKey = ByteString.copyFrom(encryptionKeyPair.privateKey.serialize()) - val plaintext = proto.build().toByteArray() - val ciphertext = MessageEncrypter.encrypt(plaintext, publicKey) - Log.d("Loki", "Sending latest encryption key pair to: $publicKey.") - val wrapper = LegacyGroupControlMessage.KeyPairWrapper(publicKey, ByteString.copyFrom(ciphertext)) - val kind = LegacyGroupControlMessage.Kind.EncryptionKeyPair(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), listOf(wrapper)) - val closedGroupControlMessage = LegacyGroupControlMessage(kind, groupID) - MessageSender.send(closedGroupControlMessage, Address.fromSerialized(publicKey)) -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 7d640954e7..8c6eb30b26 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -1,5 +1,6 @@ package org.session.libsession.messaging.sending_receiving +import android.content.Context import android.text.TextUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -9,28 +10,27 @@ import network.loki.messenger.libsession_util.ED25519 import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.avatars.AvatarHelper +import org.session.libsession.database.MessageDataProvider +import org.session.libsession.database.StorageProtocol import org.session.libsession.database.userAuth import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager +import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.jobs.AttachmentDownloadJob -import org.session.libsession.messaging.jobs.BackgroundGroupAddJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.ExpirationConfiguration.Companion.isNewConfigEnabled import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.CallMessage -import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.DataExtractionNotification import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.GroupUpdated -import org.session.libsession.messaging.messages.control.LegacyGroupControlMessage import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.control.ReadReceipt import org.session.libsession.messaging.messages.control.TypingIndicator import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.Attachment -import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage @@ -45,32 +45,23 @@ import org.session.libsession.messaging.utilities.WebRtcUtils import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID -import org.session.libsession.utilities.ProfileKeyUtil import org.session.libsession.utilities.SSKEnvironment -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.MessageType import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.getType -import org.session.libsignal.crypto.ecc.DjbECPrivateKey -import org.session.libsignal.crypto.ecc.DjbECPublicKey -import org.session.libsignal.crypto.ecc.ECKeyPair -import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage import org.session.libsignal.utilities.AccountId -import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional -import org.session.libsignal.utilities.removingIdPrefixIfNeeded -import org.session.libsignal.utilities.toHexString +import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.database.model.ReactionRecord +import org.thoughtcrime.securesms.dependencies.ConfigFactory import java.security.MessageDigest import java.security.SignatureException -import java.util.LinkedList import kotlin.math.min internal fun MessageReceiver.isBlocked(publicKey: String): Boolean { @@ -86,7 +77,6 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, when (message) { is ReadReceipt -> handleReadReceipt(message) is TypingIndicator -> handleTypingIndicator(message) - is LegacyGroupControlMessage -> handleLegacyGroupControlMessage(message) is GroupUpdated -> handleGroupUpdated(message, groupv2Id) is ExpirationTimerUpdate -> { // For groupsv2, there are dedicated mechanisms for handling expiration timers, and @@ -101,11 +91,12 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, } } is DataExtractionNotification -> handleDataExtractionNotification(message) - is ConfigurationMessage -> handleConfigurationMessage(message) is UnsendRequest -> handleUnsendRequest(message) is MessageRequestResponse -> handleMessageRequestResponse(message) is VisibleMessage -> handleVisibleMessage( - message, proto, openGroupID, threadId, + message = message, + proto = proto, + context = VisibleMessageHandlerContext(MessagingModuleConfiguration.shared, threadId, openGroupID), runThreadUpdate = true, runProfileUpdate = true ) @@ -130,7 +121,7 @@ fun MessageReceiver.messageIsOutdated(message: Message, threadId: Long, openGrou true ) val canPerformChange = storage.canPerformConfigChange( - if (threadRecipient?.address?.toString() == userPublicKey) SharedConfigMessage.Kind.USER_PROFILE.name else SharedConfigMessage.Kind.CONTACTS.name, + if (threadRecipient?.address?.toString() == userPublicKey) ConfigDatabase.USER_PROFILE_VARIANT else ConfigDatabase.CONTACTS_VARIANT, userPublicKey, message.sentTimestamp!! ) @@ -180,7 +171,10 @@ fun MessageReceiver.cancelTypingIndicatorsIfNeeded(senderPublicKey: String) { } private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate) { - SSKEnvironment.shared.messageExpirationManager.insertExpirationTimerMessage(message) + SSKEnvironment.shared.messageExpirationManager.run { + insertExpirationTimerMessage(message) + onMessageReceived(message) + } val isLegacyGroup = message.groupPublicKey != null && message.groupPublicKey?.startsWith(IdPrefix.GROUP.value) == false @@ -217,59 +211,6 @@ private fun MessageReceiver.handleDataExtractionNotification(message: DataExtrac storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.sentTimestamp!!) } -private fun handleConfigurationMessage(message: ConfigurationMessage) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - if (TextSecurePreferences.getConfigurationMessageSynced(context) - && !TextSecurePreferences.shouldUpdateProfile(context, message.sentTimestamp!!)) return - val userPublicKey = storage.getUserPublicKey() - if (userPublicKey == null || message.sender != storage.getUserPublicKey()) return - - val firstTimeSync = !TextSecurePreferences.getConfigurationMessageSynced(context) - - TextSecurePreferences.setConfigurationMessageSynced(context, true) - TextSecurePreferences.setLastProfileUpdateTime(context, message.sentTimestamp!!) - - TextSecurePreferences.setHasLegacyConfig(context, true) - if (!firstTimeSync) return - - val allClosedGroupPublicKeys = storage.getAllLegacyGroupPublicKeys() - for (closedGroup in message.closedGroups) { - if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) { - // just handle the closed group encryption key pairs to avoid sync'd devices getting out of sync - storage.addClosedGroupEncryptionKeyPair(closedGroup.encryptionKeyPair!!, closedGroup.publicKey, message.sentTimestamp!!) - } else { - // only handle new closed group if it's first time sync - handleNewLegacyGroup(message.sender!!, message.sentTimestamp!!, closedGroup.publicKey, closedGroup.name, - closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!, -1) - } - } - val allV2OpenGroups = storage.getAllOpenGroups().map { it.value.joinURL } - for (openGroup in message.openGroups.map { - it.replace(OpenGroupApi.legacyDefaultServer, OpenGroupApi.defaultServer) - .replace(OpenGroupApi.httpDefaultServer, OpenGroupApi.defaultServer) - }) { - if (allV2OpenGroups.contains(openGroup)) continue - Log.d("OpenGroup", "All open groups doesn't contain open group") - if (!storage.hasBackgroundGroupAddJob(openGroup)) { - Log.d("OpenGroup", "Doesn't contain background job for open group, adding") - JobQueue.shared.add(BackgroundGroupAddJob(openGroup)) - } - } - val profileManager = SSKEnvironment.shared.profileManager - val recipient = Recipient.from(context, Address.fromSerialized(userPublicKey), false) - if (message.displayName.isNotEmpty()) { - TextSecurePreferences.setProfileName(context, message.displayName) - profileManager.setName(context, recipient, message.displayName) - } - if (message.profileKey.isNotEmpty() && !message.profilePicture.isNullOrEmpty() - && TextSecurePreferences.getProfilePictureURL(context) != message.profilePicture) { - val profileKey = Base64.encodeBytes(message.profileKey) - ProfileKeyUtil.setEncodedProfileKey(context, profileKey) - profileManager.setProfilePicture(context, recipient, message.profilePicture, message.profileKey) - } - storage.addContacts(message.contacts) -} fun MessageReceiver.handleUnsendRequest(message: UnsendRequest): MessageId? { val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() @@ -349,66 +290,92 @@ private fun SignalServiceProtos.Content.ExpirationType.expiryMode(durationSecond } } ?: ExpiryMode.NONE +class VisibleMessageHandlerContext( + val context: Context, + val threadId: Long, + val openGroupID: String?, + val storage: StorageProtocol, + val profileManager: SSKEnvironment.ProfileManagerProtocol, + val groupManagerV2: GroupManagerV2, + val messageExpirationManager: SSKEnvironment.MessageExpirationManagerProtocol, + val messageDataProvider: MessageDataProvider, +) { + constructor(module: MessagingModuleConfiguration, threadId: Long, openGroupID: String?): + this( + context = module.context, + threadId = threadId, + openGroupID = openGroupID, + storage = module.storage, + profileManager = SSKEnvironment.shared.profileManager, + groupManagerV2 = module.groupManagerV2, + messageExpirationManager = SSKEnvironment.shared.messageExpirationManager, + messageDataProvider = module.messageDataProvider + ) + + val openGroup: OpenGroup? by lazy { + openGroupID?.let { storage.getOpenGroup(threadId) } + } + + val userBlindedKey: String? by lazy { + openGroup?.let { + val blindedKey = BlindKeyAPI.blind15KeyPairOrNull( + ed25519SecretKey = MessagingModuleConfiguration.shared.storage.getUserED25519KeyPair()!!.secretKey.data, + serverPubKey = Hex.fromStringCondensed(it.publicKey), + ) ?: return@let null + + AccountId( + IdPrefix.BLINDED, blindedKey.pubKey.data + ).hexString + } + } + + val userPublicKey: String? by lazy { + storage.getUserPublicKey() + } + + val threadRecipient: Recipient? by lazy { + storage.getRecipientForThread(threadId) + } +} + fun MessageReceiver.handleVisibleMessage( message: VisibleMessage, proto: SignalServiceProtos.Content, - openGroupID: String?, - threadId: Long, + context: VisibleMessageHandlerContext, runThreadUpdate: Boolean, runProfileUpdate: Boolean ): MessageId? { - val storage = MessagingModuleConfiguration.shared.storage - val context = MessagingModuleConfiguration.shared.context - val userPublicKey = storage.getUserPublicKey() + val userPublicKey = context.storage.getUserPublicKey() val messageSender: String? = message.sender // Do nothing if the message was outdated - if (MessageReceiver.messageIsOutdated(message, threadId, openGroupID)) { return null } - - // Get or create thread - // FIXME: In case this is an open group this actually * doesn't * create the thread if it doesn't yet - // exist. This is intentional, but it's very non-obvious. - val threadID = storage.getThreadIdFor(message.syncTarget ?: messageSender!!, message.groupPublicKey, openGroupID, createThread = true) - // Thread doesn't exist; should only be reached in a case where we are processing open group messages for a no longer existent thread - ?: throw MessageReceiver.Error.NoThread - val threadRecipient = storage.getRecipientForThread(threadID) - val userBlindedKey = openGroupID?.let { - val openGroup = storage.getOpenGroup(threadID) ?: return@let null - val blindedKey = BlindKeyAPI.blind15KeyPairOrNull( - ed25519SecretKey = MessagingModuleConfiguration.shared.storage.getUserED25519KeyPair()!!.secretKey.data, - serverPubKey = Hex.fromStringCondensed(openGroup.publicKey), - ) ?: return@let null - AccountId( - IdPrefix.BLINDED, blindedKey.pubKey.data - ).hexString - } + if (MessageReceiver.messageIsOutdated(message, context.threadId, context.openGroupID)) { return null } + // Update profile if needed - val recipient = Recipient.from(context, Address.fromSerialized(messageSender!!), false) + val recipient = Recipient.from(context.context, Address.fromSerialized(messageSender!!), false) if (runProfileUpdate) { val profile = message.profile - val isUserBlindedSender = messageSender == userBlindedKey + val isUserBlindedSender = messageSender == context.userBlindedKey if (profile != null && userPublicKey != messageSender && !isUserBlindedSender) { - val profileManager = SSKEnvironment.shared.profileManager val name = profile.displayName!! - if (name.isNotEmpty()) { - profileManager.setName(context, recipient, name) + if (name.isNotEmpty() && name != recipient.rawName) { + context.profileManager.setName(context.context, recipient, name) } val newProfileKey = profile.profileKey - val needsProfilePicture = !AvatarHelper.avatarFileExists(context, Address.fromSerialized(messageSender)) + val needsProfilePicture = !AvatarHelper.avatarFileExists(context.context, Address.fromSerialized(messageSender)) val profileKeyValid = newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true val profileKeyChanged = (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfileKey)) if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) { - profileManager.setProfilePicture(context, recipient, profile.profilePictureURL, newProfileKey) - profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN) + context.profileManager.setProfilePicture(context.context, recipient, profile.profilePictureURL, newProfileKey) } else if (newProfileKey == null || newProfileKey.isEmpty() || profile.profilePictureURL.isNullOrEmpty()) { - profileManager.setProfilePicture(context, recipient, null, null) + context.profileManager.setProfilePicture(context.context, recipient, null, null) } } - if (userPublicKey != messageSender && !isUserBlindedSender) { - storage.setBlocksCommunityMessageRequests(recipient, message.blocksMessageRequests) + if (userPublicKey != messageSender && !isUserBlindedSender && message.blocksMessageRequests != recipient.blocksCommunityMessageRequests) { + context.storage.setBlocksCommunityMessageRequests(recipient, message.blocksMessageRequests) } // update the disappearing / legacy banner for the sender @@ -416,19 +383,21 @@ fun MessageReceiver.handleVisibleMessage( proto.dataMessage.expireTimer > 0 && !proto.hasExpirationType() -> Recipient.DisappearingState.LEGACY else -> Recipient.DisappearingState.UPDATED } - storage.updateDisappearingState( - messageSender, - threadID, - disappearingState - ) + if(disappearingState != recipient.disappearingState) { + context.storage.updateDisappearingState( + messageSender, + context.threadId, + disappearingState + ) + } } // Handle group invite response if new closed group - if (threadRecipient?.isGroupV2Recipient == true) { + if (context.threadRecipient?.isGroupV2Recipient == true) { GlobalScope.launch { try { MessagingModuleConfiguration.shared.groupManagerV2 .handleInviteResponse( - AccountId(threadRecipient.address.toString()), + AccountId(context.threadRecipient!!.address.toString()), AccountId(messageSender), approved = true ) @@ -443,7 +412,7 @@ fun MessageReceiver.handleVisibleMessage( if (message.quote != null && proto.dataMessage.hasQuote()) { val quote = proto.dataMessage.quote - val author = if (quote.author == userBlindedKey) { + val author = if (quote.author == context.userBlindedKey) { Address.fromSerialized(userPublicKey!!) } else { Address.fromSerialized(quote.author) @@ -482,23 +451,23 @@ fun MessageReceiver.handleVisibleMessage( cancelTypingIndicatorsIfNeeded(message.sender!!) // Parse reaction if needed - val threadIsGroup = threadRecipient?.isGroupOrCommunityRecipient == true + val threadIsGroup = context.threadRecipient?.isGroupOrCommunityRecipient == true message.reaction?.let { reaction -> if (reaction.react == true) { reaction.serverId = message.openGroupServerMessageID?.toString() ?: message.serverHash.orEmpty() reaction.dateSent = message.sentTimestamp ?: 0 reaction.dateReceived = message.receivedTimestamp ?: 0 - storage.addReaction( - threadId = threadId, + context.storage.addReaction( + threadId = context.threadId, reaction = reaction, messageSender = messageSender, notifyUnread = !threadIsGroup ) } else { - storage.removeReaction( + context.storage.removeReaction( emoji = reaction.emoji!!, messageTimestamp = reaction.timestamp!!, - threadId = threadId, + threadId = context.threadId, author = reaction.publicKey!!, notifyUnread = threadIsGroup ) @@ -506,26 +475,31 @@ fun MessageReceiver.handleVisibleMessage( } ?: run { // A user is mentioned if their public key is in the body of a message or one of their messages // was quoted - val messageText = message.text - message.hasMention = listOf(userPublicKey, userBlindedKey) + + // Verify the incoming message length and truncate it if needed, before saving it to the db + val proStatusManager = MessagingModuleConfiguration.shared.proStatusManager + val maxChars = proStatusManager.getIncomingMessageMaxLength(message) + val messageText = message.text?.take(maxChars) // truncate to max char limit for this message + message.text = messageText + message.hasMention = listOf(userPublicKey, context.userBlindedKey) .filterNotNull() .any { key -> messageText?.contains("@$key") == true || key == (quoteModel?.author?.toString() ?: "") } // Persist the message - message.threadID = threadID + message.threadID = context.threadId // clean up the message - For example we do not want any expiration data on messages for communities if(message.openGroupServerMessageID != null){ message.expiryMode = ExpiryMode.NONE } - val messageID = storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, openGroupID, attachments, runThreadUpdate) ?: return null + val messageID = context.storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, context.openGroupID, attachments, runThreadUpdate) ?: return null // Parse & persist attachments // Start attachment downloads if needed - if (messageID.mms && (threadRecipient?.autoDownloadAttachments == true || messageSender == userPublicKey)) { - storage.getAttachmentsForMessage(messageID.id).iterator().forEach { attachment -> + if (messageID.mms && (context.threadRecipient?.autoDownloadAttachments == true || messageSender == userPublicKey)) { + context.storage.getAttachmentsForMessage(messageID.id).iterator().forEach { attachment -> attachment.attachmentId?.let { id -> val downloadJob = AttachmentDownloadJob(id.rowId, messageID.id) JobQueue.shared.add(downloadJob) @@ -533,58 +507,62 @@ fun MessageReceiver.handleVisibleMessage( } } message.openGroupServerMessageID?.let { - storage.setOpenGroupServerMessageID( + context.storage.setOpenGroupServerMessageID( messageID = messageID, serverID = it, - threadID = threadID + threadID = context.threadId ) } - SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(message) + message.id = messageID + context.messageExpirationManager.onMessageReceived(message) return messageID } return null } -fun MessageReceiver.handleOpenGroupReactions( - threadId: Long, +/** + * Constructs reaction records for a given open group message. + * + * If the open group message exists in our database, we'll construct a list of reaction records + * that is specified in the [reactions]. + * + * Note that this function does not know or check if the local message has any reactions, + * you'll be responsible for that. In simpler words, [out] only contains reactions that are given + * to this function, it will not include any existing reactions in the database. + * + * @param openGroupMessageServerID The server ID of this message + * @param context The context containing necessary data for processing reactions + * @param reactions A map of emoji to [OpenGroupApi.Reaction] objects, representing the reactions for the message + * @param out A mutable map that will be populated with [ReactionRecord]s, keyed by [MessageId] + */ +fun constructReactionRecords( openGroupMessageServerID: Long, - reactions: Map? + context: VisibleMessageHandlerContext, + reactions: Map?, + out: MutableMap> ) { if (reactions.isNullOrEmpty()) return - val storage = MessagingModuleConfiguration.shared.storage - val messageId = MessagingModuleConfiguration.shared.messageDataProvider.getMessageID(openGroupMessageServerID, threadId) ?: return - storage.deleteReactions(messageId) - val userPublicKey = storage.getUserPublicKey()!! - val openGroup = storage.getOpenGroup(threadId) - val blindedPublicKey = openGroup?.publicKey?.let { serverPublicKey -> - BlindKeyAPI.blind15KeyPairOrNull( - ed25519SecretKey = MessagingModuleConfiguration.shared.storage.getUserED25519KeyPair()!!.secretKey.data, - serverPubKey = Hex.fromStringCondensed(serverPublicKey), - ) - ?.let { AccountId(IdPrefix.BLINDED, it.pubKey.data).hexString } - } + val messageId = context.messageDataProvider.getMessageID(openGroupMessageServerID, context.threadId) ?: return + + val outList = out.getOrPut(messageId) { arrayListOf() } + for ((emoji, reaction) in reactions) { val pendingUserReaction = OpenGroupApi.pendingReactions - .filter { it.server == openGroup?.server && it.room == openGroup.room && it.messageId == openGroupMessageServerID && it.add } + .filter { it.server == context.openGroup?.server && it.room == context.openGroup?.room && it.messageId == openGroupMessageServerID && it.add } .sortedByDescending { it.seqNo } .any { it.emoji == emoji } - val shouldAddUserReaction = pendingUserReaction || reaction.you || reaction.reactors.contains(userPublicKey) - val reactorIds = reaction.reactors.filter { it != blindedPublicKey && it != userPublicKey } + val shouldAddUserReaction = pendingUserReaction || reaction.you || reaction.reactors.contains(context.userPublicKey) + val reactorIds = reaction.reactors.filter { it != context.userBlindedKey && it != context.userPublicKey } val count = if (reaction.you) reaction.count - 1 else reaction.count // Add the first reaction (with the count) reactorIds.firstOrNull()?.let { reactor -> - storage.addReaction( + outList += ReactionRecord( messageId = messageId, - reaction = Reaction( - publicKey = reactor, - emoji = emoji, - react = true, - serverId = "$openGroupMessageServerID", - count = count, - index = reaction.index - ), - messageSender = reactor, - notifyUnread = false + author = reactor, + emoji = emoji, + serverId = openGroupMessageServerID.toString(), + count = count, + sortId = reaction.index, ) } @@ -592,35 +570,25 @@ fun MessageReceiver.handleOpenGroupReactions( val maxAllowed = if (shouldAddUserReaction) 4 else 5 val lastIndex = min(maxAllowed, reactorIds.size) reactorIds.slice(1 until lastIndex).map { reactor -> - storage.addReaction( + outList += ReactionRecord( messageId = messageId, - reaction = Reaction( - publicKey = reactor, - emoji = emoji, - react = true, - serverId = "$openGroupMessageServerID", - count = 0, // Only want this on the first reaction - index = reaction.index - ), - messageSender = reactor, - notifyUnread = false + author = reactor, + emoji = emoji, + serverId = openGroupMessageServerID.toString(), + count = 0, // Only want this on the first reaction + sortId = reaction.index, ) } // Add the current user reaction (if applicable and not already included) if (shouldAddUserReaction) { - storage.addReaction( + outList += ReactionRecord( messageId = messageId, - reaction = Reaction( - publicKey = userPublicKey, - emoji = emoji, - react = true, - serverId = "$openGroupMessageServerID", - count = 1, - index = reaction.index - ), - messageSender = userPublicKey, - notifyUnread = false + author = context.userPublicKey!!, + emoji = emoji, + serverId = openGroupMessageServerID.toString(), + count = 1, + sortId = reaction.index, ) } } @@ -629,44 +597,6 @@ fun MessageReceiver.handleOpenGroupReactions( //endregion // region Closed Groups -private fun MessageReceiver.handleLegacyGroupControlMessage(message: LegacyGroupControlMessage) { - if (MessagingModuleConfiguration.shared.deprecationManager.deprecationState.value == - LegacyGroupDeprecationManager.DeprecationState.DEPRECATED) { - Log.d("ClosedGroupControlMessage", "Ignoring closed group control message post deprecation") - return - } - - when (message.kind!!) { - is LegacyGroupControlMessage.Kind.New -> handleNewLegacyGroup(message) - is LegacyGroupControlMessage.Kind.EncryptionKeyPair -> handleClosedGroupEncryptionKeyPair(message) - is LegacyGroupControlMessage.Kind.NameChange -> handleClosedGroupNameChanged(message) - is LegacyGroupControlMessage.Kind.MembersAdded -> handleClosedGroupMembersAdded(message) - is LegacyGroupControlMessage.Kind.MembersRemoved -> handleClosedGroupMembersRemoved(message) - is LegacyGroupControlMessage.Kind.MemberLeft -> handleClosedGroupMemberLeft(message) - } - if ( - message.kind !is LegacyGroupControlMessage.Kind.New && - MessagingModuleConfiguration.shared.storage.canPerformConfigChange( - SharedConfigMessage.Kind.GROUPS.name, - MessagingModuleConfiguration.shared.storage.getUserPublicKey()!!, - message.sentTimestamp!! - ) - ) { - // update the config - val closedGroupPublicKey = message.getPublicKey() - val storage = MessagingModuleConfiguration.shared.storage - storage.updateGroupConfig(closedGroupPublicKey) - } -} - -private fun LegacyGroupControlMessage.getPublicKey(): String = kind!!.let { when (it) { - is LegacyGroupControlMessage.Kind.New -> it.publicKey.toByteArray().toHexString() - is LegacyGroupControlMessage.Kind.EncryptionKeyPair -> it.publicKey?.toByteArray()?.toHexString() ?: groupPublicKey!! - is LegacyGroupControlMessage.Kind.MemberLeft -> groupPublicKey!! - is LegacyGroupControlMessage.Kind.MembersAdded -> groupPublicKey!! - is LegacyGroupControlMessage.Kind.MembersRemoved -> groupPublicKey!! - is LegacyGroupControlMessage.Kind.NameChange -> groupPublicKey!! -}} private fun MessageReceiver.handleGroupUpdated(message: GroupUpdated, closedGroup: AccountId?) { val inner = message.inner @@ -854,349 +784,6 @@ private fun verifyAdminSignature(groupSessionId: AccountId, signatureData: ByteA } } -private fun MessageReceiver.handleNewLegacyGroup(message: LegacyGroupControlMessage) { - val storage = MessagingModuleConfiguration.shared.storage - val kind = message.kind!! as? LegacyGroupControlMessage.Kind.New ?: return - val recipient = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(message.sender!!), false) - if (!recipient.isApproved && !recipient.isLocalNumber) return Log.e("Loki", "not accepting new closed group from unapproved recipient") - val groupPublicKey = kind.publicKey.toByteArray().toHexString() - // hard code check by group public key in the big function because I can't be bothered to do group double decode re-encodej - if ((storage.getThreadIdFor(message.sender!!, groupPublicKey, null, false) ?: -1L) >= 0L) return - val members = kind.members.map { it.toByteArray().toHexString() } - val admins = kind.admins.map { it.toByteArray().toHexString() } - val expirationTimer = kind.expirationTimer - handleNewLegacyGroup(message.sender!!, message.sentTimestamp!!, groupPublicKey, kind.name, kind.encryptionKeyPair!!, members, admins, message.sentTimestamp!!, expirationTimer) -} - -private fun handleNewLegacyGroup(sender: String, sentTimestamp: Long, groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: List, admins: List, formationTimestamp: Long, expirationTimer: Int) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = storage.getUserPublicKey()!! - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val groupExists = storage.getGroup(groupID) != null - - if (!storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey, sentTimestamp)) { - // If the closed group already exists then store the encryption keys (since the config only stores - // the latest key we won't be able to decrypt older messages if we were added to the group within - // the last two weeks and the key has been rotated - unfortunately if the user was added more than - // two weeks ago and the keys were rotated within the last two weeks then we won't be able to decrypt - // messages received before the key rotation) - if (groupExists) { - storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, sentTimestamp) - storage.updateGroupConfig(groupPublicKey) - } - return - } - - // Create the group - if (groupExists) { - // Update the group - if (!storage.isGroupActive(groupPublicKey)) { - // Clear zombie list if the group wasn't active - storage.setZombieMembers(groupID, listOf()) - // Update the formation timestamp - storage.updateFormationTimestamp(groupID, formationTimestamp) - } - storage.updateTitle(groupID, name) - storage.updateMembers(groupID, members.map { Address.fromSerialized(it) }) - } else { - storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }), - null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp) - } - storage.setProfileSharing(Address.fromSerialized(groupID), true) - // Add the group to the user's set of public keys to poll for - storage.addClosedGroupPublicKey(groupPublicKey) - // Store the encryption key pair - storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, sentTimestamp) - storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), formationTimestamp, encryptionKeyPair, expirationTimer) - // Notify the PN server - PushRegistryV1.register(device = MessagingModuleConfiguration.shared.device, publicKey = userPublicKey) - // Notify the user - if (userPublicKey == sender && !groupExists) { - val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTimestamp) - } else if (userPublicKey != sender) { - storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, sentTimestamp) - } -} - -private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: LegacyGroupControlMessage) { - // Prepare - val storage = MessagingModuleConfiguration.shared.storage - val senderPublicKey = message.sender ?: return - val kind = message.kind!! as? LegacyGroupControlMessage.Kind.EncryptionKeyPair ?: return - var groupPublicKey = kind.publicKey?.toByteArray()?.toHexString() - if (groupPublicKey.isNullOrEmpty()) groupPublicKey = message.groupPublicKey ?: return - val userPublicKey = storage.getUserPublicKey()!! - val userKeyPair = storage.getUserX25519KeyPair() - // Unwrap the message - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Ignoring closed group encryption key pair for nonexistent group.") - return - } - if (!group.isActive) { - Log.d("Loki", "Ignoring closed group encryption key pair for inactive group.") - return - } - if (!group.admins.map { it.toString() }.contains(senderPublicKey)) { - Log.d("Loki", "Ignoring closed group encryption key pair from non-admin.") - return - } - // Find our wrapper and decrypt it if possible - val wrapper = kind.wrappers.firstOrNull { it.publicKey!! == userPublicKey } ?: return - val encryptedKeyPair = wrapper.encryptedKeyPair!!.toByteArray() - val plaintext = MessageDecrypter.decrypt(encryptedKeyPair, userKeyPair).first - // Parse it - val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext) - val keyPair = ECKeyPair(DjbECPublicKey(proto.publicKey.toByteArray().removingIdPrefixIfNeeded()), DjbECPrivateKey(proto.privateKey.toByteArray())) - // Store it if needed - val closedGroupEncryptionKeyPairs = storage.getClosedGroupEncryptionKeyPairs(groupPublicKey) - if (closedGroupEncryptionKeyPairs.contains(keyPair)) { - Log.d("Loki", "Ignoring duplicate closed group encryption key pair.") - return - } - storage.addClosedGroupEncryptionKeyPair(keyPair, groupPublicKey, message.sentTimestamp!!) - Log.d("Loki", "Received a new closed group encryption key pair.") -} - -private fun MessageReceiver.handleClosedGroupNameChanged(message: LegacyGroupControlMessage) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val senderPublicKey = message.sender ?: return - val kind = message.kind!! as? LegacyGroupControlMessage.Kind.NameChange ?: return - val groupPublicKey = message.groupPublicKey ?: return - // Check that the sender is a member of the group (before the update) - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Ignoring closed group update for nonexistent group.") - return - } - if (!group.isActive) { - Log.d("Loki", "Ignoring closed group update for inactive group.") - return - } - // Check common group update logic - if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { - return - } - val members = group.members.map { it.toString() } - val admins = group.admins.map { it.toString() } - val name = kind.name - - // Only update the group in storage if it isn't invalidated by the config state - if (storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey!!, message.sentTimestamp!!)) { - storage.updateTitle(groupID, name) - } - - // Notify the user - if (userPublicKey == senderPublicKey) { - val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.NAME_CHANGE, name, members, admins, threadID, message.sentTimestamp!!) - } else { - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.NAME_CHANGE, name, members, admins, message.sentTimestamp!!) - } -} - -private fun MessageReceiver.handleClosedGroupMembersAdded(message: LegacyGroupControlMessage) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = storage.getUserPublicKey()!! - val senderPublicKey = message.sender ?: return - val kind = message.kind!! as? LegacyGroupControlMessage.Kind.MembersAdded ?: return - val groupPublicKey = message.groupPublicKey ?: return - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Ignoring closed group update for nonexistent group.") - return - } - if (!group.isActive) { - Log.d("Loki", "Ignoring closed group update for inactive group.") - return - } - if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { return } - val name = group.title - // Check common group update logic - val members = group.members.map { it.toString() } - val admins = group.admins.map { it.toString() } - - val updateMembers = kind.members.map { it.toByteArray().toHexString() } - val newMembers = members + updateMembers - - // Only update the group in storage if it isn't invalidated by the config state - if (storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey, message.sentTimestamp!!)) { - storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) - - // Update zombie members in case the added members are zombies - val zombies = storage.getZombieMembers(groupID) - if (zombies.intersect(updateMembers).isNotEmpty()) { - storage.setZombieMembers(groupID, zombies.minus(updateMembers).map { Address.fromSerialized(it) }) - } - } - - // Notify the user - if (userPublicKey == senderPublicKey) { - val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.MEMBER_ADDED, name, updateMembers, admins, threadID, message.sentTimestamp!!) - } else { - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.MEMBER_ADDED, name, updateMembers, admins, message.sentTimestamp!!) - } - if (userPublicKey in admins) { - // Send the latest encryption key pair to the added members if the current user is the admin of the group - // - // This fixes a race condition where: - // • A member removes another member. - // • A member adds someone to the group and sends them the latest group key pair. - // • The admin is offline during all of this. - // • When the admin comes back online they see the member removed message and generate + distribute a new key pair, - // but they don't know about the added member yet. - // • Now they see the member added message. - // - // Without the code below, the added member(s) would never get the key pair that was generated by the admin when they saw - // the member removed message. - val encryptionKeyPair = pendingKeyPairs[groupPublicKey]?.orNull() - ?: storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) - if (encryptionKeyPair == null) { - Log.d("Loki", "Couldn't get encryption key pair for closed group.") - } else { - for (user in updateMembers) { - MessageSender.sendEncryptionKeyPair(groupPublicKey, encryptionKeyPair, setOf(user), targetUser = user, force = false) - } - } - } -} - -/// Removes the given members from the group IF -/// • it wasn't the admin that was removed (that should happen through a `MEMBER_LEFT` message). -/// • the admin sent the message (only the admin can truly remove members). -/// If we're among the users that were removed, delete all encryption key pairs and the group public key, unsubscribe -/// from push notifications for this closed group, and remove the given members from the zombie list for this group. -private fun MessageReceiver.handleClosedGroupMembersRemoved(message: LegacyGroupControlMessage) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = storage.getUserPublicKey()!! - val senderPublicKey = message.sender ?: return - val kind = message.kind!! as? LegacyGroupControlMessage.Kind.MembersRemoved ?: return - val groupPublicKey = message.groupPublicKey ?: return - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Ignoring closed group update for nonexistent group.") - return - } - if (!group.isActive) { - Log.d("Loki", "Ignoring closed group update for inactive group.") - return - } - val name = group.title - // Check common group update logic - val members = group.members.map { it.toString() } - val admins = group.admins.map { it.toString() } - val removedMembers = kind.members.map { it.toByteArray().toHexString() } - val zombies: Set = storage.getZombieMembers(groupID) - // Check that the admin wasn't removed - if (removedMembers.contains(admins.first())) { - Log.d("Loki", "Ignoring invalid closed group update.") - return - } - // Check that the message was sent by the group admin - if (!admins.contains(senderPublicKey)) { - Log.d("Loki", "Ignoring invalid closed group update.") - return - } - if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { return } - // If the admin leaves the group is disbanded - val didAdminLeave = admins.any { it in removedMembers } - val newMembers = members - removedMembers - // A user should be posting a MEMBERS_LEFT in case they leave, so this shouldn't be encountered - val senderLeft = senderPublicKey in removedMembers - if (senderLeft) { - Log.d("Loki", "Received a MEMBERS_REMOVED instead of a MEMBERS_LEFT from sender: $senderPublicKey.") - } - val wasCurrentUserRemoved = userPublicKey in removedMembers - - // Only update the group in storage if it isn't invalidated by the config state - if (storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey, message.sentTimestamp!!)) { - // Admin should send a MEMBERS_LEFT message but handled here just in case - if (didAdminLeave || wasCurrentUserRemoved) { - disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey, true) - return - } else { - storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) - // Update zombie members - storage.setZombieMembers(groupID, zombies.minus(removedMembers).map { Address.fromSerialized(it) }) - } - } - - // Notify the user - val type = if (senderLeft) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.MEMBER_REMOVED - // We don't display zombie members in the notification as users have already been notified when those members left - val notificationMembers = removedMembers.minus(zombies) - if (notificationMembers.isNotEmpty()) { - // No notification to display when only zombies have been removed - if (userPublicKey == senderPublicKey) { - val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, type, name, notificationMembers, admins, threadID, message.sentTimestamp!!) - } else { - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, notificationMembers, admins, message.sentTimestamp!!) - } - } -} - -/// If a regular member left: -/// • Mark them as a zombie (to be removed by the admin later). -/// If the admin left: -/// • Unsubscribe from PNs, delete the group public key, etc. as the group will be disbanded. -private fun MessageReceiver.handleClosedGroupMemberLeft(message: LegacyGroupControlMessage) { - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val senderPublicKey = message.sender ?: return - val userPublicKey = storage.getUserPublicKey()!! - if (message.kind!! !is LegacyGroupControlMessage.Kind.MemberLeft) return - val groupPublicKey = message.groupPublicKey ?: return - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = storage.getGroup(groupID) ?: run { - Log.d("Loki", "Ignoring closed group update for nonexistent group.") - return - } - if (!group.isActive) { - Log.d("Loki", "Ignoring closed group update for inactive group.") - return - } - val name = group.title - // Check common group update logic - val members = group.members.map { it.toString() } - val admins = group.admins.map { it.toString() } - if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { - return - } - // If admin leaves the group is disbanded - val didAdminLeave = admins.contains(senderPublicKey) - val updatedMemberList = members - senderPublicKey - val userLeft = (userPublicKey == senderPublicKey) - - // Only update the group in storage if it isn't invalidated by the config state - if (storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey, message.sentTimestamp!!)) { - if (didAdminLeave || userLeft) { - disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey, delete = userLeft) - - if (userLeft) { - return - } - } else { - storage.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) }) - // Update zombie members - val zombies = storage.getZombieMembers(groupID) - storage.setZombieMembers(groupID, zombies.plus(senderPublicKey).map { Address.fromSerialized(it) }) - } - } - - // Notify the user - if (!userLeft) { - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.QUIT, name, listOf(senderPublicKey), admins, message.sentTimestamp!!) - } -} - private fun isValidGroupUpdate(group: GroupRecord, sentTimestamp: Long, senderPublicKey: String): Boolean { val oldMembers = group.members.map { it.toString() } // Check that the message isn't from before the group was created diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 7a061bd1b9..4afa237c7a 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -4,6 +4,7 @@ import com.google.protobuf.ByteString import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay @@ -35,7 +36,6 @@ import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.open_groups.OpenGroupMessage import org.session.libsession.messaging.sending_receiving.MessageReceiver import org.session.libsession.messaging.sending_receiving.handle -import org.session.libsession.messaging.sending_receiving.handleOpenGroupReactions import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.Address @@ -169,6 +169,15 @@ class OpenGroupPoller @AssistedInject constructor( manualPollRequest.receiveAsFlow() ).first() + // We might have more than one manual poll request, collect them all now so + // they don't trigger unnecessary pollings + val extraTokens = buildList { + while (true) { + val nexToken = manualPollRequest.tryReceive().getOrNull() ?: break + add(nexToken) + } + } + mutableIsCaughtUp.value = false var delayDuration = POLL_INTERVAL_MILLS try { @@ -176,6 +185,7 @@ class OpenGroupPoller @AssistedInject constructor( pollOnce() mutableIsCaughtUp.value = true token?.trySend(Result.success(Unit)) + extraTokens.forEach { it.trySend(Result.success(Unit)) } } catch (e: Exception) { Log.e(TAG, "Error while polling open group messages", e) delayDuration = 2000L @@ -225,17 +235,27 @@ class OpenGroupPoller @AssistedInject constructor( } } } catch (e: Exception) { - updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, e) + if (e !is CancellationException) { + Log.e(TAG, "Error while polling open group messages", e) + updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, e) + } + throw e } } - suspend fun manualPollOnce() { + suspend fun requestPollOnceAndWait() { val token = Channel>() manualPollRequest.send(token) token.receive().getOrThrow() } + fun requestPollOnce() { + scope.launch { + manualPollRequest.send(Channel()) + } + } + private fun updateCapabilitiesIfNeeded(isPostCapabilitiesRetry: Boolean, exception: Exception) { if (exception is OnionRequestAPI.HTTPRequestFailedBlindingRequiredException) { if (!isPostCapabilitiesRetry) { @@ -347,10 +367,6 @@ class OpenGroupPoller @AssistedInject constructor( .setTimestamp(message.sentTimestamp) .build() envelopes.add(Triple( message.serverID, envelope, message.reactions)) - } else if (!message.reactions.isNullOrEmpty()) { - message.serverID?.let { - MessageReceiver.handleOpenGroupReactions(threadId, it, message.reactions) - } } } diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerManager.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerManager.kt index 7df532438c..87f15318bb 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerManager.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerManager.kt @@ -98,7 +98,7 @@ class OpenGroupPollerManager @Inject constructor( pollers.value.map { (server, handle) -> handle.pollerScope.launch { runCatching { - handle.poller.manualPollOnce() + handle.poller.requestPollOnceAndWait() }.onFailure { Log.e(TAG, "Error polling open group ${server}", it) } diff --git a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt b/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt index 796dd77bed..0fbd0f3151 100644 --- a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -2,23 +2,19 @@ package org.session.libsession.messaging.utilities import android.content.Context import com.squareup.phrase.Phrase -import network.loki.messenger.libsession_util.getOrNull import network.loki.messenger.R +import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.calls.CallMessageType.CALL_FIRST_MISSED import org.session.libsession.messaging.calls.CallMessageType.CALL_INCOMING import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.SCREENSHOT import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ExpirationUtil -import org.session.libsession.utilities.getExpirationTypeDisplayValue -import org.session.libsession.utilities.truncateIdForDisplay -import org.session.libsignal.utilities.Log import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.DISAPPEARING_MESSAGES_TYPE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY @@ -27,6 +23,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_K import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.getGroup import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.Log object UpdateMessageBuilder { const val TAG = "UpdateMessageBuilder" @@ -316,15 +313,12 @@ object UpdateMessageBuilder { } } - fun buildExpirationTimerMessage( context: Context, - duration: Long, - isGroup: Boolean, // Note: isGroup should cover both closed groups AND communities - senderId: String? = null, - isOutgoing: Boolean = false, - timestamp: Long, - expireStarted: Long + mode: ExpiryMode, + isGroup: Boolean, // Note: isGroup should cover both closed groups AND communities + senderId: String?, + isOutgoing: Boolean, ): CharSequence { if (!isOutgoing && senderId == null) { Log.w(TAG, "buildExpirationTimerMessage: Cannot build for outgoing message when senderId is null.") @@ -334,7 +328,7 @@ object UpdateMessageBuilder { val senderName = if (isOutgoing) context.getString(R.string.you) else getGroupMemberName(senderId!!) // Case 1.) Disappearing messages have been turned off.. - if (duration <= 0) { + if (mode == ExpiryMode.NONE) { // ..by you.. return if (isOutgoing) { // in a group @@ -356,8 +350,12 @@ object UpdateMessageBuilder { } // Case 2.) Disappearing message settings have been changed but not turned off. - val time = ExpirationUtil.getExpirationDisplayValue(context, duration.toInt()) - val action = context.getExpirationTypeDisplayValue(timestamp >= expireStarted) + val time = ExpirationUtil.getExpirationDisplayValue(context, mode.duration) + val action = if (mode is ExpiryMode.AfterSend) { + context.getString(R.string.disappearingMessagesTypeSent) + } else { + context.getString(R.string.disappearingMessagesTypeRead) + } //..by you.. if (isOutgoing) { @@ -384,6 +382,30 @@ object UpdateMessageBuilder { } } + + @Deprecated("Use the version with ExpiryMode instead. This will be removed in a future release.") + fun buildExpirationTimerMessage( + context: Context, + duration: Long, + isGroup: Boolean, // Note: isGroup should cover both closed groups AND communities + senderId: String? = null, + isOutgoing: Boolean = false, + timestamp: Long, + expireStarted: Long + ): CharSequence { + return buildExpirationTimerMessage( + context, + mode = when { + duration == 0L -> ExpiryMode.NONE + timestamp >= expireStarted -> ExpiryMode.AfterSend(duration) // Not the greatest logic here but keeping it for backwards compatibility, can be removed once migrated over + else -> ExpiryMode.AfterRead(duration) + }, + isGroup = isGroup, + senderId = senderId, + isOutgoing = isOutgoing, + ) + } + fun buildDataExtractionMessage(context: Context, kind: DataExtractionNotificationInfoMessage.Kind, senderId: String? = null): CharSequence { diff --git a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt index d55004dabe..66734f463a 100644 --- a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt +++ b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt @@ -162,6 +162,7 @@ suspend fun ConfigFactoryProtocol.waitUntilUserConfigsPushed(timeoutMills: Long * * This function will check the group configs immediately, if nothing needs to be pushed, it will return immediately. * + * @param timeoutMills The maximum time to wait for the group configs to be pushed, in milliseconds. 0 means no timeout. * @return True if all group configs are pushed, false if the timeout is reached. */ suspend fun ConfigFactoryProtocol.waitUntilGroupConfigsPushed(groupId: AccountId, timeoutMills: Long = 10_000L): Boolean { @@ -169,12 +170,16 @@ suspend fun ConfigFactoryProtocol.waitUntilGroupConfigsPushed(groupId: AccountId configs.groupInfo.needsPush() || configs.groupMembers.needsPush() } - return withTimeoutOrNull(timeoutMills) { - configUpdateNotifications - .onStart { emit(ConfigUpdateNotification.GroupConfigsUpdated(groupId)) } // Trigger the filtering immediately - .filter { it == ConfigUpdateNotification.GroupConfigsUpdated(groupId) && !needsPush() } - .first() - } != null + val pushed = configUpdateNotifications + .onStart { emit(ConfigUpdateNotification.GroupConfigsUpdated(groupId, fromMerge = false)) } // Trigger the filtering immediately + .filter { it is ConfigUpdateNotification.GroupConfigsUpdated && it.groupId == groupId && !needsPush() } + + if (timeoutMills > 0) { + return withTimeoutOrNull(timeoutMills) { pushed.first() } != null + } else { + pushed.first() + return true + } } interface UserConfigs { @@ -235,5 +240,5 @@ sealed interface ConfigUpdateNotification { */ data class UserConfigsMerged(val configType: UserConfigType) : ConfigUpdateNotification - data class GroupConfigsUpdated(val groupId: AccountId) : ConfigUpdateNotification + data class GroupConfigsUpdated(val groupId: AccountId, val fromMerge: Boolean) : ConfigUpdateNotification } diff --git a/app/src/main/java/org/session/libsession/utilities/ExpirationUtil.kt b/app/src/main/java/org/session/libsession/utilities/ExpirationUtil.kt index 6e1663391a..d1d90560b9 100644 --- a/app/src/main/java/org/session/libsession/utilities/ExpirationUtil.kt +++ b/app/src/main/java/org/session/libsession/utilities/ExpirationUtil.kt @@ -1,14 +1,10 @@ package org.session.libsession.utilities import android.content.Context +import org.session.libsession.LocalisedTimeUtil import java.util.concurrent.TimeUnit import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -import org.session.libsession.LocalisedTimeUtil -import network.loki.messenger.R -fun Context.getExpirationTypeDisplayValue(sent: Boolean) = if (sent) getString(R.string.disappearingMessagesTypeSent) - else getString(R.string.disappearingMessagesTypeRead) object ExpirationUtil { @@ -16,10 +12,6 @@ object ExpirationUtil { fun getExpirationDisplayValue(context: Context, duration: Duration): String = LocalisedTimeUtil.getDurationWithSingleLargestTimeUnit(context, duration) - @JvmStatic - fun getExpirationDisplayValue(context: Context, expirationTimeSecs: Int) = - LocalisedTimeUtil.getDurationWithSingleLargestTimeUnit(context, expirationTimeSecs.seconds) - fun getExpirationAbbreviatedDisplayValue(expirationTimeSecs: Long): String { return if (expirationTimeSecs < TimeUnit.MINUTES.toSeconds(1)) { expirationTimeSecs.toString() + "s" diff --git a/app/src/main/java/org/session/libsession/utilities/MediaTypes.kt b/app/src/main/java/org/session/libsession/utilities/MediaTypes.kt index 4ef7c10c11..decd1d5a10 100644 --- a/app/src/main/java/org/session/libsession/utilities/MediaTypes.kt +++ b/app/src/main/java/org/session/libsession/utilities/MediaTypes.kt @@ -5,7 +5,7 @@ object MediaTypes { const val IMAGE_JPEG = "image/jpeg" const val IMAGE_WEBP = "image/webp" const val IMAGE_GIF = "image/gif" - const val AUDIO_AAC = "audio/aac" + const val AUDIO_MP4 = "audio/mp4" const val AUDIO_UNSPECIFIED = "audio/*" const val VIDEO_UNSPECIFIED = "video/*" const val VCARD = "text/x-vcard" diff --git a/app/src/main/java/org/session/libsession/utilities/NonTranslatableStringConstants.kt b/app/src/main/java/org/session/libsession/utilities/NonTranslatableStringConstants.kt index 62b96951a4..5b27b02580 100644 --- a/app/src/main/java/org/session/libsession/utilities/NonTranslatableStringConstants.kt +++ b/app/src/main/java/org/session/libsession/utilities/NonTranslatableStringConstants.kt @@ -12,5 +12,6 @@ object NonTranslatableStringConstants { const val TOKEN_NAME_SHORT = "SESH" const val USD_NAME_SHORT = "USD" const val SESSION_NETWORK_DATA_PRICE = "Price data powered by CoinGecko\nAccurate at {date_time}" + const val APP_PRO = "Session Pro" } diff --git a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 22fbbcc5a7..b7bc0e542a 100644 --- a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -1,14 +1,12 @@ package org.session.libsession.utilities import android.content.Context -import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.messages.Message -import org.session.libsession.messaging.messages.control.LegacyGroupControlMessage import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier -import org.session.libsession.snode.SnodeAPI.nowWithOffset import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.database.model.MessageId class SSKEnvironment( val typingIndicators: TypingIndicatorsProtocol, @@ -36,46 +34,14 @@ class SSKEnvironment( fun setNickname(context: Context, recipient: Recipient, nickname: String?) fun setName(context: Context, recipient: Recipient, name: String?) fun setProfilePicture(context: Context, recipient: Recipient, profilePictureURL: String?, profileKey: ByteArray?) - fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) fun contactUpdatedInternal(contact: Contact): String? } interface MessageExpirationManagerProtocol { fun insertExpirationTimerMessage(message: ExpirationTimerUpdate) - fun startAnyExpiration(timestamp: Long, author: String, expireStartedAt: Long) - fun maybeStartExpiration(message: Message, startDisappearAfterRead: Boolean = false) { - if ( - message is ExpirationTimerUpdate && message.isGroup || - message is LegacyGroupControlMessage || - message.openGroupServerMessageID != null // ignore expiration on communities since they do not support disappearing mesasges - ) return - - maybeStartExpiration( - message.sentTimestamp ?: return, - message.sender ?: return, - message.expiryMode, - startDisappearAfterRead || message.isSenderSelf - ) - } - - fun startDisappearAfterRead(timestamp: Long, sender: String) { - startAnyExpiration( - timestamp, - sender, - expireStartedAt = nowWithOffset.coerceAtLeast(timestamp + 1) - ) - } - - fun maybeStartExpiration(timestamp: Long, sender: String, mode: ExpiryMode, startDisappearAfterRead: Boolean = false) { - val expireStartedAt = when (mode) { - is ExpiryMode.AfterSend -> timestamp - is ExpiryMode.AfterRead -> if (startDisappearAfterRead) nowWithOffset.coerceAtLeast(timestamp + 1) else return - else -> return - } - - startAnyExpiration(timestamp, sender, expireStartedAt) - } + fun onMessageSent(message: Message) + fun onMessageReceived(message: Message) } companion object { diff --git a/app/src/main/java/org/session/libsession/utilities/StringSubKeys.kt b/app/src/main/java/org/session/libsession/utilities/StringSubKeys.kt index 2671c82b1b..054d42d980 100644 --- a/app/src/main/java/org/session/libsession/utilities/StringSubKeys.kt +++ b/app/src/main/java/org/session/libsession/utilities/StringSubKeys.kt @@ -42,4 +42,5 @@ object StringSubstitutionConstants { const val URL_KEY = "url" const val VALUE_KEY = "value" const val VERSION_KEY = "version" + const val LIMIT_KEY = "limit" } \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 2a599f9f03..b88154beb9 100644 --- a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -6,6 +6,7 @@ import android.net.Uri import android.provider.Settings import androidx.annotation.ArrayRes import androidx.annotation.StyleRes +import androidx.camera.core.CameraSelector import androidx.core.app.NotificationCompat import androidx.core.content.edit import androidx.preference.PreferenceManager.getDefaultSharedPreferences @@ -34,6 +35,9 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_DA import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT import org.session.libsession.utilities.TextSecurePreferences.Companion.SELECTED_ACCENT_COLOR import org.session.libsession.utilities.TextSecurePreferences.Companion.SELECTED_STYLE +import org.session.libsession.utilities.TextSecurePreferences.Companion.SET_FORCE_CURRENT_USER_PRO +import org.session.libsession.utilities.TextSecurePreferences.Companion.SET_FORCE_INCOMING_MESSAGE_PRO +import org.session.libsession.utilities.TextSecurePreferences.Companion.SET_FORCE_POST_PRO import org.session.libsession.utilities.TextSecurePreferences.Companion.SHOWN_CALL_NOTIFICATION import org.session.libsession.utilities.TextSecurePreferences.Companion.SHOWN_CALL_WARNING import org.session.libsession.utilities.TextSecurePreferences.Companion._events @@ -97,8 +101,8 @@ interface TextSecurePreferences { fun getProfilePictureURL(): String? fun getNotificationPriority(): Int fun getMessageBodyTextSize(): Int - fun setDirectCaptureCameraId(value: Int) - fun getDirectCaptureCameraId(): Int + fun setPreferredCameraDirection(value: CameraSelector) + fun getPreferredCameraDirection(): CameraSelector fun getNotificationPrivacy(): NotificationPrivacyPreference fun getRepeatAlertsCount(): Int fun getLocalRegistrationId(): Int @@ -168,6 +172,12 @@ interface TextSecurePreferences { fun setHasSeenLinkPreviewSuggestionDialog() fun hasHiddenMessageRequests(): Boolean fun setHasHiddenMessageRequests(hidden: Boolean) + fun forceCurrentUserAsPro(): Boolean + fun setForceCurrentUserAsPro(hidden: Boolean) + fun forceIncomingMessagesAsPro(): Boolean + fun setForceIncomingMessagesAsPro(hidden: Boolean) + fun forcePostPro(): Boolean + fun setForcePostPro(hidden: Boolean) fun hasHiddenNoteToSelf(): Boolean fun setHasHiddenNoteToSelf(hidden: Boolean) fun setShownCallWarning(): Boolean @@ -204,6 +214,8 @@ interface TextSecurePreferences { var migratedToDisablingKDF: Boolean var migratedToMultiPartConfig: Boolean + var migratedDisappearingMessagesToMessageContent: Boolean + var selectedActivityAliasName: String? companion object { @@ -285,6 +297,9 @@ interface TextSecurePreferences { const val LAST_OPEN_DATE = "pref_last_open_date" const val HAS_HIDDEN_MESSAGE_REQUESTS = "pref_message_requests_hidden" const val HAS_HIDDEN_NOTE_TO_SELF = "pref_note_to_self_hidden" + const val SET_FORCE_CURRENT_USER_PRO = "pref_force_current_user_pro" + const val SET_FORCE_INCOMING_MESSAGE_PRO = "pref_force_incoming_message_pro" + const val SET_FORCE_POST_PRO = "pref_force_post_pro" const val CALL_NOTIFICATIONS_ENABLED = "pref_call_notifications_enabled" const val SHOWN_CALL_WARNING = "pref_shown_call_warning" // call warning is user-facing warning of enabling calls const val SHOWN_CALL_NOTIFICATION = "pref_shown_call_notification" // call notification is a prompt to check privacy settings @@ -570,26 +585,6 @@ interface TextSecurePreferences { return getStringPreference(context, PROFILE_AVATAR_URL_PREF, null) } - @JvmStatic - fun getNotificationPriority(context: Context): Int { - return getStringPreference(context, NOTIFICATION_PRIORITY_PREF, NotificationCompat.PRIORITY_HIGH.toString())!!.toInt() - } - - @JvmStatic - fun getMessageBodyTextSize(context: Context): Int { - return getStringPreference(context, MESSAGE_BODY_TEXT_SIZE_PREF, "16")!!.toInt() - } - - @JvmStatic - fun setDirectCaptureCameraId(context: Context, value: Int) { - setIntegerPreference(context, DIRECT_CAPTURE_CAMERA_ID, value) - } - - @JvmStatic - fun getDirectCaptureCameraId(context: Context): Int { - return getIntegerPreference(context, DIRECT_CAPTURE_CAMERA_ID, Camera.CameraInfo.CAMERA_FACING_BACK) - } - @JvmStatic fun getNotificationPrivacy(context: Context): NotificationPrivacyPreference { return NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all")) @@ -1025,6 +1020,10 @@ class AppTextSecurePreferences @Inject constructor( get() = getBooleanPreference(TextSecurePreferences.MIGRATED_TO_MULTIPART_CONFIG, false) set(value) = setBooleanPreference(TextSecurePreferences.MIGRATED_TO_MULTIPART_CONFIG, value) + override var migratedDisappearingMessagesToMessageContent: Boolean + get() = getBooleanPreference("migrated_disappearing_messages_to_message_content", false) + set(value) = setBooleanPreference("migrated_disappearing_messages_to_message_content", value) + override fun getConfigurationMessageSynced(): Boolean { return getBooleanPreference(TextSecurePreferences.CONFIGURATION_SYNCED, false) } @@ -1227,12 +1226,19 @@ class AppTextSecurePreferences @Inject constructor( return getStringPreference(TextSecurePreferences.MESSAGE_BODY_TEXT_SIZE_PREF, "16")!!.toInt() } - override fun setDirectCaptureCameraId(value: Int) { - setIntegerPreference(TextSecurePreferences.DIRECT_CAPTURE_CAMERA_ID, value) + override fun setPreferredCameraDirection(value: CameraSelector) { + setIntegerPreference(TextSecurePreferences.DIRECT_CAPTURE_CAMERA_ID, + when(value){ + CameraSelector.DEFAULT_FRONT_CAMERA -> Camera.CameraInfo.CAMERA_FACING_FRONT + else -> Camera.CameraInfo.CAMERA_FACING_BACK + }) } - override fun getDirectCaptureCameraId(): Int { - return getIntegerPreference(TextSecurePreferences.DIRECT_CAPTURE_CAMERA_ID, Camera.CameraInfo.CAMERA_FACING_BACK) + override fun getPreferredCameraDirection(): CameraSelector { + return when(getIntegerPreference(TextSecurePreferences.DIRECT_CAPTURE_CAMERA_ID, Camera.CameraInfo.CAMERA_FACING_BACK)){ + Camera.CameraInfo.CAMERA_FACING_FRONT -> CameraSelector.DEFAULT_FRONT_CAMERA + else -> CameraSelector.DEFAULT_BACK_CAMERA + } } override fun getNotificationPrivacy(): NotificationPrivacyPreference { @@ -1604,6 +1610,33 @@ class AppTextSecurePreferences @Inject constructor( _events.tryEmit(HAS_HIDDEN_NOTE_TO_SELF) } + override fun forceCurrentUserAsPro(): Boolean { + return getBooleanPreference(SET_FORCE_CURRENT_USER_PRO, false) + } + + override fun setForceCurrentUserAsPro(hidden: Boolean) { + setBooleanPreference(SET_FORCE_CURRENT_USER_PRO, hidden) + _events.tryEmit(SET_FORCE_CURRENT_USER_PRO) + } + + override fun forceIncomingMessagesAsPro(): Boolean { + return getBooleanPreference(SET_FORCE_INCOMING_MESSAGE_PRO, false) + } + + override fun setForceIncomingMessagesAsPro(hidden: Boolean) { + setBooleanPreference(SET_FORCE_INCOMING_MESSAGE_PRO, hidden) + _events.tryEmit(SET_FORCE_INCOMING_MESSAGE_PRO) + } + + override fun forcePostPro(): Boolean { + return getBooleanPreference(SET_FORCE_POST_PRO, false) + } + + override fun setForcePostPro(hidden: Boolean) { + setBooleanPreference(SET_FORCE_POST_PRO, hidden) + _events.tryEmit(SET_FORCE_POST_PRO) + } + override fun getFingerprintKeyGenerated(): Boolean { return getBooleanPreference(TextSecurePreferences.FINGERPRINT_KEY_GENERATED, false) } diff --git a/app/src/main/java/org/session/libsession/utilities/Util.kt b/app/src/main/java/org/session/libsession/utilities/Util.kt index ae08085769..5afa6c09bd 100644 --- a/app/src/main/java/org/session/libsession/utilities/Util.kt +++ b/app/src/main/java/org/session/libsession/utilities/Util.kt @@ -233,12 +233,6 @@ object Util { return utf8String.toByteArray(StandardCharsets.UTF_8) } - @JvmStatic - @SuppressLint("NewApi") - fun isDefaultSmsProvider(context: Context): Boolean { - return context.packageName == Telephony.Sms.getDefaultSmsPackage(context) - } - @JvmStatic @Throws(IOException::class) fun readFully(`in`: InputStream?, buffer: ByteArray) { diff --git a/app/src/main/java/org/session/libsession/utilities/ViewUtils.kt b/app/src/main/java/org/session/libsession/utilities/ViewUtils.kt index 7be7224f3a..196e9fa181 100644 --- a/app/src/main/java/org/session/libsession/utilities/ViewUtils.kt +++ b/app/src/main/java/org/session/libsession/utilities/ViewUtils.kt @@ -1,11 +1,21 @@ package org.session.libsession.utilities import android.content.Context +import android.os.Build +import android.text.Layout +import android.text.StaticLayout +import android.text.TextDirectionHeuristics import android.util.TypedValue import android.view.View +import android.view.View.TEXT_ALIGNMENT_CENTER +import android.view.View.TEXT_ALIGNMENT_TEXT_END +import android.view.View.TEXT_ALIGNMENT_VIEW_END import android.view.ViewGroup +import android.widget.TextView import androidx.annotation.AttrRes import androidx.annotation.ColorInt +import androidx.core.text.TextDirectionHeuristicsCompat +import androidx.core.widget.TextViewCompat @ColorInt fun Context.getColorFromAttr( @@ -20,3 +30,48 @@ fun Context.getColorFromAttr( inline fun View.modifyLayoutParams(function: LP.() -> Unit) { layoutParams = (layoutParams as LP).apply { function() } } + +fun TextView.needsCollapsing( + availableWidthPx: Int, + maxLines: Int +): Boolean { + if (availableWidthPx <= 0 || text.isNullOrEmpty()) return false + + // The exact text that will be drawn (all-caps, password dots …) + val textForLayout = transformationMethod?.getTransformation(text, this) ?: text + + // Build a StaticLayout that mirrors this TextView’s wrap rules + val builder = StaticLayout.Builder + .obtain(textForLayout, 0, textForLayout.length, paint, availableWidthPx) + .setIncludePad(includeFontPadding) + .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier) + .setBreakStrategy(breakStrategy) // API 23+ + .setHyphenationFrequency(hyphenationFrequency) + .setMaxLines(Int.MAX_VALUE) + + // Alignment (honours RTL if textAlignment is END/VIEW_END) + builder.setAlignment( + when (textAlignment) { + TEXT_ALIGNMENT_CENTER -> Layout.Alignment.ALIGN_CENTER + TEXT_ALIGNMENT_VIEW_END, + TEXT_ALIGNMENT_TEXT_END -> Layout.Alignment.ALIGN_OPPOSITE + else -> Layout.Alignment.ALIGN_NORMAL + } + ) + + // Direction heuristic + val dir = when (textDirection) { + View.TEXT_DIRECTION_FIRST_STRONG_RTL -> TextDirectionHeuristics.FIRSTSTRONG_RTL + View.TEXT_DIRECTION_RTL -> TextDirectionHeuristics.RTL + View.TEXT_DIRECTION_LTR -> TextDirectionHeuristics.LTR + else -> TextDirectionHeuristics.FIRSTSTRONG_LTR + } + builder.setTextDirection(dir) + + builder.setEllipsize(ellipsize) + + builder.setJustificationMode(justificationMode) + + val layout = builder.build() + return layout.lineCount > maxLines +} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java index 23c1c28852..61a6b806d7 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java @@ -33,7 +33,6 @@ import org.session.libsession.avatars.ProfileContactPhoto; import org.session.libsession.avatars.SystemContactPhoto; import org.session.libsession.avatars.TransparentContactPhoto; -import org.session.libsession.database.StorageProtocol; import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.contacts.Contact; import org.session.libsession.utilities.Address; @@ -101,11 +100,8 @@ public class Recipient implements RecipientModifiedListener, Cloneable { private boolean profileSharing; private String notificationChannel; private boolean forceSmsSelection; - private String wrapperHash; private boolean blocksCommunityMessageRequests; - private @NonNull UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.ENABLED; - @SuppressWarnings("ConstantConditions") public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, boolean asynchronous) { if (address == null) throw new AssertionError(address); @@ -162,7 +158,6 @@ public static boolean removeCached(@NonNull Address address) { this.profileName = stale.profileName; this.profileAvatar = stale.profileAvatar; this.profileSharing = stale.profileSharing; - this.unidentifiedAccessMode = stale.unidentifiedAccessMode; this.forceSmsSelection = stale.forceSmsSelection; this.notifyType = stale.notifyType; this.disappearingState = stale.disappearingState; @@ -194,7 +189,6 @@ public static boolean removeCached(@NonNull Address address) { this.profileName = details.get().profileName; this.profileAvatar = details.get().profileAvatar; this.profileSharing = details.get().profileSharing; - this.unidentifiedAccessMode = details.get().unidentifiedAccessMode; this.forceSmsSelection = details.get().forceSmsSelection; this.notifyType = details.get().notifyType; this.autoDownloadAttachments = details.get().autoDownloadAttachments; @@ -233,7 +227,6 @@ public void onSuccess(RecipientDetails result) { Recipient.this.profileName = result.profileName; Recipient.this.profileAvatar = result.profileAvatar; Recipient.this.profileSharing = result.profileSharing; - Recipient.this.unidentifiedAccessMode = result.unidentifiedAccessMode; Recipient.this.forceSmsSelection = result.forceSmsSelection; Recipient.this.notifyType = result.notifyType; Recipient.this.disappearingState = result.disappearingState; @@ -291,9 +284,7 @@ public void onFailure(ExecutionException error) { this.profileName = details.profileName; this.profileAvatar = details.profileAvatar; this.profileSharing = details.profileSharing; - this.unidentifiedAccessMode = details.unidentifiedAccessMode; this.forceSmsSelection = details.forceSmsSelection; - this.wrapperHash = details.wrapperHash; this.blocksCommunityMessageRequests = details.blocksCommunityMessageRequests; this.participants.addAll(details.participants); @@ -342,6 +333,9 @@ public void setContactUri(@Nullable Uri contactUri) { } } + //todo SESSIONHERO refactor: This can be removed + public synchronized String getRawName() { return name; } + public void setName(@Nullable String name) { boolean notify = false; @@ -770,30 +764,6 @@ public void setProfileKey(@Nullable byte[] profileKey) { notifyListeners(); } - public @NonNull synchronized UnidentifiedAccessMode getUnidentifiedAccessMode() { - return unidentifiedAccessMode; - } - - public String getWrapperHash() { - return wrapperHash; - } - - public void setWrapperHash(String wrapperHash) { - this.wrapperHash = wrapperHash; - } - - public void setUnidentifiedAccessMode(@NonNull UnidentifiedAccessMode unidentifiedAccessMode) { - synchronized (this) { - this.unidentifiedAccessMode = unidentifiedAccessMode; - } - - notifyListeners(); - } - - public synchronized boolean isSystemContact() { - return contactUri != null; - } - public synchronized Recipient resolve() { while (resolving) Util.wait(this, 0); return this; @@ -822,7 +792,6 @@ public boolean equals(Object o) { && Arrays.equals(profileKey, recipient.profileKey) && Objects.equals(profileName, recipient.profileName) && Objects.equals(profileAvatar, recipient.profileAvatar) - && Objects.equals(wrapperHash, recipient.wrapperHash) && blocksCommunityMessageRequests == recipient.blocksCommunityMessageRequests; } @@ -842,7 +811,6 @@ public int hashCode() { expireMessages, profileName, profileAvatar, - wrapperHash, blocksCommunityMessageRequests ); result = 31 * result + Arrays.hashCode(profileKey); @@ -923,24 +891,6 @@ public static RegisteredState fromId(int id) { } } - public enum UnidentifiedAccessMode { - UNKNOWN(0), DISABLED(1), ENABLED(2), UNRESTRICTED(3); - - private final int mode; - - UnidentifiedAccessMode(int mode) { - this.mode = mode; - } - - public int getMode() { - return mode; - } - - public static UnidentifiedAccessMode fromMode(int mode) { - return values()[mode]; - } - } - public static class RecipientSettings { private final boolean blocked; private final boolean approved; @@ -966,9 +916,7 @@ public static class RecipientSettings { private final String signalProfileAvatar; private final boolean profileSharing; private final String notificationChannel; - private final UnidentifiedAccessMode unidentifiedAccessMode; private final boolean forceSmsSelection; - private final String wrapperHash; private final boolean blocksCommunityMessageRequests; public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, long muteUntil, @@ -992,9 +940,7 @@ public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, @Nullable String signalProfileAvatar, boolean profileSharing, @Nullable String notificationChannel, - @NonNull UnidentifiedAccessMode unidentifiedAccessMode, boolean forceSmsSelection, - String wrapperHash, boolean blocksCommunityMessageRequests ) { @@ -1022,9 +968,7 @@ public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, this.signalProfileAvatar = signalProfileAvatar; this.profileSharing = profileSharing; this.notificationChannel = notificationChannel; - this.unidentifiedAccessMode = unidentifiedAccessMode; this.forceSmsSelection = forceSmsSelection; - this.wrapperHash = wrapperHash; this.blocksCommunityMessageRequests = blocksCommunityMessageRequests; } @@ -1124,18 +1068,10 @@ public boolean isProfileSharing() { return notificationChannel; } - public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() { - return unidentifiedAccessMode; - } - public boolean isForceSmsSelection() { return forceSmsSelection; } - public String getWrapperHash() { - return wrapperHash; - } - public boolean getBlocksCommunityMessageRequests() { return blocksCommunityMessageRequests; } diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java index 645e2c12b7..bb65805268 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java @@ -34,7 +34,6 @@ import org.session.libsession.utilities.recipients.Recipient.DisappearingState; import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; import org.session.libsession.utilities.recipients.Recipient.RegisteredState; -import org.session.libsession.utilities.recipients.Recipient.UnidentifiedAccessMode; import org.session.libsession.utilities.recipients.Recipient.VibrateState; import org.session.libsignal.utilities.guava.Optional; @@ -178,9 +177,7 @@ static class RecipientDetails { final boolean systemContact; final boolean isLocalNumber; @Nullable final String notificationChannel; - @NonNull final UnidentifiedAccessMode unidentifiedAccessMode; final boolean forceSmsSelection; - final String wrapperHash; final boolean blocksCommunityMessageRequests; RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId, @@ -214,9 +211,7 @@ static class RecipientDetails { this.systemContact = systemContact; this.isLocalNumber = isLocalNumber; this.notificationChannel = settings != null ? settings.getNotificationChannel() : null; - this.unidentifiedAccessMode = settings != null ? settings.getUnidentifiedAccessMode() : UnidentifiedAccessMode.DISABLED; this.forceSmsSelection = settings != null && settings.isForceSmsSelection(); - this.wrapperHash = settings != null ? settings.getWrapperHash() : null; this.blocksCommunityMessageRequests = settings != null && settings.getBlocksCommunityMessageRequests(); if (name == null && settings != null) this.name = settings.getSystemDisplayName(); diff --git a/app/src/main/java/org/session/libsignal/exceptions/NonSuccessfulResponseCodeException.java b/app/src/main/java/org/session/libsignal/exceptions/NonSuccessfulResponseCodeException.java deleted file mode 100644 index 8e3262ef48..0000000000 --- a/app/src/main/java/org/session/libsignal/exceptions/NonSuccessfulResponseCodeException.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ - -package org.session.libsignal.exceptions; - -import java.io.IOException; - -public class NonSuccessfulResponseCodeException extends IOException { - - public NonSuccessfulResponseCodeException(String s) { - super(s); - } -} diff --git a/app/src/main/java/org/session/libsignal/exceptions/PushNetworkException.java b/app/src/main/java/org/session/libsignal/exceptions/PushNetworkException.java deleted file mode 100644 index 2b41fe8405..0000000000 --- a/app/src/main/java/org/session/libsignal/exceptions/PushNetworkException.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ - -package org.session.libsignal.exceptions; - -import java.io.IOException; - -public class PushNetworkException extends IOException { - - public PushNetworkException(Exception exception) { - super(exception); - } - - public PushNetworkException(String s) { - super(s); - } - -} diff --git a/app/src/main/java/org/session/libsignal/messages/SignalServiceContent.java b/app/src/main/java/org/session/libsignal/messages/SignalServiceContent.java deleted file mode 100644 index fe3a0a9df7..0000000000 --- a/app/src/main/java/org/session/libsignal/messages/SignalServiceContent.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ - -package org.session.libsignal.messages; - -import org.session.libsignal.utilities.guava.Optional; -import org.session.libsignal.protos.SignalServiceProtos; - -public class SignalServiceContent { - private final String sender; - private final int senderDevice; - private final long timestamp; - private final boolean needsReceipt; - - // Loki - private Optional message; - private final Optional readMessage; - private final Optional typingMessage; - - // Loki - public Optional configurationMessageProto = Optional.absent(); - public Optional senderDisplayName = Optional.absent(); - public Optional senderProfilePictureURL = Optional.absent(); - - public SignalServiceContent(SignalServiceDataMessage message, String sender, int senderDevice, long timestamp, boolean needsReceipt) { - this.sender = sender; - this.senderDevice = senderDevice; - this.timestamp = timestamp; - this.needsReceipt = needsReceipt; - this.message = Optional.fromNullable(message); - this.readMessage = Optional.absent(); - this.typingMessage = Optional.absent(); - } - - public SignalServiceContent(SignalServiceReceiptMessage receiptMessage, String sender, int senderDevice, long timestamp) { - this.sender = sender; - this.senderDevice = senderDevice; - this.timestamp = timestamp; - this.needsReceipt = false; - this.message = Optional.absent(); - this.readMessage = Optional.of(receiptMessage); - this.typingMessage = Optional.absent(); - } - - public SignalServiceContent(SignalServiceTypingMessage typingMessage, String sender, int senderDevice, long timestamp) { - this.sender = sender; - this.senderDevice = senderDevice; - this.timestamp = timestamp; - this.needsReceipt = false; - this.message = Optional.absent(); - this.readMessage = Optional.absent(); - this.typingMessage = Optional.of(typingMessage); - } - - public SignalServiceContent(SignalServiceProtos.Content configurationMessageProto, String sender, int senderDevice, long timestamp) { - this.sender = sender; - this.senderDevice = senderDevice; - this.timestamp = timestamp; - this.needsReceipt = false; - this.message = Optional.absent(); - this.readMessage = Optional.absent(); - this.typingMessage = Optional.absent(); - this.configurationMessageProto = Optional.fromNullable(configurationMessageProto); - } - - public Optional getDataMessage() { - return message; - } - - public void setDataMessage(SignalServiceDataMessage message) { this.message = Optional.fromNullable(message); } - - public Optional getReceiptMessage() { - return readMessage; - } - - public Optional getTypingMessage() { - return typingMessage; - } - - public String getSender() { - return sender; - } - - public int getSenderDevice() { - return senderDevice; - } - - public long getTimestamp() { - return timestamp; - } - - public boolean isNeedsReceipt() { - return needsReceipt; - } - - // Loki - public void setSenderDisplayName(String displayName) { senderDisplayName = Optional.fromNullable(displayName); } - - public void setSenderProfilePictureURL(String url) { senderProfilePictureURL = Optional.fromNullable(url); } -} diff --git a/app/src/main/java/org/session/libsignal/messages/SignalServiceDataMessage.java b/app/src/main/java/org/session/libsignal/messages/SignalServiceDataMessage.java index 8f908284eb..f9c3f3dc21 100644 --- a/app/src/main/java/org/session/libsignal/messages/SignalServiceDataMessage.java +++ b/app/src/main/java/org/session/libsignal/messages/SignalServiceDataMessage.java @@ -8,7 +8,6 @@ import org.session.libsignal.utilities.guava.Optional; import org.session.libsignal.utilities.SignalServiceAddress; -import org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage; import java.util.LinkedList; import java.util.List; @@ -27,8 +26,6 @@ public class SignalServiceDataMessage { private final Optional quote; public final Optional> contacts; private final Optional> previews; - // Loki - private final Optional closedGroupControlMessage; private final Optional syncTarget; /** @@ -121,16 +118,16 @@ public SignalServiceDataMessage(long timestamp, SignalServiceGroup group, boolean expirationUpdate, byte[] profileKey, Quote quote, List sharedContacts, List previews) { - this(timestamp, group, attachments, body, expiresInSeconds, expirationUpdate, profileKey, quote, sharedContacts, previews, null, null); + this(timestamp, group, attachments, body, expiresInSeconds, expirationUpdate, profileKey, quote, sharedContacts, previews, null); } /** * Construct a SignalServiceDataMessage. * - * @param timestamp The sent timestamp. - * @param group The group information (or null if none). - * @param attachments The attachments (or null if none). - * @param body The message contents. + * @param timestamp The sent timestamp. + * @param group The group information (or null if none). + * @param attachments The attachments (or null if none). + * @param body The message contents. * @param expiresInSeconds Number of seconds in which the message should disappear after being seen. */ public SignalServiceDataMessage(long timestamp, SignalServiceGroup group, @@ -138,7 +135,6 @@ public SignalServiceDataMessage(long timestamp, SignalServiceGroup group, String body, int expiresInSeconds, boolean expirationUpdate, byte[] profileKey, Quote quote, List sharedContacts, List previews, - ClosedGroupControlMessage closedGroupControlMessage, String syncTarget) { this.timestamp = timestamp; @@ -148,7 +144,6 @@ public SignalServiceDataMessage(long timestamp, SignalServiceGroup group, this.expirationUpdate = expirationUpdate; this.profileKey = Optional.fromNullable(profileKey); this.quote = Optional.fromNullable(quote); - this.closedGroupControlMessage = Optional.fromNullable(closedGroupControlMessage); this.syncTarget = Optional.fromNullable(syncTarget); if (attachments != null && !attachments.isEmpty()) { @@ -236,9 +231,6 @@ public Optional> getPreviews() { return previews; } - // Loki - public Optional getClosedGroupControlMessage() { return closedGroupControlMessage; } - public boolean hasVisibleContent() { return (body.isPresent() && !body.get().isEmpty()) || (attachments.isPresent() && !attachments.get().isEmpty()); @@ -323,7 +315,7 @@ public SignalServiceDataMessage build(long fallbackTimestamp) { if (timestamp == 0) timestamp = fallbackTimestamp; // closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob) return new SignalServiceDataMessage(timestamp, group, attachments, body, expiresInSeconds, expirationUpdate, profileKey, quote, sharedContacts, previews, - null, syncTarget); + syncTarget); } } diff --git a/app/src/main/java/org/session/libsignal/messages/SignalServiceReceiptMessage.java b/app/src/main/java/org/session/libsignal/messages/SignalServiceReceiptMessage.java deleted file mode 100644 index 77371ab9ed..0000000000 --- a/app/src/main/java/org/session/libsignal/messages/SignalServiceReceiptMessage.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.session.libsignal.messages; - -import java.util.List; - -public class SignalServiceReceiptMessage { - - public enum Type { - UNKNOWN, DELIVERY, READ - } - - private final Type type; - private final List timestamps; - private final long when; - - public SignalServiceReceiptMessage(Type type, List timestamps, long when) { - this.type = type; - this.timestamps = timestamps; - this.when = when; - } - - public Type getType() { - return type; - } - - public List getTimestamps() { - return timestamps; - } - - public long getWhen() { - return when; - } - - public boolean isDeliveryReceipt() { - return type == Type.DELIVERY; - } - - public boolean isReadReceipt() { - return type == Type.READ; - } - - public int getTTL() { return 0; } -} diff --git a/app/src/main/java/org/session/libsignal/messages/SignalServiceTypingMessage.java b/app/src/main/java/org/session/libsignal/messages/SignalServiceTypingMessage.java deleted file mode 100644 index 579b09437b..0000000000 --- a/app/src/main/java/org/session/libsignal/messages/SignalServiceTypingMessage.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.session.libsignal.messages; - -public class SignalServiceTypingMessage { - - public enum Action { - UNKNOWN, STARTED, STOPPED - } - - private final Action action; - private final long timestamp; - - public SignalServiceTypingMessage(Action action, long timestamp) { - this.action = action; - this.timestamp = timestamp; - } - - public Action getAction() { - return action; - } - - public long getTimestamp() { - return timestamp; - } - - public boolean isTypingStarted() { - return action == Action.STARTED; - } - - public boolean isTypingStopped() { - return action == Action.STOPPED; - } - - public int getTTL() { return 0; } -} diff --git a/app/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java b/app/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java index c6f53a46ac..d2ce6e08f7 100644 --- a/app/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java +++ b/app/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java @@ -2811,21 +2811,6 @@ public interface ContentOrBuilder extends */ org.session.libsignal.protos.SignalServiceProtos.TypingMessageOrBuilder getTypingMessageOrBuilder(); - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - * @return Whether the configurationMessage field is set. - */ - boolean hasConfigurationMessage(); - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - * @return The configurationMessage. - */ - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage getConfigurationMessage(); - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - */ - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessageOrBuilder getConfigurationMessageOrBuilder(); - /** * optional .signalservice.DataExtractionNotification dataExtractionNotification = 8; * @return Whether the dataExtractionNotification field is set. @@ -2871,21 +2856,6 @@ public interface ContentOrBuilder extends */ org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponseOrBuilder getMessageRequestResponseOrBuilder(); - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - * @return Whether the sharedConfigMessage field is set. - */ - boolean hasSharedConfigMessage(); - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - * @return The sharedConfigMessage. - */ - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage getSharedConfigMessage(); - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - */ - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBuilder getSharedConfigMessageOrBuilder(); - /** * optional .signalservice.Content.ExpirationType expirationType = 12; * @return Whether the expirationType field is set. @@ -3176,32 +3146,6 @@ public org.session.libsignal.protos.SignalServiceProtos.TypingMessageOrBuilder g return typingMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.TypingMessage.getDefaultInstance() : typingMessage_; } - public static final int CONFIGURATIONMESSAGE_FIELD_NUMBER = 7; - private org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage configurationMessage_; - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - * @return Whether the configurationMessage field is set. - */ - @java.lang.Override - public boolean hasConfigurationMessage() { - return ((bitField0_ & 0x00000010) != 0); - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - * @return The configurationMessage. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage getConfigurationMessage() { - return configurationMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.getDefaultInstance() : configurationMessage_; - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessageOrBuilder getConfigurationMessageOrBuilder() { - return configurationMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.getDefaultInstance() : configurationMessage_; - } - public static final int DATAEXTRACTIONNOTIFICATION_FIELD_NUMBER = 8; private org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotification dataExtractionNotification_; /** @@ -3210,7 +3154,7 @@ public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessageOrBu */ @java.lang.Override public boolean hasDataExtractionNotification() { - return ((bitField0_ & 0x00000020) != 0); + return ((bitField0_ & 0x00000010) != 0); } /** * optional .signalservice.DataExtractionNotification dataExtractionNotification = 8; @@ -3236,7 +3180,7 @@ public org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotificati */ @java.lang.Override public boolean hasUnsendRequest() { - return ((bitField0_ & 0x00000040) != 0); + return ((bitField0_ & 0x00000020) != 0); } /** * optional .signalservice.UnsendRequest unsendRequest = 9; @@ -3262,7 +3206,7 @@ public org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder g */ @java.lang.Override public boolean hasMessageRequestResponse() { - return ((bitField0_ & 0x00000080) != 0); + return ((bitField0_ & 0x00000040) != 0); } /** * optional .signalservice.MessageRequestResponse messageRequestResponse = 10; @@ -3280,32 +3224,6 @@ public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponseOr return messageRequestResponse_ == null ? org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.getDefaultInstance() : messageRequestResponse_; } - public static final int SHAREDCONFIGMESSAGE_FIELD_NUMBER = 11; - private org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage sharedConfigMessage_; - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - * @return Whether the sharedConfigMessage field is set. - */ - @java.lang.Override - public boolean hasSharedConfigMessage() { - return ((bitField0_ & 0x00000100) != 0); - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - * @return The sharedConfigMessage. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage getSharedConfigMessage() { - return sharedConfigMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance() : sharedConfigMessage_; - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBuilder getSharedConfigMessageOrBuilder() { - return sharedConfigMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance() : sharedConfigMessage_; - } - public static final int EXPIRATIONTYPE_FIELD_NUMBER = 12; private int expirationType_ = 0; /** @@ -3313,7 +3231,7 @@ public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBui * @return Whether the expirationType field is set. */ @java.lang.Override public boolean hasExpirationType() { - return ((bitField0_ & 0x00000200) != 0); + return ((bitField0_ & 0x00000080) != 0); } /** * optional .signalservice.Content.ExpirationType expirationType = 12; @@ -3332,7 +3250,7 @@ public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBui */ @java.lang.Override public boolean hasExpirationTimer() { - return ((bitField0_ & 0x00000400) != 0); + return ((bitField0_ & 0x00000100) != 0); } /** * optional uint32 expirationTimer = 13; @@ -3351,7 +3269,7 @@ public int getExpirationTimer() { */ @java.lang.Override public boolean hasSigTimestamp() { - return ((bitField0_ & 0x00000800) != 0); + return ((bitField0_ & 0x00000200) != 0); } /** * optional uint64 sigTimestamp = 15; @@ -3393,12 +3311,6 @@ public final boolean isInitialized() { return false; } } - if (hasConfigurationMessage()) { - if (!getConfigurationMessage().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } if (hasDataExtractionNotification()) { if (!getDataExtractionNotification().isInitialized()) { memoizedIsInitialized = 0; @@ -3417,12 +3329,6 @@ public final boolean isInitialized() { return false; } } - if (hasSharedConfigMessage()) { - if (!getSharedConfigMessage().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } memoizedIsInitialized = 1; return true; } @@ -3443,27 +3349,21 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) output.writeMessage(6, getTypingMessage()); } if (((bitField0_ & 0x00000010) != 0)) { - output.writeMessage(7, getConfigurationMessage()); - } - if (((bitField0_ & 0x00000020) != 0)) { output.writeMessage(8, getDataExtractionNotification()); } - if (((bitField0_ & 0x00000040) != 0)) { + if (((bitField0_ & 0x00000020) != 0)) { output.writeMessage(9, getUnsendRequest()); } - if (((bitField0_ & 0x00000080) != 0)) { + if (((bitField0_ & 0x00000040) != 0)) { output.writeMessage(10, getMessageRequestResponse()); } - if (((bitField0_ & 0x00000100) != 0)) { - output.writeMessage(11, getSharedConfigMessage()); - } - if (((bitField0_ & 0x00000200) != 0)) { + if (((bitField0_ & 0x00000080) != 0)) { output.writeEnum(12, expirationType_); } - if (((bitField0_ & 0x00000400) != 0)) { + if (((bitField0_ & 0x00000100) != 0)) { output.writeUInt32(13, expirationTimer_); } - if (((bitField0_ & 0x00000800) != 0)) { + if (((bitField0_ & 0x00000200) != 0)) { output.writeUInt64(15, sigTimestamp_); } getUnknownFields().writeTo(output); @@ -3492,34 +3392,26 @@ public int getSerializedSize() { .computeMessageSize(6, getTypingMessage()); } if (((bitField0_ & 0x00000010) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(7, getConfigurationMessage()); - } - if (((bitField0_ & 0x00000020) != 0)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(8, getDataExtractionNotification()); } - if (((bitField0_ & 0x00000040) != 0)) { + if (((bitField0_ & 0x00000020) != 0)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(9, getUnsendRequest()); } - if (((bitField0_ & 0x00000080) != 0)) { + if (((bitField0_ & 0x00000040) != 0)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(10, getMessageRequestResponse()); } - if (((bitField0_ & 0x00000100) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(11, getSharedConfigMessage()); - } - if (((bitField0_ & 0x00000200) != 0)) { + if (((bitField0_ & 0x00000080) != 0)) { size += com.google.protobuf.CodedOutputStream .computeEnumSize(12, expirationType_); } - if (((bitField0_ & 0x00000400) != 0)) { + if (((bitField0_ & 0x00000100) != 0)) { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(13, expirationTimer_); } - if (((bitField0_ & 0x00000800) != 0)) { + if (((bitField0_ & 0x00000200) != 0)) { size += com.google.protobuf.CodedOutputStream .computeUInt64Size(15, sigTimestamp_); } @@ -3558,11 +3450,6 @@ public boolean equals(final java.lang.Object obj) { if (!getTypingMessage() .equals(other.getTypingMessage())) return false; } - if (hasConfigurationMessage() != other.hasConfigurationMessage()) return false; - if (hasConfigurationMessage()) { - if (!getConfigurationMessage() - .equals(other.getConfigurationMessage())) return false; - } if (hasDataExtractionNotification() != other.hasDataExtractionNotification()) return false; if (hasDataExtractionNotification()) { if (!getDataExtractionNotification() @@ -3578,11 +3465,6 @@ public boolean equals(final java.lang.Object obj) { if (!getMessageRequestResponse() .equals(other.getMessageRequestResponse())) return false; } - if (hasSharedConfigMessage() != other.hasSharedConfigMessage()) return false; - if (hasSharedConfigMessage()) { - if (!getSharedConfigMessage() - .equals(other.getSharedConfigMessage())) return false; - } if (hasExpirationType() != other.hasExpirationType()) return false; if (hasExpirationType()) { if (expirationType_ != other.expirationType_) return false; @@ -3624,10 +3506,6 @@ public int hashCode() { hash = (37 * hash) + TYPINGMESSAGE_FIELD_NUMBER; hash = (53 * hash) + getTypingMessage().hashCode(); } - if (hasConfigurationMessage()) { - hash = (37 * hash) + CONFIGURATIONMESSAGE_FIELD_NUMBER; - hash = (53 * hash) + getConfigurationMessage().hashCode(); - } if (hasDataExtractionNotification()) { hash = (37 * hash) + DATAEXTRACTIONNOTIFICATION_FIELD_NUMBER; hash = (53 * hash) + getDataExtractionNotification().hashCode(); @@ -3640,10 +3518,6 @@ public int hashCode() { hash = (37 * hash) + MESSAGEREQUESTRESPONSE_FIELD_NUMBER; hash = (53 * hash) + getMessageRequestResponse().hashCode(); } - if (hasSharedConfigMessage()) { - hash = (37 * hash) + SHAREDCONFIGMESSAGE_FIELD_NUMBER; - hash = (53 * hash) + getSharedConfigMessage().hashCode(); - } if (hasExpirationType()) { hash = (37 * hash) + EXPIRATIONTYPE_FIELD_NUMBER; hash = (53 * hash) + expirationType_; @@ -3791,11 +3665,9 @@ private void maybeForceBuilderInitialization() { getCallMessageFieldBuilder(); getReceiptMessageFieldBuilder(); getTypingMessageFieldBuilder(); - getConfigurationMessageFieldBuilder(); getDataExtractionNotificationFieldBuilder(); getUnsendRequestFieldBuilder(); getMessageRequestResponseFieldBuilder(); - getSharedConfigMessageFieldBuilder(); } } @java.lang.Override @@ -3822,11 +3694,6 @@ public Builder clear() { typingMessageBuilder_.dispose(); typingMessageBuilder_ = null; } - configurationMessage_ = null; - if (configurationMessageBuilder_ != null) { - configurationMessageBuilder_.dispose(); - configurationMessageBuilder_ = null; - } dataExtractionNotification_ = null; if (dataExtractionNotificationBuilder_ != null) { dataExtractionNotificationBuilder_.dispose(); @@ -3842,11 +3709,6 @@ public Builder clear() { messageRequestResponseBuilder_.dispose(); messageRequestResponseBuilder_ = null; } - sharedConfigMessage_ = null; - if (sharedConfigMessageBuilder_ != null) { - sharedConfigMessageBuilder_.dispose(); - sharedConfigMessageBuilder_ = null; - } expirationType_ = 0; expirationTimer_ = 0; sigTimestamp_ = 0L; @@ -3909,46 +3771,34 @@ private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.Cont to_bitField0_ |= 0x00000008; } if (((from_bitField0_ & 0x00000010) != 0)) { - result.configurationMessage_ = configurationMessageBuilder_ == null - ? configurationMessage_ - : configurationMessageBuilder_.build(); - to_bitField0_ |= 0x00000010; - } - if (((from_bitField0_ & 0x00000020) != 0)) { result.dataExtractionNotification_ = dataExtractionNotificationBuilder_ == null ? dataExtractionNotification_ : dataExtractionNotificationBuilder_.build(); - to_bitField0_ |= 0x00000020; + to_bitField0_ |= 0x00000010; } - if (((from_bitField0_ & 0x00000040) != 0)) { + if (((from_bitField0_ & 0x00000020) != 0)) { result.unsendRequest_ = unsendRequestBuilder_ == null ? unsendRequest_ : unsendRequestBuilder_.build(); - to_bitField0_ |= 0x00000040; + to_bitField0_ |= 0x00000020; } - if (((from_bitField0_ & 0x00000080) != 0)) { + if (((from_bitField0_ & 0x00000040) != 0)) { result.messageRequestResponse_ = messageRequestResponseBuilder_ == null ? messageRequestResponse_ : messageRequestResponseBuilder_.build(); + to_bitField0_ |= 0x00000040; + } + if (((from_bitField0_ & 0x00000080) != 0)) { + result.expirationType_ = expirationType_; to_bitField0_ |= 0x00000080; } if (((from_bitField0_ & 0x00000100) != 0)) { - result.sharedConfigMessage_ = sharedConfigMessageBuilder_ == null - ? sharedConfigMessage_ - : sharedConfigMessageBuilder_.build(); + result.expirationTimer_ = expirationTimer_; to_bitField0_ |= 0x00000100; } if (((from_bitField0_ & 0x00000200) != 0)) { - result.expirationType_ = expirationType_; - to_bitField0_ |= 0x00000200; - } - if (((from_bitField0_ & 0x00000400) != 0)) { - result.expirationTimer_ = expirationTimer_; - to_bitField0_ |= 0x00000400; - } - if (((from_bitField0_ & 0x00000800) != 0)) { result.sigTimestamp_ = sigTimestamp_; - to_bitField0_ |= 0x00000800; + to_bitField0_ |= 0x00000200; } result.bitField0_ |= to_bitField0_; } @@ -3977,9 +3827,6 @@ public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.Conten if (other.hasTypingMessage()) { mergeTypingMessage(other.getTypingMessage()); } - if (other.hasConfigurationMessage()) { - mergeConfigurationMessage(other.getConfigurationMessage()); - } if (other.hasDataExtractionNotification()) { mergeDataExtractionNotification(other.getDataExtractionNotification()); } @@ -3989,9 +3836,6 @@ public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.Conten if (other.hasMessageRequestResponse()) { mergeMessageRequestResponse(other.getMessageRequestResponse()); } - if (other.hasSharedConfigMessage()) { - mergeSharedConfigMessage(other.getSharedConfigMessage()); - } if (other.hasExpirationType()) { setExpirationType(other.getExpirationType()); } @@ -4028,11 +3872,6 @@ public final boolean isInitialized() { return false; } } - if (hasConfigurationMessage()) { - if (!getConfigurationMessage().isInitialized()) { - return false; - } - } if (hasDataExtractionNotification()) { if (!getDataExtractionNotification().isInitialized()) { return false; @@ -4048,11 +3887,6 @@ public final boolean isInitialized() { return false; } } - if (hasSharedConfigMessage()) { - if (!getSharedConfigMessage().isInitialized()) { - return false; - } - } return true; } @@ -4100,41 +3934,27 @@ public Builder mergeFrom( bitField0_ |= 0x00000008; break; } // case 50 - case 58: { - input.readMessage( - getConfigurationMessageFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000010; - break; - } // case 58 case 66: { input.readMessage( getDataExtractionNotificationFieldBuilder().getBuilder(), extensionRegistry); - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000010; break; } // case 66 case 74: { input.readMessage( getUnsendRequestFieldBuilder().getBuilder(), extensionRegistry); - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000020; break; } // case 74 case 82: { input.readMessage( getMessageRequestResponseFieldBuilder().getBuilder(), extensionRegistry); - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; break; } // case 82 - case 90: { - input.readMessage( - getSharedConfigMessageFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000100; - break; - } // case 90 case 96: { int tmpRaw = input.readEnum(); org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType tmpValue = @@ -4143,18 +3963,18 @@ public Builder mergeFrom( mergeUnknownVarintField(12, tmpRaw); } else { expirationType_ = tmpRaw; - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000080; } break; } // case 96 case 104: { expirationTimer_ = input.readUInt32(); - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000100; break; } // case 104 case 120: { sigTimestamp_ = input.readUInt64(); - bitField0_ |= 0x00000800; + bitField0_ |= 0x00000200; break; } // case 120 default: { @@ -4658,127 +4478,6 @@ public org.session.libsignal.protos.SignalServiceProtos.TypingMessageOrBuilder g return typingMessageBuilder_; } - private org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage configurationMessage_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessageOrBuilder> configurationMessageBuilder_; - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - * @return Whether the configurationMessage field is set. - */ - public boolean hasConfigurationMessage() { - return ((bitField0_ & 0x00000010) != 0); - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - * @return The configurationMessage. - */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage getConfigurationMessage() { - if (configurationMessageBuilder_ == null) { - return configurationMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.getDefaultInstance() : configurationMessage_; - } else { - return configurationMessageBuilder_.getMessage(); - } - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - */ - public Builder setConfigurationMessage(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage value) { - if (configurationMessageBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - configurationMessage_ = value; - } else { - configurationMessageBuilder_.setMessage(value); - } - bitField0_ |= 0x00000010; - onChanged(); - return this; - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - */ - public Builder setConfigurationMessage( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Builder builderForValue) { - if (configurationMessageBuilder_ == null) { - configurationMessage_ = builderForValue.build(); - } else { - configurationMessageBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000010; - onChanged(); - return this; - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - */ - public Builder mergeConfigurationMessage(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage value) { - if (configurationMessageBuilder_ == null) { - if (((bitField0_ & 0x00000010) != 0) && - configurationMessage_ != null && - configurationMessage_ != org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.getDefaultInstance()) { - getConfigurationMessageBuilder().mergeFrom(value); - } else { - configurationMessage_ = value; - } - } else { - configurationMessageBuilder_.mergeFrom(value); - } - if (configurationMessage_ != null) { - bitField0_ |= 0x00000010; - onChanged(); - } - return this; - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - */ - public Builder clearConfigurationMessage() { - bitField0_ = (bitField0_ & ~0x00000010); - configurationMessage_ = null; - if (configurationMessageBuilder_ != null) { - configurationMessageBuilder_.dispose(); - configurationMessageBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Builder getConfigurationMessageBuilder() { - bitField0_ |= 0x00000010; - onChanged(); - return getConfigurationMessageFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessageOrBuilder getConfigurationMessageOrBuilder() { - if (configurationMessageBuilder_ != null) { - return configurationMessageBuilder_.getMessageOrBuilder(); - } else { - return configurationMessage_ == null ? - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.getDefaultInstance() : configurationMessage_; - } - } - /** - * optional .signalservice.ConfigurationMessage configurationMessage = 7; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessageOrBuilder> - getConfigurationMessageFieldBuilder() { - if (configurationMessageBuilder_ == null) { - configurationMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessageOrBuilder>( - getConfigurationMessage(), - getParentForChildren(), - isClean()); - configurationMessage_ = null; - } - return configurationMessageBuilder_; - } - private org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotification dataExtractionNotification_; private com.google.protobuf.SingleFieldBuilder< org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotification, org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotification.Builder, org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotificationOrBuilder> dataExtractionNotificationBuilder_; @@ -4787,7 +4486,7 @@ public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessageOrBu * @return Whether the dataExtractionNotification field is set. */ public boolean hasDataExtractionNotification() { - return ((bitField0_ & 0x00000020) != 0); + return ((bitField0_ & 0x00000010) != 0); } /** * optional .signalservice.DataExtractionNotification dataExtractionNotification = 8; @@ -4812,7 +4511,7 @@ public Builder setDataExtractionNotification(org.session.libsignal.protos.Signal } else { dataExtractionNotificationBuilder_.setMessage(value); } - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000010; onChanged(); return this; } @@ -4826,7 +4525,7 @@ public Builder setDataExtractionNotification( } else { dataExtractionNotificationBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000010; onChanged(); return this; } @@ -4835,7 +4534,7 @@ public Builder setDataExtractionNotification( */ public Builder mergeDataExtractionNotification(org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotification value) { if (dataExtractionNotificationBuilder_ == null) { - if (((bitField0_ & 0x00000020) != 0) && + if (((bitField0_ & 0x00000010) != 0) && dataExtractionNotification_ != null && dataExtractionNotification_ != org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotification.getDefaultInstance()) { getDataExtractionNotificationBuilder().mergeFrom(value); @@ -4846,7 +4545,7 @@ public Builder mergeDataExtractionNotification(org.session.libsignal.protos.Sign dataExtractionNotificationBuilder_.mergeFrom(value); } if (dataExtractionNotification_ != null) { - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000010; onChanged(); } return this; @@ -4855,7 +4554,7 @@ public Builder mergeDataExtractionNotification(org.session.libsignal.protos.Sign * optional .signalservice.DataExtractionNotification dataExtractionNotification = 8; */ public Builder clearDataExtractionNotification() { - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000010); dataExtractionNotification_ = null; if (dataExtractionNotificationBuilder_ != null) { dataExtractionNotificationBuilder_.dispose(); @@ -4868,7 +4567,7 @@ public Builder clearDataExtractionNotification() { * optional .signalservice.DataExtractionNotification dataExtractionNotification = 8; */ public org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotification.Builder getDataExtractionNotificationBuilder() { - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000010; onChanged(); return getDataExtractionNotificationFieldBuilder().getBuilder(); } @@ -4908,7 +4607,7 @@ public org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotificati * @return Whether the unsendRequest field is set. */ public boolean hasUnsendRequest() { - return ((bitField0_ & 0x00000040) != 0); + return ((bitField0_ & 0x00000020) != 0); } /** * optional .signalservice.UnsendRequest unsendRequest = 9; @@ -4933,7 +4632,7 @@ public Builder setUnsendRequest(org.session.libsignal.protos.SignalServiceProtos } else { unsendRequestBuilder_.setMessage(value); } - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000020; onChanged(); return this; } @@ -4947,7 +4646,7 @@ public Builder setUnsendRequest( } else { unsendRequestBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000020; onChanged(); return this; } @@ -4956,7 +4655,7 @@ public Builder setUnsendRequest( */ public Builder mergeUnsendRequest(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest value) { if (unsendRequestBuilder_ == null) { - if (((bitField0_ & 0x00000040) != 0) && + if (((bitField0_ & 0x00000020) != 0) && unsendRequest_ != null && unsendRequest_ != org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance()) { getUnsendRequestBuilder().mergeFrom(value); @@ -4967,7 +4666,7 @@ public Builder mergeUnsendRequest(org.session.libsignal.protos.SignalServiceProt unsendRequestBuilder_.mergeFrom(value); } if (unsendRequest_ != null) { - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000020; onChanged(); } return this; @@ -4976,7 +4675,7 @@ public Builder mergeUnsendRequest(org.session.libsignal.protos.SignalServiceProt * optional .signalservice.UnsendRequest unsendRequest = 9; */ public Builder clearUnsendRequest() { - bitField0_ = (bitField0_ & ~0x00000040); + bitField0_ = (bitField0_ & ~0x00000020); unsendRequest_ = null; if (unsendRequestBuilder_ != null) { unsendRequestBuilder_.dispose(); @@ -4989,7 +4688,7 @@ public Builder clearUnsendRequest() { * optional .signalservice.UnsendRequest unsendRequest = 9; */ public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder getUnsendRequestBuilder() { - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000020; onChanged(); return getUnsendRequestFieldBuilder().getBuilder(); } @@ -5029,7 +4728,7 @@ public org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder g * @return Whether the messageRequestResponse field is set. */ public boolean hasMessageRequestResponse() { - return ((bitField0_ & 0x00000080) != 0); + return ((bitField0_ & 0x00000040) != 0); } /** * optional .signalservice.MessageRequestResponse messageRequestResponse = 10; @@ -5054,7 +4753,7 @@ public Builder setMessageRequestResponse(org.session.libsignal.protos.SignalServ } else { messageRequestResponseBuilder_.setMessage(value); } - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; onChanged(); return this; } @@ -5068,7 +4767,7 @@ public Builder setMessageRequestResponse( } else { messageRequestResponseBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; onChanged(); return this; } @@ -5077,7 +4776,7 @@ public Builder setMessageRequestResponse( */ public Builder mergeMessageRequestResponse(org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse value) { if (messageRequestResponseBuilder_ == null) { - if (((bitField0_ & 0x00000080) != 0) && + if (((bitField0_ & 0x00000040) != 0) && messageRequestResponse_ != null && messageRequestResponse_ != org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.getDefaultInstance()) { getMessageRequestResponseBuilder().mergeFrom(value); @@ -5088,7 +4787,7 @@ public Builder mergeMessageRequestResponse(org.session.libsignal.protos.SignalSe messageRequestResponseBuilder_.mergeFrom(value); } if (messageRequestResponse_ != null) { - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; onChanged(); } return this; @@ -5097,7 +4796,7 @@ public Builder mergeMessageRequestResponse(org.session.libsignal.protos.SignalSe * optional .signalservice.MessageRequestResponse messageRequestResponse = 10; */ public Builder clearMessageRequestResponse() { - bitField0_ = (bitField0_ & ~0x00000080); + bitField0_ = (bitField0_ & ~0x00000040); messageRequestResponse_ = null; if (messageRequestResponseBuilder_ != null) { messageRequestResponseBuilder_.dispose(); @@ -5110,7 +4809,7 @@ public Builder clearMessageRequestResponse() { * optional .signalservice.MessageRequestResponse messageRequestResponse = 10; */ public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.Builder getMessageRequestResponseBuilder() { - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; onChanged(); return getMessageRequestResponseFieldBuilder().getBuilder(); } @@ -5142,134 +4841,13 @@ public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponseOr return messageRequestResponseBuilder_; } - private org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage sharedConfigMessage_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBuilder> sharedConfigMessageBuilder_; - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - * @return Whether the sharedConfigMessage field is set. - */ - public boolean hasSharedConfigMessage() { - return ((bitField0_ & 0x00000100) != 0); - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - * @return The sharedConfigMessage. - */ - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage getSharedConfigMessage() { - if (sharedConfigMessageBuilder_ == null) { - return sharedConfigMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance() : sharedConfigMessage_; - } else { - return sharedConfigMessageBuilder_.getMessage(); - } - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - */ - public Builder setSharedConfigMessage(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage value) { - if (sharedConfigMessageBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - sharedConfigMessage_ = value; - } else { - sharedConfigMessageBuilder_.setMessage(value); - } - bitField0_ |= 0x00000100; - onChanged(); - return this; - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - */ - public Builder setSharedConfigMessage( - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder builderForValue) { - if (sharedConfigMessageBuilder_ == null) { - sharedConfigMessage_ = builderForValue.build(); - } else { - sharedConfigMessageBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000100; - onChanged(); - return this; - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - */ - public Builder mergeSharedConfigMessage(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage value) { - if (sharedConfigMessageBuilder_ == null) { - if (((bitField0_ & 0x00000100) != 0) && - sharedConfigMessage_ != null && - sharedConfigMessage_ != org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance()) { - getSharedConfigMessageBuilder().mergeFrom(value); - } else { - sharedConfigMessage_ = value; - } - } else { - sharedConfigMessageBuilder_.mergeFrom(value); - } - if (sharedConfigMessage_ != null) { - bitField0_ |= 0x00000100; - onChanged(); - } - return this; - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - */ - public Builder clearSharedConfigMessage() { - bitField0_ = (bitField0_ & ~0x00000100); - sharedConfigMessage_ = null; - if (sharedConfigMessageBuilder_ != null) { - sharedConfigMessageBuilder_.dispose(); - sharedConfigMessageBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - */ - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder getSharedConfigMessageBuilder() { - bitField0_ |= 0x00000100; - onChanged(); - return getSharedConfigMessageFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - */ - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBuilder getSharedConfigMessageOrBuilder() { - if (sharedConfigMessageBuilder_ != null) { - return sharedConfigMessageBuilder_.getMessageOrBuilder(); - } else { - return sharedConfigMessage_ == null ? - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance() : sharedConfigMessage_; - } - } - /** - * optional .signalservice.SharedConfigMessage sharedConfigMessage = 11; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBuilder> - getSharedConfigMessageFieldBuilder() { - if (sharedConfigMessageBuilder_ == null) { - sharedConfigMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBuilder>( - getSharedConfigMessage(), - getParentForChildren(), - isClean()); - sharedConfigMessage_ = null; - } - return sharedConfigMessageBuilder_; - } - private int expirationType_ = 0; /** * optional .signalservice.Content.ExpirationType expirationType = 12; * @return Whether the expirationType field is set. */ @java.lang.Override public boolean hasExpirationType() { - return ((bitField0_ & 0x00000200) != 0); + return ((bitField0_ & 0x00000080) != 0); } /** * optional .signalservice.Content.ExpirationType expirationType = 12; @@ -5289,7 +4867,7 @@ public Builder setExpirationType(org.session.libsignal.protos.SignalServiceProto if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000080; expirationType_ = value.getNumber(); onChanged(); return this; @@ -5299,7 +4877,7 @@ public Builder setExpirationType(org.session.libsignal.protos.SignalServiceProto * @return This builder for chaining. */ public Builder clearExpirationType() { - bitField0_ = (bitField0_ & ~0x00000200); + bitField0_ = (bitField0_ & ~0x00000080); expirationType_ = 0; onChanged(); return this; @@ -5312,7 +4890,7 @@ public Builder clearExpirationType() { */ @java.lang.Override public boolean hasExpirationTimer() { - return ((bitField0_ & 0x00000400) != 0); + return ((bitField0_ & 0x00000100) != 0); } /** * optional uint32 expirationTimer = 13; @@ -5330,7 +4908,7 @@ public int getExpirationTimer() { public Builder setExpirationTimer(int value) { expirationTimer_ = value; - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000100; onChanged(); return this; } @@ -5339,7 +4917,7 @@ public Builder setExpirationTimer(int value) { * @return This builder for chaining. */ public Builder clearExpirationTimer() { - bitField0_ = (bitField0_ & ~0x00000400); + bitField0_ = (bitField0_ & ~0x00000100); expirationTimer_ = 0; onChanged(); return this; @@ -5352,7 +4930,7 @@ public Builder clearExpirationTimer() { */ @java.lang.Override public boolean hasSigTimestamp() { - return ((bitField0_ & 0x00000800) != 0); + return ((bitField0_ & 0x00000200) != 0); } /** * optional uint64 sigTimestamp = 15; @@ -5370,7 +4948,7 @@ public long getSigTimestamp() { public Builder setSigTimestamp(long value) { sigTimestamp_ = value; - bitField0_ |= 0x00000800; + bitField0_ |= 0x00000200; onChanged(); return this; } @@ -5379,7 +4957,7 @@ public Builder setSigTimestamp(long value) { * @return This builder for chaining. */ public Builder clearSigTimestamp() { - bitField0_ = (bitField0_ & ~0x00000800); + bitField0_ = (bitField0_ & ~0x00000200); sigTimestamp_ = 0L; onChanged(); return this; @@ -6976,21 +6554,6 @@ org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder ge */ org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder getOpenGroupInvitationOrBuilder(); - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - * @return Whether the closedGroupControlMessage field is set. - */ - boolean hasClosedGroupControlMessage(); - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - * @return The closedGroupControlMessage. - */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage getClosedGroupControlMessage(); - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessageOrBuilder getClosedGroupControlMessageOrBuilder(); - /** * optional string syncTarget = 105; * @return Whether the syncTarget field is set. @@ -21005,8 +20568,8 @@ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateM } - public interface ClosedGroupControlMessageOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.DataMessage.ClosedGroupControlMessage) + public interface ReactionOrBuilder extends + // @@protoc_insertion_point(interface_extends:signalservice.DataMessage.Reaction) com.google.protobuf.MessageOrBuilder { /** @@ -21014,161 +20577,92 @@ public interface ClosedGroupControlMessageOrBuilder extends * @required * * - * required .signalservice.DataMessage.ClosedGroupControlMessage.Type type = 1; - * @return Whether the type field is set. + * required uint64 id = 1; + * @return Whether the id field is set. */ - boolean hasType(); + boolean hasId(); /** *
        * @required
        * 
* - * required .signalservice.DataMessage.ClosedGroupControlMessage.Type type = 1; - * @return The type. - */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type getType(); - - /** - * optional bytes publicKey = 2; - * @return Whether the publicKey field is set. - */ - boolean hasPublicKey(); - /** - * optional bytes publicKey = 2; - * @return The publicKey. + * required uint64 id = 1; + * @return The id. */ - com.google.protobuf.ByteString getPublicKey(); + long getId(); /** - * optional string name = 3; - * @return Whether the name field is set. + *
+       * @required
+       * 
+ * + * required string author = 2; + * @return Whether the author field is set. */ - boolean hasName(); + boolean hasAuthor(); /** - * optional string name = 3; - * @return The name. + *
+       * @required
+       * 
+ * + * required string author = 2; + * @return The author. */ - java.lang.String getName(); + java.lang.String getAuthor(); /** - * optional string name = 3; - * @return The bytes for name. + *
+       * @required
+       * 
+ * + * required string author = 2; + * @return The bytes for author. */ com.google.protobuf.ByteString - getNameBytes(); - - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - * @return Whether the encryptionKeyPair field is set. - */ - boolean hasEncryptionKeyPair(); - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - * @return The encryptionKeyPair. - */ - org.session.libsignal.protos.SignalServiceProtos.KeyPair getEncryptionKeyPair(); - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - */ - org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder getEncryptionKeyPairOrBuilder(); - - /** - * repeated bytes members = 5; - * @return A list containing the members. - */ - java.util.List getMembersList(); - /** - * repeated bytes members = 5; - * @return The count of members. - */ - int getMembersCount(); - /** - * repeated bytes members = 5; - * @param index The index of the element to return. - * @return The members at the given index. - */ - com.google.protobuf.ByteString getMembers(int index); - - /** - * repeated bytes admins = 6; - * @return A list containing the admins. - */ - java.util.List getAdminsList(); - /** - * repeated bytes admins = 6; - * @return The count of admins. - */ - int getAdminsCount(); - /** - * repeated bytes admins = 6; - * @param index The index of the element to return. - * @return The admins at the given index. - */ - com.google.protobuf.ByteString getAdmins(int index); - - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - java.util.List - getWrappersList(); - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper getWrappers(int index); - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - int getWrappersCount(); - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - java.util.List - getWrappersOrBuilderList(); - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapperOrBuilder getWrappersOrBuilder( - int index); + getAuthorBytes(); /** - * optional uint32 expirationTimer = 8; - * @return Whether the expirationTimer field is set. - */ - boolean hasExpirationTimer(); - /** - * optional uint32 expirationTimer = 8; - * @return The expirationTimer. + * optional string emoji = 3; + * @return Whether the emoji field is set. */ - int getExpirationTimer(); - + boolean hasEmoji(); /** - * optional bytes memberPrivateKey = 9; - * @return Whether the memberPrivateKey field is set. + * optional string emoji = 3; + * @return The emoji. */ - boolean hasMemberPrivateKey(); + java.lang.String getEmoji(); /** - * optional bytes memberPrivateKey = 9; - * @return The memberPrivateKey. + * optional string emoji = 3; + * @return The bytes for emoji. */ - com.google.protobuf.ByteString getMemberPrivateKey(); + com.google.protobuf.ByteString + getEmojiBytes(); /** - * optional bytes privateKey = 10; - * @return Whether the privateKey field is set. + *
+       * @required
+       * 
+ * + * required .signalservice.DataMessage.Reaction.Action action = 4; + * @return Whether the action field is set. */ - boolean hasPrivateKey(); + boolean hasAction(); /** - * optional bytes privateKey = 10; - * @return The privateKey. + *
+       * @required
+       * 
+ * + * required .signalservice.DataMessage.Reaction.Action action = 4; + * @return The action. */ - com.google.protobuf.ByteString getPrivateKey(); + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action getAction(); } /** - * Protobuf type {@code signalservice.DataMessage.ClosedGroupControlMessage} + * Protobuf type {@code signalservice.DataMessage.Reaction} */ - public static final class ClosedGroupControlMessage extends + public static final class Reaction extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.DataMessage.ClosedGroupControlMessage) - ClosedGroupControlMessageOrBuilder { + // @@protoc_insertion_point(message_implements:signalservice.DataMessage.Reaction) + ReactionOrBuilder { private static final long serialVersionUID = 0L; static { com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( @@ -21177,220 +20671,63 @@ public static final class ClosedGroupControlMessage extends /* minor= */ 29, /* patch= */ 3, /* suffix= */ "", - ClosedGroupControlMessage.class.getName()); + Reaction.class.getName()); } - // Use ClosedGroupControlMessage.newBuilder() to construct. - private ClosedGroupControlMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + // Use Reaction.newBuilder() to construct. + private Reaction(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); } - private ClosedGroupControlMessage() { - type_ = 1; - publicKey_ = com.google.protobuf.ByteString.EMPTY; - name_ = ""; - members_ = emptyList(com.google.protobuf.ByteString.class); - admins_ = emptyList(com.google.protobuf.ByteString.class); - wrappers_ = java.util.Collections.emptyList(); - memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - privateKey_ = com.google.protobuf.ByteString.EMPTY; + private Reaction() { + author_ = ""; + emoji_ = ""; + action_ = 0; } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor; + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_descriptor; } @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_fieldAccessorTable + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder.class); + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder.class); } /** - * Protobuf enum {@code signalservice.DataMessage.ClosedGroupControlMessage.Type} + * Protobuf enum {@code signalservice.DataMessage.Reaction.Action} */ - public enum Type + public enum Action implements com.google.protobuf.ProtocolMessageEnum { /** - *
-         * publicKey, name, encryptionKeyPair, members, admins, expireTimer
-         * 
- * - * NEW = 1; + * REACT = 0; */ - NEW(1), + REACT(0), /** - *
-         * publicKey, wrappers
-         * 
- * - * ENCRYPTION_KEY_PAIR = 3; + * REMOVE = 1; */ - ENCRYPTION_KEY_PAIR(3), + REMOVE(1), + ; + + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 29, + /* patch= */ 3, + /* suffix= */ "", + Action.class.getName()); + } /** - *
-         * name
-         * 
- * - * NAME_CHANGE = 4; + * REACT = 0; */ - NAME_CHANGE(4), + public static final int REACT_VALUE = 0; /** - *
-         * members
-         * 
- * - * MEMBERS_ADDED = 5; - */ - MEMBERS_ADDED(5), - /** - *
-         * members
-         * 
- * - * MEMBERS_REMOVED = 6; - */ - MEMBERS_REMOVED(6), - /** - * MEMBER_LEFT = 7; - */ - MEMBER_LEFT(7), - /** - *
-         * publicKey, name, memberPrivateKey
-         * 
- * - * INVITE = 9; - */ - INVITE(9), - /** - *
-         * publicKey, privateKey
-         * 
- * - * PROMOTE = 10; - */ - PROMOTE(10), - /** - *
-         * publicKey, members
-         * 
- * - * DELETE_GROUP = 11; - */ - DELETE_GROUP(11), - /** - *
-         * publicKey
-         * 
- * - * DELETE_MESSAGES = 12; - */ - DELETE_MESSAGES(12), - /** - *
-         * publicKey
-         * 
- * - * DELETE_ATTACHMENTS = 13; - */ - DELETE_ATTACHMENTS(13), - ; - - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - Type.class.getName()); - } - /** - *
-         * publicKey, name, encryptionKeyPair, members, admins, expireTimer
-         * 
- * - * NEW = 1; - */ - public static final int NEW_VALUE = 1; - /** - *
-         * publicKey, wrappers
-         * 
- * - * ENCRYPTION_KEY_PAIR = 3; - */ - public static final int ENCRYPTION_KEY_PAIR_VALUE = 3; - /** - *
-         * name
-         * 
- * - * NAME_CHANGE = 4; - */ - public static final int NAME_CHANGE_VALUE = 4; - /** - *
-         * members
-         * 
- * - * MEMBERS_ADDED = 5; - */ - public static final int MEMBERS_ADDED_VALUE = 5; - /** - *
-         * members
-         * 
- * - * MEMBERS_REMOVED = 6; - */ - public static final int MEMBERS_REMOVED_VALUE = 6; - /** - * MEMBER_LEFT = 7; - */ - public static final int MEMBER_LEFT_VALUE = 7; - /** - *
-         * publicKey, name, memberPrivateKey
-         * 
- * - * INVITE = 9; - */ - public static final int INVITE_VALUE = 9; - /** - *
-         * publicKey, privateKey
-         * 
- * - * PROMOTE = 10; - */ - public static final int PROMOTE_VALUE = 10; - /** - *
-         * publicKey, members
-         * 
- * - * DELETE_GROUP = 11; - */ - public static final int DELETE_GROUP_VALUE = 11; - /** - *
-         * publicKey
-         * 
- * - * DELETE_MESSAGES = 12; - */ - public static final int DELETE_MESSAGES_VALUE = 12; - /** - *
-         * publicKey
-         * 
- * - * DELETE_ATTACHMENTS = 13; + * REMOVE = 1; */ - public static final int DELETE_ATTACHMENTS_VALUE = 13; + public static final int REMOVE_VALUE = 1; public final int getNumber() { @@ -21403,7 +20740,7 @@ public final int getNumber() { * @deprecated Use {@link #forNumber(int)} instead. */ @java.lang.Deprecated - public static Type valueOf(int value) { + public static Action valueOf(int value) { return forNumber(value); } @@ -21411,32 +20748,23 @@ public static Type valueOf(int value) { * @param value The numeric wire value of the corresponding enum entry. * @return The enum associated with the given numeric wire value. */ - public static Type forNumber(int value) { + public static Action forNumber(int value) { switch (value) { - case 1: return NEW; - case 3: return ENCRYPTION_KEY_PAIR; - case 4: return NAME_CHANGE; - case 5: return MEMBERS_ADDED; - case 6: return MEMBERS_REMOVED; - case 7: return MEMBER_LEFT; - case 9: return INVITE; - case 10: return PROMOTE; - case 11: return DELETE_GROUP; - case 12: return DELETE_MESSAGES; - case 13: return DELETE_ATTACHMENTS; + case 0: return REACT; + case 1: return REMOVE; default: return null; } } - public static com.google.protobuf.Internal.EnumLiteMap + public static com.google.protobuf.Internal.EnumLiteMap internalGetValueMap() { return internalValueMap; } private static final com.google.protobuf.Internal.EnumLiteMap< - Type> internalValueMap = - new com.google.protobuf.Internal.EnumLiteMap() { - public Type findValueByNumber(int number) { - return Type.forNumber(number); + Action> internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Action findValueByNumber(int number) { + return Action.forNumber(number); } }; @@ -21450,12 +20778,12 @@ public Type findValueByNumber(int number) { } public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDescriptor().getEnumTypes().get(0); + return org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDescriptor().getEnumTypes().get(0); } - private static final Type[] VALUES = values(); + private static final Action[] VALUES = values(); - public static Type valueOf( + public static Action valueOf( com.google.protobuf.Descriptors.EnumValueDescriptor desc) { if (desc.getType() != getDescriptor()) { throw new java.lang.IllegalArgumentException( @@ -21466,11673 +20794,679 @@ public static Type valueOf( private final int value; - private Type(int value) { + private Action(int value) { this.value = value; } - // @@protoc_insertion_point(enum_scope:signalservice.DataMessage.ClosedGroupControlMessage.Type) + // @@protoc_insertion_point(enum_scope:signalservice.DataMessage.Reaction.Action) } - public interface KeyPairWrapperOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) - com.google.protobuf.MessageOrBuilder { - - /** - *
-         * @required
-         * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - boolean hasPublicKey(); - /** - *
-         * @required
-         * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - com.google.protobuf.ByteString getPublicKey(); - - /** - *
-         * @required
-         * 
- * - * required bytes encryptedKeyPair = 2; - * @return Whether the encryptedKeyPair field is set. - */ - boolean hasEncryptedKeyPair(); - /** - *
-         * @required
-         * 
- * - * required bytes encryptedKeyPair = 2; - * @return The encryptedKeyPair. - */ - com.google.protobuf.ByteString getEncryptedKeyPair(); + private int bitField0_; + public static final int ID_FIELD_NUMBER = 1; + private long id_ = 0L; + /** + *
+       * @required
+       * 
+ * + * required uint64 id = 1; + * @return Whether the id field is set. + */ + @java.lang.Override + public boolean hasId() { + return ((bitField0_ & 0x00000001) != 0); } /** - * Protobuf type {@code signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper} + *
+       * @required
+       * 
+ * + * required uint64 id = 1; + * @return The id. */ - public static final class KeyPairWrapper extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) - KeyPairWrapperOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - KeyPairWrapper.class.getName()); - } - // Use KeyPairWrapper.newBuilder() to construct. - private KeyPairWrapper(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private KeyPairWrapper() { - publicKey_ = com.google.protobuf.ByteString.EMPTY; - encryptedKeyPair_ = com.google.protobuf.ByteString.EMPTY; - } + @java.lang.Override + public long getId() { + return id_; + } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor; + public static final int AUTHOR_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object author_ = ""; + /** + *
+       * @required
+       * 
+ * + * required string author = 2; + * @return Whether the author field is set. + */ + @java.lang.Override + public boolean hasAuthor() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + *
+       * @required
+       * 
+ * + * required string author = 2; + * @return The author. + */ + @java.lang.Override + public java.lang.String getAuthor() { + java.lang.Object ref = author_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + author_ = s; + } + return s; } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder.class); + } + /** + *
+       * @required
+       * 
+ * + * required string author = 2; + * @return The bytes for author. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getAuthorBytes() { + java.lang.Object ref = author_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + author_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; } + } - private int bitField0_; - public static final int PUBLICKEY_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-         * @required
-         * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); + public static final int EMOJI_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private volatile java.lang.Object emoji_ = ""; + /** + * optional string emoji = 3; + * @return Whether the emoji field is set. + */ + @java.lang.Override + public boolean hasEmoji() { + return ((bitField0_ & 0x00000004) != 0); + } + /** + * optional string emoji = 3; + * @return The emoji. + */ + @java.lang.Override + public java.lang.String getEmoji() { + java.lang.Object ref = emoji_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + emoji_ = s; + } + return s; } - /** - *
-         * @required
-         * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; + } + /** + * optional string emoji = 3; + * @return The bytes for emoji. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getEmojiBytes() { + java.lang.Object ref = emoji_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + emoji_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; } + } - public static final int ENCRYPTEDKEYPAIR_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString encryptedKeyPair_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-         * @required
-         * 
- * - * required bytes encryptedKeyPair = 2; - * @return Whether the encryptedKeyPair field is set. - */ - @java.lang.Override - public boolean hasEncryptedKeyPair() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-         * @required
-         * 
- * - * required bytes encryptedKeyPair = 2; - * @return The encryptedKeyPair. - */ - @java.lang.Override - public com.google.protobuf.ByteString getEncryptedKeyPair() { - return encryptedKeyPair_; - } + public static final int ACTION_FIELD_NUMBER = 4; + private int action_ = 0; + /** + *
+       * @required
+       * 
+ * + * required .signalservice.DataMessage.Reaction.Action action = 4; + * @return Whether the action field is set. + */ + @java.lang.Override public boolean hasAction() { + return ((bitField0_ & 0x00000008) != 0); + } + /** + *
+       * @required
+       * 
+ * + * required .signalservice.DataMessage.Reaction.Action action = 4; + * @return The action. + */ + @java.lang.Override public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action getAction() { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action result = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.forNumber(action_); + return result == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.REACT : result; + } - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; - if (!hasPublicKey()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasEncryptedKeyPair()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; + if (!hasId()) { + memoizedIsInitialized = 0; + return false; } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - output.writeBytes(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - output.writeBytes(2, encryptedKeyPair_); - } - getUnknownFields().writeTo(output); + if (!hasAuthor()) { + memoizedIsInitialized = 0; + return false; } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, encryptedKeyPair_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; + if (!hasAction()) { + memoizedIsInitialized = 0; + return false; } + memoizedIsInitialized = 1; + return true; + } - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper other = (org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) obj; - - if (hasPublicKey() != other.hasPublicKey()) return false; - if (hasPublicKey()) { - if (!getPublicKey() - .equals(other.getPublicKey())) return false; - } - if (hasEncryptedKeyPair() != other.hasEncryptedKeyPair()) return false; - if (hasEncryptedKeyPair()) { - if (!getEncryptedKeyPair() - .equals(other.getEncryptedKeyPair())) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) != 0)) { + output.writeUInt64(1, id_); } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasPublicKey()) { - hash = (37 * hash) + PUBLICKEY_FIELD_NUMBER; - hash = (53 * hash) + getPublicKey().hashCode(); - } - if (hasEncryptedKeyPair()) { - hash = (37 * hash) + ENCRYPTEDKEYPAIR_FIELD_NUMBER; - hash = (53 * hash) + getEncryptedKeyPair().hashCode(); - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; + if (((bitField0_ & 0x00000002) != 0)) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, author_); } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); + if (((bitField0_ & 0x00000004) != 0)) { + com.google.protobuf.GeneratedMessage.writeString(output, 3, emoji_); } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); + if (((bitField0_ & 0x00000008) != 0)) { + output.writeEnum(4, action_); } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(1, id_); } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); + if (((bitField0_ & 0x00000002) != 0)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(2, author_); } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); + if (((bitField0_ & 0x00000004) != 0)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(3, emoji_); } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); + if (((bitField0_ & 0x00000008) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(4, action_); } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); + if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction)) { + return super.equals(obj); } + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction other = (org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction) obj; - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); + if (hasId() != other.hasId()) return false; + if (hasId()) { + if (getId() + != other.getId()) return false; } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + if (hasAuthor() != other.hasAuthor()) return false; + if (hasAuthor()) { + if (!getAuthor() + .equals(other.getAuthor())) return false; } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); + if (hasEmoji() != other.hasEmoji()) return false; + if (hasEmoji()) { + if (!getEmoji() + .equals(other.getEmoji())) return false; } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); + if (hasAction() != other.hasAction()) return false; + if (hasAction()) { + if (action_ != other.action_) return false; } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasId()) { + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getId()); } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); + if (hasAuthor()) { + hash = (37 * hash) + AUTHOR_FIELD_NUMBER; + hash = (53 * hash) + getAuthor().hashCode(); + } + if (hasEmoji()) { + hash = (37 * hash) + EMOJI_FIELD_NUMBER; + hash = (53 * hash) + getEmoji().hashCode(); + } + if (hasAction()) { + hash = (37 * hash) + ACTION_FIELD_NUMBER; + hash = (53 * hash) + action_; + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.DataMessage.Reaction} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:signalservice.DataMessage.Reaction) + org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_descriptor; } @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder.class); } - /** - * Protobuf type {@code signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapperOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor; - } - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder.class); - } + // Construct using org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.newBuilder() + private Builder() { - // Construct using org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.newBuilder() - private Builder() { + } - } + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + id_ = 0L; + author_ = ""; + emoji_ = ""; + action_ = 0; + return this; + } - } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - publicKey_ = com.google.protobuf.ByteString.EMPTY; - encryptedKeyPair_ = com.google.protobuf.ByteString.EMPTY; - return this; - } + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_descriptor; + } - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor; - } + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance(); + } - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.getDefaultInstance(); + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction build() { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper build() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction result = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper result = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper(this); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; - } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.publicKey_ = publicKey_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000002) != 0)) { - result.encryptedKeyPair_ = encryptedKeyPair_; - to_bitField0_ |= 0x00000002; - } - result.bitField0_ |= to_bitField0_; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper)other); - } else { - super.mergeFrom(other); - return this; - } + private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction result) { + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.id_ = id_; + to_bitField0_ |= 0x00000001; } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.getDefaultInstance()) return this; - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasEncryptedKeyPair()) { - setEncryptedKeyPair(other.getEncryptedKeyPair()); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; + if (((from_bitField0_ & 0x00000002) != 0)) { + result.author_ = author_; + to_bitField0_ |= 0x00000002; } - - @java.lang.Override - public final boolean isInitialized() { - if (!hasPublicKey()) { - return false; - } - if (!hasEncryptedKeyPair()) { - return false; - } - return true; + if (((from_bitField0_ & 0x00000004) != 0)) { + result.emoji_ = emoji_; + to_bitField0_ |= 0x00000004; } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 10: { - publicKey_ = input.readBytes(); - bitField0_ |= 0x00000001; - break; - } // case 10 - case 18: { - encryptedKeyPair_ = input.readBytes(); - bitField0_ |= 0x00000002; - break; - } // case 18 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; + if (((from_bitField0_ & 0x00000008) != 0)) { + result.action_ = action_; + to_bitField0_ |= 0x00000008; } - private int bitField0_; + result.bitField0_ |= to_bitField0_; + } - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-           * @required
-           * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-           * @required
-           * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - /** - *
-           * @required
-           * 
- * - * required bytes publicKey = 1; - * @param value The publicKey to set. - * @return This builder for chaining. - */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - publicKey_ = value; - bitField0_ |= 0x00000001; - onChanged(); - return this; - } - /** - *
-           * @required
-           * 
- * - * required bytes publicKey = 1; - * @return This builder for chaining. - */ - public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000001); - publicKey_ = getDefaultInstance().getPublicKey(); - onChanged(); + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction)other); + } else { + super.mergeFrom(other); return this; } + } - private com.google.protobuf.ByteString encryptedKeyPair_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-           * @required
-           * 
- * - * required bytes encryptedKeyPair = 2; - * @return Whether the encryptedKeyPair field is set. - */ - @java.lang.Override - public boolean hasEncryptedKeyPair() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-           * @required
-           * 
- * - * required bytes encryptedKeyPair = 2; - * @return The encryptedKeyPair. - */ - @java.lang.Override - public com.google.protobuf.ByteString getEncryptedKeyPair() { - return encryptedKeyPair_; + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); } - /** - *
-           * @required
-           * 
- * - * required bytes encryptedKeyPair = 2; - * @param value The encryptedKeyPair to set. - * @return This builder for chaining. - */ - public Builder setEncryptedKeyPair(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - encryptedKeyPair_ = value; + if (other.hasAuthor()) { + author_ = other.author_; bitField0_ |= 0x00000002; onChanged(); - return this; } - /** - *
-           * @required
-           * 
- * - * required bytes encryptedKeyPair = 2; - * @return This builder for chaining. - */ - public Builder clearEncryptedKeyPair() { - bitField0_ = (bitField0_ & ~0x00000002); - encryptedKeyPair_ = getDefaultInstance().getEncryptedKeyPair(); + if (other.hasEmoji()) { + emoji_ = other.emoji_; + bitField0_ |= 0x00000004; onChanged(); - return this; } - - // @@protoc_insertion_point(builder_scope:signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) - } - - // @@protoc_insertion_point(class_scope:signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) - private static final org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public KeyPairWrapper parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); + if (other.hasAction()) { + setAction(other.getAction()); } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; } @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + public final boolean isInitialized() { + if (!hasId()) { + return false; + } + if (!hasAuthor()) { + return false; + } + if (!hasAction()) { + return false; + } + return true; } @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper getDefaultInstanceForType() { - return DEFAULT_INSTANCE; + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + id_ = input.readUInt64(); + bitField0_ |= 0x00000001; + break; + } // case 8 + case 18: { + author_ = input.readBytes(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 26: { + emoji_ = input.readBytes(); + bitField0_ |= 0x00000004; + break; + } // case 26 + case 32: { + int tmpRaw = input.readEnum(); + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action tmpValue = + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.forNumber(tmpRaw); + if (tmpValue == null) { + mergeUnknownVarintField(4, tmpRaw); + } else { + action_ = tmpRaw; + bitField0_ |= 0x00000008; + } + break; + } // case 32 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; } + private int bitField0_; - } - - private int bitField0_; - public static final int TYPE_FIELD_NUMBER = 1; - private int type_ = 1; - /** - *
-       * @required
-       * 
- * - * required .signalservice.DataMessage.ClosedGroupControlMessage.Type type = 1; - * @return Whether the type field is set. - */ - @java.lang.Override public boolean hasType() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-       * @required
-       * 
- * - * required .signalservice.DataMessage.ClosedGroupControlMessage.Type type = 1; - * @return The type. - */ - @java.lang.Override public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type getType() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type result = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.forNumber(type_); - return result == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.NEW : result; - } - - public static final int PUBLICKEY_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes publicKey = 2; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - * optional bytes publicKey = 2; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - - public static final int NAME_FIELD_NUMBER = 3; - @SuppressWarnings("serial") - private volatile java.lang.Object name_ = ""; - /** - * optional string name = 3; - * @return Whether the name field is set. - */ - @java.lang.Override - public boolean hasName() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - * optional string name = 3; - * @return The name. - */ - @java.lang.Override - public java.lang.String getName() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } - } - /** - * optional string name = 3; - * @return The bytes for name. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int ENCRYPTIONKEYPAIR_FIELD_NUMBER = 4; - private org.session.libsignal.protos.SignalServiceProtos.KeyPair encryptionKeyPair_; - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - * @return Whether the encryptionKeyPair field is set. - */ - @java.lang.Override - public boolean hasEncryptionKeyPair() { - return ((bitField0_ & 0x00000008) != 0); - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - * @return The encryptionKeyPair. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.KeyPair getEncryptionKeyPair() { - return encryptionKeyPair_ == null ? org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance() : encryptionKeyPair_; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder getEncryptionKeyPairOrBuilder() { - return encryptionKeyPair_ == null ? org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance() : encryptionKeyPair_; - } - - public static final int MEMBERS_FIELD_NUMBER = 5; - @SuppressWarnings("serial") - private com.google.protobuf.Internal.ProtobufList members_ = - emptyList(com.google.protobuf.ByteString.class); - /** - * repeated bytes members = 5; - * @return A list containing the members. - */ - @java.lang.Override - public java.util.List - getMembersList() { - return members_; - } - /** - * repeated bytes members = 5; - * @return The count of members. - */ - public int getMembersCount() { - return members_.size(); - } - /** - * repeated bytes members = 5; - * @param index The index of the element to return. - * @return The members at the given index. - */ - public com.google.protobuf.ByteString getMembers(int index) { - return members_.get(index); - } - - public static final int ADMINS_FIELD_NUMBER = 6; - @SuppressWarnings("serial") - private com.google.protobuf.Internal.ProtobufList admins_ = - emptyList(com.google.protobuf.ByteString.class); - /** - * repeated bytes admins = 6; - * @return A list containing the admins. - */ - @java.lang.Override - public java.util.List - getAdminsList() { - return admins_; - } - /** - * repeated bytes admins = 6; - * @return The count of admins. - */ - public int getAdminsCount() { - return admins_.size(); - } - /** - * repeated bytes admins = 6; - * @param index The index of the element to return. - * @return The admins at the given index. - */ - public com.google.protobuf.ByteString getAdmins(int index) { - return admins_.get(index); - } - - public static final int WRAPPERS_FIELD_NUMBER = 7; - @SuppressWarnings("serial") - private java.util.List wrappers_; - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - @java.lang.Override - public java.util.List getWrappersList() { - return wrappers_; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - @java.lang.Override - public java.util.List - getWrappersOrBuilderList() { - return wrappers_; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - @java.lang.Override - public int getWrappersCount() { - return wrappers_.size(); - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper getWrappers(int index) { - return wrappers_.get(index); - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapperOrBuilder getWrappersOrBuilder( - int index) { - return wrappers_.get(index); - } - - public static final int EXPIRATIONTIMER_FIELD_NUMBER = 8; - private int expirationTimer_ = 0; - /** - * optional uint32 expirationTimer = 8; - * @return Whether the expirationTimer field is set. - */ - @java.lang.Override - public boolean hasExpirationTimer() { - return ((bitField0_ & 0x00000010) != 0); - } - /** - * optional uint32 expirationTimer = 8; - * @return The expirationTimer. - */ - @java.lang.Override - public int getExpirationTimer() { - return expirationTimer_; - } - - public static final int MEMBERPRIVATEKEY_FIELD_NUMBER = 9; - private com.google.protobuf.ByteString memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes memberPrivateKey = 9; - * @return Whether the memberPrivateKey field is set. - */ - @java.lang.Override - public boolean hasMemberPrivateKey() { - return ((bitField0_ & 0x00000020) != 0); - } - /** - * optional bytes memberPrivateKey = 9; - * @return The memberPrivateKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getMemberPrivateKey() { - return memberPrivateKey_; - } - - public static final int PRIVATEKEY_FIELD_NUMBER = 10; - private com.google.protobuf.ByteString privateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes privateKey = 10; - * @return Whether the privateKey field is set. - */ - @java.lang.Override - public boolean hasPrivateKey() { - return ((bitField0_ & 0x00000040) != 0); - } - /** - * optional bytes privateKey = 10; - * @return The privateKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPrivateKey() { - return privateKey_; - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - if (!hasType()) { - memoizedIsInitialized = 0; - return false; - } - if (hasEncryptionKeyPair()) { - if (!getEncryptionKeyPair().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - for (int i = 0; i < getWrappersCount(); i++) { - if (!getWrappers(i).isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - output.writeEnum(1, type_); - } - if (((bitField0_ & 0x00000002) != 0)) { - output.writeBytes(2, publicKey_); - } - if (((bitField0_ & 0x00000004) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 3, name_); - } - if (((bitField0_ & 0x00000008) != 0)) { - output.writeMessage(4, getEncryptionKeyPair()); - } - for (int i = 0; i < members_.size(); i++) { - output.writeBytes(5, members_.get(i)); - } - for (int i = 0; i < admins_.size(); i++) { - output.writeBytes(6, admins_.get(i)); - } - for (int i = 0; i < wrappers_.size(); i++) { - output.writeMessage(7, wrappers_.get(i)); - } - if (((bitField0_ & 0x00000010) != 0)) { - output.writeUInt32(8, expirationTimer_); - } - if (((bitField0_ & 0x00000020) != 0)) { - output.writeBytes(9, memberPrivateKey_); - } - if (((bitField0_ & 0x00000040) != 0)) { - output.writeBytes(10, privateKey_); - } - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeEnumSize(1, type_); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, publicKey_); - } - if (((bitField0_ & 0x00000004) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(3, name_); - } - if (((bitField0_ & 0x00000008) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(4, getEncryptionKeyPair()); - } - { - int dataSize = 0; - for (int i = 0; i < members_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream - .computeBytesSizeNoTag(members_.get(i)); - } - size += dataSize; - size += 1 * getMembersList().size(); - } - { - int dataSize = 0; - for (int i = 0; i < admins_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream - .computeBytesSizeNoTag(admins_.get(i)); - } - size += dataSize; - size += 1 * getAdminsList().size(); - } - for (int i = 0; i < wrappers_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(7, wrappers_.get(i)); - } - if (((bitField0_ & 0x00000010) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(8, expirationTimer_); - } - if (((bitField0_ & 0x00000020) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(9, memberPrivateKey_); - } - if (((bitField0_ & 0x00000040) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(10, privateKey_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage other = (org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage) obj; - - if (hasType() != other.hasType()) return false; - if (hasType()) { - if (type_ != other.type_) return false; - } - if (hasPublicKey() != other.hasPublicKey()) return false; - if (hasPublicKey()) { - if (!getPublicKey() - .equals(other.getPublicKey())) return false; - } - if (hasName() != other.hasName()) return false; - if (hasName()) { - if (!getName() - .equals(other.getName())) return false; - } - if (hasEncryptionKeyPair() != other.hasEncryptionKeyPair()) return false; - if (hasEncryptionKeyPair()) { - if (!getEncryptionKeyPair() - .equals(other.getEncryptionKeyPair())) return false; - } - if (!getMembersList() - .equals(other.getMembersList())) return false; - if (!getAdminsList() - .equals(other.getAdminsList())) return false; - if (!getWrappersList() - .equals(other.getWrappersList())) return false; - if (hasExpirationTimer() != other.hasExpirationTimer()) return false; - if (hasExpirationTimer()) { - if (getExpirationTimer() - != other.getExpirationTimer()) return false; - } - if (hasMemberPrivateKey() != other.hasMemberPrivateKey()) return false; - if (hasMemberPrivateKey()) { - if (!getMemberPrivateKey() - .equals(other.getMemberPrivateKey())) return false; - } - if (hasPrivateKey() != other.hasPrivateKey()) return false; - if (hasPrivateKey()) { - if (!getPrivateKey() - .equals(other.getPrivateKey())) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasType()) { - hash = (37 * hash) + TYPE_FIELD_NUMBER; - hash = (53 * hash) + type_; - } - if (hasPublicKey()) { - hash = (37 * hash) + PUBLICKEY_FIELD_NUMBER; - hash = (53 * hash) + getPublicKey().hashCode(); - } - if (hasName()) { - hash = (37 * hash) + NAME_FIELD_NUMBER; - hash = (53 * hash) + getName().hashCode(); - } - if (hasEncryptionKeyPair()) { - hash = (37 * hash) + ENCRYPTIONKEYPAIR_FIELD_NUMBER; - hash = (53 * hash) + getEncryptionKeyPair().hashCode(); - } - if (getMembersCount() > 0) { - hash = (37 * hash) + MEMBERS_FIELD_NUMBER; - hash = (53 * hash) + getMembersList().hashCode(); - } - if (getAdminsCount() > 0) { - hash = (37 * hash) + ADMINS_FIELD_NUMBER; - hash = (53 * hash) + getAdminsList().hashCode(); - } - if (getWrappersCount() > 0) { - hash = (37 * hash) + WRAPPERS_FIELD_NUMBER; - hash = (53 * hash) + getWrappersList().hashCode(); - } - if (hasExpirationTimer()) { - hash = (37 * hash) + EXPIRATIONTIMER_FIELD_NUMBER; - hash = (53 * hash) + getExpirationTimer(); - } - if (hasMemberPrivateKey()) { - hash = (37 * hash) + MEMBERPRIVATEKEY_FIELD_NUMBER; - hash = (53 * hash) + getMemberPrivateKey().hashCode(); - } - if (hasPrivateKey()) { - hash = (37 * hash) + PRIVATEKEY_FIELD_NUMBER; - hash = (53 * hash) + getPrivateKey().hashCode(); - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.DataMessage.ClosedGroupControlMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.DataMessage.ClosedGroupControlMessage) - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage - .alwaysUseFieldBuilders) { - getEncryptionKeyPairFieldBuilder(); - getWrappersFieldBuilder(); - } - } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - type_ = 1; - publicKey_ = com.google.protobuf.ByteString.EMPTY; - name_ = ""; - encryptionKeyPair_ = null; - if (encryptionKeyPairBuilder_ != null) { - encryptionKeyPairBuilder_.dispose(); - encryptionKeyPairBuilder_ = null; - } - members_ = emptyList(com.google.protobuf.ByteString.class); - admins_ = emptyList(com.google.protobuf.ByteString.class); - if (wrappersBuilder_ == null) { - wrappers_ = java.util.Collections.emptyList(); - } else { - wrappers_ = null; - wrappersBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000040); - expirationTimer_ = 0; - memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - privateKey_ = com.google.protobuf.ByteString.EMPTY; - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance(); - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage build() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage result = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage(this); - buildPartialRepeatedFields(result); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; - } - - private void buildPartialRepeatedFields(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage result) { - if (wrappersBuilder_ == null) { - if (((bitField0_ & 0x00000040) != 0)) { - wrappers_ = java.util.Collections.unmodifiableList(wrappers_); - bitField0_ = (bitField0_ & ~0x00000040); - } - result.wrappers_ = wrappers_; - } else { - result.wrappers_ = wrappersBuilder_.build(); - } - } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.type_ = type_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000002) != 0)) { - result.publicKey_ = publicKey_; - to_bitField0_ |= 0x00000002; - } - if (((from_bitField0_ & 0x00000004) != 0)) { - result.name_ = name_; - to_bitField0_ |= 0x00000004; - } - if (((from_bitField0_ & 0x00000008) != 0)) { - result.encryptionKeyPair_ = encryptionKeyPairBuilder_ == null - ? encryptionKeyPair_ - : encryptionKeyPairBuilder_.build(); - to_bitField0_ |= 0x00000008; - } - if (((from_bitField0_ & 0x00000010) != 0)) { - members_.makeImmutable(); - result.members_ = members_; - } - if (((from_bitField0_ & 0x00000020) != 0)) { - admins_.makeImmutable(); - result.admins_ = admins_; - } - if (((from_bitField0_ & 0x00000080) != 0)) { - result.expirationTimer_ = expirationTimer_; - to_bitField0_ |= 0x00000010; - } - if (((from_bitField0_ & 0x00000100) != 0)) { - result.memberPrivateKey_ = memberPrivateKey_; - to_bitField0_ |= 0x00000020; - } - if (((from_bitField0_ & 0x00000200) != 0)) { - result.privateKey_ = privateKey_; - to_bitField0_ |= 0x00000040; - } - result.bitField0_ |= to_bitField0_; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance()) return this; - if (other.hasType()) { - setType(other.getType()); - } - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasName()) { - name_ = other.name_; - bitField0_ |= 0x00000004; - onChanged(); - } - if (other.hasEncryptionKeyPair()) { - mergeEncryptionKeyPair(other.getEncryptionKeyPair()); - } - if (!other.members_.isEmpty()) { - if (members_.isEmpty()) { - members_ = other.members_; - members_.makeImmutable(); - bitField0_ |= 0x00000010; - } else { - ensureMembersIsMutable(); - members_.addAll(other.members_); - } - onChanged(); - } - if (!other.admins_.isEmpty()) { - if (admins_.isEmpty()) { - admins_ = other.admins_; - admins_.makeImmutable(); - bitField0_ |= 0x00000020; - } else { - ensureAdminsIsMutable(); - admins_.addAll(other.admins_); - } - onChanged(); - } - if (wrappersBuilder_ == null) { - if (!other.wrappers_.isEmpty()) { - if (wrappers_.isEmpty()) { - wrappers_ = other.wrappers_; - bitField0_ = (bitField0_ & ~0x00000040); - } else { - ensureWrappersIsMutable(); - wrappers_.addAll(other.wrappers_); - } - onChanged(); - } - } else { - if (!other.wrappers_.isEmpty()) { - if (wrappersBuilder_.isEmpty()) { - wrappersBuilder_.dispose(); - wrappersBuilder_ = null; - wrappers_ = other.wrappers_; - bitField0_ = (bitField0_ & ~0x00000040); - wrappersBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? - getWrappersFieldBuilder() : null; - } else { - wrappersBuilder_.addAllMessages(other.wrappers_); - } - } - } - if (other.hasExpirationTimer()) { - setExpirationTimer(other.getExpirationTimer()); - } - if (other.hasMemberPrivateKey()) { - setMemberPrivateKey(other.getMemberPrivateKey()); - } - if (other.hasPrivateKey()) { - setPrivateKey(other.getPrivateKey()); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - if (!hasType()) { - return false; - } - if (hasEncryptionKeyPair()) { - if (!getEncryptionKeyPair().isInitialized()) { - return false; - } - } - for (int i = 0; i < getWrappersCount(); i++) { - if (!getWrappers(i).isInitialized()) { - return false; - } - } - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 8: { - int tmpRaw = input.readEnum(); - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type tmpValue = - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.forNumber(tmpRaw); - if (tmpValue == null) { - mergeUnknownVarintField(1, tmpRaw); - } else { - type_ = tmpRaw; - bitField0_ |= 0x00000001; - } - break; - } // case 8 - case 18: { - publicKey_ = input.readBytes(); - bitField0_ |= 0x00000002; - break; - } // case 18 - case 26: { - name_ = input.readBytes(); - bitField0_ |= 0x00000004; - break; - } // case 26 - case 34: { - input.readMessage( - getEncryptionKeyPairFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000008; - break; - } // case 34 - case 42: { - com.google.protobuf.ByteString v = input.readBytes(); - ensureMembersIsMutable(); - members_.add(v); - break; - } // case 42 - case 50: { - com.google.protobuf.ByteString v = input.readBytes(); - ensureAdminsIsMutable(); - admins_.add(v); - break; - } // case 50 - case 58: { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper m = - input.readMessage( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.parser(), - extensionRegistry); - if (wrappersBuilder_ == null) { - ensureWrappersIsMutable(); - wrappers_.add(m); - } else { - wrappersBuilder_.addMessage(m); - } - break; - } // case 58 - case 64: { - expirationTimer_ = input.readUInt32(); - bitField0_ |= 0x00000080; - break; - } // case 64 - case 74: { - memberPrivateKey_ = input.readBytes(); - bitField0_ |= 0x00000100; - break; - } // case 74 - case 82: { - privateKey_ = input.readBytes(); - bitField0_ |= 0x00000200; - break; - } // case 82 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; - } - private int bitField0_; - - private int type_ = 1; - /** - *
-         * @required
-         * 
- * - * required .signalservice.DataMessage.ClosedGroupControlMessage.Type type = 1; - * @return Whether the type field is set. - */ - @java.lang.Override public boolean hasType() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-         * @required
-         * 
- * - * required .signalservice.DataMessage.ClosedGroupControlMessage.Type type = 1; - * @return The type. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type getType() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type result = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.forNumber(type_); - return result == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.NEW : result; - } - /** - *
-         * @required
-         * 
- * - * required .signalservice.DataMessage.ClosedGroupControlMessage.Type type = 1; - * @param value The type to set. - * @return This builder for chaining. - */ - public Builder setType(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - type_ = value.getNumber(); - onChanged(); - return this; - } - /** - *
-         * @required
-         * 
- * - * required .signalservice.DataMessage.ClosedGroupControlMessage.Type type = 1; - * @return This builder for chaining. - */ - public Builder clearType() { - bitField0_ = (bitField0_ & ~0x00000001); - type_ = 1; - onChanged(); - return this; - } - - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes publicKey = 2; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - * optional bytes publicKey = 2; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - /** - * optional bytes publicKey = 2; - * @param value The publicKey to set. - * @return This builder for chaining. - */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - publicKey_ = value; - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - /** - * optional bytes publicKey = 2; - * @return This builder for chaining. - */ - public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000002); - publicKey_ = getDefaultInstance().getPublicKey(); - onChanged(); - return this; - } - - private java.lang.Object name_ = ""; - /** - * optional string name = 3; - * @return Whether the name field is set. - */ - public boolean hasName() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - * optional string name = 3; - * @return The name. - */ - public java.lang.String getName() { - java.lang.Object ref = name_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string name = 3; - * @return The bytes for name. - */ - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string name = 3; - * @param value The name to set. - * @return This builder for chaining. - */ - public Builder setName( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - name_ = value; - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - /** - * optional string name = 3; - * @return This builder for chaining. - */ - public Builder clearName() { - name_ = getDefaultInstance().getName(); - bitField0_ = (bitField0_ & ~0x00000004); - onChanged(); - return this; - } - /** - * optional string name = 3; - * @param value The bytes for name to set. - * @return This builder for chaining. - */ - public Builder setNameBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - name_ = value; - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - - private org.session.libsignal.protos.SignalServiceProtos.KeyPair encryptionKeyPair_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.KeyPair, org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder, org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder> encryptionKeyPairBuilder_; - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - * @return Whether the encryptionKeyPair field is set. - */ - public boolean hasEncryptionKeyPair() { - return ((bitField0_ & 0x00000008) != 0); - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - * @return The encryptionKeyPair. - */ - public org.session.libsignal.protos.SignalServiceProtos.KeyPair getEncryptionKeyPair() { - if (encryptionKeyPairBuilder_ == null) { - return encryptionKeyPair_ == null ? org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance() : encryptionKeyPair_; - } else { - return encryptionKeyPairBuilder_.getMessage(); - } - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - */ - public Builder setEncryptionKeyPair(org.session.libsignal.protos.SignalServiceProtos.KeyPair value) { - if (encryptionKeyPairBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - encryptionKeyPair_ = value; - } else { - encryptionKeyPairBuilder_.setMessage(value); - } - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - */ - public Builder setEncryptionKeyPair( - org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder builderForValue) { - if (encryptionKeyPairBuilder_ == null) { - encryptionKeyPair_ = builderForValue.build(); - } else { - encryptionKeyPairBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - */ - public Builder mergeEncryptionKeyPair(org.session.libsignal.protos.SignalServiceProtos.KeyPair value) { - if (encryptionKeyPairBuilder_ == null) { - if (((bitField0_ & 0x00000008) != 0) && - encryptionKeyPair_ != null && - encryptionKeyPair_ != org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance()) { - getEncryptionKeyPairBuilder().mergeFrom(value); - } else { - encryptionKeyPair_ = value; - } - } else { - encryptionKeyPairBuilder_.mergeFrom(value); - } - if (encryptionKeyPair_ != null) { - bitField0_ |= 0x00000008; - onChanged(); - } - return this; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - */ - public Builder clearEncryptionKeyPair() { - bitField0_ = (bitField0_ & ~0x00000008); - encryptionKeyPair_ = null; - if (encryptionKeyPairBuilder_ != null) { - encryptionKeyPairBuilder_.dispose(); - encryptionKeyPairBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - */ - public org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder getEncryptionKeyPairBuilder() { - bitField0_ |= 0x00000008; - onChanged(); - return getEncryptionKeyPairFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - */ - public org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder getEncryptionKeyPairOrBuilder() { - if (encryptionKeyPairBuilder_ != null) { - return encryptionKeyPairBuilder_.getMessageOrBuilder(); - } else { - return encryptionKeyPair_ == null ? - org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance() : encryptionKeyPair_; - } - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 4; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.KeyPair, org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder, org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder> - getEncryptionKeyPairFieldBuilder() { - if (encryptionKeyPairBuilder_ == null) { - encryptionKeyPairBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.KeyPair, org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder, org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder>( - getEncryptionKeyPair(), - getParentForChildren(), - isClean()); - encryptionKeyPair_ = null; - } - return encryptionKeyPairBuilder_; - } - - private com.google.protobuf.Internal.ProtobufList members_ = emptyList(com.google.protobuf.ByteString.class); - private void ensureMembersIsMutable() { - if (!members_.isModifiable()) { - members_ = makeMutableCopy(members_); - } - bitField0_ |= 0x00000010; - } - /** - * repeated bytes members = 5; - * @return A list containing the members. - */ - public java.util.List - getMembersList() { - members_.makeImmutable(); - return members_; - } - /** - * repeated bytes members = 5; - * @return The count of members. - */ - public int getMembersCount() { - return members_.size(); - } - /** - * repeated bytes members = 5; - * @param index The index of the element to return. - * @return The members at the given index. - */ - public com.google.protobuf.ByteString getMembers(int index) { - return members_.get(index); - } - /** - * repeated bytes members = 5; - * @param index The index to set the value at. - * @param value The members to set. - * @return This builder for chaining. - */ - public Builder setMembers( - int index, com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureMembersIsMutable(); - members_.set(index, value); - bitField0_ |= 0x00000010; - onChanged(); - return this; - } - /** - * repeated bytes members = 5; - * @param value The members to add. - * @return This builder for chaining. - */ - public Builder addMembers(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureMembersIsMutable(); - members_.add(value); - bitField0_ |= 0x00000010; - onChanged(); - return this; - } - /** - * repeated bytes members = 5; - * @param values The members to add. - * @return This builder for chaining. - */ - public Builder addAllMembers( - java.lang.Iterable values) { - ensureMembersIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, members_); - bitField0_ |= 0x00000010; - onChanged(); - return this; - } - /** - * repeated bytes members = 5; - * @return This builder for chaining. - */ - public Builder clearMembers() { - members_ = emptyList(com.google.protobuf.ByteString.class); - bitField0_ = (bitField0_ & ~0x00000010); - onChanged(); - return this; - } - - private com.google.protobuf.Internal.ProtobufList admins_ = emptyList(com.google.protobuf.ByteString.class); - private void ensureAdminsIsMutable() { - if (!admins_.isModifiable()) { - admins_ = makeMutableCopy(admins_); - } - bitField0_ |= 0x00000020; - } - /** - * repeated bytes admins = 6; - * @return A list containing the admins. - */ - public java.util.List - getAdminsList() { - admins_.makeImmutable(); - return admins_; - } - /** - * repeated bytes admins = 6; - * @return The count of admins. - */ - public int getAdminsCount() { - return admins_.size(); - } - /** - * repeated bytes admins = 6; - * @param index The index of the element to return. - * @return The admins at the given index. - */ - public com.google.protobuf.ByteString getAdmins(int index) { - return admins_.get(index); - } - /** - * repeated bytes admins = 6; - * @param index The index to set the value at. - * @param value The admins to set. - * @return This builder for chaining. - */ - public Builder setAdmins( - int index, com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureAdminsIsMutable(); - admins_.set(index, value); - bitField0_ |= 0x00000020; - onChanged(); - return this; - } - /** - * repeated bytes admins = 6; - * @param value The admins to add. - * @return This builder for chaining. - */ - public Builder addAdmins(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureAdminsIsMutable(); - admins_.add(value); - bitField0_ |= 0x00000020; - onChanged(); - return this; - } - /** - * repeated bytes admins = 6; - * @param values The admins to add. - * @return This builder for chaining. - */ - public Builder addAllAdmins( - java.lang.Iterable values) { - ensureAdminsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, admins_); - bitField0_ |= 0x00000020; - onChanged(); - return this; - } - /** - * repeated bytes admins = 6; - * @return This builder for chaining. - */ - public Builder clearAdmins() { - admins_ = emptyList(com.google.protobuf.ByteString.class); - bitField0_ = (bitField0_ & ~0x00000020); - onChanged(); - return this; - } - - private java.util.List wrappers_ = - java.util.Collections.emptyList(); - private void ensureWrappersIsMutable() { - if (!((bitField0_ & 0x00000040) != 0)) { - wrappers_ = new java.util.ArrayList(wrappers_); - bitField0_ |= 0x00000040; - } - } - - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapperOrBuilder> wrappersBuilder_; - - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public java.util.List getWrappersList() { - if (wrappersBuilder_ == null) { - return java.util.Collections.unmodifiableList(wrappers_); - } else { - return wrappersBuilder_.getMessageList(); - } - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public int getWrappersCount() { - if (wrappersBuilder_ == null) { - return wrappers_.size(); - } else { - return wrappersBuilder_.getCount(); - } - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper getWrappers(int index) { - if (wrappersBuilder_ == null) { - return wrappers_.get(index); - } else { - return wrappersBuilder_.getMessage(index); - } - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public Builder setWrappers( - int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper value) { - if (wrappersBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureWrappersIsMutable(); - wrappers_.set(index, value); - onChanged(); - } else { - wrappersBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public Builder setWrappers( - int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder builderForValue) { - if (wrappersBuilder_ == null) { - ensureWrappersIsMutable(); - wrappers_.set(index, builderForValue.build()); - onChanged(); - } else { - wrappersBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public Builder addWrappers(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper value) { - if (wrappersBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureWrappersIsMutable(); - wrappers_.add(value); - onChanged(); - } else { - wrappersBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public Builder addWrappers( - int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper value) { - if (wrappersBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureWrappersIsMutable(); - wrappers_.add(index, value); - onChanged(); - } else { - wrappersBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public Builder addWrappers( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder builderForValue) { - if (wrappersBuilder_ == null) { - ensureWrappersIsMutable(); - wrappers_.add(builderForValue.build()); - onChanged(); - } else { - wrappersBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public Builder addWrappers( - int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder builderForValue) { - if (wrappersBuilder_ == null) { - ensureWrappersIsMutable(); - wrappers_.add(index, builderForValue.build()); - onChanged(); - } else { - wrappersBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public Builder addAllWrappers( - java.lang.Iterable values) { - if (wrappersBuilder_ == null) { - ensureWrappersIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, wrappers_); - onChanged(); - } else { - wrappersBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public Builder clearWrappers() { - if (wrappersBuilder_ == null) { - wrappers_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000040); - onChanged(); - } else { - wrappersBuilder_.clear(); - } - return this; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public Builder removeWrappers(int index) { - if (wrappersBuilder_ == null) { - ensureWrappersIsMutable(); - wrappers_.remove(index); - onChanged(); - } else { - wrappersBuilder_.remove(index); - } - return this; - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder getWrappersBuilder( - int index) { - return getWrappersFieldBuilder().getBuilder(index); - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapperOrBuilder getWrappersOrBuilder( - int index) { - if (wrappersBuilder_ == null) { - return wrappers_.get(index); } else { - return wrappersBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public java.util.List - getWrappersOrBuilderList() { - if (wrappersBuilder_ != null) { - return wrappersBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(wrappers_); - } - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder addWrappersBuilder() { - return getWrappersFieldBuilder().addBuilder( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.getDefaultInstance()); - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder addWrappersBuilder( - int index) { - return getWrappersFieldBuilder().addBuilder( - index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.getDefaultInstance()); - } - /** - * repeated .signalservice.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; - */ - public java.util.List - getWrappersBuilderList() { - return getWrappersFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapperOrBuilder> - getWrappersFieldBuilder() { - if (wrappersBuilder_ == null) { - wrappersBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapperOrBuilder>( - wrappers_, - ((bitField0_ & 0x00000040) != 0), - getParentForChildren(), - isClean()); - wrappers_ = null; - } - return wrappersBuilder_; - } - - private int expirationTimer_ ; - /** - * optional uint32 expirationTimer = 8; - * @return Whether the expirationTimer field is set. - */ - @java.lang.Override - public boolean hasExpirationTimer() { - return ((bitField0_ & 0x00000080) != 0); - } - /** - * optional uint32 expirationTimer = 8; - * @return The expirationTimer. - */ - @java.lang.Override - public int getExpirationTimer() { - return expirationTimer_; - } - /** - * optional uint32 expirationTimer = 8; - * @param value The expirationTimer to set. - * @return This builder for chaining. - */ - public Builder setExpirationTimer(int value) { - - expirationTimer_ = value; - bitField0_ |= 0x00000080; - onChanged(); - return this; - } - /** - * optional uint32 expirationTimer = 8; - * @return This builder for chaining. - */ - public Builder clearExpirationTimer() { - bitField0_ = (bitField0_ & ~0x00000080); - expirationTimer_ = 0; - onChanged(); - return this; - } - - private com.google.protobuf.ByteString memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes memberPrivateKey = 9; - * @return Whether the memberPrivateKey field is set. - */ - @java.lang.Override - public boolean hasMemberPrivateKey() { - return ((bitField0_ & 0x00000100) != 0); - } - /** - * optional bytes memberPrivateKey = 9; - * @return The memberPrivateKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getMemberPrivateKey() { - return memberPrivateKey_; - } - /** - * optional bytes memberPrivateKey = 9; - * @param value The memberPrivateKey to set. - * @return This builder for chaining. - */ - public Builder setMemberPrivateKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - memberPrivateKey_ = value; - bitField0_ |= 0x00000100; - onChanged(); - return this; - } - /** - * optional bytes memberPrivateKey = 9; - * @return This builder for chaining. - */ - public Builder clearMemberPrivateKey() { - bitField0_ = (bitField0_ & ~0x00000100); - memberPrivateKey_ = getDefaultInstance().getMemberPrivateKey(); - onChanged(); - return this; - } - - private com.google.protobuf.ByteString privateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes privateKey = 10; - * @return Whether the privateKey field is set. - */ - @java.lang.Override - public boolean hasPrivateKey() { - return ((bitField0_ & 0x00000200) != 0); - } - /** - * optional bytes privateKey = 10; - * @return The privateKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPrivateKey() { - return privateKey_; - } - /** - * optional bytes privateKey = 10; - * @param value The privateKey to set. - * @return This builder for chaining. - */ - public Builder setPrivateKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - privateKey_ = value; - bitField0_ |= 0x00000200; - onChanged(); - return this; - } - /** - * optional bytes privateKey = 10; - * @return This builder for chaining. - */ - public Builder clearPrivateKey() { - bitField0_ = (bitField0_ & ~0x00000200); - privateKey_ = getDefaultInstance().getPrivateKey(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.DataMessage.ClosedGroupControlMessage) - } - - // @@protoc_insertion_point(class_scope:signalservice.DataMessage.ClosedGroupControlMessage) - private static final org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public ClosedGroupControlMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface ReactionOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.DataMessage.Reaction) - com.google.protobuf.MessageOrBuilder { - - /** - *
-       * @required
-       * 
- * - * required uint64 id = 1; - * @return Whether the id field is set. - */ - boolean hasId(); - /** - *
-       * @required
-       * 
- * - * required uint64 id = 1; - * @return The id. - */ - long getId(); - - /** - *
-       * @required
-       * 
- * - * required string author = 2; - * @return Whether the author field is set. - */ - boolean hasAuthor(); - /** - *
-       * @required
-       * 
- * - * required string author = 2; - * @return The author. - */ - java.lang.String getAuthor(); - /** - *
-       * @required
-       * 
- * - * required string author = 2; - * @return The bytes for author. - */ - com.google.protobuf.ByteString - getAuthorBytes(); - - /** - * optional string emoji = 3; - * @return Whether the emoji field is set. - */ - boolean hasEmoji(); - /** - * optional string emoji = 3; - * @return The emoji. - */ - java.lang.String getEmoji(); - /** - * optional string emoji = 3; - * @return The bytes for emoji. - */ - com.google.protobuf.ByteString - getEmojiBytes(); - - /** - *
-       * @required
-       * 
- * - * required .signalservice.DataMessage.Reaction.Action action = 4; - * @return Whether the action field is set. - */ - boolean hasAction(); - /** - *
-       * @required
-       * 
- * - * required .signalservice.DataMessage.Reaction.Action action = 4; - * @return The action. - */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action getAction(); - } - /** - * Protobuf type {@code signalservice.DataMessage.Reaction} - */ - public static final class Reaction extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.DataMessage.Reaction) - ReactionOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - Reaction.class.getName()); - } - // Use Reaction.newBuilder() to construct. - private Reaction(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private Reaction() { - author_ = ""; - emoji_ = ""; - action_ = 0; - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder.class); - } - - /** - * Protobuf enum {@code signalservice.DataMessage.Reaction.Action} - */ - public enum Action - implements com.google.protobuf.ProtocolMessageEnum { - /** - * REACT = 0; - */ - REACT(0), - /** - * REMOVE = 1; - */ - REMOVE(1), - ; - - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - Action.class.getName()); - } - /** - * REACT = 0; - */ - public static final int REACT_VALUE = 0; - /** - * REMOVE = 1; - */ - public static final int REMOVE_VALUE = 1; - - - public final int getNumber() { - return value; - } - - /** - * @param value The numeric wire value of the corresponding enum entry. - * @return The enum associated with the given numeric wire value. - * @deprecated Use {@link #forNumber(int)} instead. - */ - @java.lang.Deprecated - public static Action valueOf(int value) { - return forNumber(value); - } - - /** - * @param value The numeric wire value of the corresponding enum entry. - * @return The enum associated with the given numeric wire value. - */ - public static Action forNumber(int value) { - switch (value) { - case 0: return REACT; - case 1: return REMOVE; - default: return null; - } - } - - public static com.google.protobuf.Internal.EnumLiteMap - internalGetValueMap() { - return internalValueMap; - } - private static final com.google.protobuf.Internal.EnumLiteMap< - Action> internalValueMap = - new com.google.protobuf.Internal.EnumLiteMap() { - public Action findValueByNumber(int number) { - return Action.forNumber(number); - } - }; - - public final com.google.protobuf.Descriptors.EnumValueDescriptor - getValueDescriptor() { - return getDescriptor().getValues().get(ordinal()); - } - public final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptorForType() { - return getDescriptor(); - } - public static final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDescriptor().getEnumTypes().get(0); - } - - private static final Action[] VALUES = values(); - - public static Action valueOf( - com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException( - "EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } - - private final int value; - - private Action(int value) { - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:signalservice.DataMessage.Reaction.Action) - } - - private int bitField0_; - public static final int ID_FIELD_NUMBER = 1; - private long id_ = 0L; - /** - *
-       * @required
-       * 
- * - * required uint64 id = 1; - * @return Whether the id field is set. - */ - @java.lang.Override - public boolean hasId() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-       * @required
-       * 
- * - * required uint64 id = 1; - * @return The id. - */ - @java.lang.Override - public long getId() { - return id_; - } - - public static final int AUTHOR_FIELD_NUMBER = 2; - @SuppressWarnings("serial") - private volatile java.lang.Object author_ = ""; - /** - *
-       * @required
-       * 
- * - * required string author = 2; - * @return Whether the author field is set. - */ - @java.lang.Override - public boolean hasAuthor() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-       * @required
-       * 
- * - * required string author = 2; - * @return The author. - */ - @java.lang.Override - public java.lang.String getAuthor() { - java.lang.Object ref = author_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - author_ = s; - } - return s; - } - } - /** - *
-       * @required
-       * 
- * - * required string author = 2; - * @return The bytes for author. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getAuthorBytes() { - java.lang.Object ref = author_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - author_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int EMOJI_FIELD_NUMBER = 3; - @SuppressWarnings("serial") - private volatile java.lang.Object emoji_ = ""; - /** - * optional string emoji = 3; - * @return Whether the emoji field is set. - */ - @java.lang.Override - public boolean hasEmoji() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - * optional string emoji = 3; - * @return The emoji. - */ - @java.lang.Override - public java.lang.String getEmoji() { - java.lang.Object ref = emoji_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - emoji_ = s; - } - return s; - } - } - /** - * optional string emoji = 3; - * @return The bytes for emoji. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getEmojiBytes() { - java.lang.Object ref = emoji_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - emoji_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int ACTION_FIELD_NUMBER = 4; - private int action_ = 0; - /** - *
-       * @required
-       * 
- * - * required .signalservice.DataMessage.Reaction.Action action = 4; - * @return Whether the action field is set. - */ - @java.lang.Override public boolean hasAction() { - return ((bitField0_ & 0x00000008) != 0); - } - /** - *
-       * @required
-       * 
- * - * required .signalservice.DataMessage.Reaction.Action action = 4; - * @return The action. - */ - @java.lang.Override public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action getAction() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action result = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.forNumber(action_); - return result == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.REACT : result; - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - if (!hasId()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasAuthor()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasAction()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - output.writeUInt64(1, id_); - } - if (((bitField0_ & 0x00000002) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 2, author_); - } - if (((bitField0_ & 0x00000004) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 3, emoji_); - } - if (((bitField0_ & 0x00000008) != 0)) { - output.writeEnum(4, action_); - } - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt64Size(1, id_); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(2, author_); - } - if (((bitField0_ & 0x00000004) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(3, emoji_); - } - if (((bitField0_ & 0x00000008) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeEnumSize(4, action_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction other = (org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction) obj; - - if (hasId() != other.hasId()) return false; - if (hasId()) { - if (getId() - != other.getId()) return false; - } - if (hasAuthor() != other.hasAuthor()) return false; - if (hasAuthor()) { - if (!getAuthor() - .equals(other.getAuthor())) return false; - } - if (hasEmoji() != other.hasEmoji()) return false; - if (hasEmoji()) { - if (!getEmoji() - .equals(other.getEmoji())) return false; - } - if (hasAction() != other.hasAction()) return false; - if (hasAction()) { - if (action_ != other.action_) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasId()) { - hash = (37 * hash) + ID_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getId()); - } - if (hasAuthor()) { - hash = (37 * hash) + AUTHOR_FIELD_NUMBER; - hash = (53 * hash) + getAuthor().hashCode(); - } - if (hasEmoji()) { - hash = (37 * hash) + EMOJI_FIELD_NUMBER; - hash = (53 * hash) + getEmoji().hashCode(); - } - if (hasAction()) { - hash = (37 * hash) + ACTION_FIELD_NUMBER; - hash = (53 * hash) + action_; - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.DataMessage.Reaction} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.DataMessage.Reaction) - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.newBuilder() - private Builder() { - - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - - } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - id_ = 0L; - author_ = ""; - emoji_ = ""; - action_ = 0; - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_Reaction_descriptor; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance(); - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction build() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction result = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction(this); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; - } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.id_ = id_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000002) != 0)) { - result.author_ = author_; - to_bitField0_ |= 0x00000002; - } - if (((from_bitField0_ & 0x00000004) != 0)) { - result.emoji_ = emoji_; - to_bitField0_ |= 0x00000004; - } - if (((from_bitField0_ & 0x00000008) != 0)) { - result.action_ = action_; - to_bitField0_ |= 0x00000008; - } - result.bitField0_ |= to_bitField0_; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance()) return this; - if (other.hasId()) { - setId(other.getId()); - } - if (other.hasAuthor()) { - author_ = other.author_; - bitField0_ |= 0x00000002; - onChanged(); - } - if (other.hasEmoji()) { - emoji_ = other.emoji_; - bitField0_ |= 0x00000004; - onChanged(); - } - if (other.hasAction()) { - setAction(other.getAction()); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - if (!hasId()) { - return false; - } - if (!hasAuthor()) { - return false; - } - if (!hasAction()) { - return false; - } - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 8: { - id_ = input.readUInt64(); - bitField0_ |= 0x00000001; - break; - } // case 8 - case 18: { - author_ = input.readBytes(); - bitField0_ |= 0x00000002; - break; - } // case 18 - case 26: { - emoji_ = input.readBytes(); - bitField0_ |= 0x00000004; - break; - } // case 26 - case 32: { - int tmpRaw = input.readEnum(); - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action tmpValue = - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.forNumber(tmpRaw); - if (tmpValue == null) { - mergeUnknownVarintField(4, tmpRaw); - } else { - action_ = tmpRaw; - bitField0_ |= 0x00000008; - } - break; - } // case 32 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; - } - private int bitField0_; - - private long id_ ; - /** - *
-         * @required
-         * 
- * - * required uint64 id = 1; - * @return Whether the id field is set. - */ - @java.lang.Override - public boolean hasId() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-         * @required
-         * 
- * - * required uint64 id = 1; - * @return The id. - */ - @java.lang.Override - public long getId() { - return id_; - } - /** - *
-         * @required
-         * 
- * - * required uint64 id = 1; - * @param value The id to set. - * @return This builder for chaining. - */ - public Builder setId(long value) { - - id_ = value; - bitField0_ |= 0x00000001; - onChanged(); - return this; - } - /** - *
-         * @required
-         * 
- * - * required uint64 id = 1; - * @return This builder for chaining. - */ - public Builder clearId() { - bitField0_ = (bitField0_ & ~0x00000001); - id_ = 0L; - onChanged(); - return this; - } - - private java.lang.Object author_ = ""; - /** - *
-         * @required
-         * 
- * - * required string author = 2; - * @return Whether the author field is set. - */ - public boolean hasAuthor() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-         * @required
-         * 
- * - * required string author = 2; - * @return The author. - */ - public java.lang.String getAuthor() { - java.lang.Object ref = author_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - author_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - /** - *
-         * @required
-         * 
- * - * required string author = 2; - * @return The bytes for author. - */ - public com.google.protobuf.ByteString - getAuthorBytes() { - java.lang.Object ref = author_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - author_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - *
-         * @required
-         * 
- * - * required string author = 2; - * @param value The author to set. - * @return This builder for chaining. - */ - public Builder setAuthor( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - author_ = value; - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - /** - *
-         * @required
-         * 
- * - * required string author = 2; - * @return This builder for chaining. - */ - public Builder clearAuthor() { - author_ = getDefaultInstance().getAuthor(); - bitField0_ = (bitField0_ & ~0x00000002); - onChanged(); - return this; - } - /** - *
-         * @required
-         * 
- * - * required string author = 2; - * @param value The bytes for author to set. - * @return This builder for chaining. - */ - public Builder setAuthorBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - author_ = value; - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - - private java.lang.Object emoji_ = ""; - /** - * optional string emoji = 3; - * @return Whether the emoji field is set. - */ - public boolean hasEmoji() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - * optional string emoji = 3; - * @return The emoji. - */ - public java.lang.String getEmoji() { - java.lang.Object ref = emoji_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - emoji_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string emoji = 3; - * @return The bytes for emoji. - */ - public com.google.protobuf.ByteString - getEmojiBytes() { - java.lang.Object ref = emoji_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - emoji_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string emoji = 3; - * @param value The emoji to set. - * @return This builder for chaining. - */ - public Builder setEmoji( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - emoji_ = value; - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - /** - * optional string emoji = 3; - * @return This builder for chaining. - */ - public Builder clearEmoji() { - emoji_ = getDefaultInstance().getEmoji(); - bitField0_ = (bitField0_ & ~0x00000004); - onChanged(); - return this; - } - /** - * optional string emoji = 3; - * @param value The bytes for emoji to set. - * @return This builder for chaining. - */ - public Builder setEmojiBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - emoji_ = value; - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - - private int action_ = 0; - /** - *
-         * @required
-         * 
- * - * required .signalservice.DataMessage.Reaction.Action action = 4; - * @return Whether the action field is set. - */ - @java.lang.Override public boolean hasAction() { - return ((bitField0_ & 0x00000008) != 0); - } - /** - *
-         * @required
-         * 
- * - * required .signalservice.DataMessage.Reaction.Action action = 4; - * @return The action. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action getAction() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action result = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.forNumber(action_); - return result == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.REACT : result; - } - /** - *
-         * @required
-         * 
- * - * required .signalservice.DataMessage.Reaction.Action action = 4; - * @param value The action to set. - * @return This builder for chaining. - */ - public Builder setAction(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000008; - action_ = value.getNumber(); - onChanged(); - return this; - } - /** - *
-         * @required
-         * 
- * - * required .signalservice.DataMessage.Reaction.Action action = 4; - * @return This builder for chaining. - */ - public Builder clearAction() { - bitField0_ = (bitField0_ & ~0x00000008); - action_ = 0; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.DataMessage.Reaction) - } - - // @@protoc_insertion_point(class_scope:signalservice.DataMessage.Reaction) - private static final org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public Reaction parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - private int bitField0_; - public static final int BODY_FIELD_NUMBER = 1; - @SuppressWarnings("serial") - private volatile java.lang.Object body_ = ""; - /** - * optional string body = 1; - * @return Whether the body field is set. - */ - @java.lang.Override - public boolean hasBody() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - * optional string body = 1; - * @return The body. - */ - @java.lang.Override - public java.lang.String getBody() { - java.lang.Object ref = body_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - body_ = s; - } - return s; - } - } - /** - * optional string body = 1; - * @return The bytes for body. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getBodyBytes() { - java.lang.Object ref = body_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - body_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int ATTACHMENTS_FIELD_NUMBER = 2; - @SuppressWarnings("serial") - private java.util.List attachments_; - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - @java.lang.Override - public java.util.List getAttachmentsList() { - return attachments_; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - @java.lang.Override - public java.util.List - getAttachmentsOrBuilderList() { - return attachments_; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - @java.lang.Override - public int getAttachmentsCount() { - return attachments_.size(); - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAttachments(int index) { - return attachments_.get(index); - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAttachmentsOrBuilder( - int index) { - return attachments_.get(index); - } - - public static final int FLAGS_FIELD_NUMBER = 4; - private int flags_ = 0; - /** - * optional uint32 flags = 4; - * @return Whether the flags field is set. - */ - @java.lang.Override - public boolean hasFlags() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - * optional uint32 flags = 4; - * @return The flags. - */ - @java.lang.Override - public int getFlags() { - return flags_; - } - - public static final int EXPIRETIMER_FIELD_NUMBER = 5; - private int expireTimer_ = 0; - /** - * optional uint32 expireTimer = 5; - * @return Whether the expireTimer field is set. - */ - @java.lang.Override - public boolean hasExpireTimer() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - * optional uint32 expireTimer = 5; - * @return The expireTimer. - */ - @java.lang.Override - public int getExpireTimer() { - return expireTimer_; - } - - public static final int PROFILEKEY_FIELD_NUMBER = 6; - private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes profileKey = 6; - * @return Whether the profileKey field is set. - */ - @java.lang.Override - public boolean hasProfileKey() { - return ((bitField0_ & 0x00000008) != 0); - } - /** - * optional bytes profileKey = 6; - * @return The profileKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getProfileKey() { - return profileKey_; - } - - public static final int TIMESTAMP_FIELD_NUMBER = 7; - private long timestamp_ = 0L; - /** - * optional uint64 timestamp = 7; - * @return Whether the timestamp field is set. - */ - @java.lang.Override - public boolean hasTimestamp() { - return ((bitField0_ & 0x00000010) != 0); - } - /** - * optional uint64 timestamp = 7; - * @return The timestamp. - */ - @java.lang.Override - public long getTimestamp() { - return timestamp_; - } - - public static final int QUOTE_FIELD_NUMBER = 8; - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote quote_; - /** - * optional .signalservice.DataMessage.Quote quote = 8; - * @return Whether the quote field is set. - */ - @java.lang.Override - public boolean hasQuote() { - return ((bitField0_ & 0x00000020) != 0); - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - * @return The quote. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote getQuote() { - return quote_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance() : quote_; - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder getQuoteOrBuilder() { - return quote_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance() : quote_; - } - - public static final int PREVIEW_FIELD_NUMBER = 10; - @SuppressWarnings("serial") - private java.util.List preview_; - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - @java.lang.Override - public java.util.List getPreviewList() { - return preview_; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - @java.lang.Override - public java.util.List - getPreviewOrBuilderList() { - return preview_; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - @java.lang.Override - public int getPreviewCount() { - return preview_.size(); - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview getPreview(int index) { - return preview_.get(index); - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder getPreviewOrBuilder( - int index) { - return preview_.get(index); - } - - public static final int REACTION_FIELD_NUMBER = 11; - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction reaction_; - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - * @return Whether the reaction field is set. - */ - @java.lang.Override - public boolean hasReaction() { - return ((bitField0_ & 0x00000040) != 0); - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - * @return The reaction. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getReaction() { - return reaction_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance() : reaction_; - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder getReactionOrBuilder() { - return reaction_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance() : reaction_; - } - - public static final int PROFILE_FIELD_NUMBER = 101; - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - * @return Whether the profile field is set. - */ - @java.lang.Override - public boolean hasProfile() { - return ((bitField0_ & 0x00000080) != 0); - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - * @return The profile. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { - return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { - return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; - } - - public static final int OPENGROUPINVITATION_FIELD_NUMBER = 102; - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation openGroupInvitation_; - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - * @return Whether the openGroupInvitation field is set. - */ - @java.lang.Override - public boolean hasOpenGroupInvitation() { - return ((bitField0_ & 0x00000100) != 0); - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - * @return The openGroupInvitation. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation getOpenGroupInvitation() { - return openGroupInvitation_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance() : openGroupInvitation_; - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder getOpenGroupInvitationOrBuilder() { - return openGroupInvitation_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance() : openGroupInvitation_; - } - - public static final int CLOSEDGROUPCONTROLMESSAGE_FIELD_NUMBER = 104; - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage closedGroupControlMessage_; - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - * @return Whether the closedGroupControlMessage field is set. - */ - @java.lang.Override - public boolean hasClosedGroupControlMessage() { - return ((bitField0_ & 0x00000200) != 0); - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - * @return The closedGroupControlMessage. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage getClosedGroupControlMessage() { - return closedGroupControlMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance() : closedGroupControlMessage_; - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessageOrBuilder getClosedGroupControlMessageOrBuilder() { - return closedGroupControlMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance() : closedGroupControlMessage_; - } - - public static final int SYNCTARGET_FIELD_NUMBER = 105; - @SuppressWarnings("serial") - private volatile java.lang.Object syncTarget_ = ""; - /** - * optional string syncTarget = 105; - * @return Whether the syncTarget field is set. - */ - @java.lang.Override - public boolean hasSyncTarget() { - return ((bitField0_ & 0x00000400) != 0); - } - /** - * optional string syncTarget = 105; - * @return The syncTarget. - */ - @java.lang.Override - public java.lang.String getSyncTarget() { - java.lang.Object ref = syncTarget_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - syncTarget_ = s; - } - return s; - } - } - /** - * optional string syncTarget = 105; - * @return The bytes for syncTarget. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getSyncTargetBytes() { - java.lang.Object ref = syncTarget_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - syncTarget_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int BLOCKSCOMMUNITYMESSAGEREQUESTS_FIELD_NUMBER = 106; - private boolean blocksCommunityMessageRequests_ = false; - /** - * optional bool blocksCommunityMessageRequests = 106; - * @return Whether the blocksCommunityMessageRequests field is set. - */ - @java.lang.Override - public boolean hasBlocksCommunityMessageRequests() { - return ((bitField0_ & 0x00000800) != 0); - } - /** - * optional bool blocksCommunityMessageRequests = 106; - * @return The blocksCommunityMessageRequests. - */ - @java.lang.Override - public boolean getBlocksCommunityMessageRequests() { - return blocksCommunityMessageRequests_; - } - - public static final int GROUPUPDATEMESSAGE_FIELD_NUMBER = 120; - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage groupUpdateMessage_; - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - * @return Whether the groupUpdateMessage field is set. - */ - @java.lang.Override - public boolean hasGroupUpdateMessage() { - return ((bitField0_ & 0x00001000) != 0); - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - * @return The groupUpdateMessage. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage getGroupUpdateMessage() { - return groupUpdateMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance() : groupUpdateMessage_; - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder getGroupUpdateMessageOrBuilder() { - return groupUpdateMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance() : groupUpdateMessage_; - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - for (int i = 0; i < getAttachmentsCount(); i++) { - if (!getAttachments(i).isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - if (hasQuote()) { - if (!getQuote().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - for (int i = 0; i < getPreviewCount(); i++) { - if (!getPreview(i).isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - if (hasReaction()) { - if (!getReaction().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - if (hasOpenGroupInvitation()) { - if (!getOpenGroupInvitation().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - if (hasClosedGroupControlMessage()) { - if (!getClosedGroupControlMessage().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - if (hasGroupUpdateMessage()) { - if (!getGroupUpdateMessage().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 1, body_); - } - for (int i = 0; i < attachments_.size(); i++) { - output.writeMessage(2, attachments_.get(i)); - } - if (((bitField0_ & 0x00000002) != 0)) { - output.writeUInt32(4, flags_); - } - if (((bitField0_ & 0x00000004) != 0)) { - output.writeUInt32(5, expireTimer_); - } - if (((bitField0_ & 0x00000008) != 0)) { - output.writeBytes(6, profileKey_); - } - if (((bitField0_ & 0x00000010) != 0)) { - output.writeUInt64(7, timestamp_); - } - if (((bitField0_ & 0x00000020) != 0)) { - output.writeMessage(8, getQuote()); - } - for (int i = 0; i < preview_.size(); i++) { - output.writeMessage(10, preview_.get(i)); - } - if (((bitField0_ & 0x00000040) != 0)) { - output.writeMessage(11, getReaction()); - } - if (((bitField0_ & 0x00000080) != 0)) { - output.writeMessage(101, getProfile()); - } - if (((bitField0_ & 0x00000100) != 0)) { - output.writeMessage(102, getOpenGroupInvitation()); - } - if (((bitField0_ & 0x00000200) != 0)) { - output.writeMessage(104, getClosedGroupControlMessage()); - } - if (((bitField0_ & 0x00000400) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 105, syncTarget_); - } - if (((bitField0_ & 0x00000800) != 0)) { - output.writeBool(106, blocksCommunityMessageRequests_); - } - if (((bitField0_ & 0x00001000) != 0)) { - output.writeMessage(120, getGroupUpdateMessage()); - } - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(1, body_); - } - for (int i = 0; i < attachments_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(2, attachments_.get(i)); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(4, flags_); - } - if (((bitField0_ & 0x00000004) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(5, expireTimer_); - } - if (((bitField0_ & 0x00000008) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(6, profileKey_); - } - if (((bitField0_ & 0x00000010) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt64Size(7, timestamp_); - } - if (((bitField0_ & 0x00000020) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(8, getQuote()); - } - for (int i = 0; i < preview_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(10, preview_.get(i)); - } - if (((bitField0_ & 0x00000040) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(11, getReaction()); - } - if (((bitField0_ & 0x00000080) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(101, getProfile()); - } - if (((bitField0_ & 0x00000100) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(102, getOpenGroupInvitation()); - } - if (((bitField0_ & 0x00000200) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(104, getClosedGroupControlMessage()); - } - if (((bitField0_ & 0x00000400) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(105, syncTarget_); - } - if (((bitField0_ & 0x00000800) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBoolSize(106, blocksCommunityMessageRequests_); - } - if (((bitField0_ & 0x00001000) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(120, getGroupUpdateMessage()); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.DataMessage other = (org.session.libsignal.protos.SignalServiceProtos.DataMessage) obj; - - if (hasBody() != other.hasBody()) return false; - if (hasBody()) { - if (!getBody() - .equals(other.getBody())) return false; - } - if (!getAttachmentsList() - .equals(other.getAttachmentsList())) return false; - if (hasFlags() != other.hasFlags()) return false; - if (hasFlags()) { - if (getFlags() - != other.getFlags()) return false; - } - if (hasExpireTimer() != other.hasExpireTimer()) return false; - if (hasExpireTimer()) { - if (getExpireTimer() - != other.getExpireTimer()) return false; - } - if (hasProfileKey() != other.hasProfileKey()) return false; - if (hasProfileKey()) { - if (!getProfileKey() - .equals(other.getProfileKey())) return false; - } - if (hasTimestamp() != other.hasTimestamp()) return false; - if (hasTimestamp()) { - if (getTimestamp() - != other.getTimestamp()) return false; - } - if (hasQuote() != other.hasQuote()) return false; - if (hasQuote()) { - if (!getQuote() - .equals(other.getQuote())) return false; - } - if (!getPreviewList() - .equals(other.getPreviewList())) return false; - if (hasReaction() != other.hasReaction()) return false; - if (hasReaction()) { - if (!getReaction() - .equals(other.getReaction())) return false; - } - if (hasProfile() != other.hasProfile()) return false; - if (hasProfile()) { - if (!getProfile() - .equals(other.getProfile())) return false; - } - if (hasOpenGroupInvitation() != other.hasOpenGroupInvitation()) return false; - if (hasOpenGroupInvitation()) { - if (!getOpenGroupInvitation() - .equals(other.getOpenGroupInvitation())) return false; - } - if (hasClosedGroupControlMessage() != other.hasClosedGroupControlMessage()) return false; - if (hasClosedGroupControlMessage()) { - if (!getClosedGroupControlMessage() - .equals(other.getClosedGroupControlMessage())) return false; - } - if (hasSyncTarget() != other.hasSyncTarget()) return false; - if (hasSyncTarget()) { - if (!getSyncTarget() - .equals(other.getSyncTarget())) return false; - } - if (hasBlocksCommunityMessageRequests() != other.hasBlocksCommunityMessageRequests()) return false; - if (hasBlocksCommunityMessageRequests()) { - if (getBlocksCommunityMessageRequests() - != other.getBlocksCommunityMessageRequests()) return false; - } - if (hasGroupUpdateMessage() != other.hasGroupUpdateMessage()) return false; - if (hasGroupUpdateMessage()) { - if (!getGroupUpdateMessage() - .equals(other.getGroupUpdateMessage())) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasBody()) { - hash = (37 * hash) + BODY_FIELD_NUMBER; - hash = (53 * hash) + getBody().hashCode(); - } - if (getAttachmentsCount() > 0) { - hash = (37 * hash) + ATTACHMENTS_FIELD_NUMBER; - hash = (53 * hash) + getAttachmentsList().hashCode(); - } - if (hasFlags()) { - hash = (37 * hash) + FLAGS_FIELD_NUMBER; - hash = (53 * hash) + getFlags(); - } - if (hasExpireTimer()) { - hash = (37 * hash) + EXPIRETIMER_FIELD_NUMBER; - hash = (53 * hash) + getExpireTimer(); - } - if (hasProfileKey()) { - hash = (37 * hash) + PROFILEKEY_FIELD_NUMBER; - hash = (53 * hash) + getProfileKey().hashCode(); - } - if (hasTimestamp()) { - hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getTimestamp()); - } - if (hasQuote()) { - hash = (37 * hash) + QUOTE_FIELD_NUMBER; - hash = (53 * hash) + getQuote().hashCode(); - } - if (getPreviewCount() > 0) { - hash = (37 * hash) + PREVIEW_FIELD_NUMBER; - hash = (53 * hash) + getPreviewList().hashCode(); - } - if (hasReaction()) { - hash = (37 * hash) + REACTION_FIELD_NUMBER; - hash = (53 * hash) + getReaction().hashCode(); - } - if (hasProfile()) { - hash = (37 * hash) + PROFILE_FIELD_NUMBER; - hash = (53 * hash) + getProfile().hashCode(); - } - if (hasOpenGroupInvitation()) { - hash = (37 * hash) + OPENGROUPINVITATION_FIELD_NUMBER; - hash = (53 * hash) + getOpenGroupInvitation().hashCode(); - } - if (hasClosedGroupControlMessage()) { - hash = (37 * hash) + CLOSEDGROUPCONTROLMESSAGE_FIELD_NUMBER; - hash = (53 * hash) + getClosedGroupControlMessage().hashCode(); - } - if (hasSyncTarget()) { - hash = (37 * hash) + SYNCTARGET_FIELD_NUMBER; - hash = (53 * hash) + getSyncTarget().hashCode(); - } - if (hasBlocksCommunityMessageRequests()) { - hash = (37 * hash) + BLOCKSCOMMUNITYMESSAGEREQUESTS_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getBlocksCommunityMessageRequests()); - } - if (hasGroupUpdateMessage()) { - hash = (37 * hash) + GROUPUPDATEMESSAGE_FIELD_NUMBER; - hash = (53 * hash) + getGroupUpdateMessage().hashCode(); - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.DataMessage prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.DataMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.DataMessage) - org.session.libsignal.protos.SignalServiceProtos.DataMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.DataMessage.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage - .alwaysUseFieldBuilders) { - getAttachmentsFieldBuilder(); - getQuoteFieldBuilder(); - getPreviewFieldBuilder(); - getReactionFieldBuilder(); - getProfileFieldBuilder(); - getOpenGroupInvitationFieldBuilder(); - getClosedGroupControlMessageFieldBuilder(); - getGroupUpdateMessageFieldBuilder(); - } - } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - body_ = ""; - if (attachmentsBuilder_ == null) { - attachments_ = java.util.Collections.emptyList(); - } else { - attachments_ = null; - attachmentsBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000002); - flags_ = 0; - expireTimer_ = 0; - profileKey_ = com.google.protobuf.ByteString.EMPTY; - timestamp_ = 0L; - quote_ = null; - if (quoteBuilder_ != null) { - quoteBuilder_.dispose(); - quoteBuilder_ = null; - } - if (previewBuilder_ == null) { - preview_ = java.util.Collections.emptyList(); - } else { - preview_ = null; - previewBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000080); - reaction_ = null; - if (reactionBuilder_ != null) { - reactionBuilder_.dispose(); - reactionBuilder_ = null; - } - profile_ = null; - if (profileBuilder_ != null) { - profileBuilder_.dispose(); - profileBuilder_ = null; - } - openGroupInvitation_ = null; - if (openGroupInvitationBuilder_ != null) { - openGroupInvitationBuilder_.dispose(); - openGroupInvitationBuilder_ = null; - } - closedGroupControlMessage_ = null; - if (closedGroupControlMessageBuilder_ != null) { - closedGroupControlMessageBuilder_.dispose(); - closedGroupControlMessageBuilder_ = null; - } - syncTarget_ = ""; - blocksCommunityMessageRequests_ = false; - groupUpdateMessage_ = null; - if (groupUpdateMessageBuilder_ != null) { - groupUpdateMessageBuilder_.dispose(); - groupUpdateMessageBuilder_ = null; - } - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_descriptor; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.DataMessage.getDefaultInstance(); - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage build() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage result = new org.session.libsignal.protos.SignalServiceProtos.DataMessage(this); - buildPartialRepeatedFields(result); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; - } - - private void buildPartialRepeatedFields(org.session.libsignal.protos.SignalServiceProtos.DataMessage result) { - if (attachmentsBuilder_ == null) { - if (((bitField0_ & 0x00000002) != 0)) { - attachments_ = java.util.Collections.unmodifiableList(attachments_); - bitField0_ = (bitField0_ & ~0x00000002); - } - result.attachments_ = attachments_; - } else { - result.attachments_ = attachmentsBuilder_.build(); - } - if (previewBuilder_ == null) { - if (((bitField0_ & 0x00000080) != 0)) { - preview_ = java.util.Collections.unmodifiableList(preview_); - bitField0_ = (bitField0_ & ~0x00000080); - } - result.preview_ = preview_; - } else { - result.preview_ = previewBuilder_.build(); - } - } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.DataMessage result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.body_ = body_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000004) != 0)) { - result.flags_ = flags_; - to_bitField0_ |= 0x00000002; - } - if (((from_bitField0_ & 0x00000008) != 0)) { - result.expireTimer_ = expireTimer_; - to_bitField0_ |= 0x00000004; - } - if (((from_bitField0_ & 0x00000010) != 0)) { - result.profileKey_ = profileKey_; - to_bitField0_ |= 0x00000008; - } - if (((from_bitField0_ & 0x00000020) != 0)) { - result.timestamp_ = timestamp_; - to_bitField0_ |= 0x00000010; - } - if (((from_bitField0_ & 0x00000040) != 0)) { - result.quote_ = quoteBuilder_ == null - ? quote_ - : quoteBuilder_.build(); - to_bitField0_ |= 0x00000020; - } - if (((from_bitField0_ & 0x00000100) != 0)) { - result.reaction_ = reactionBuilder_ == null - ? reaction_ - : reactionBuilder_.build(); - to_bitField0_ |= 0x00000040; - } - if (((from_bitField0_ & 0x00000200) != 0)) { - result.profile_ = profileBuilder_ == null - ? profile_ - : profileBuilder_.build(); - to_bitField0_ |= 0x00000080; - } - if (((from_bitField0_ & 0x00000400) != 0)) { - result.openGroupInvitation_ = openGroupInvitationBuilder_ == null - ? openGroupInvitation_ - : openGroupInvitationBuilder_.build(); - to_bitField0_ |= 0x00000100; - } - if (((from_bitField0_ & 0x00000800) != 0)) { - result.closedGroupControlMessage_ = closedGroupControlMessageBuilder_ == null - ? closedGroupControlMessage_ - : closedGroupControlMessageBuilder_.build(); - to_bitField0_ |= 0x00000200; - } - if (((from_bitField0_ & 0x00001000) != 0)) { - result.syncTarget_ = syncTarget_; - to_bitField0_ |= 0x00000400; - } - if (((from_bitField0_ & 0x00002000) != 0)) { - result.blocksCommunityMessageRequests_ = blocksCommunityMessageRequests_; - to_bitField0_ |= 0x00000800; - } - if (((from_bitField0_ & 0x00004000) != 0)) { - result.groupUpdateMessage_ = groupUpdateMessageBuilder_ == null - ? groupUpdateMessage_ - : groupUpdateMessageBuilder_.build(); - to_bitField0_ |= 0x00001000; - } - result.bitField0_ |= to_bitField0_; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.DataMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.DataMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.DataMessage.getDefaultInstance()) return this; - if (other.hasBody()) { - body_ = other.body_; - bitField0_ |= 0x00000001; - onChanged(); - } - if (attachmentsBuilder_ == null) { - if (!other.attachments_.isEmpty()) { - if (attachments_.isEmpty()) { - attachments_ = other.attachments_; - bitField0_ = (bitField0_ & ~0x00000002); - } else { - ensureAttachmentsIsMutable(); - attachments_.addAll(other.attachments_); - } - onChanged(); - } - } else { - if (!other.attachments_.isEmpty()) { - if (attachmentsBuilder_.isEmpty()) { - attachmentsBuilder_.dispose(); - attachmentsBuilder_ = null; - attachments_ = other.attachments_; - bitField0_ = (bitField0_ & ~0x00000002); - attachmentsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? - getAttachmentsFieldBuilder() : null; - } else { - attachmentsBuilder_.addAllMessages(other.attachments_); - } - } - } - if (other.hasFlags()) { - setFlags(other.getFlags()); - } - if (other.hasExpireTimer()) { - setExpireTimer(other.getExpireTimer()); - } - if (other.hasProfileKey()) { - setProfileKey(other.getProfileKey()); - } - if (other.hasTimestamp()) { - setTimestamp(other.getTimestamp()); - } - if (other.hasQuote()) { - mergeQuote(other.getQuote()); - } - if (previewBuilder_ == null) { - if (!other.preview_.isEmpty()) { - if (preview_.isEmpty()) { - preview_ = other.preview_; - bitField0_ = (bitField0_ & ~0x00000080); - } else { - ensurePreviewIsMutable(); - preview_.addAll(other.preview_); - } - onChanged(); - } - } else { - if (!other.preview_.isEmpty()) { - if (previewBuilder_.isEmpty()) { - previewBuilder_.dispose(); - previewBuilder_ = null; - preview_ = other.preview_; - bitField0_ = (bitField0_ & ~0x00000080); - previewBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? - getPreviewFieldBuilder() : null; - } else { - previewBuilder_.addAllMessages(other.preview_); - } - } - } - if (other.hasReaction()) { - mergeReaction(other.getReaction()); - } - if (other.hasProfile()) { - mergeProfile(other.getProfile()); - } - if (other.hasOpenGroupInvitation()) { - mergeOpenGroupInvitation(other.getOpenGroupInvitation()); - } - if (other.hasClosedGroupControlMessage()) { - mergeClosedGroupControlMessage(other.getClosedGroupControlMessage()); - } - if (other.hasSyncTarget()) { - syncTarget_ = other.syncTarget_; - bitField0_ |= 0x00001000; - onChanged(); - } - if (other.hasBlocksCommunityMessageRequests()) { - setBlocksCommunityMessageRequests(other.getBlocksCommunityMessageRequests()); - } - if (other.hasGroupUpdateMessage()) { - mergeGroupUpdateMessage(other.getGroupUpdateMessage()); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - for (int i = 0; i < getAttachmentsCount(); i++) { - if (!getAttachments(i).isInitialized()) { - return false; - } - } - if (hasQuote()) { - if (!getQuote().isInitialized()) { - return false; - } - } - for (int i = 0; i < getPreviewCount(); i++) { - if (!getPreview(i).isInitialized()) { - return false; - } - } - if (hasReaction()) { - if (!getReaction().isInitialized()) { - return false; - } - } - if (hasOpenGroupInvitation()) { - if (!getOpenGroupInvitation().isInitialized()) { - return false; - } - } - if (hasClosedGroupControlMessage()) { - if (!getClosedGroupControlMessage().isInitialized()) { - return false; - } - } - if (hasGroupUpdateMessage()) { - if (!getGroupUpdateMessage().isInitialized()) { - return false; - } - } - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 10: { - body_ = input.readBytes(); - bitField0_ |= 0x00000001; - break; - } // case 10 - case 18: { - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer m = - input.readMessage( - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.parser(), - extensionRegistry); - if (attachmentsBuilder_ == null) { - ensureAttachmentsIsMutable(); - attachments_.add(m); - } else { - attachmentsBuilder_.addMessage(m); - } - break; - } // case 18 - case 32: { - flags_ = input.readUInt32(); - bitField0_ |= 0x00000004; - break; - } // case 32 - case 40: { - expireTimer_ = input.readUInt32(); - bitField0_ |= 0x00000008; - break; - } // case 40 - case 50: { - profileKey_ = input.readBytes(); - bitField0_ |= 0x00000010; - break; - } // case 50 - case 56: { - timestamp_ = input.readUInt64(); - bitField0_ |= 0x00000020; - break; - } // case 56 - case 66: { - input.readMessage( - getQuoteFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000040; - break; - } // case 66 - case 82: { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview m = - input.readMessage( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.parser(), - extensionRegistry); - if (previewBuilder_ == null) { - ensurePreviewIsMutable(); - preview_.add(m); - } else { - previewBuilder_.addMessage(m); - } - break; - } // case 82 - case 90: { - input.readMessage( - getReactionFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000100; - break; - } // case 90 - case 810: { - input.readMessage( - getProfileFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000200; - break; - } // case 810 - case 818: { - input.readMessage( - getOpenGroupInvitationFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000400; - break; - } // case 818 - case 834: { - input.readMessage( - getClosedGroupControlMessageFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000800; - break; - } // case 834 - case 842: { - syncTarget_ = input.readBytes(); - bitField0_ |= 0x00001000; - break; - } // case 842 - case 848: { - blocksCommunityMessageRequests_ = input.readBool(); - bitField0_ |= 0x00002000; - break; - } // case 848 - case 962: { - input.readMessage( - getGroupUpdateMessageFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00004000; - break; - } // case 962 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; - } - private int bitField0_; - - private java.lang.Object body_ = ""; - /** - * optional string body = 1; - * @return Whether the body field is set. - */ - public boolean hasBody() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - * optional string body = 1; - * @return The body. - */ - public java.lang.String getBody() { - java.lang.Object ref = body_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - body_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string body = 1; - * @return The bytes for body. - */ - public com.google.protobuf.ByteString - getBodyBytes() { - java.lang.Object ref = body_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - body_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string body = 1; - * @param value The body to set. - * @return This builder for chaining. - */ - public Builder setBody( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - body_ = value; - bitField0_ |= 0x00000001; - onChanged(); - return this; - } - /** - * optional string body = 1; - * @return This builder for chaining. - */ - public Builder clearBody() { - body_ = getDefaultInstance().getBody(); - bitField0_ = (bitField0_ & ~0x00000001); - onChanged(); - return this; - } - /** - * optional string body = 1; - * @param value The bytes for body to set. - * @return This builder for chaining. - */ - public Builder setBodyBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - body_ = value; - bitField0_ |= 0x00000001; - onChanged(); - return this; - } - - private java.util.List attachments_ = - java.util.Collections.emptyList(); - private void ensureAttachmentsIsMutable() { - if (!((bitField0_ & 0x00000002) != 0)) { - attachments_ = new java.util.ArrayList(attachments_); - bitField0_ |= 0x00000002; - } - } - - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder> attachmentsBuilder_; - - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public java.util.List getAttachmentsList() { - if (attachmentsBuilder_ == null) { - return java.util.Collections.unmodifiableList(attachments_); - } else { - return attachmentsBuilder_.getMessageList(); - } - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public int getAttachmentsCount() { - if (attachmentsBuilder_ == null) { - return attachments_.size(); - } else { - return attachmentsBuilder_.getCount(); - } - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAttachments(int index) { - if (attachmentsBuilder_ == null) { - return attachments_.get(index); - } else { - return attachmentsBuilder_.getMessage(index); - } - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public Builder setAttachments( - int index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { - if (attachmentsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureAttachmentsIsMutable(); - attachments_.set(index, value); - onChanged(); - } else { - attachmentsBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public Builder setAttachments( - int index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder builderForValue) { - if (attachmentsBuilder_ == null) { - ensureAttachmentsIsMutable(); - attachments_.set(index, builderForValue.build()); - onChanged(); - } else { - attachmentsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public Builder addAttachments(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { - if (attachmentsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureAttachmentsIsMutable(); - attachments_.add(value); - onChanged(); - } else { - attachmentsBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public Builder addAttachments( - int index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { - if (attachmentsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureAttachmentsIsMutable(); - attachments_.add(index, value); - onChanged(); - } else { - attachmentsBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public Builder addAttachments( - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder builderForValue) { - if (attachmentsBuilder_ == null) { - ensureAttachmentsIsMutable(); - attachments_.add(builderForValue.build()); - onChanged(); - } else { - attachmentsBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public Builder addAttachments( - int index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder builderForValue) { - if (attachmentsBuilder_ == null) { - ensureAttachmentsIsMutable(); - attachments_.add(index, builderForValue.build()); - onChanged(); - } else { - attachmentsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public Builder addAllAttachments( - java.lang.Iterable values) { - if (attachmentsBuilder_ == null) { - ensureAttachmentsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, attachments_); - onChanged(); - } else { - attachmentsBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public Builder clearAttachments() { - if (attachmentsBuilder_ == null) { - attachments_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - onChanged(); - } else { - attachmentsBuilder_.clear(); - } - return this; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public Builder removeAttachments(int index) { - if (attachmentsBuilder_ == null) { - ensureAttachmentsIsMutable(); - attachments_.remove(index); - onChanged(); - } else { - attachmentsBuilder_.remove(index); - } - return this; - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder getAttachmentsBuilder( - int index) { - return getAttachmentsFieldBuilder().getBuilder(index); - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAttachmentsOrBuilder( - int index) { - if (attachmentsBuilder_ == null) { - return attachments_.get(index); } else { - return attachmentsBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public java.util.List - getAttachmentsOrBuilderList() { - if (attachmentsBuilder_ != null) { - return attachmentsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(attachments_); - } - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder addAttachmentsBuilder() { - return getAttachmentsFieldBuilder().addBuilder( - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance()); - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder addAttachmentsBuilder( - int index) { - return getAttachmentsFieldBuilder().addBuilder( - index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance()); - } - /** - * repeated .signalservice.AttachmentPointer attachments = 2; - */ - public java.util.List - getAttachmentsBuilderList() { - return getAttachmentsFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder> - getAttachmentsFieldBuilder() { - if (attachmentsBuilder_ == null) { - attachmentsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder>( - attachments_, - ((bitField0_ & 0x00000002) != 0), - getParentForChildren(), - isClean()); - attachments_ = null; - } - return attachmentsBuilder_; - } - - private int flags_ ; - /** - * optional uint32 flags = 4; - * @return Whether the flags field is set. - */ - @java.lang.Override - public boolean hasFlags() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - * optional uint32 flags = 4; - * @return The flags. - */ - @java.lang.Override - public int getFlags() { - return flags_; - } - /** - * optional uint32 flags = 4; - * @param value The flags to set. - * @return This builder for chaining. - */ - public Builder setFlags(int value) { - - flags_ = value; - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - /** - * optional uint32 flags = 4; - * @return This builder for chaining. - */ - public Builder clearFlags() { - bitField0_ = (bitField0_ & ~0x00000004); - flags_ = 0; - onChanged(); - return this; - } - - private int expireTimer_ ; - /** - * optional uint32 expireTimer = 5; - * @return Whether the expireTimer field is set. - */ - @java.lang.Override - public boolean hasExpireTimer() { - return ((bitField0_ & 0x00000008) != 0); - } - /** - * optional uint32 expireTimer = 5; - * @return The expireTimer. - */ - @java.lang.Override - public int getExpireTimer() { - return expireTimer_; - } - /** - * optional uint32 expireTimer = 5; - * @param value The expireTimer to set. - * @return This builder for chaining. - */ - public Builder setExpireTimer(int value) { - - expireTimer_ = value; - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - /** - * optional uint32 expireTimer = 5; - * @return This builder for chaining. - */ - public Builder clearExpireTimer() { - bitField0_ = (bitField0_ & ~0x00000008); - expireTimer_ = 0; - onChanged(); - return this; - } - - private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes profileKey = 6; - * @return Whether the profileKey field is set. - */ - @java.lang.Override - public boolean hasProfileKey() { - return ((bitField0_ & 0x00000010) != 0); - } - /** - * optional bytes profileKey = 6; - * @return The profileKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getProfileKey() { - return profileKey_; - } - /** - * optional bytes profileKey = 6; - * @param value The profileKey to set. - * @return This builder for chaining. - */ - public Builder setProfileKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - profileKey_ = value; - bitField0_ |= 0x00000010; - onChanged(); - return this; - } - /** - * optional bytes profileKey = 6; - * @return This builder for chaining. - */ - public Builder clearProfileKey() { - bitField0_ = (bitField0_ & ~0x00000010); - profileKey_ = getDefaultInstance().getProfileKey(); - onChanged(); - return this; - } - - private long timestamp_ ; - /** - * optional uint64 timestamp = 7; - * @return Whether the timestamp field is set. - */ - @java.lang.Override - public boolean hasTimestamp() { - return ((bitField0_ & 0x00000020) != 0); - } - /** - * optional uint64 timestamp = 7; - * @return The timestamp. - */ - @java.lang.Override - public long getTimestamp() { - return timestamp_; - } - /** - * optional uint64 timestamp = 7; - * @param value The timestamp to set. - * @return This builder for chaining. - */ - public Builder setTimestamp(long value) { - - timestamp_ = value; - bitField0_ |= 0x00000020; - onChanged(); - return this; - } - /** - * optional uint64 timestamp = 7; - * @return This builder for chaining. - */ - public Builder clearTimestamp() { - bitField0_ = (bitField0_ & ~0x00000020); - timestamp_ = 0L; - onChanged(); - return this; - } - - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote quote_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder> quoteBuilder_; - /** - * optional .signalservice.DataMessage.Quote quote = 8; - * @return Whether the quote field is set. - */ - public boolean hasQuote() { - return ((bitField0_ & 0x00000040) != 0); - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - * @return The quote. - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote getQuote() { - if (quoteBuilder_ == null) { - return quote_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance() : quote_; - } else { - return quoteBuilder_.getMessage(); - } - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - */ - public Builder setQuote(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote value) { - if (quoteBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - quote_ = value; - } else { - quoteBuilder_.setMessage(value); - } - bitField0_ |= 0x00000040; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - */ - public Builder setQuote( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder builderForValue) { - if (quoteBuilder_ == null) { - quote_ = builderForValue.build(); - } else { - quoteBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000040; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - */ - public Builder mergeQuote(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote value) { - if (quoteBuilder_ == null) { - if (((bitField0_ & 0x00000040) != 0) && - quote_ != null && - quote_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance()) { - getQuoteBuilder().mergeFrom(value); - } else { - quote_ = value; - } - } else { - quoteBuilder_.mergeFrom(value); - } - if (quote_ != null) { - bitField0_ |= 0x00000040; - onChanged(); - } - return this; - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - */ - public Builder clearQuote() { - bitField0_ = (bitField0_ & ~0x00000040); - quote_ = null; - if (quoteBuilder_ != null) { - quoteBuilder_.dispose(); - quoteBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder getQuoteBuilder() { - bitField0_ |= 0x00000040; - onChanged(); - return getQuoteFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder getQuoteOrBuilder() { - if (quoteBuilder_ != null) { - return quoteBuilder_.getMessageOrBuilder(); - } else { - return quote_ == null ? - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance() : quote_; - } - } - /** - * optional .signalservice.DataMessage.Quote quote = 8; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder> - getQuoteFieldBuilder() { - if (quoteBuilder_ == null) { - quoteBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder>( - getQuote(), - getParentForChildren(), - isClean()); - quote_ = null; - } - return quoteBuilder_; - } - - private java.util.List preview_ = - java.util.Collections.emptyList(); - private void ensurePreviewIsMutable() { - if (!((bitField0_ & 0x00000080) != 0)) { - preview_ = new java.util.ArrayList(preview_); - bitField0_ |= 0x00000080; - } - } - - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder> previewBuilder_; - - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public java.util.List getPreviewList() { - if (previewBuilder_ == null) { - return java.util.Collections.unmodifiableList(preview_); - } else { - return previewBuilder_.getMessageList(); - } - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public int getPreviewCount() { - if (previewBuilder_ == null) { - return preview_.size(); - } else { - return previewBuilder_.getCount(); - } - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview getPreview(int index) { - if (previewBuilder_ == null) { - return preview_.get(index); - } else { - return previewBuilder_.getMessage(index); - } - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public Builder setPreview( - int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview value) { - if (previewBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePreviewIsMutable(); - preview_.set(index, value); - onChanged(); - } else { - previewBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public Builder setPreview( - int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder builderForValue) { - if (previewBuilder_ == null) { - ensurePreviewIsMutable(); - preview_.set(index, builderForValue.build()); - onChanged(); - } else { - previewBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public Builder addPreview(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview value) { - if (previewBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePreviewIsMutable(); - preview_.add(value); - onChanged(); - } else { - previewBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public Builder addPreview( - int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview value) { - if (previewBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePreviewIsMutable(); - preview_.add(index, value); - onChanged(); - } else { - previewBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public Builder addPreview( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder builderForValue) { - if (previewBuilder_ == null) { - ensurePreviewIsMutable(); - preview_.add(builderForValue.build()); - onChanged(); - } else { - previewBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public Builder addPreview( - int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder builderForValue) { - if (previewBuilder_ == null) { - ensurePreviewIsMutable(); - preview_.add(index, builderForValue.build()); - onChanged(); - } else { - previewBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public Builder addAllPreview( - java.lang.Iterable values) { - if (previewBuilder_ == null) { - ensurePreviewIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, preview_); - onChanged(); - } else { - previewBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public Builder clearPreview() { - if (previewBuilder_ == null) { - preview_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000080); - onChanged(); - } else { - previewBuilder_.clear(); - } - return this; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public Builder removePreview(int index) { - if (previewBuilder_ == null) { - ensurePreviewIsMutable(); - preview_.remove(index); - onChanged(); - } else { - previewBuilder_.remove(index); - } - return this; - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder getPreviewBuilder( - int index) { - return getPreviewFieldBuilder().getBuilder(index); - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder getPreviewOrBuilder( - int index) { - if (previewBuilder_ == null) { - return preview_.get(index); } else { - return previewBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public java.util.List - getPreviewOrBuilderList() { - if (previewBuilder_ != null) { - return previewBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(preview_); - } - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder addPreviewBuilder() { - return getPreviewFieldBuilder().addBuilder( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.getDefaultInstance()); - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder addPreviewBuilder( - int index) { - return getPreviewFieldBuilder().addBuilder( - index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.getDefaultInstance()); - } - /** - * repeated .signalservice.DataMessage.Preview preview = 10; - */ - public java.util.List - getPreviewBuilderList() { - return getPreviewFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder> - getPreviewFieldBuilder() { - if (previewBuilder_ == null) { - previewBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder>( - preview_, - ((bitField0_ & 0x00000080) != 0), - getParentForChildren(), - isClean()); - preview_ = null; - } - return previewBuilder_; - } - - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction reaction_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder> reactionBuilder_; - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - * @return Whether the reaction field is set. - */ - public boolean hasReaction() { - return ((bitField0_ & 0x00000100) != 0); - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - * @return The reaction. - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getReaction() { - if (reactionBuilder_ == null) { - return reaction_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance() : reaction_; - } else { - return reactionBuilder_.getMessage(); - } - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - */ - public Builder setReaction(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction value) { - if (reactionBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - reaction_ = value; - } else { - reactionBuilder_.setMessage(value); - } - bitField0_ |= 0x00000100; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - */ - public Builder setReaction( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder builderForValue) { - if (reactionBuilder_ == null) { - reaction_ = builderForValue.build(); - } else { - reactionBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000100; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - */ - public Builder mergeReaction(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction value) { - if (reactionBuilder_ == null) { - if (((bitField0_ & 0x00000100) != 0) && - reaction_ != null && - reaction_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance()) { - getReactionBuilder().mergeFrom(value); - } else { - reaction_ = value; - } - } else { - reactionBuilder_.mergeFrom(value); - } - if (reaction_ != null) { - bitField0_ |= 0x00000100; - onChanged(); - } - return this; - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - */ - public Builder clearReaction() { - bitField0_ = (bitField0_ & ~0x00000100); - reaction_ = null; - if (reactionBuilder_ != null) { - reactionBuilder_.dispose(); - reactionBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder getReactionBuilder() { - bitField0_ |= 0x00000100; - onChanged(); - return getReactionFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder getReactionOrBuilder() { - if (reactionBuilder_ != null) { - return reactionBuilder_.getMessageOrBuilder(); - } else { - return reaction_ == null ? - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance() : reaction_; - } - } - /** - * optional .signalservice.DataMessage.Reaction reaction = 11; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder> - getReactionFieldBuilder() { - if (reactionBuilder_ == null) { - reactionBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder>( - getReaction(), - getParentForChildren(), - isClean()); - reaction_ = null; - } - return reactionBuilder_; - } - - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> profileBuilder_; - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - * @return Whether the profile field is set. - */ - public boolean hasProfile() { - return ((bitField0_ & 0x00000200) != 0); - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - * @return The profile. - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { - if (profileBuilder_ == null) { - return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; - } else { - return profileBuilder_.getMessage(); - } - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - */ - public Builder setProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { - if (profileBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - profile_ = value; - } else { - profileBuilder_.setMessage(value); - } - bitField0_ |= 0x00000200; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - */ - public Builder setProfile( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder builderForValue) { - if (profileBuilder_ == null) { - profile_ = builderForValue.build(); - } else { - profileBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000200; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - */ - public Builder mergeProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { - if (profileBuilder_ == null) { - if (((bitField0_ & 0x00000200) != 0) && - profile_ != null && - profile_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance()) { - getProfileBuilder().mergeFrom(value); - } else { - profile_ = value; - } - } else { - profileBuilder_.mergeFrom(value); - } - if (profile_ != null) { - bitField0_ |= 0x00000200; - onChanged(); - } - return this; - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - */ - public Builder clearProfile() { - bitField0_ = (bitField0_ & ~0x00000200); - profile_ = null; - if (profileBuilder_ != null) { - profileBuilder_.dispose(); - profileBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder getProfileBuilder() { - bitField0_ |= 0x00000200; - onChanged(); - return getProfileFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { - if (profileBuilder_ != null) { - return profileBuilder_.getMessageOrBuilder(); - } else { - return profile_ == null ? - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; - } - } - /** - * optional .signalservice.DataMessage.LokiProfile profile = 101; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> - getProfileFieldBuilder() { - if (profileBuilder_ == null) { - profileBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder>( - getProfile(), - getParentForChildren(), - isClean()); - profile_ = null; - } - return profileBuilder_; - } - - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation openGroupInvitation_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder> openGroupInvitationBuilder_; - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - * @return Whether the openGroupInvitation field is set. - */ - public boolean hasOpenGroupInvitation() { - return ((bitField0_ & 0x00000400) != 0); - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - * @return The openGroupInvitation. - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation getOpenGroupInvitation() { - if (openGroupInvitationBuilder_ == null) { - return openGroupInvitation_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance() : openGroupInvitation_; - } else { - return openGroupInvitationBuilder_.getMessage(); - } - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - */ - public Builder setOpenGroupInvitation(org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation value) { - if (openGroupInvitationBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - openGroupInvitation_ = value; - } else { - openGroupInvitationBuilder_.setMessage(value); - } - bitField0_ |= 0x00000400; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - */ - public Builder setOpenGroupInvitation( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder builderForValue) { - if (openGroupInvitationBuilder_ == null) { - openGroupInvitation_ = builderForValue.build(); - } else { - openGroupInvitationBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000400; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - */ - public Builder mergeOpenGroupInvitation(org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation value) { - if (openGroupInvitationBuilder_ == null) { - if (((bitField0_ & 0x00000400) != 0) && - openGroupInvitation_ != null && - openGroupInvitation_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance()) { - getOpenGroupInvitationBuilder().mergeFrom(value); - } else { - openGroupInvitation_ = value; - } - } else { - openGroupInvitationBuilder_.mergeFrom(value); - } - if (openGroupInvitation_ != null) { - bitField0_ |= 0x00000400; - onChanged(); - } - return this; - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - */ - public Builder clearOpenGroupInvitation() { - bitField0_ = (bitField0_ & ~0x00000400); - openGroupInvitation_ = null; - if (openGroupInvitationBuilder_ != null) { - openGroupInvitationBuilder_.dispose(); - openGroupInvitationBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder getOpenGroupInvitationBuilder() { - bitField0_ |= 0x00000400; - onChanged(); - return getOpenGroupInvitationFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder getOpenGroupInvitationOrBuilder() { - if (openGroupInvitationBuilder_ != null) { - return openGroupInvitationBuilder_.getMessageOrBuilder(); - } else { - return openGroupInvitation_ == null ? - org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance() : openGroupInvitation_; - } - } - /** - * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder> - getOpenGroupInvitationFieldBuilder() { - if (openGroupInvitationBuilder_ == null) { - openGroupInvitationBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder>( - getOpenGroupInvitation(), - getParentForChildren(), - isClean()); - openGroupInvitation_ = null; - } - return openGroupInvitationBuilder_; - } - - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage closedGroupControlMessage_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessageOrBuilder> closedGroupControlMessageBuilder_; - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - * @return Whether the closedGroupControlMessage field is set. - */ - public boolean hasClosedGroupControlMessage() { - return ((bitField0_ & 0x00000800) != 0); - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - * @return The closedGroupControlMessage. - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage getClosedGroupControlMessage() { - if (closedGroupControlMessageBuilder_ == null) { - return closedGroupControlMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance() : closedGroupControlMessage_; - } else { - return closedGroupControlMessageBuilder_.getMessage(); - } - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - */ - public Builder setClosedGroupControlMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage value) { - if (closedGroupControlMessageBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - closedGroupControlMessage_ = value; - } else { - closedGroupControlMessageBuilder_.setMessage(value); - } - bitField0_ |= 0x00000800; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - */ - public Builder setClosedGroupControlMessage( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder builderForValue) { - if (closedGroupControlMessageBuilder_ == null) { - closedGroupControlMessage_ = builderForValue.build(); - } else { - closedGroupControlMessageBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000800; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - */ - public Builder mergeClosedGroupControlMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage value) { - if (closedGroupControlMessageBuilder_ == null) { - if (((bitField0_ & 0x00000800) != 0) && - closedGroupControlMessage_ != null && - closedGroupControlMessage_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance()) { - getClosedGroupControlMessageBuilder().mergeFrom(value); - } else { - closedGroupControlMessage_ = value; - } - } else { - closedGroupControlMessageBuilder_.mergeFrom(value); - } - if (closedGroupControlMessage_ != null) { - bitField0_ |= 0x00000800; - onChanged(); - } - return this; - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - */ - public Builder clearClosedGroupControlMessage() { - bitField0_ = (bitField0_ & ~0x00000800); - closedGroupControlMessage_ = null; - if (closedGroupControlMessageBuilder_ != null) { - closedGroupControlMessageBuilder_.dispose(); - closedGroupControlMessageBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder getClosedGroupControlMessageBuilder() { - bitField0_ |= 0x00000800; - onChanged(); - return getClosedGroupControlMessageFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessageOrBuilder getClosedGroupControlMessageOrBuilder() { - if (closedGroupControlMessageBuilder_ != null) { - return closedGroupControlMessageBuilder_.getMessageOrBuilder(); - } else { - return closedGroupControlMessage_ == null ? - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance() : closedGroupControlMessage_; - } - } - /** - * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessageOrBuilder> - getClosedGroupControlMessageFieldBuilder() { - if (closedGroupControlMessageBuilder_ == null) { - closedGroupControlMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessageOrBuilder>( - getClosedGroupControlMessage(), - getParentForChildren(), - isClean()); - closedGroupControlMessage_ = null; - } - return closedGroupControlMessageBuilder_; - } - - private java.lang.Object syncTarget_ = ""; - /** - * optional string syncTarget = 105; - * @return Whether the syncTarget field is set. - */ - public boolean hasSyncTarget() { - return ((bitField0_ & 0x00001000) != 0); - } - /** - * optional string syncTarget = 105; - * @return The syncTarget. - */ - public java.lang.String getSyncTarget() { - java.lang.Object ref = syncTarget_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - syncTarget_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string syncTarget = 105; - * @return The bytes for syncTarget. - */ - public com.google.protobuf.ByteString - getSyncTargetBytes() { - java.lang.Object ref = syncTarget_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - syncTarget_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string syncTarget = 105; - * @param value The syncTarget to set. - * @return This builder for chaining. - */ - public Builder setSyncTarget( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - syncTarget_ = value; - bitField0_ |= 0x00001000; - onChanged(); - return this; - } - /** - * optional string syncTarget = 105; - * @return This builder for chaining. - */ - public Builder clearSyncTarget() { - syncTarget_ = getDefaultInstance().getSyncTarget(); - bitField0_ = (bitField0_ & ~0x00001000); - onChanged(); - return this; - } - /** - * optional string syncTarget = 105; - * @param value The bytes for syncTarget to set. - * @return This builder for chaining. - */ - public Builder setSyncTargetBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - syncTarget_ = value; - bitField0_ |= 0x00001000; - onChanged(); - return this; - } - - private boolean blocksCommunityMessageRequests_ ; - /** - * optional bool blocksCommunityMessageRequests = 106; - * @return Whether the blocksCommunityMessageRequests field is set. - */ - @java.lang.Override - public boolean hasBlocksCommunityMessageRequests() { - return ((bitField0_ & 0x00002000) != 0); - } - /** - * optional bool blocksCommunityMessageRequests = 106; - * @return The blocksCommunityMessageRequests. - */ - @java.lang.Override - public boolean getBlocksCommunityMessageRequests() { - return blocksCommunityMessageRequests_; - } - /** - * optional bool blocksCommunityMessageRequests = 106; - * @param value The blocksCommunityMessageRequests to set. - * @return This builder for chaining. - */ - public Builder setBlocksCommunityMessageRequests(boolean value) { - - blocksCommunityMessageRequests_ = value; - bitField0_ |= 0x00002000; - onChanged(); - return this; - } - /** - * optional bool blocksCommunityMessageRequests = 106; - * @return This builder for chaining. - */ - public Builder clearBlocksCommunityMessageRequests() { - bitField0_ = (bitField0_ & ~0x00002000); - blocksCommunityMessageRequests_ = false; - onChanged(); - return this; - } - - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage groupUpdateMessage_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder> groupUpdateMessageBuilder_; - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - * @return Whether the groupUpdateMessage field is set. - */ - public boolean hasGroupUpdateMessage() { - return ((bitField0_ & 0x00004000) != 0); - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - * @return The groupUpdateMessage. - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage getGroupUpdateMessage() { - if (groupUpdateMessageBuilder_ == null) { - return groupUpdateMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance() : groupUpdateMessage_; - } else { - return groupUpdateMessageBuilder_.getMessage(); - } - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - */ - public Builder setGroupUpdateMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage value) { - if (groupUpdateMessageBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - groupUpdateMessage_ = value; - } else { - groupUpdateMessageBuilder_.setMessage(value); - } - bitField0_ |= 0x00004000; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - */ - public Builder setGroupUpdateMessage( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder builderForValue) { - if (groupUpdateMessageBuilder_ == null) { - groupUpdateMessage_ = builderForValue.build(); - } else { - groupUpdateMessageBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00004000; - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - */ - public Builder mergeGroupUpdateMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage value) { - if (groupUpdateMessageBuilder_ == null) { - if (((bitField0_ & 0x00004000) != 0) && - groupUpdateMessage_ != null && - groupUpdateMessage_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance()) { - getGroupUpdateMessageBuilder().mergeFrom(value); - } else { - groupUpdateMessage_ = value; - } - } else { - groupUpdateMessageBuilder_.mergeFrom(value); - } - if (groupUpdateMessage_ != null) { - bitField0_ |= 0x00004000; - onChanged(); - } - return this; - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - */ - public Builder clearGroupUpdateMessage() { - bitField0_ = (bitField0_ & ~0x00004000); - groupUpdateMessage_ = null; - if (groupUpdateMessageBuilder_ != null) { - groupUpdateMessageBuilder_.dispose(); - groupUpdateMessageBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder getGroupUpdateMessageBuilder() { - bitField0_ |= 0x00004000; - onChanged(); - return getGroupUpdateMessageFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder getGroupUpdateMessageOrBuilder() { - if (groupUpdateMessageBuilder_ != null) { - return groupUpdateMessageBuilder_.getMessageOrBuilder(); - } else { - return groupUpdateMessage_ == null ? - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance() : groupUpdateMessage_; - } - } - /** - * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder> - getGroupUpdateMessageFieldBuilder() { - if (groupUpdateMessageBuilder_ == null) { - groupUpdateMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder>( - getGroupUpdateMessage(), - getParentForChildren(), - isClean()); - groupUpdateMessage_ = null; - } - return groupUpdateMessageBuilder_; - } - - // @@protoc_insertion_point(builder_scope:signalservice.DataMessage) - } - - // @@protoc_insertion_point(class_scope:signalservice.DataMessage) - private static final org.session.libsignal.protos.SignalServiceProtos.DataMessage DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.DataMessage(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public DataMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface GroupDeleteMessageOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.GroupDeleteMessage) - com.google.protobuf.MessageOrBuilder { - - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - boolean hasPublicKey(); - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - com.google.protobuf.ByteString getPublicKey(); - - /** - *
-     * @required
-     * 
- * - * required bytes lastEncryptionKey = 2; - * @return Whether the lastEncryptionKey field is set. - */ - boolean hasLastEncryptionKey(); - /** - *
-     * @required
-     * 
- * - * required bytes lastEncryptionKey = 2; - * @return The lastEncryptionKey. - */ - com.google.protobuf.ByteString getLastEncryptionKey(); - } - /** - * Protobuf type {@code signalservice.GroupDeleteMessage} - */ - public static final class GroupDeleteMessage extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.GroupDeleteMessage) - GroupDeleteMessageOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - GroupDeleteMessage.class.getName()); - } - // Use GroupDeleteMessage.newBuilder() to construct. - private GroupDeleteMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private GroupDeleteMessage() { - publicKey_ = com.google.protobuf.ByteString.EMPTY; - lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder.class); - } - - private int bitField0_; - public static final int PUBLICKEY_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - - public static final int LASTENCRYPTIONKEY_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-     * @required
-     * 
- * - * required bytes lastEncryptionKey = 2; - * @return Whether the lastEncryptionKey field is set. - */ - @java.lang.Override - public boolean hasLastEncryptionKey() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-     * @required
-     * 
- * - * required bytes lastEncryptionKey = 2; - * @return The lastEncryptionKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getLastEncryptionKey() { - return lastEncryptionKey_; - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - if (!hasPublicKey()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasLastEncryptionKey()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - output.writeBytes(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - output.writeBytes(2, lastEncryptionKey_); - } - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, lastEncryptionKey_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage other = (org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage) obj; - - if (hasPublicKey() != other.hasPublicKey()) return false; - if (hasPublicKey()) { - if (!getPublicKey() - .equals(other.getPublicKey())) return false; - } - if (hasLastEncryptionKey() != other.hasLastEncryptionKey()) return false; - if (hasLastEncryptionKey()) { - if (!getLastEncryptionKey() - .equals(other.getLastEncryptionKey())) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasPublicKey()) { - hash = (37 * hash) + PUBLICKEY_FIELD_NUMBER; - hash = (53 * hash) + getPublicKey().hashCode(); - } - if (hasLastEncryptionKey()) { - hash = (37 * hash) + LASTENCRYPTIONKEY_FIELD_NUMBER; - hash = (53 * hash) + getLastEncryptionKey().hashCode(); - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.GroupDeleteMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.GroupDeleteMessage) - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.newBuilder() - private Builder() { - - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - - } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - publicKey_ = com.google.protobuf.ByteString.EMPTY; - lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_descriptor; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage build() { - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage(this); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; - } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.publicKey_ = publicKey_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000002) != 0)) { - result.lastEncryptionKey_ = lastEncryptionKey_; - to_bitField0_ |= 0x00000002; - } - result.bitField0_ |= to_bitField0_; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance()) return this; - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasLastEncryptionKey()) { - setLastEncryptionKey(other.getLastEncryptionKey()); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - if (!hasPublicKey()) { - return false; - } - if (!hasLastEncryptionKey()) { - return false; - } - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 10: { - publicKey_ = input.readBytes(); - bitField0_ |= 0x00000001; - break; - } // case 10 - case 18: { - lastEncryptionKey_ = input.readBytes(); - bitField0_ |= 0x00000002; - break; - } // case 18 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; - } - private int bitField0_; - - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @param value The publicKey to set. - * @return This builder for chaining. - */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - publicKey_ = value; - bitField0_ |= 0x00000001; - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return This builder for chaining. - */ - public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000001); - publicKey_ = getDefaultInstance().getPublicKey(); - onChanged(); - return this; - } - - private com.google.protobuf.ByteString lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-       * @required
-       * 
- * - * required bytes lastEncryptionKey = 2; - * @return Whether the lastEncryptionKey field is set. - */ - @java.lang.Override - public boolean hasLastEncryptionKey() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-       * @required
-       * 
- * - * required bytes lastEncryptionKey = 2; - * @return The lastEncryptionKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getLastEncryptionKey() { - return lastEncryptionKey_; - } - /** - *
-       * @required
-       * 
- * - * required bytes lastEncryptionKey = 2; - * @param value The lastEncryptionKey to set. - * @return This builder for chaining. - */ - public Builder setLastEncryptionKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - lastEncryptionKey_ = value; - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required bytes lastEncryptionKey = 2; - * @return This builder for chaining. - */ - public Builder clearLastEncryptionKey() { - bitField0_ = (bitField0_ & ~0x00000002); - lastEncryptionKey_ = getDefaultInstance().getLastEncryptionKey(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.GroupDeleteMessage) - } - - // @@protoc_insertion_point(class_scope:signalservice.GroupDeleteMessage) - private static final org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public GroupDeleteMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface GroupMemberLeftMessageOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.GroupMemberLeftMessage) - com.google.protobuf.MessageOrBuilder { - } - /** - *
-   * the pubkey of the member left is included as part of the closed group encryption logic (senderIdentity on desktop)
-   * 
- * - * Protobuf type {@code signalservice.GroupMemberLeftMessage} - */ - public static final class GroupMemberLeftMessage extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.GroupMemberLeftMessage) - GroupMemberLeftMessageOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - GroupMemberLeftMessage.class.getName()); - } - // Use GroupMemberLeftMessage.newBuilder() to construct. - private GroupMemberLeftMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private GroupMemberLeftMessage() { - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder.class); - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage other = (org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage) obj; - - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - *
-     * the pubkey of the member left is included as part of the closed group encryption logic (senderIdentity on desktop)
-     * 
- * - * Protobuf type {@code signalservice.GroupMemberLeftMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.GroupMemberLeftMessage) - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.newBuilder() - private Builder() { - - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - - } - @java.lang.Override - public Builder clear() { - super.clear(); - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_descriptor; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage build() { - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage(this); - onBuilt(); - return result; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance()) return this; - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.GroupMemberLeftMessage) - } - - // @@protoc_insertion_point(class_scope:signalservice.GroupMemberLeftMessage) - private static final org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public GroupMemberLeftMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface GroupInviteMessageOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.GroupInviteMessage) - com.google.protobuf.MessageOrBuilder { - - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - boolean hasPublicKey(); - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - com.google.protobuf.ByteString getPublicKey(); - - /** - *
-     * @required
-     * 
- * - * required string name = 2; - * @return Whether the name field is set. - */ - boolean hasName(); - /** - *
-     * @required
-     * 
- * - * required string name = 2; - * @return The name. - */ - java.lang.String getName(); - /** - *
-     * @required
-     * 
- * - * required string name = 2; - * @return The bytes for name. - */ - com.google.protobuf.ByteString - getNameBytes(); - - /** - *
-     * @required
-     * 
- * - * required bytes memberPrivateKey = 3; - * @return Whether the memberPrivateKey field is set. - */ - boolean hasMemberPrivateKey(); - /** - *
-     * @required
-     * 
- * - * required bytes memberPrivateKey = 3; - * @return The memberPrivateKey. - */ - com.google.protobuf.ByteString getMemberPrivateKey(); - } - /** - * Protobuf type {@code signalservice.GroupInviteMessage} - */ - public static final class GroupInviteMessage extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.GroupInviteMessage) - GroupInviteMessageOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - GroupInviteMessage.class.getName()); - } - // Use GroupInviteMessage.newBuilder() to construct. - private GroupInviteMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private GroupInviteMessage() { - publicKey_ = com.google.protobuf.ByteString.EMPTY; - name_ = ""; - memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder.class); - } - - private int bitField0_; - public static final int PUBLICKEY_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - - public static final int NAME_FIELD_NUMBER = 2; - @SuppressWarnings("serial") - private volatile java.lang.Object name_ = ""; - /** - *
-     * @required
-     * 
- * - * required string name = 2; - * @return Whether the name field is set. - */ - @java.lang.Override - public boolean hasName() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-     * @required
-     * 
- * - * required string name = 2; - * @return The name. - */ - @java.lang.Override - public java.lang.String getName() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } - } - /** - *
-     * @required
-     * 
- * - * required string name = 2; - * @return The bytes for name. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int MEMBERPRIVATEKEY_FIELD_NUMBER = 3; - private com.google.protobuf.ByteString memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-     * @required
-     * 
- * - * required bytes memberPrivateKey = 3; - * @return Whether the memberPrivateKey field is set. - */ - @java.lang.Override - public boolean hasMemberPrivateKey() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - *
-     * @required
-     * 
- * - * required bytes memberPrivateKey = 3; - * @return The memberPrivateKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getMemberPrivateKey() { - return memberPrivateKey_; - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - if (!hasPublicKey()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasName()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasMemberPrivateKey()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - output.writeBytes(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 2, name_); - } - if (((bitField0_ & 0x00000004) != 0)) { - output.writeBytes(3, memberPrivateKey_); - } - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(2, name_); - } - if (((bitField0_ & 0x00000004) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(3, memberPrivateKey_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage other = (org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage) obj; - - if (hasPublicKey() != other.hasPublicKey()) return false; - if (hasPublicKey()) { - if (!getPublicKey() - .equals(other.getPublicKey())) return false; - } - if (hasName() != other.hasName()) return false; - if (hasName()) { - if (!getName() - .equals(other.getName())) return false; - } - if (hasMemberPrivateKey() != other.hasMemberPrivateKey()) return false; - if (hasMemberPrivateKey()) { - if (!getMemberPrivateKey() - .equals(other.getMemberPrivateKey())) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasPublicKey()) { - hash = (37 * hash) + PUBLICKEY_FIELD_NUMBER; - hash = (53 * hash) + getPublicKey().hashCode(); - } - if (hasName()) { - hash = (37 * hash) + NAME_FIELD_NUMBER; - hash = (53 * hash) + getName().hashCode(); - } - if (hasMemberPrivateKey()) { - hash = (37 * hash) + MEMBERPRIVATEKEY_FIELD_NUMBER; - hash = (53 * hash) + getMemberPrivateKey().hashCode(); - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.GroupInviteMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.GroupInviteMessage) - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.newBuilder() - private Builder() { - - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - - } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - publicKey_ = com.google.protobuf.ByteString.EMPTY; - name_ = ""; - memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_descriptor; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage build() { - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage(this); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; - } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.publicKey_ = publicKey_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000002) != 0)) { - result.name_ = name_; - to_bitField0_ |= 0x00000002; - } - if (((from_bitField0_ & 0x00000004) != 0)) { - result.memberPrivateKey_ = memberPrivateKey_; - to_bitField0_ |= 0x00000004; - } - result.bitField0_ |= to_bitField0_; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance()) return this; - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasName()) { - name_ = other.name_; - bitField0_ |= 0x00000002; - onChanged(); - } - if (other.hasMemberPrivateKey()) { - setMemberPrivateKey(other.getMemberPrivateKey()); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - if (!hasPublicKey()) { - return false; - } - if (!hasName()) { - return false; - } - if (!hasMemberPrivateKey()) { - return false; - } - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 10: { - publicKey_ = input.readBytes(); - bitField0_ |= 0x00000001; - break; - } // case 10 - case 18: { - name_ = input.readBytes(); - bitField0_ |= 0x00000002; - break; - } // case 18 - case 26: { - memberPrivateKey_ = input.readBytes(); - bitField0_ |= 0x00000004; - break; - } // case 26 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; - } - private int bitField0_; - - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @param value The publicKey to set. - * @return This builder for chaining. - */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - publicKey_ = value; - bitField0_ |= 0x00000001; - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return This builder for chaining. - */ - public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000001); - publicKey_ = getDefaultInstance().getPublicKey(); - onChanged(); - return this; - } - - private java.lang.Object name_ = ""; - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return Whether the name field is set. - */ - public boolean hasName() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return The name. - */ - public java.lang.String getName() { - java.lang.Object ref = name_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return The bytes for name. - */ - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @param value The name to set. - * @return This builder for chaining. - */ - public Builder setName( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - name_ = value; - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return This builder for chaining. - */ - public Builder clearName() { - name_ = getDefaultInstance().getName(); - bitField0_ = (bitField0_ & ~0x00000002); - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @param value The bytes for name to set. - * @return This builder for chaining. - */ - public Builder setNameBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - name_ = value; - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - - private com.google.protobuf.ByteString memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-       * @required
-       * 
- * - * required bytes memberPrivateKey = 3; - * @return Whether the memberPrivateKey field is set. - */ - @java.lang.Override - public boolean hasMemberPrivateKey() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - *
-       * @required
-       * 
- * - * required bytes memberPrivateKey = 3; - * @return The memberPrivateKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getMemberPrivateKey() { - return memberPrivateKey_; - } - /** - *
-       * @required
-       * 
- * - * required bytes memberPrivateKey = 3; - * @param value The memberPrivateKey to set. - * @return This builder for chaining. - */ - public Builder setMemberPrivateKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - memberPrivateKey_ = value; - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required bytes memberPrivateKey = 3; - * @return This builder for chaining. - */ - public Builder clearMemberPrivateKey() { - bitField0_ = (bitField0_ & ~0x00000004); - memberPrivateKey_ = getDefaultInstance().getMemberPrivateKey(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.GroupInviteMessage) - } - - // @@protoc_insertion_point(class_scope:signalservice.GroupInviteMessage) - private static final org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public GroupInviteMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface GroupPromoteMessageOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.GroupPromoteMessage) - com.google.protobuf.MessageOrBuilder { - - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - boolean hasPublicKey(); - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - com.google.protobuf.ByteString getPublicKey(); - - /** - *
-     * @required
-     * 
- * - * required bytes encryptedPrivateKey = 2; - * @return Whether the encryptedPrivateKey field is set. - */ - boolean hasEncryptedPrivateKey(); - /** - *
-     * @required
-     * 
- * - * required bytes encryptedPrivateKey = 2; - * @return The encryptedPrivateKey. - */ - com.google.protobuf.ByteString getEncryptedPrivateKey(); - } - /** - * Protobuf type {@code signalservice.GroupPromoteMessage} - */ - public static final class GroupPromoteMessage extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.GroupPromoteMessage) - GroupPromoteMessageOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - GroupPromoteMessage.class.getName()); - } - // Use GroupPromoteMessage.newBuilder() to construct. - private GroupPromoteMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private GroupPromoteMessage() { - publicKey_ = com.google.protobuf.ByteString.EMPTY; - encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder.class); - } - - private int bitField0_; - public static final int PUBLICKEY_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-     * @required
-     * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - - public static final int ENCRYPTEDPRIVATEKEY_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-     * @required
-     * 
- * - * required bytes encryptedPrivateKey = 2; - * @return Whether the encryptedPrivateKey field is set. - */ - @java.lang.Override - public boolean hasEncryptedPrivateKey() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-     * @required
-     * 
- * - * required bytes encryptedPrivateKey = 2; - * @return The encryptedPrivateKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getEncryptedPrivateKey() { - return encryptedPrivateKey_; - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - if (!hasPublicKey()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasEncryptedPrivateKey()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - output.writeBytes(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - output.writeBytes(2, encryptedPrivateKey_); - } - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, encryptedPrivateKey_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage other = (org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage) obj; - - if (hasPublicKey() != other.hasPublicKey()) return false; - if (hasPublicKey()) { - if (!getPublicKey() - .equals(other.getPublicKey())) return false; - } - if (hasEncryptedPrivateKey() != other.hasEncryptedPrivateKey()) return false; - if (hasEncryptedPrivateKey()) { - if (!getEncryptedPrivateKey() - .equals(other.getEncryptedPrivateKey())) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasPublicKey()) { - hash = (37 * hash) + PUBLICKEY_FIELD_NUMBER; - hash = (53 * hash) + getPublicKey().hashCode(); - } - if (hasEncryptedPrivateKey()) { - hash = (37 * hash) + ENCRYPTEDPRIVATEKEY_FIELD_NUMBER; - hash = (53 * hash) + getEncryptedPrivateKey().hashCode(); - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.GroupPromoteMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.GroupPromoteMessage) - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.newBuilder() - private Builder() { - - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - - } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - publicKey_ = com.google.protobuf.ByteString.EMPTY; - encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_descriptor; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage build() { - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage(this); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; - } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.publicKey_ = publicKey_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000002) != 0)) { - result.encryptedPrivateKey_ = encryptedPrivateKey_; - to_bitField0_ |= 0x00000002; - } - result.bitField0_ |= to_bitField0_; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance()) return this; - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasEncryptedPrivateKey()) { - setEncryptedPrivateKey(other.getEncryptedPrivateKey()); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - if (!hasPublicKey()) { - return false; - } - if (!hasEncryptedPrivateKey()) { - return false; - } - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 10: { - publicKey_ = input.readBytes(); - bitField0_ |= 0x00000001; - break; - } // case 10 - case 18: { - encryptedPrivateKey_ = input.readBytes(); - bitField0_ |= 0x00000002; - break; - } // case 18 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; - } - private int bitField0_; - - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @param value The publicKey to set. - * @return This builder for chaining. - */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - publicKey_ = value; - bitField0_ |= 0x00000001; - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return This builder for chaining. - */ - public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000001); - publicKey_ = getDefaultInstance().getPublicKey(); - onChanged(); - return this; - } - - private com.google.protobuf.ByteString encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-       * @required
-       * 
- * - * required bytes encryptedPrivateKey = 2; - * @return Whether the encryptedPrivateKey field is set. - */ - @java.lang.Override - public boolean hasEncryptedPrivateKey() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-       * @required
-       * 
- * - * required bytes encryptedPrivateKey = 2; - * @return The encryptedPrivateKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getEncryptedPrivateKey() { - return encryptedPrivateKey_; - } - /** - *
-       * @required
-       * 
- * - * required bytes encryptedPrivateKey = 2; - * @param value The encryptedPrivateKey to set. - * @return This builder for chaining. - */ - public Builder setEncryptedPrivateKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - encryptedPrivateKey_ = value; - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required bytes encryptedPrivateKey = 2; - * @return This builder for chaining. - */ - public Builder clearEncryptedPrivateKey() { - bitField0_ = (bitField0_ & ~0x00000002); - encryptedPrivateKey_ = getDefaultInstance().getEncryptedPrivateKey(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.GroupPromoteMessage) - } - - // @@protoc_insertion_point(class_scope:signalservice.GroupPromoteMessage) - private static final org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public GroupPromoteMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface CallMessageOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.CallMessage) - com.google.protobuf.MessageOrBuilder { - - /** - *
-     * @required
-     * 
- * - * required .signalservice.CallMessage.Type type = 1; - * @return Whether the type field is set. - */ - boolean hasType(); - /** - *
-     * @required
-     * 
- * - * required .signalservice.CallMessage.Type type = 1; - * @return The type. - */ - org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type getType(); - - /** - * repeated string sdps = 2; - * @return A list containing the sdps. - */ - java.util.List - getSdpsList(); - /** - * repeated string sdps = 2; - * @return The count of sdps. - */ - int getSdpsCount(); - /** - * repeated string sdps = 2; - * @param index The index of the element to return. - * @return The sdps at the given index. - */ - java.lang.String getSdps(int index); - /** - * repeated string sdps = 2; - * @param index The index of the value to return. - * @return The bytes of the sdps at the given index. - */ - com.google.protobuf.ByteString - getSdpsBytes(int index); - - /** - * repeated uint32 sdpMLineIndexes = 3; - * @return A list containing the sdpMLineIndexes. - */ - java.util.List getSdpMLineIndexesList(); - /** - * repeated uint32 sdpMLineIndexes = 3; - * @return The count of sdpMLineIndexes. - */ - int getSdpMLineIndexesCount(); - /** - * repeated uint32 sdpMLineIndexes = 3; - * @param index The index of the element to return. - * @return The sdpMLineIndexes at the given index. - */ - int getSdpMLineIndexes(int index); - - /** - * repeated string sdpMids = 4; - * @return A list containing the sdpMids. - */ - java.util.List - getSdpMidsList(); - /** - * repeated string sdpMids = 4; - * @return The count of sdpMids. - */ - int getSdpMidsCount(); - /** - * repeated string sdpMids = 4; - * @param index The index of the element to return. - * @return The sdpMids at the given index. - */ - java.lang.String getSdpMids(int index); - /** - * repeated string sdpMids = 4; - * @param index The index of the value to return. - * @return The bytes of the sdpMids at the given index. - */ - com.google.protobuf.ByteString - getSdpMidsBytes(int index); - - /** - *
-     * @required
-     * 
- * - * required string uuid = 5; - * @return Whether the uuid field is set. - */ - boolean hasUuid(); - /** - *
-     * @required
-     * 
- * - * required string uuid = 5; - * @return The uuid. - */ - java.lang.String getUuid(); - /** - *
-     * @required
-     * 
- * - * required string uuid = 5; - * @return The bytes for uuid. - */ - com.google.protobuf.ByteString - getUuidBytes(); - } - /** - * Protobuf type {@code signalservice.CallMessage} - */ - public static final class CallMessage extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.CallMessage) - CallMessageOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - CallMessage.class.getName()); - } - // Use CallMessage.newBuilder() to construct. - private CallMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private CallMessage() { - type_ = 6; - sdps_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - sdpMLineIndexes_ = emptyIntList(); - sdpMids_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - uuid_ = ""; - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.CallMessage.class, org.session.libsignal.protos.SignalServiceProtos.CallMessage.Builder.class); - } - - /** - * Protobuf enum {@code signalservice.CallMessage.Type} - */ - public enum Type - implements com.google.protobuf.ProtocolMessageEnum { - /** - * PRE_OFFER = 6; - */ - PRE_OFFER(6), - /** - * OFFER = 1; - */ - OFFER(1), - /** - * ANSWER = 2; - */ - ANSWER(2), - /** - * PROVISIONAL_ANSWER = 3; - */ - PROVISIONAL_ANSWER(3), - /** - * ICE_CANDIDATES = 4; - */ - ICE_CANDIDATES(4), - /** - * END_CALL = 5; - */ - END_CALL(5), - ; - - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - Type.class.getName()); - } - /** - * PRE_OFFER = 6; - */ - public static final int PRE_OFFER_VALUE = 6; - /** - * OFFER = 1; - */ - public static final int OFFER_VALUE = 1; - /** - * ANSWER = 2; - */ - public static final int ANSWER_VALUE = 2; - /** - * PROVISIONAL_ANSWER = 3; - */ - public static final int PROVISIONAL_ANSWER_VALUE = 3; - /** - * ICE_CANDIDATES = 4; - */ - public static final int ICE_CANDIDATES_VALUE = 4; - /** - * END_CALL = 5; - */ - public static final int END_CALL_VALUE = 5; - - - public final int getNumber() { - return value; - } - - /** - * @param value The numeric wire value of the corresponding enum entry. - * @return The enum associated with the given numeric wire value. - * @deprecated Use {@link #forNumber(int)} instead. - */ - @java.lang.Deprecated - public static Type valueOf(int value) { - return forNumber(value); - } - - /** - * @param value The numeric wire value of the corresponding enum entry. - * @return The enum associated with the given numeric wire value. - */ - public static Type forNumber(int value) { - switch (value) { - case 6: return PRE_OFFER; - case 1: return OFFER; - case 2: return ANSWER; - case 3: return PROVISIONAL_ANSWER; - case 4: return ICE_CANDIDATES; - case 5: return END_CALL; - default: return null; - } - } - - public static com.google.protobuf.Internal.EnumLiteMap - internalGetValueMap() { - return internalValueMap; - } - private static final com.google.protobuf.Internal.EnumLiteMap< - Type> internalValueMap = - new com.google.protobuf.Internal.EnumLiteMap() { - public Type findValueByNumber(int number) { - return Type.forNumber(number); - } - }; - - public final com.google.protobuf.Descriptors.EnumValueDescriptor - getValueDescriptor() { - return getDescriptor().getValues().get(ordinal()); - } - public final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptorForType() { - return getDescriptor(); - } - public static final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.CallMessage.getDescriptor().getEnumTypes().get(0); - } - - private static final Type[] VALUES = values(); - - public static Type valueOf( - com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException( - "EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } - - private final int value; - - private Type(int value) { - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:signalservice.CallMessage.Type) - } - - private int bitField0_; - public static final int TYPE_FIELD_NUMBER = 1; - private int type_ = 6; - /** - *
-     * @required
-     * 
- * - * required .signalservice.CallMessage.Type type = 1; - * @return Whether the type field is set. - */ - @java.lang.Override public boolean hasType() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-     * @required
-     * 
- * - * required .signalservice.CallMessage.Type type = 1; - * @return The type. - */ - @java.lang.Override public org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type getType() { - org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type result = org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.forNumber(type_); - return result == null ? org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PRE_OFFER : result; - } - - public static final int SDPS_FIELD_NUMBER = 2; - @SuppressWarnings("serial") - private com.google.protobuf.LazyStringArrayList sdps_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - /** - * repeated string sdps = 2; - * @return A list containing the sdps. - */ - public com.google.protobuf.ProtocolStringList - getSdpsList() { - return sdps_; - } - /** - * repeated string sdps = 2; - * @return The count of sdps. - */ - public int getSdpsCount() { - return sdps_.size(); - } - /** - * repeated string sdps = 2; - * @param index The index of the element to return. - * @return The sdps at the given index. - */ - public java.lang.String getSdps(int index) { - return sdps_.get(index); - } - /** - * repeated string sdps = 2; - * @param index The index of the value to return. - * @return The bytes of the sdps at the given index. - */ - public com.google.protobuf.ByteString - getSdpsBytes(int index) { - return sdps_.getByteString(index); - } - - public static final int SDPMLINEINDEXES_FIELD_NUMBER = 3; - @SuppressWarnings("serial") - private com.google.protobuf.Internal.IntList sdpMLineIndexes_ = - emptyIntList(); - /** - * repeated uint32 sdpMLineIndexes = 3; - * @return A list containing the sdpMLineIndexes. - */ - @java.lang.Override - public java.util.List - getSdpMLineIndexesList() { - return sdpMLineIndexes_; - } - /** - * repeated uint32 sdpMLineIndexes = 3; - * @return The count of sdpMLineIndexes. - */ - public int getSdpMLineIndexesCount() { - return sdpMLineIndexes_.size(); - } - /** - * repeated uint32 sdpMLineIndexes = 3; - * @param index The index of the element to return. - * @return The sdpMLineIndexes at the given index. - */ - public int getSdpMLineIndexes(int index) { - return sdpMLineIndexes_.getInt(index); - } - - public static final int SDPMIDS_FIELD_NUMBER = 4; - @SuppressWarnings("serial") - private com.google.protobuf.LazyStringArrayList sdpMids_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - /** - * repeated string sdpMids = 4; - * @return A list containing the sdpMids. - */ - public com.google.protobuf.ProtocolStringList - getSdpMidsList() { - return sdpMids_; - } - /** - * repeated string sdpMids = 4; - * @return The count of sdpMids. - */ - public int getSdpMidsCount() { - return sdpMids_.size(); - } - /** - * repeated string sdpMids = 4; - * @param index The index of the element to return. - * @return The sdpMids at the given index. - */ - public java.lang.String getSdpMids(int index) { - return sdpMids_.get(index); - } - /** - * repeated string sdpMids = 4; - * @param index The index of the value to return. - * @return The bytes of the sdpMids at the given index. - */ - public com.google.protobuf.ByteString - getSdpMidsBytes(int index) { - return sdpMids_.getByteString(index); - } - - public static final int UUID_FIELD_NUMBER = 5; - @SuppressWarnings("serial") - private volatile java.lang.Object uuid_ = ""; - /** - *
-     * @required
-     * 
- * - * required string uuid = 5; - * @return Whether the uuid field is set. - */ - @java.lang.Override - public boolean hasUuid() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - *
-     * @required
-     * 
- * - * required string uuid = 5; - * @return The uuid. - */ - @java.lang.Override - public java.lang.String getUuid() { - java.lang.Object ref = uuid_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - uuid_ = s; - } - return s; - } - } - /** - *
-     * @required
-     * 
- * - * required string uuid = 5; - * @return The bytes for uuid. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getUuidBytes() { - java.lang.Object ref = uuid_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - uuid_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - if (!hasType()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasUuid()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - output.writeEnum(1, type_); - } - for (int i = 0; i < sdps_.size(); i++) { - com.google.protobuf.GeneratedMessage.writeString(output, 2, sdps_.getRaw(i)); - } - for (int i = 0; i < sdpMLineIndexes_.size(); i++) { - output.writeUInt32(3, sdpMLineIndexes_.getInt(i)); - } - for (int i = 0; i < sdpMids_.size(); i++) { - com.google.protobuf.GeneratedMessage.writeString(output, 4, sdpMids_.getRaw(i)); - } - if (((bitField0_ & 0x00000002) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 5, uuid_); - } - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeEnumSize(1, type_); - } - { - int dataSize = 0; - for (int i = 0; i < sdps_.size(); i++) { - dataSize += computeStringSizeNoTag(sdps_.getRaw(i)); - } - size += dataSize; - size += 1 * getSdpsList().size(); - } - { - int dataSize = 0; - for (int i = 0; i < sdpMLineIndexes_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream - .computeUInt32SizeNoTag(sdpMLineIndexes_.getInt(i)); - } - size += dataSize; - size += 1 * getSdpMLineIndexesList().size(); - } - { - int dataSize = 0; - for (int i = 0; i < sdpMids_.size(); i++) { - dataSize += computeStringSizeNoTag(sdpMids_.getRaw(i)); - } - size += dataSize; - size += 1 * getSdpMidsList().size(); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(5, uuid_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.CallMessage)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.CallMessage other = (org.session.libsignal.protos.SignalServiceProtos.CallMessage) obj; - - if (hasType() != other.hasType()) return false; - if (hasType()) { - if (type_ != other.type_) return false; - } - if (!getSdpsList() - .equals(other.getSdpsList())) return false; - if (!getSdpMLineIndexesList() - .equals(other.getSdpMLineIndexesList())) return false; - if (!getSdpMidsList() - .equals(other.getSdpMidsList())) return false; - if (hasUuid() != other.hasUuid()) return false; - if (hasUuid()) { - if (!getUuid() - .equals(other.getUuid())) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasType()) { - hash = (37 * hash) + TYPE_FIELD_NUMBER; - hash = (53 * hash) + type_; - } - if (getSdpsCount() > 0) { - hash = (37 * hash) + SDPS_FIELD_NUMBER; - hash = (53 * hash) + getSdpsList().hashCode(); - } - if (getSdpMLineIndexesCount() > 0) { - hash = (37 * hash) + SDPMLINEINDEXES_FIELD_NUMBER; - hash = (53 * hash) + getSdpMLineIndexesList().hashCode(); - } - if (getSdpMidsCount() > 0) { - hash = (37 * hash) + SDPMIDS_FIELD_NUMBER; - hash = (53 * hash) + getSdpMidsList().hashCode(); - } - if (hasUuid()) { - hash = (37 * hash) + UUID_FIELD_NUMBER; - hash = (53 * hash) + getUuid().hashCode(); - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.CallMessage prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.CallMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.CallMessage) - org.session.libsignal.protos.SignalServiceProtos.CallMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.CallMessage.class, org.session.libsignal.protos.SignalServiceProtos.CallMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.CallMessage.newBuilder() - private Builder() { - - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - - } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - type_ = 6; - sdps_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - sdpMLineIndexes_ = emptyIntList(); - sdpMids_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - uuid_ = ""; - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_descriptor; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.CallMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.CallMessage.getDefaultInstance(); - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.CallMessage build() { - org.session.libsignal.protos.SignalServiceProtos.CallMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.CallMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.CallMessage result = new org.session.libsignal.protos.SignalServiceProtos.CallMessage(this); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; - } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.CallMessage result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.type_ = type_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000002) != 0)) { - sdps_.makeImmutable(); - result.sdps_ = sdps_; - } - if (((from_bitField0_ & 0x00000004) != 0)) { - sdpMLineIndexes_.makeImmutable(); - result.sdpMLineIndexes_ = sdpMLineIndexes_; - } - if (((from_bitField0_ & 0x00000008) != 0)) { - sdpMids_.makeImmutable(); - result.sdpMids_ = sdpMids_; - } - if (((from_bitField0_ & 0x00000010) != 0)) { - result.uuid_ = uuid_; - to_bitField0_ |= 0x00000002; - } - result.bitField0_ |= to_bitField0_; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.CallMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.CallMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.CallMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.CallMessage.getDefaultInstance()) return this; - if (other.hasType()) { - setType(other.getType()); - } - if (!other.sdps_.isEmpty()) { - if (sdps_.isEmpty()) { - sdps_ = other.sdps_; - bitField0_ |= 0x00000002; - } else { - ensureSdpsIsMutable(); - sdps_.addAll(other.sdps_); - } - onChanged(); - } - if (!other.sdpMLineIndexes_.isEmpty()) { - if (sdpMLineIndexes_.isEmpty()) { - sdpMLineIndexes_ = other.sdpMLineIndexes_; - sdpMLineIndexes_.makeImmutable(); - bitField0_ |= 0x00000004; - } else { - ensureSdpMLineIndexesIsMutable(); - sdpMLineIndexes_.addAll(other.sdpMLineIndexes_); - } - onChanged(); - } - if (!other.sdpMids_.isEmpty()) { - if (sdpMids_.isEmpty()) { - sdpMids_ = other.sdpMids_; - bitField0_ |= 0x00000008; - } else { - ensureSdpMidsIsMutable(); - sdpMids_.addAll(other.sdpMids_); - } - onChanged(); - } - if (other.hasUuid()) { - uuid_ = other.uuid_; - bitField0_ |= 0x00000010; - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - if (!hasType()) { - return false; - } - if (!hasUuid()) { - return false; - } - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 8: { - int tmpRaw = input.readEnum(); - org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type tmpValue = - org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.forNumber(tmpRaw); - if (tmpValue == null) { - mergeUnknownVarintField(1, tmpRaw); - } else { - type_ = tmpRaw; - bitField0_ |= 0x00000001; - } - break; - } // case 8 - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - ensureSdpsIsMutable(); - sdps_.add(bs); - break; - } // case 18 - case 24: { - int v = input.readUInt32(); - ensureSdpMLineIndexesIsMutable(); - sdpMLineIndexes_.addInt(v); - break; - } // case 24 - case 26: { - int length = input.readRawVarint32(); - int limit = input.pushLimit(length); - ensureSdpMLineIndexesIsMutable(); - while (input.getBytesUntilLimit() > 0) { - sdpMLineIndexes_.addInt(input.readUInt32()); - } - input.popLimit(limit); - break; - } // case 26 - case 34: { - com.google.protobuf.ByteString bs = input.readBytes(); - ensureSdpMidsIsMutable(); - sdpMids_.add(bs); - break; - } // case 34 - case 42: { - uuid_ = input.readBytes(); - bitField0_ |= 0x00000010; - break; - } // case 42 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; - } - private int bitField0_; - - private int type_ = 6; - /** - *
-       * @required
-       * 
- * - * required .signalservice.CallMessage.Type type = 1; - * @return Whether the type field is set. - */ - @java.lang.Override public boolean hasType() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-       * @required
-       * 
- * - * required .signalservice.CallMessage.Type type = 1; - * @return The type. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type getType() { - org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type result = org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.forNumber(type_); - return result == null ? org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PRE_OFFER : result; - } - /** - *
-       * @required
-       * 
- * - * required .signalservice.CallMessage.Type type = 1; - * @param value The type to set. - * @return This builder for chaining. - */ - public Builder setType(org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - type_ = value.getNumber(); - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required .signalservice.CallMessage.Type type = 1; - * @return This builder for chaining. - */ - public Builder clearType() { - bitField0_ = (bitField0_ & ~0x00000001); - type_ = 6; - onChanged(); - return this; - } - - private com.google.protobuf.LazyStringArrayList sdps_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - private void ensureSdpsIsMutable() { - if (!sdps_.isModifiable()) { - sdps_ = new com.google.protobuf.LazyStringArrayList(sdps_); - } - bitField0_ |= 0x00000002; - } - /** - * repeated string sdps = 2; - * @return A list containing the sdps. - */ - public com.google.protobuf.ProtocolStringList - getSdpsList() { - sdps_.makeImmutable(); - return sdps_; - } - /** - * repeated string sdps = 2; - * @return The count of sdps. - */ - public int getSdpsCount() { - return sdps_.size(); - } - /** - * repeated string sdps = 2; - * @param index The index of the element to return. - * @return The sdps at the given index. - */ - public java.lang.String getSdps(int index) { - return sdps_.get(index); - } - /** - * repeated string sdps = 2; - * @param index The index of the value to return. - * @return The bytes of the sdps at the given index. - */ - public com.google.protobuf.ByteString - getSdpsBytes(int index) { - return sdps_.getByteString(index); - } - /** - * repeated string sdps = 2; - * @param index The index to set the value at. - * @param value The sdps to set. - * @return This builder for chaining. - */ - public Builder setSdps( - int index, java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - ensureSdpsIsMutable(); - sdps_.set(index, value); - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - /** - * repeated string sdps = 2; - * @param value The sdps to add. - * @return This builder for chaining. - */ - public Builder addSdps( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - ensureSdpsIsMutable(); - sdps_.add(value); - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - /** - * repeated string sdps = 2; - * @param values The sdps to add. - * @return This builder for chaining. - */ - public Builder addAllSdps( - java.lang.Iterable values) { - ensureSdpsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, sdps_); - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - /** - * repeated string sdps = 2; - * @return This builder for chaining. - */ - public Builder clearSdps() { - sdps_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002);; - onChanged(); - return this; - } - /** - * repeated string sdps = 2; - * @param value The bytes of the sdps to add. - * @return This builder for chaining. - */ - public Builder addSdpsBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureSdpsIsMutable(); - sdps_.add(value); - bitField0_ |= 0x00000002; - onChanged(); - return this; - } - - private com.google.protobuf.Internal.IntList sdpMLineIndexes_ = emptyIntList(); - private void ensureSdpMLineIndexesIsMutable() { - if (!sdpMLineIndexes_.isModifiable()) { - sdpMLineIndexes_ = makeMutableCopy(sdpMLineIndexes_); - } - bitField0_ |= 0x00000004; - } - /** - * repeated uint32 sdpMLineIndexes = 3; - * @return A list containing the sdpMLineIndexes. - */ - public java.util.List - getSdpMLineIndexesList() { - sdpMLineIndexes_.makeImmutable(); - return sdpMLineIndexes_; - } - /** - * repeated uint32 sdpMLineIndexes = 3; - * @return The count of sdpMLineIndexes. - */ - public int getSdpMLineIndexesCount() { - return sdpMLineIndexes_.size(); - } - /** - * repeated uint32 sdpMLineIndexes = 3; - * @param index The index of the element to return. - * @return The sdpMLineIndexes at the given index. - */ - public int getSdpMLineIndexes(int index) { - return sdpMLineIndexes_.getInt(index); - } - /** - * repeated uint32 sdpMLineIndexes = 3; - * @param index The index to set the value at. - * @param value The sdpMLineIndexes to set. - * @return This builder for chaining. - */ - public Builder setSdpMLineIndexes( - int index, int value) { - - ensureSdpMLineIndexesIsMutable(); - sdpMLineIndexes_.setInt(index, value); - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - /** - * repeated uint32 sdpMLineIndexes = 3; - * @param value The sdpMLineIndexes to add. - * @return This builder for chaining. - */ - public Builder addSdpMLineIndexes(int value) { - - ensureSdpMLineIndexesIsMutable(); - sdpMLineIndexes_.addInt(value); - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - /** - * repeated uint32 sdpMLineIndexes = 3; - * @param values The sdpMLineIndexes to add. - * @return This builder for chaining. - */ - public Builder addAllSdpMLineIndexes( - java.lang.Iterable values) { - ensureSdpMLineIndexesIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, sdpMLineIndexes_); - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - /** - * repeated uint32 sdpMLineIndexes = 3; - * @return This builder for chaining. - */ - public Builder clearSdpMLineIndexes() { - sdpMLineIndexes_ = emptyIntList(); - bitField0_ = (bitField0_ & ~0x00000004); - onChanged(); - return this; - } - - private com.google.protobuf.LazyStringArrayList sdpMids_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - private void ensureSdpMidsIsMutable() { - if (!sdpMids_.isModifiable()) { - sdpMids_ = new com.google.protobuf.LazyStringArrayList(sdpMids_); - } - bitField0_ |= 0x00000008; - } - /** - * repeated string sdpMids = 4; - * @return A list containing the sdpMids. - */ - public com.google.protobuf.ProtocolStringList - getSdpMidsList() { - sdpMids_.makeImmutable(); - return sdpMids_; - } - /** - * repeated string sdpMids = 4; - * @return The count of sdpMids. - */ - public int getSdpMidsCount() { - return sdpMids_.size(); - } - /** - * repeated string sdpMids = 4; - * @param index The index of the element to return. - * @return The sdpMids at the given index. - */ - public java.lang.String getSdpMids(int index) { - return sdpMids_.get(index); - } - /** - * repeated string sdpMids = 4; - * @param index The index of the value to return. - * @return The bytes of the sdpMids at the given index. - */ - public com.google.protobuf.ByteString - getSdpMidsBytes(int index) { - return sdpMids_.getByteString(index); - } - /** - * repeated string sdpMids = 4; - * @param index The index to set the value at. - * @param value The sdpMids to set. - * @return This builder for chaining. - */ - public Builder setSdpMids( - int index, java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - ensureSdpMidsIsMutable(); - sdpMids_.set(index, value); - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - /** - * repeated string sdpMids = 4; - * @param value The sdpMids to add. - * @return This builder for chaining. - */ - public Builder addSdpMids( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - ensureSdpMidsIsMutable(); - sdpMids_.add(value); - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - /** - * repeated string sdpMids = 4; - * @param values The sdpMids to add. - * @return This builder for chaining. - */ - public Builder addAllSdpMids( - java.lang.Iterable values) { - ensureSdpMidsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, sdpMids_); - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - /** - * repeated string sdpMids = 4; - * @return This builder for chaining. - */ - public Builder clearSdpMids() { - sdpMids_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - bitField0_ = (bitField0_ & ~0x00000008);; - onChanged(); - return this; - } - /** - * repeated string sdpMids = 4; - * @param value The bytes of the sdpMids to add. - * @return This builder for chaining. - */ - public Builder addSdpMidsBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureSdpMidsIsMutable(); - sdpMids_.add(value); - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - - private java.lang.Object uuid_ = ""; - /** - *
-       * @required
-       * 
- * - * required string uuid = 5; - * @return Whether the uuid field is set. - */ - public boolean hasUuid() { - return ((bitField0_ & 0x00000010) != 0); - } - /** - *
-       * @required
-       * 
- * - * required string uuid = 5; - * @return The uuid. - */ - public java.lang.String getUuid() { - java.lang.Object ref = uuid_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - uuid_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - /** - *
-       * @required
-       * 
- * - * required string uuid = 5; - * @return The bytes for uuid. - */ - public com.google.protobuf.ByteString - getUuidBytes() { - java.lang.Object ref = uuid_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - uuid_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - *
-       * @required
-       * 
- * - * required string uuid = 5; - * @param value The uuid to set. - * @return This builder for chaining. - */ - public Builder setUuid( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - uuid_ = value; - bitField0_ |= 0x00000010; - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required string uuid = 5; - * @return This builder for chaining. - */ - public Builder clearUuid() { - uuid_ = getDefaultInstance().getUuid(); - bitField0_ = (bitField0_ & ~0x00000010); - onChanged(); - return this; - } - /** - *
-       * @required
-       * 
- * - * required string uuid = 5; - * @param value The bytes for uuid to set. - * @return This builder for chaining. - */ - public Builder setUuidBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - uuid_ = value; - bitField0_ |= 0x00000010; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.CallMessage) - } - - // @@protoc_insertion_point(class_scope:signalservice.CallMessage) - private static final org.session.libsignal.protos.SignalServiceProtos.CallMessage DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.CallMessage(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.CallMessage getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public CallMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.CallMessage getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface ConfigurationMessageOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.ConfigurationMessage) - com.google.protobuf.MessageOrBuilder { - - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - java.util.List - getClosedGroupsList(); - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup getClosedGroups(int index); - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - int getClosedGroupsCount(); - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - java.util.List - getClosedGroupsOrBuilderList(); - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroupOrBuilder getClosedGroupsOrBuilder( - int index); - - /** - * repeated string openGroups = 2; - * @return A list containing the openGroups. - */ - java.util.List - getOpenGroupsList(); - /** - * repeated string openGroups = 2; - * @return The count of openGroups. - */ - int getOpenGroupsCount(); - /** - * repeated string openGroups = 2; - * @param index The index of the element to return. - * @return The openGroups at the given index. - */ - java.lang.String getOpenGroups(int index); - /** - * repeated string openGroups = 2; - * @param index The index of the value to return. - * @return The bytes of the openGroups at the given index. - */ - com.google.protobuf.ByteString - getOpenGroupsBytes(int index); - - /** - * optional string displayName = 3; - * @return Whether the displayName field is set. - */ - boolean hasDisplayName(); - /** - * optional string displayName = 3; - * @return The displayName. - */ - java.lang.String getDisplayName(); - /** - * optional string displayName = 3; - * @return The bytes for displayName. - */ - com.google.protobuf.ByteString - getDisplayNameBytes(); - - /** - * optional string profilePicture = 4; - * @return Whether the profilePicture field is set. - */ - boolean hasProfilePicture(); - /** - * optional string profilePicture = 4; - * @return The profilePicture. - */ - java.lang.String getProfilePicture(); - /** - * optional string profilePicture = 4; - * @return The bytes for profilePicture. - */ - com.google.protobuf.ByteString - getProfilePictureBytes(); - - /** - * optional bytes profileKey = 5; - * @return Whether the profileKey field is set. - */ - boolean hasProfileKey(); - /** - * optional bytes profileKey = 5; - * @return The profileKey. - */ - com.google.protobuf.ByteString getProfileKey(); - - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - java.util.List - getContactsList(); - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact getContacts(int index); - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - int getContactsCount(); - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - java.util.List - getContactsOrBuilderList(); - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ContactOrBuilder getContactsOrBuilder( - int index); - } - /** - * Protobuf type {@code signalservice.ConfigurationMessage} - */ - public static final class ConfigurationMessage extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.ConfigurationMessage) - ConfigurationMessageOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - ConfigurationMessage.class.getName()); - } - // Use ConfigurationMessage.newBuilder() to construct. - private ConfigurationMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private ConfigurationMessage() { - closedGroups_ = java.util.Collections.emptyList(); - openGroups_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - displayName_ = ""; - profilePicture_ = ""; - profileKey_ = com.google.protobuf.ByteString.EMPTY; - contacts_ = java.util.Collections.emptyList(); - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.class, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Builder.class); - } - - public interface ClosedGroupOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.ConfigurationMessage.ClosedGroup) - com.google.protobuf.MessageOrBuilder { - - /** - * optional bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - boolean hasPublicKey(); - /** - * optional bytes publicKey = 1; - * @return The publicKey. - */ - com.google.protobuf.ByteString getPublicKey(); - - /** - * optional string name = 2; - * @return Whether the name field is set. - */ - boolean hasName(); - /** - * optional string name = 2; - * @return The name. - */ - java.lang.String getName(); - /** - * optional string name = 2; - * @return The bytes for name. - */ - com.google.protobuf.ByteString - getNameBytes(); - - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - * @return Whether the encryptionKeyPair field is set. - */ - boolean hasEncryptionKeyPair(); - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - * @return The encryptionKeyPair. - */ - org.session.libsignal.protos.SignalServiceProtos.KeyPair getEncryptionKeyPair(); - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - */ - org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder getEncryptionKeyPairOrBuilder(); - - /** - * repeated bytes members = 4; - * @return A list containing the members. - */ - java.util.List getMembersList(); - /** - * repeated bytes members = 4; - * @return The count of members. - */ - int getMembersCount(); - /** - * repeated bytes members = 4; - * @param index The index of the element to return. - * @return The members at the given index. - */ - com.google.protobuf.ByteString getMembers(int index); - - /** - * repeated bytes admins = 5; - * @return A list containing the admins. - */ - java.util.List getAdminsList(); - /** - * repeated bytes admins = 5; - * @return The count of admins. - */ - int getAdminsCount(); - /** - * repeated bytes admins = 5; - * @param index The index of the element to return. - * @return The admins at the given index. - */ - com.google.protobuf.ByteString getAdmins(int index); - - /** - * optional uint32 expirationTimer = 6; - * @return Whether the expirationTimer field is set. - */ - boolean hasExpirationTimer(); - /** - * optional uint32 expirationTimer = 6; - * @return The expirationTimer. - */ - int getExpirationTimer(); - } - /** - * Protobuf type {@code signalservice.ConfigurationMessage.ClosedGroup} - */ - public static final class ClosedGroup extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.ConfigurationMessage.ClosedGroup) - ClosedGroupOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - ClosedGroup.class.getName()); - } - // Use ClosedGroup.newBuilder() to construct. - private ClosedGroup(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private ClosedGroup() { - publicKey_ = com.google.protobuf.ByteString.EMPTY; - name_ = ""; - members_ = emptyList(com.google.protobuf.ByteString.class); - admins_ = emptyList(com.google.protobuf.ByteString.class); - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_ClosedGroup_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_ClosedGroup_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.class, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder.class); - } - - private int bitField0_; - public static final int PUBLICKEY_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - * optional bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - - public static final int NAME_FIELD_NUMBER = 2; - @SuppressWarnings("serial") - private volatile java.lang.Object name_ = ""; - /** - * optional string name = 2; - * @return Whether the name field is set. - */ - @java.lang.Override - public boolean hasName() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - * optional string name = 2; - * @return The name. - */ - @java.lang.Override - public java.lang.String getName() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } - } - /** - * optional string name = 2; - * @return The bytes for name. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int ENCRYPTIONKEYPAIR_FIELD_NUMBER = 3; - private org.session.libsignal.protos.SignalServiceProtos.KeyPair encryptionKeyPair_; - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - * @return Whether the encryptionKeyPair field is set. - */ - @java.lang.Override - public boolean hasEncryptionKeyPair() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - * @return The encryptionKeyPair. - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.KeyPair getEncryptionKeyPair() { - return encryptionKeyPair_ == null ? org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance() : encryptionKeyPair_; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder getEncryptionKeyPairOrBuilder() { - return encryptionKeyPair_ == null ? org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance() : encryptionKeyPair_; - } - - public static final int MEMBERS_FIELD_NUMBER = 4; - @SuppressWarnings("serial") - private com.google.protobuf.Internal.ProtobufList members_ = - emptyList(com.google.protobuf.ByteString.class); - /** - * repeated bytes members = 4; - * @return A list containing the members. - */ - @java.lang.Override - public java.util.List - getMembersList() { - return members_; - } - /** - * repeated bytes members = 4; - * @return The count of members. - */ - public int getMembersCount() { - return members_.size(); - } - /** - * repeated bytes members = 4; - * @param index The index of the element to return. - * @return The members at the given index. - */ - public com.google.protobuf.ByteString getMembers(int index) { - return members_.get(index); - } - - public static final int ADMINS_FIELD_NUMBER = 5; - @SuppressWarnings("serial") - private com.google.protobuf.Internal.ProtobufList admins_ = - emptyList(com.google.protobuf.ByteString.class); - /** - * repeated bytes admins = 5; - * @return A list containing the admins. - */ - @java.lang.Override - public java.util.List - getAdminsList() { - return admins_; - } - /** - * repeated bytes admins = 5; - * @return The count of admins. - */ - public int getAdminsCount() { - return admins_.size(); - } - /** - * repeated bytes admins = 5; - * @param index The index of the element to return. - * @return The admins at the given index. - */ - public com.google.protobuf.ByteString getAdmins(int index) { - return admins_.get(index); - } - - public static final int EXPIRATIONTIMER_FIELD_NUMBER = 6; - private int expirationTimer_ = 0; - /** - * optional uint32 expirationTimer = 6; - * @return Whether the expirationTimer field is set. - */ - @java.lang.Override - public boolean hasExpirationTimer() { - return ((bitField0_ & 0x00000008) != 0); - } - /** - * optional uint32 expirationTimer = 6; - * @return The expirationTimer. - */ - @java.lang.Override - public int getExpirationTimer() { - return expirationTimer_; - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - if (hasEncryptionKeyPair()) { - if (!getEncryptionKeyPair().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - output.writeBytes(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 2, name_); - } - if (((bitField0_ & 0x00000004) != 0)) { - output.writeMessage(3, getEncryptionKeyPair()); - } - for (int i = 0; i < members_.size(); i++) { - output.writeBytes(4, members_.get(i)); - } - for (int i = 0; i < admins_.size(); i++) { - output.writeBytes(5, admins_.get(i)); - } - if (((bitField0_ & 0x00000008) != 0)) { - output.writeUInt32(6, expirationTimer_); - } - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(2, name_); - } - if (((bitField0_ & 0x00000004) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(3, getEncryptionKeyPair()); - } - { - int dataSize = 0; - for (int i = 0; i < members_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream - .computeBytesSizeNoTag(members_.get(i)); - } - size += dataSize; - size += 1 * getMembersList().size(); - } - { - int dataSize = 0; - for (int i = 0; i < admins_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream - .computeBytesSizeNoTag(admins_.get(i)); - } - size += dataSize; - size += 1 * getAdminsList().size(); - } - if (((bitField0_ & 0x00000008) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(6, expirationTimer_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup other = (org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup) obj; - - if (hasPublicKey() != other.hasPublicKey()) return false; - if (hasPublicKey()) { - if (!getPublicKey() - .equals(other.getPublicKey())) return false; - } - if (hasName() != other.hasName()) return false; - if (hasName()) { - if (!getName() - .equals(other.getName())) return false; - } - if (hasEncryptionKeyPair() != other.hasEncryptionKeyPair()) return false; - if (hasEncryptionKeyPair()) { - if (!getEncryptionKeyPair() - .equals(other.getEncryptionKeyPair())) return false; - } - if (!getMembersList() - .equals(other.getMembersList())) return false; - if (!getAdminsList() - .equals(other.getAdminsList())) return false; - if (hasExpirationTimer() != other.hasExpirationTimer()) return false; - if (hasExpirationTimer()) { - if (getExpirationTimer() - != other.getExpirationTimer()) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasPublicKey()) { - hash = (37 * hash) + PUBLICKEY_FIELD_NUMBER; - hash = (53 * hash) + getPublicKey().hashCode(); - } - if (hasName()) { - hash = (37 * hash) + NAME_FIELD_NUMBER; - hash = (53 * hash) + getName().hashCode(); - } - if (hasEncryptionKeyPair()) { - hash = (37 * hash) + ENCRYPTIONKEYPAIR_FIELD_NUMBER; - hash = (53 * hash) + getEncryptionKeyPair().hashCode(); - } - if (getMembersCount() > 0) { - hash = (37 * hash) + MEMBERS_FIELD_NUMBER; - hash = (53 * hash) + getMembersList().hashCode(); - } - if (getAdminsCount() > 0) { - hash = (37 * hash) + ADMINS_FIELD_NUMBER; - hash = (53 * hash) + getAdminsList().hashCode(); - } - if (hasExpirationTimer()) { - hash = (37 * hash) + EXPIRATIONTIMER_FIELD_NUMBER; - hash = (53 * hash) + getExpirationTimer(); - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.ConfigurationMessage.ClosedGroup} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.ConfigurationMessage.ClosedGroup) - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroupOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_ClosedGroup_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_ClosedGroup_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.class, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage - .alwaysUseFieldBuilders) { - getEncryptionKeyPairFieldBuilder(); - } - } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - publicKey_ = com.google.protobuf.ByteString.EMPTY; - name_ = ""; - encryptionKeyPair_ = null; - if (encryptionKeyPairBuilder_ != null) { - encryptionKeyPairBuilder_.dispose(); - encryptionKeyPairBuilder_ = null; - } - members_ = emptyList(com.google.protobuf.ByteString.class); - admins_ = emptyList(com.google.protobuf.ByteString.class); - expirationTimer_ = 0; - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_ClosedGroup_descriptor; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.getDefaultInstance(); - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup build() { - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup result = new org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup(this); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; - } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.publicKey_ = publicKey_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000002) != 0)) { - result.name_ = name_; - to_bitField0_ |= 0x00000002; - } - if (((from_bitField0_ & 0x00000004) != 0)) { - result.encryptionKeyPair_ = encryptionKeyPairBuilder_ == null - ? encryptionKeyPair_ - : encryptionKeyPairBuilder_.build(); - to_bitField0_ |= 0x00000004; - } - if (((from_bitField0_ & 0x00000008) != 0)) { - members_.makeImmutable(); - result.members_ = members_; - } - if (((from_bitField0_ & 0x00000010) != 0)) { - admins_.makeImmutable(); - result.admins_ = admins_; - } - if (((from_bitField0_ & 0x00000020) != 0)) { - result.expirationTimer_ = expirationTimer_; - to_bitField0_ |= 0x00000008; - } - result.bitField0_ |= to_bitField0_; - } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.getDefaultInstance()) return this; - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasName()) { - name_ = other.name_; - bitField0_ |= 0x00000002; - onChanged(); - } - if (other.hasEncryptionKeyPair()) { - mergeEncryptionKeyPair(other.getEncryptionKeyPair()); - } - if (!other.members_.isEmpty()) { - if (members_.isEmpty()) { - members_ = other.members_; - members_.makeImmutable(); - bitField0_ |= 0x00000008; - } else { - ensureMembersIsMutable(); - members_.addAll(other.members_); - } - onChanged(); - } - if (!other.admins_.isEmpty()) { - if (admins_.isEmpty()) { - admins_ = other.admins_; - admins_.makeImmutable(); - bitField0_ |= 0x00000010; - } else { - ensureAdminsIsMutable(); - admins_.addAll(other.admins_); - } - onChanged(); - } - if (other.hasExpirationTimer()) { - setExpirationTimer(other.getExpirationTimer()); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - if (hasEncryptionKeyPair()) { - if (!getEncryptionKeyPair().isInitialized()) { - return false; - } - } - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 10: { - publicKey_ = input.readBytes(); - bitField0_ |= 0x00000001; - break; - } // case 10 - case 18: { - name_ = input.readBytes(); - bitField0_ |= 0x00000002; - break; - } // case 18 - case 26: { - input.readMessage( - getEncryptionKeyPairFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000004; - break; - } // case 26 - case 34: { - com.google.protobuf.ByteString v = input.readBytes(); - ensureMembersIsMutable(); - members_.add(v); - break; - } // case 34 - case 42: { - com.google.protobuf.ByteString v = input.readBytes(); - ensureAdminsIsMutable(); - admins_.add(v); - break; - } // case 42 - case 48: { - expirationTimer_ = input.readUInt32(); - bitField0_ |= 0x00000020; - break; - } // case 48 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally - return this; - } - private int bitField0_; - - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; + private long id_ ; /** - * optional bytes publicKey = 1; - * @return Whether the publicKey field is set. + *
+         * @required
+         * 
+ * + * required uint64 id = 1; + * @return Whether the id field is set. */ @java.lang.Override - public boolean hasPublicKey() { + public boolean hasId() { return ((bitField0_ & 0x00000001) != 0); } /** - * optional bytes publicKey = 1; - * @return The publicKey. + *
+         * @required
+         * 
+ * + * required uint64 id = 1; + * @return The id. */ @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; + public long getId() { + return id_; } /** - * optional bytes publicKey = 1; - * @param value The publicKey to set. + *
+         * @required
+         * 
+ * + * required uint64 id = 1; + * @param value The id to set. * @return This builder for chaining. */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - publicKey_ = value; + public Builder setId(long value) { + + id_ = value; bitField0_ |= 0x00000001; onChanged(); return this; } /** - * optional bytes publicKey = 1; + *
+         * @required
+         * 
+ * + * required uint64 id = 1; * @return This builder for chaining. */ - public Builder clearPublicKey() { + public Builder clearId() { bitField0_ = (bitField0_ & ~0x00000001); - publicKey_ = getDefaultInstance().getPublicKey(); + id_ = 0L; onChanged(); return this; } - private java.lang.Object name_ = ""; + private java.lang.Object author_ = ""; /** - * optional string name = 2; - * @return Whether the name field is set. + *
+         * @required
+         * 
+ * + * required string author = 2; + * @return Whether the author field is set. */ - public boolean hasName() { + public boolean hasAuthor() { return ((bitField0_ & 0x00000002) != 0); } /** - * optional string name = 2; - * @return The name. + *
+         * @required
+         * 
+ * + * required string author = 2; + * @return The author. */ - public java.lang.String getName() { - java.lang.Object ref = name_; + public java.lang.String getAuthor() { + java.lang.Object ref = author_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { - name_ = s; + author_ = s; } return s; } else { @@ -33140,405 +21474,230 @@ public java.lang.String getName() { } } /** - * optional string name = 2; - * @return The bytes for name. + *
+         * @required
+         * 
+ * + * required string author = 2; + * @return The bytes for author. */ public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; + getAuthorBytes() { + java.lang.Object ref = author_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - name_ = b; + author_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** - * optional string name = 2; - * @param value The name to set. + *
+         * @required
+         * 
+ * + * required string author = 2; + * @param value The author to set. * @return This builder for chaining. */ - public Builder setName( + public Builder setAuthor( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - name_ = value; + author_ = value; bitField0_ |= 0x00000002; onChanged(); return this; } /** - * optional string name = 2; + *
+         * @required
+         * 
+ * + * required string author = 2; * @return This builder for chaining. */ - public Builder clearName() { - name_ = getDefaultInstance().getName(); + public Builder clearAuthor() { + author_ = getDefaultInstance().getAuthor(); bitField0_ = (bitField0_ & ~0x00000002); onChanged(); return this; } /** - * optional string name = 2; - * @param value The bytes for name to set. + *
+         * @required
+         * 
+ * + * required string author = 2; + * @param value The bytes for author to set. * @return This builder for chaining. */ - public Builder setNameBytes( + public Builder setAuthorBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - name_ = value; + author_ = value; bitField0_ |= 0x00000002; onChanged(); return this; } - private org.session.libsignal.protos.SignalServiceProtos.KeyPair encryptionKeyPair_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.KeyPair, org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder, org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder> encryptionKeyPairBuilder_; + private java.lang.Object emoji_ = ""; /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - * @return Whether the encryptionKeyPair field is set. + * optional string emoji = 3; + * @return Whether the emoji field is set. */ - public boolean hasEncryptionKeyPair() { + public boolean hasEmoji() { return ((bitField0_ & 0x00000004) != 0); } /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - * @return The encryptionKeyPair. - */ - public org.session.libsignal.protos.SignalServiceProtos.KeyPair getEncryptionKeyPair() { - if (encryptionKeyPairBuilder_ == null) { - return encryptionKeyPair_ == null ? org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance() : encryptionKeyPair_; - } else { - return encryptionKeyPairBuilder_.getMessage(); - } - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - */ - public Builder setEncryptionKeyPair(org.session.libsignal.protos.SignalServiceProtos.KeyPair value) { - if (encryptionKeyPairBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - encryptionKeyPair_ = value; - } else { - encryptionKeyPairBuilder_.setMessage(value); - } - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - */ - public Builder setEncryptionKeyPair( - org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder builderForValue) { - if (encryptionKeyPairBuilder_ == null) { - encryptionKeyPair_ = builderForValue.build(); - } else { - encryptionKeyPairBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000004; - onChanged(); - return this; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; + * optional string emoji = 3; + * @return The emoji. */ - public Builder mergeEncryptionKeyPair(org.session.libsignal.protos.SignalServiceProtos.KeyPair value) { - if (encryptionKeyPairBuilder_ == null) { - if (((bitField0_ & 0x00000004) != 0) && - encryptionKeyPair_ != null && - encryptionKeyPair_ != org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance()) { - getEncryptionKeyPairBuilder().mergeFrom(value); - } else { - encryptionKeyPair_ = value; + public java.lang.String getEmoji() { + java.lang.Object ref = emoji_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + emoji_ = s; } - } else { - encryptionKeyPairBuilder_.mergeFrom(value); - } - if (encryptionKeyPair_ != null) { - bitField0_ |= 0x00000004; - onChanged(); - } - return this; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - */ - public Builder clearEncryptionKeyPair() { - bitField0_ = (bitField0_ & ~0x00000004); - encryptionKeyPair_ = null; - if (encryptionKeyPairBuilder_ != null) { - encryptionKeyPairBuilder_.dispose(); - encryptionKeyPairBuilder_ = null; - } - onChanged(); - return this; - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - */ - public org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder getEncryptionKeyPairBuilder() { - bitField0_ |= 0x00000004; - onChanged(); - return getEncryptionKeyPairFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - */ - public org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder getEncryptionKeyPairOrBuilder() { - if (encryptionKeyPairBuilder_ != null) { - return encryptionKeyPairBuilder_.getMessageOrBuilder(); - } else { - return encryptionKeyPair_ == null ? - org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance() : encryptionKeyPair_; - } - } - /** - * optional .signalservice.KeyPair encryptionKeyPair = 3; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.KeyPair, org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder, org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder> - getEncryptionKeyPairFieldBuilder() { - if (encryptionKeyPairBuilder_ == null) { - encryptionKeyPairBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.KeyPair, org.session.libsignal.protos.SignalServiceProtos.KeyPair.Builder, org.session.libsignal.protos.SignalServiceProtos.KeyPairOrBuilder>( - getEncryptionKeyPair(), - getParentForChildren(), - isClean()); - encryptionKeyPair_ = null; - } - return encryptionKeyPairBuilder_; - } - - private com.google.protobuf.Internal.ProtobufList members_ = emptyList(com.google.protobuf.ByteString.class); - private void ensureMembersIsMutable() { - if (!members_.isModifiable()) { - members_ = makeMutableCopy(members_); - } - bitField0_ |= 0x00000008; - } - /** - * repeated bytes members = 4; - * @return A list containing the members. - */ - public java.util.List - getMembersList() { - members_.makeImmutable(); - return members_; - } - /** - * repeated bytes members = 4; - * @return The count of members. - */ - public int getMembersCount() { - return members_.size(); - } - /** - * repeated bytes members = 4; - * @param index The index of the element to return. - * @return The members at the given index. - */ - public com.google.protobuf.ByteString getMembers(int index) { - return members_.get(index); - } - /** - * repeated bytes members = 4; - * @param index The index to set the value at. - * @param value The members to set. - * @return This builder for chaining. - */ - public Builder setMembers( - int index, com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureMembersIsMutable(); - members_.set(index, value); - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - /** - * repeated bytes members = 4; - * @param value The members to add. - * @return This builder for chaining. - */ - public Builder addMembers(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureMembersIsMutable(); - members_.add(value); - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - /** - * repeated bytes members = 4; - * @param values The members to add. - * @return This builder for chaining. - */ - public Builder addAllMembers( - java.lang.Iterable values) { - ensureMembersIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, members_); - bitField0_ |= 0x00000008; - onChanged(); - return this; - } - /** - * repeated bytes members = 4; - * @return This builder for chaining. - */ - public Builder clearMembers() { - members_ = emptyList(com.google.protobuf.ByteString.class); - bitField0_ = (bitField0_ & ~0x00000008); - onChanged(); - return this; - } - - private com.google.protobuf.Internal.ProtobufList admins_ = emptyList(com.google.protobuf.ByteString.class); - private void ensureAdminsIsMutable() { - if (!admins_.isModifiable()) { - admins_ = makeMutableCopy(admins_); - } - bitField0_ |= 0x00000010; - } - /** - * repeated bytes admins = 5; - * @return A list containing the admins. - */ - public java.util.List - getAdminsList() { - admins_.makeImmutable(); - return admins_; - } - /** - * repeated bytes admins = 5; - * @return The count of admins. - */ - public int getAdminsCount() { - return admins_.size(); - } - /** - * repeated bytes admins = 5; - * @param index The index of the element to return. - * @return The admins at the given index. - */ - public com.google.protobuf.ByteString getAdmins(int index) { - return admins_.get(index); + return s; + } else { + return (java.lang.String) ref; + } } /** - * repeated bytes admins = 5; - * @param index The index to set the value at. - * @param value The admins to set. - * @return This builder for chaining. + * optional string emoji = 3; + * @return The bytes for emoji. */ - public Builder setAdmins( - int index, com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureAdminsIsMutable(); - admins_.set(index, value); - bitField0_ |= 0x00000010; - onChanged(); - return this; + public com.google.protobuf.ByteString + getEmojiBytes() { + java.lang.Object ref = emoji_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + emoji_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } } /** - * repeated bytes admins = 5; - * @param value The admins to add. + * optional string emoji = 3; + * @param value The emoji to set. * @return This builder for chaining. */ - public Builder addAdmins(com.google.protobuf.ByteString value) { + public Builder setEmoji( + java.lang.String value) { if (value == null) { throw new NullPointerException(); } - ensureAdminsIsMutable(); - admins_.add(value); - bitField0_ |= 0x00000010; + emoji_ = value; + bitField0_ |= 0x00000004; onChanged(); return this; } /** - * repeated bytes admins = 5; - * @param values The admins to add. + * optional string emoji = 3; * @return This builder for chaining. */ - public Builder addAllAdmins( - java.lang.Iterable values) { - ensureAdminsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, admins_); - bitField0_ |= 0x00000010; + public Builder clearEmoji() { + emoji_ = getDefaultInstance().getEmoji(); + bitField0_ = (bitField0_ & ~0x00000004); onChanged(); return this; } /** - * repeated bytes admins = 5; + * optional string emoji = 3; + * @param value The bytes for emoji to set. * @return This builder for chaining. */ - public Builder clearAdmins() { - admins_ = emptyList(com.google.protobuf.ByteString.class); - bitField0_ = (bitField0_ & ~0x00000010); + public Builder setEmojiBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + emoji_ = value; + bitField0_ |= 0x00000004; onChanged(); return this; } - private int expirationTimer_ ; + private int action_ = 0; /** - * optional uint32 expirationTimer = 6; - * @return Whether the expirationTimer field is set. + *
+         * @required
+         * 
+ * + * required .signalservice.DataMessage.Reaction.Action action = 4; + * @return Whether the action field is set. */ - @java.lang.Override - public boolean hasExpirationTimer() { - return ((bitField0_ & 0x00000020) != 0); + @java.lang.Override public boolean hasAction() { + return ((bitField0_ & 0x00000008) != 0); } /** - * optional uint32 expirationTimer = 6; - * @return The expirationTimer. + *
+         * @required
+         * 
+ * + * required .signalservice.DataMessage.Reaction.Action action = 4; + * @return The action. */ @java.lang.Override - public int getExpirationTimer() { - return expirationTimer_; + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action getAction() { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action result = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.forNumber(action_); + return result == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action.REACT : result; } /** - * optional uint32 expirationTimer = 6; - * @param value The expirationTimer to set. + *
+         * @required
+         * 
+ * + * required .signalservice.DataMessage.Reaction.Action action = 4; + * @param value The action to set. * @return This builder for chaining. */ - public Builder setExpirationTimer(int value) { - - expirationTimer_ = value; - bitField0_ |= 0x00000020; + public Builder setAction(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Action value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + action_ = value.getNumber(); onChanged(); return this; } /** - * optional uint32 expirationTimer = 6; + *
+         * @required
+         * 
+ * + * required .signalservice.DataMessage.Reaction.Action action = 4; * @return This builder for chaining. */ - public Builder clearExpirationTimer() { - bitField0_ = (bitField0_ & ~0x00000020); - expirationTimer_ = 0; + public Builder clearAction() { + bitField0_ = (bitField0_ & ~0x00000008); + action_ = 0; onChanged(); return this; } - // @@protoc_insertion_point(builder_scope:signalservice.ConfigurationMessage.ClosedGroup) + // @@protoc_insertion_point(builder_scope:signalservice.DataMessage.Reaction) } - // @@protoc_insertion_point(class_scope:signalservice.ConfigurationMessage.ClosedGroup) - private static final org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup DEFAULT_INSTANCE; + // @@protoc_insertion_point(class_scope:signalservice.DataMessage.Reaction) + private static final org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction DEFAULT_INSTANCE; static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup(); + DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction(); } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup getDefaultInstance() { + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getDefaultInstance() { return DEFAULT_INSTANCE; } - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override - public ClosedGroup parsePartialFrom( + public Reaction parsePartialFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { @@ -33557,2504 +21716,2534 @@ public ClosedGroup parsePartialFrom( } }; - public static com.google.protobuf.Parser parser() { + public static com.google.protobuf.Parser parser() { return PARSER; } @java.lang.Override - public com.google.protobuf.Parser getParserForType() { + public com.google.protobuf.Parser getParserForType() { return PARSER; } @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup getDefaultInstanceForType() { + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getDefaultInstanceForType() { return DEFAULT_INSTANCE; } } - public interface ContactOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.ConfigurationMessage.Contact) - com.google.protobuf.MessageOrBuilder { + private int bitField0_; + public static final int BODY_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object body_ = ""; + /** + * optional string body = 1; + * @return Whether the body field is set. + */ + @java.lang.Override + public boolean hasBody() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional string body = 1; + * @return The body. + */ + @java.lang.Override + public java.lang.String getBody() { + java.lang.Object ref = body_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + body_ = s; + } + return s; + } + } + /** + * optional string body = 1; + * @return The bytes for body. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getBodyBytes() { + java.lang.Object ref = body_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + body_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int ATTACHMENTS_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private java.util.List attachments_; + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + @java.lang.Override + public java.util.List getAttachmentsList() { + return attachments_; + } + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + @java.lang.Override + public java.util.List + getAttachmentsOrBuilderList() { + return attachments_; + } + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + @java.lang.Override + public int getAttachmentsCount() { + return attachments_.size(); + } + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAttachments(int index) { + return attachments_.get(index); + } + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + int index) { + return attachments_.get(index); + } + + public static final int FLAGS_FIELD_NUMBER = 4; + private int flags_ = 0; + /** + * optional uint32 flags = 4; + * @return Whether the flags field is set. + */ + @java.lang.Override + public boolean hasFlags() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional uint32 flags = 4; + * @return The flags. + */ + @java.lang.Override + public int getFlags() { + return flags_; + } + + public static final int EXPIRETIMER_FIELD_NUMBER = 5; + private int expireTimer_ = 0; + /** + * optional uint32 expireTimer = 5; + * @return Whether the expireTimer field is set. + */ + @java.lang.Override + public boolean hasExpireTimer() { + return ((bitField0_ & 0x00000004) != 0); + } + /** + * optional uint32 expireTimer = 5; + * @return The expireTimer. + */ + @java.lang.Override + public int getExpireTimer() { + return expireTimer_; + } + + public static final int PROFILEKEY_FIELD_NUMBER = 6; + private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes profileKey = 6; + * @return Whether the profileKey field is set. + */ + @java.lang.Override + public boolean hasProfileKey() { + return ((bitField0_ & 0x00000008) != 0); + } + /** + * optional bytes profileKey = 6; + * @return The profileKey. + */ + @java.lang.Override + public com.google.protobuf.ByteString getProfileKey() { + return profileKey_; + } + + public static final int TIMESTAMP_FIELD_NUMBER = 7; + private long timestamp_ = 0L; + /** + * optional uint64 timestamp = 7; + * @return Whether the timestamp field is set. + */ + @java.lang.Override + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000010) != 0); + } + /** + * optional uint64 timestamp = 7; + * @return The timestamp. + */ + @java.lang.Override + public long getTimestamp() { + return timestamp_; + } + + public static final int QUOTE_FIELD_NUMBER = 8; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote quote_; + /** + * optional .signalservice.DataMessage.Quote quote = 8; + * @return Whether the quote field is set. + */ + @java.lang.Override + public boolean hasQuote() { + return ((bitField0_ & 0x00000020) != 0); + } + /** + * optional .signalservice.DataMessage.Quote quote = 8; + * @return The quote. + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote getQuote() { + return quote_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance() : quote_; + } + /** + * optional .signalservice.DataMessage.Quote quote = 8; + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder getQuoteOrBuilder() { + return quote_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance() : quote_; + } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - boolean hasPublicKey(); - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - com.google.protobuf.ByteString getPublicKey(); + public static final int PREVIEW_FIELD_NUMBER = 10; + @SuppressWarnings("serial") + private java.util.List preview_; + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + @java.lang.Override + public java.util.List getPreviewList() { + return preview_; + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + @java.lang.Override + public java.util.List + getPreviewOrBuilderList() { + return preview_; + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + @java.lang.Override + public int getPreviewCount() { + return preview_.size(); + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview getPreview(int index) { + return preview_.get(index); + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder getPreviewOrBuilder( + int index) { + return preview_.get(index); + } - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return Whether the name field is set. - */ - boolean hasName(); - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return The name. - */ - java.lang.String getName(); - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return The bytes for name. - */ - com.google.protobuf.ByteString - getNameBytes(); + public static final int REACTION_FIELD_NUMBER = 11; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction reaction_; + /** + * optional .signalservice.DataMessage.Reaction reaction = 11; + * @return Whether the reaction field is set. + */ + @java.lang.Override + public boolean hasReaction() { + return ((bitField0_ & 0x00000040) != 0); + } + /** + * optional .signalservice.DataMessage.Reaction reaction = 11; + * @return The reaction. + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getReaction() { + return reaction_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance() : reaction_; + } + /** + * optional .signalservice.DataMessage.Reaction reaction = 11; + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder getReactionOrBuilder() { + return reaction_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance() : reaction_; + } - /** - * optional string profilePicture = 3; - * @return Whether the profilePicture field is set. - */ - boolean hasProfilePicture(); - /** - * optional string profilePicture = 3; - * @return The profilePicture. - */ - java.lang.String getProfilePicture(); - /** - * optional string profilePicture = 3; - * @return The bytes for profilePicture. - */ - com.google.protobuf.ByteString - getProfilePictureBytes(); + public static final int PROFILE_FIELD_NUMBER = 101; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; + /** + * optional .signalservice.DataMessage.LokiProfile profile = 101; + * @return Whether the profile field is set. + */ + @java.lang.Override + public boolean hasProfile() { + return ((bitField0_ & 0x00000080) != 0); + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 101; + * @return The profile. + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { + return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 101; + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { + return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; + } - /** - * optional bytes profileKey = 4; - * @return Whether the profileKey field is set. - */ - boolean hasProfileKey(); - /** - * optional bytes profileKey = 4; - * @return The profileKey. - */ - com.google.protobuf.ByteString getProfileKey(); + public static final int OPENGROUPINVITATION_FIELD_NUMBER = 102; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation openGroupInvitation_; + /** + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; + * @return Whether the openGroupInvitation field is set. + */ + @java.lang.Override + public boolean hasOpenGroupInvitation() { + return ((bitField0_ & 0x00000100) != 0); + } + /** + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; + * @return The openGroupInvitation. + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation getOpenGroupInvitation() { + return openGroupInvitation_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance() : openGroupInvitation_; + } + /** + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder getOpenGroupInvitationOrBuilder() { + return openGroupInvitation_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance() : openGroupInvitation_; + } - /** - * optional bool isApproved = 5; - * @return Whether the isApproved field is set. - */ - boolean hasIsApproved(); - /** - * optional bool isApproved = 5; - * @return The isApproved. - */ - boolean getIsApproved(); + public static final int SYNCTARGET_FIELD_NUMBER = 105; + @SuppressWarnings("serial") + private volatile java.lang.Object syncTarget_ = ""; + /** + * optional string syncTarget = 105; + * @return Whether the syncTarget field is set. + */ + @java.lang.Override + public boolean hasSyncTarget() { + return ((bitField0_ & 0x00000200) != 0); + } + /** + * optional string syncTarget = 105; + * @return The syncTarget. + */ + @java.lang.Override + public java.lang.String getSyncTarget() { + java.lang.Object ref = syncTarget_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + syncTarget_ = s; + } + return s; + } + } + /** + * optional string syncTarget = 105; + * @return The bytes for syncTarget. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getSyncTargetBytes() { + java.lang.Object ref = syncTarget_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + syncTarget_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } - /** - * optional bool isBlocked = 6; - * @return Whether the isBlocked field is set. - */ - boolean hasIsBlocked(); - /** - * optional bool isBlocked = 6; - * @return The isBlocked. - */ - boolean getIsBlocked(); + public static final int BLOCKSCOMMUNITYMESSAGEREQUESTS_FIELD_NUMBER = 106; + private boolean blocksCommunityMessageRequests_ = false; + /** + * optional bool blocksCommunityMessageRequests = 106; + * @return Whether the blocksCommunityMessageRequests field is set. + */ + @java.lang.Override + public boolean hasBlocksCommunityMessageRequests() { + return ((bitField0_ & 0x00000400) != 0); + } + /** + * optional bool blocksCommunityMessageRequests = 106; + * @return The blocksCommunityMessageRequests. + */ + @java.lang.Override + public boolean getBlocksCommunityMessageRequests() { + return blocksCommunityMessageRequests_; + } - /** - * optional bool didApproveMe = 7; - * @return Whether the didApproveMe field is set. - */ - boolean hasDidApproveMe(); - /** - * optional bool didApproveMe = 7; - * @return The didApproveMe. - */ - boolean getDidApproveMe(); + public static final int GROUPUPDATEMESSAGE_FIELD_NUMBER = 120; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage groupUpdateMessage_; + /** + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; + * @return Whether the groupUpdateMessage field is set. + */ + @java.lang.Override + public boolean hasGroupUpdateMessage() { + return ((bitField0_ & 0x00000800) != 0); } /** - * Protobuf type {@code signalservice.ConfigurationMessage.Contact} + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; + * @return The groupUpdateMessage. */ - public static final class Contact extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.ConfigurationMessage.Contact) - ContactOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - Contact.class.getName()); + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage getGroupUpdateMessage() { + return groupUpdateMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance() : groupUpdateMessage_; + } + /** + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder getGroupUpdateMessageOrBuilder() { + return groupUpdateMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance() : groupUpdateMessage_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + for (int i = 0; i < getAttachmentsCount(); i++) { + if (!getAttachments(i).isInitialized()) { + memoizedIsInitialized = 0; + return false; + } } - // Use Contact.newBuilder() to construct. - private Contact(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); + if (hasQuote()) { + if (!getQuote().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } } - private Contact() { - publicKey_ = com.google.protobuf.ByteString.EMPTY; - name_ = ""; - profilePicture_ = ""; - profileKey_ = com.google.protobuf.ByteString.EMPTY; + for (int i = 0; i < getPreviewCount(); i++) { + if (!getPreview(i).isInitialized()) { + memoizedIsInitialized = 0; + return false; + } } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_Contact_descriptor; + if (hasReaction()) { + if (!getReaction().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_Contact_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.class, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder.class); + if (hasOpenGroupInvitation()) { + if (!getOpenGroupInvitation().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } + if (hasGroupUpdateMessage()) { + if (!getGroupUpdateMessage().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } } + memoizedIsInitialized = 1; + return true; + } - private int bitField0_; - public static final int PUBLICKEY_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) != 0)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, body_); } - /** - *
-       * @required
-       * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; + for (int i = 0; i < attachments_.size(); i++) { + output.writeMessage(2, attachments_.get(i)); + } + if (((bitField0_ & 0x00000002) != 0)) { + output.writeUInt32(4, flags_); + } + if (((bitField0_ & 0x00000004) != 0)) { + output.writeUInt32(5, expireTimer_); + } + if (((bitField0_ & 0x00000008) != 0)) { + output.writeBytes(6, profileKey_); + } + if (((bitField0_ & 0x00000010) != 0)) { + output.writeUInt64(7, timestamp_); + } + if (((bitField0_ & 0x00000020) != 0)) { + output.writeMessage(8, getQuote()); + } + for (int i = 0; i < preview_.size(); i++) { + output.writeMessage(10, preview_.get(i)); + } + if (((bitField0_ & 0x00000040) != 0)) { + output.writeMessage(11, getReaction()); + } + if (((bitField0_ & 0x00000080) != 0)) { + output.writeMessage(101, getProfile()); + } + if (((bitField0_ & 0x00000100) != 0)) { + output.writeMessage(102, getOpenGroupInvitation()); + } + if (((bitField0_ & 0x00000200) != 0)) { + com.google.protobuf.GeneratedMessage.writeString(output, 105, syncTarget_); + } + if (((bitField0_ & 0x00000400) != 0)) { + output.writeBool(106, blocksCommunityMessageRequests_); + } + if (((bitField0_ & 0x00000800) != 0)) { + output.writeMessage(120, getGroupUpdateMessage()); } + getUnknownFields().writeTo(output); + } - public static final int NAME_FIELD_NUMBER = 2; - @SuppressWarnings("serial") - private volatile java.lang.Object name_ = ""; - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return Whether the name field is set. - */ - @java.lang.Override - public boolean hasName() { - return ((bitField0_ & 0x00000002) != 0); + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, body_); } - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return The name. - */ - @java.lang.Override - public java.lang.String getName() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } + for (int i = 0; i < attachments_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, attachments_.get(i)); + } + if (((bitField0_ & 0x00000002) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(4, flags_); + } + if (((bitField0_ & 0x00000004) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(5, expireTimer_); + } + if (((bitField0_ & 0x00000008) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(6, profileKey_); + } + if (((bitField0_ & 0x00000010) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(7, timestamp_); } - /** - *
-       * @required
-       * 
- * - * required string name = 2; - * @return The bytes for name. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } + if (((bitField0_ & 0x00000020) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(8, getQuote()); } - - public static final int PROFILEPICTURE_FIELD_NUMBER = 3; - @SuppressWarnings("serial") - private volatile java.lang.Object profilePicture_ = ""; - /** - * optional string profilePicture = 3; - * @return Whether the profilePicture field is set. - */ - @java.lang.Override - public boolean hasProfilePicture() { - return ((bitField0_ & 0x00000004) != 0); + for (int i = 0; i < preview_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(10, preview_.get(i)); } - /** - * optional string profilePicture = 3; - * @return The profilePicture. - */ - @java.lang.Override - public java.lang.String getProfilePicture() { - java.lang.Object ref = profilePicture_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - profilePicture_ = s; - } - return s; - } + if (((bitField0_ & 0x00000040) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(11, getReaction()); } - /** - * optional string profilePicture = 3; - * @return The bytes for profilePicture. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getProfilePictureBytes() { - java.lang.Object ref = profilePicture_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - profilePicture_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } + if (((bitField0_ & 0x00000080) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(101, getProfile()); } - - public static final int PROFILEKEY_FIELD_NUMBER = 4; - private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes profileKey = 4; - * @return Whether the profileKey field is set. - */ - @java.lang.Override - public boolean hasProfileKey() { - return ((bitField0_ & 0x00000008) != 0); + if (((bitField0_ & 0x00000100) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(102, getOpenGroupInvitation()); } - /** - * optional bytes profileKey = 4; - * @return The profileKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getProfileKey() { - return profileKey_; + if (((bitField0_ & 0x00000200) != 0)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(105, syncTarget_); } - - public static final int ISAPPROVED_FIELD_NUMBER = 5; - private boolean isApproved_ = false; - /** - * optional bool isApproved = 5; - * @return Whether the isApproved field is set. - */ - @java.lang.Override - public boolean hasIsApproved() { - return ((bitField0_ & 0x00000010) != 0); + if (((bitField0_ & 0x00000400) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(106, blocksCommunityMessageRequests_); } - /** - * optional bool isApproved = 5; - * @return The isApproved. - */ - @java.lang.Override - public boolean getIsApproved() { - return isApproved_; + if (((bitField0_ & 0x00000800) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(120, getGroupUpdateMessage()); } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } - public static final int ISBLOCKED_FIELD_NUMBER = 6; - private boolean isBlocked_ = false; - /** - * optional bool isBlocked = 6; - * @return Whether the isBlocked field is set. - */ - @java.lang.Override - public boolean hasIsBlocked() { - return ((bitField0_ & 0x00000020) != 0); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; } - /** - * optional bool isBlocked = 6; - * @return The isBlocked. - */ - @java.lang.Override - public boolean getIsBlocked() { - return isBlocked_; + if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage)) { + return super.equals(obj); } + org.session.libsignal.protos.SignalServiceProtos.DataMessage other = (org.session.libsignal.protos.SignalServiceProtos.DataMessage) obj; - public static final int DIDAPPROVEME_FIELD_NUMBER = 7; - private boolean didApproveMe_ = false; - /** - * optional bool didApproveMe = 7; - * @return Whether the didApproveMe field is set. - */ - @java.lang.Override - public boolean hasDidApproveMe() { - return ((bitField0_ & 0x00000040) != 0); + if (hasBody() != other.hasBody()) return false; + if (hasBody()) { + if (!getBody() + .equals(other.getBody())) return false; } - /** - * optional bool didApproveMe = 7; - * @return The didApproveMe. - */ - @java.lang.Override - public boolean getDidApproveMe() { - return didApproveMe_; + if (!getAttachmentsList() + .equals(other.getAttachmentsList())) return false; + if (hasFlags() != other.hasFlags()) return false; + if (hasFlags()) { + if (getFlags() + != other.getFlags()) return false; } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - if (!hasPublicKey()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasName()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; + if (hasExpireTimer() != other.hasExpireTimer()) return false; + if (hasExpireTimer()) { + if (getExpireTimer() + != other.getExpireTimer()) return false; } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) != 0)) { - output.writeBytes(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 2, name_); - } - if (((bitField0_ & 0x00000004) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 3, profilePicture_); - } - if (((bitField0_ & 0x00000008) != 0)) { - output.writeBytes(4, profileKey_); - } - if (((bitField0_ & 0x00000010) != 0)) { - output.writeBool(5, isApproved_); - } - if (((bitField0_ & 0x00000020) != 0)) { - output.writeBool(6, isBlocked_); - } - if (((bitField0_ & 0x00000040) != 0)) { - output.writeBool(7, didApproveMe_); - } - getUnknownFields().writeTo(output); + if (hasProfileKey() != other.hasProfileKey()) return false; + if (hasProfileKey()) { + if (!getProfileKey() + .equals(other.getProfileKey())) return false; } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, publicKey_); - } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(2, name_); - } - if (((bitField0_ & 0x00000004) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(3, profilePicture_); - } - if (((bitField0_ & 0x00000008) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(4, profileKey_); - } - if (((bitField0_ & 0x00000010) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBoolSize(5, isApproved_); - } - if (((bitField0_ & 0x00000020) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBoolSize(6, isBlocked_); - } - if (((bitField0_ & 0x00000040) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBoolSize(7, didApproveMe_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; + if (hasTimestamp() != other.hasTimestamp()) return false; + if (hasTimestamp()) { + if (getTimestamp() + != other.getTimestamp()) return false; + } + if (hasQuote() != other.hasQuote()) return false; + if (hasQuote()) { + if (!getQuote() + .equals(other.getQuote())) return false; + } + if (!getPreviewList() + .equals(other.getPreviewList())) return false; + if (hasReaction() != other.hasReaction()) return false; + if (hasReaction()) { + if (!getReaction() + .equals(other.getReaction())) return false; + } + if (hasProfile() != other.hasProfile()) return false; + if (hasProfile()) { + if (!getProfile() + .equals(other.getProfile())) return false; + } + if (hasOpenGroupInvitation() != other.hasOpenGroupInvitation()) return false; + if (hasOpenGroupInvitation()) { + if (!getOpenGroupInvitation() + .equals(other.getOpenGroupInvitation())) return false; + } + if (hasSyncTarget() != other.hasSyncTarget()) return false; + if (hasSyncTarget()) { + if (!getSyncTarget() + .equals(other.getSyncTarget())) return false; } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact)) { - return super.equals(obj); - } - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact other = (org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact) obj; - - if (hasPublicKey() != other.hasPublicKey()) return false; - if (hasPublicKey()) { - if (!getPublicKey() - .equals(other.getPublicKey())) return false; - } - if (hasName() != other.hasName()) return false; - if (hasName()) { - if (!getName() - .equals(other.getName())) return false; - } - if (hasProfilePicture() != other.hasProfilePicture()) return false; - if (hasProfilePicture()) { - if (!getProfilePicture() - .equals(other.getProfilePicture())) return false; - } - if (hasProfileKey() != other.hasProfileKey()) return false; - if (hasProfileKey()) { - if (!getProfileKey() - .equals(other.getProfileKey())) return false; - } - if (hasIsApproved() != other.hasIsApproved()) return false; - if (hasIsApproved()) { - if (getIsApproved() - != other.getIsApproved()) return false; - } - if (hasIsBlocked() != other.hasIsBlocked()) return false; - if (hasIsBlocked()) { - if (getIsBlocked() - != other.getIsBlocked()) return false; - } - if (hasDidApproveMe() != other.hasDidApproveMe()) return false; - if (hasDidApproveMe()) { - if (getDidApproveMe() - != other.getDidApproveMe()) return false; - } - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; + if (hasBlocksCommunityMessageRequests() != other.hasBlocksCommunityMessageRequests()) return false; + if (hasBlocksCommunityMessageRequests()) { + if (getBlocksCommunityMessageRequests() + != other.getBlocksCommunityMessageRequests()) return false; } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (hasPublicKey()) { - hash = (37 * hash) + PUBLICKEY_FIELD_NUMBER; - hash = (53 * hash) + getPublicKey().hashCode(); - } - if (hasName()) { - hash = (37 * hash) + NAME_FIELD_NUMBER; - hash = (53 * hash) + getName().hashCode(); - } - if (hasProfilePicture()) { - hash = (37 * hash) + PROFILEPICTURE_FIELD_NUMBER; - hash = (53 * hash) + getProfilePicture().hashCode(); - } - if (hasProfileKey()) { - hash = (37 * hash) + PROFILEKEY_FIELD_NUMBER; - hash = (53 * hash) + getProfileKey().hashCode(); - } - if (hasIsApproved()) { - hash = (37 * hash) + ISAPPROVED_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getIsApproved()); - } - if (hasIsBlocked()) { - hash = (37 * hash) + ISBLOCKED_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getIsBlocked()); - } - if (hasDidApproveMe()) { - hash = (37 * hash) + DIDAPPROVEME_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getDidApproveMe()); - } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; + if (hasGroupUpdateMessage() != other.hasGroupUpdateMessage()) return false; + if (hasGroupUpdateMessage()) { + if (!getGroupUpdateMessage() + .equals(other.getGroupUpdateMessage())) return false; } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasBody()) { + hash = (37 * hash) + BODY_FIELD_NUMBER; + hash = (53 * hash) + getBody().hashCode(); } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); + if (getAttachmentsCount() > 0) { + hash = (37 * hash) + ATTACHMENTS_FIELD_NUMBER; + hash = (53 * hash) + getAttachmentsList().hashCode(); } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); + if (hasFlags()) { + hash = (37 * hash) + FLAGS_FIELD_NUMBER; + hash = (53 * hash) + getFlags(); } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); + if (hasExpireTimer()) { + hash = (37 * hash) + EXPIRETIMER_FIELD_NUMBER; + hash = (53 * hash) + getExpireTimer(); } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); + if (hasProfileKey()) { + hash = (37 * hash) + PROFILEKEY_FIELD_NUMBER; + hash = (53 * hash) + getProfileKey().hashCode(); } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); + if (hasTimestamp()) { + hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getTimestamp()); + } + if (hasQuote()) { + hash = (37 * hash) + QUOTE_FIELD_NUMBER; + hash = (53 * hash) + getQuote().hashCode(); + } + if (getPreviewCount() > 0) { + hash = (37 * hash) + PREVIEW_FIELD_NUMBER; + hash = (53 * hash) + getPreviewList().hashCode(); + } + if (hasReaction()) { + hash = (37 * hash) + REACTION_FIELD_NUMBER; + hash = (53 * hash) + getReaction().hashCode(); + } + if (hasProfile()) { + hash = (37 * hash) + PROFILE_FIELD_NUMBER; + hash = (53 * hash) + getProfile().hashCode(); + } + if (hasOpenGroupInvitation()) { + hash = (37 * hash) + OPENGROUPINVITATION_FIELD_NUMBER; + hash = (53 * hash) + getOpenGroupInvitation().hashCode(); + } + if (hasSyncTarget()) { + hash = (37 * hash) + SYNCTARGET_FIELD_NUMBER; + hash = (53 * hash) + getSyncTarget().hashCode(); + } + if (hasBlocksCommunityMessageRequests()) { + hash = (37 * hash) + BLOCKSCOMMUNITYMESSAGEREQUESTS_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getBlocksCommunityMessageRequests()); + } + if (hasGroupUpdateMessage()) { + hash = (37 * hash) + GROUPUPDATEMESSAGE_FIELD_NUMBER; + hash = (53 * hash) + getGroupUpdateMessage().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.DataMessage prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.DataMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:signalservice.DataMessage) + org.session.libsignal.protos.SignalServiceProtos.DataMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_descriptor; } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Builder.class); } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); + // Construct using org.session.libsignal.protos.SignalServiceProtos.DataMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage + .alwaysUseFieldBuilders) { + getAttachmentsFieldBuilder(); + getQuoteFieldBuilder(); + getPreviewFieldBuilder(); + getReactionFieldBuilder(); + getProfileFieldBuilder(); + getOpenGroupInvitationFieldBuilder(); + getGroupUpdateMessageFieldBuilder(); + } } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + body_ = ""; + if (attachmentsBuilder_ == null) { + attachments_ = java.util.Collections.emptyList(); + } else { + attachments_ = null; + attachmentsBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000002); + flags_ = 0; + expireTimer_ = 0; + profileKey_ = com.google.protobuf.ByteString.EMPTY; + timestamp_ = 0L; + quote_ = null; + if (quoteBuilder_ != null) { + quoteBuilder_.dispose(); + quoteBuilder_ = null; + } + if (previewBuilder_ == null) { + preview_ = java.util.Collections.emptyList(); + } else { + preview_ = null; + previewBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000080); + reaction_ = null; + if (reactionBuilder_ != null) { + reactionBuilder_.dispose(); + reactionBuilder_ = null; + } + profile_ = null; + if (profileBuilder_ != null) { + profileBuilder_.dispose(); + profileBuilder_ = null; + } + openGroupInvitation_ = null; + if (openGroupInvitationBuilder_ != null) { + openGroupInvitationBuilder_.dispose(); + openGroupInvitationBuilder_ = null; + } + syncTarget_ = ""; + blocksCommunityMessageRequests_ = false; + groupUpdateMessage_ = null; + if (groupUpdateMessageBuilder_ != null) { + groupUpdateMessageBuilder_.dispose(); + groupUpdateMessageBuilder_ = null; + } + return this; } @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_descriptor; } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.DataMessage.getDefaultInstance(); } + @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); + public org.session.libsignal.protos.SignalServiceProtos.DataMessage build() { + org.session.libsignal.protos.SignalServiceProtos.DataMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; } @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; + public org.session.libsignal.protos.SignalServiceProtos.DataMessage buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.DataMessage result = new org.session.libsignal.protos.SignalServiceProtos.DataMessage(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; } - /** - * Protobuf type {@code signalservice.ConfigurationMessage.Contact} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.ConfigurationMessage.Contact) - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ContactOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_Contact_descriptor; - } - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_Contact_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.class, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder.class); + private void buildPartialRepeatedFields(org.session.libsignal.protos.SignalServiceProtos.DataMessage result) { + if (attachmentsBuilder_ == null) { + if (((bitField0_ & 0x00000002) != 0)) { + attachments_ = java.util.Collections.unmodifiableList(attachments_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.attachments_ = attachments_; + } else { + result.attachments_ = attachmentsBuilder_.build(); } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.newBuilder() - private Builder() { - + if (previewBuilder_ == null) { + if (((bitField0_ & 0x00000080) != 0)) { + preview_ = java.util.Collections.unmodifiableList(preview_); + bitField0_ = (bitField0_ & ~0x00000080); + } + result.preview_ = preview_; + } else { + result.preview_ = previewBuilder_.build(); } + } - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - + private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.DataMessage result) { + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.body_ = body_; + to_bitField0_ |= 0x00000001; } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - publicKey_ = com.google.protobuf.ByteString.EMPTY; - name_ = ""; - profilePicture_ = ""; - profileKey_ = com.google.protobuf.ByteString.EMPTY; - isApproved_ = false; - isBlocked_ = false; - didApproveMe_ = false; - return this; + if (((from_bitField0_ & 0x00000004) != 0)) { + result.flags_ = flags_; + to_bitField0_ |= 0x00000002; } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_Contact_descriptor; + if (((from_bitField0_ & 0x00000008) != 0)) { + result.expireTimer_ = expireTimer_; + to_bitField0_ |= 0x00000004; } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.getDefaultInstance(); + if (((from_bitField0_ & 0x00000010) != 0)) { + result.profileKey_ = profileKey_; + to_bitField0_ |= 0x00000008; } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact build() { - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; + if (((from_bitField0_ & 0x00000020) != 0)) { + result.timestamp_ = timestamp_; + to_bitField0_ |= 0x00000010; } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact result = new org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact(this); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; + if (((from_bitField0_ & 0x00000040) != 0)) { + result.quote_ = quoteBuilder_ == null + ? quote_ + : quoteBuilder_.build(); + to_bitField0_ |= 0x00000020; } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact result) { - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) != 0)) { - result.publicKey_ = publicKey_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000002) != 0)) { - result.name_ = name_; - to_bitField0_ |= 0x00000002; - } - if (((from_bitField0_ & 0x00000004) != 0)) { - result.profilePicture_ = profilePicture_; - to_bitField0_ |= 0x00000004; - } - if (((from_bitField0_ & 0x00000008) != 0)) { - result.profileKey_ = profileKey_; - to_bitField0_ |= 0x00000008; - } - if (((from_bitField0_ & 0x00000010) != 0)) { - result.isApproved_ = isApproved_; - to_bitField0_ |= 0x00000010; - } - if (((from_bitField0_ & 0x00000020) != 0)) { - result.isBlocked_ = isBlocked_; - to_bitField0_ |= 0x00000020; - } - if (((from_bitField0_ & 0x00000040) != 0)) { - result.didApproveMe_ = didApproveMe_; - to_bitField0_ |= 0x00000040; - } - result.bitField0_ |= to_bitField0_; + if (((from_bitField0_ & 0x00000100) != 0)) { + result.reaction_ = reactionBuilder_ == null + ? reaction_ + : reactionBuilder_.build(); + to_bitField0_ |= 0x00000040; + } + if (((from_bitField0_ & 0x00000200) != 0)) { + result.profile_ = profileBuilder_ == null + ? profile_ + : profileBuilder_.build(); + to_bitField0_ |= 0x00000080; } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact)other); - } else { - super.mergeFrom(other); - return this; - } + if (((from_bitField0_ & 0x00000400) != 0)) { + result.openGroupInvitation_ = openGroupInvitationBuilder_ == null + ? openGroupInvitation_ + : openGroupInvitationBuilder_.build(); + to_bitField0_ |= 0x00000100; } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.getDefaultInstance()) return this; - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasName()) { - name_ = other.name_; - bitField0_ |= 0x00000002; - onChanged(); - } - if (other.hasProfilePicture()) { - profilePicture_ = other.profilePicture_; - bitField0_ |= 0x00000004; - onChanged(); - } - if (other.hasProfileKey()) { - setProfileKey(other.getProfileKey()); - } - if (other.hasIsApproved()) { - setIsApproved(other.getIsApproved()); - } - if (other.hasIsBlocked()) { - setIsBlocked(other.getIsBlocked()); - } - if (other.hasDidApproveMe()) { - setDidApproveMe(other.getDidApproveMe()); - } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); - return this; + if (((from_bitField0_ & 0x00000800) != 0)) { + result.syncTarget_ = syncTarget_; + to_bitField0_ |= 0x00000200; } - - @java.lang.Override - public final boolean isInitialized() { - if (!hasPublicKey()) { - return false; - } - if (!hasName()) { - return false; - } - return true; + if (((from_bitField0_ & 0x00001000) != 0)) { + result.blocksCommunityMessageRequests_ = blocksCommunityMessageRequests_; + to_bitField0_ |= 0x00000400; + } + if (((from_bitField0_ & 0x00002000) != 0)) { + result.groupUpdateMessage_ = groupUpdateMessageBuilder_ == null + ? groupUpdateMessage_ + : groupUpdateMessageBuilder_.build(); + to_bitField0_ |= 0x00000800; } + result.bitField0_ |= to_bitField0_; + } - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 10: { - publicKey_ = input.readBytes(); - bitField0_ |= 0x00000001; - break; - } // case 10 - case 18: { - name_ = input.readBytes(); - bitField0_ |= 0x00000002; - break; - } // case 18 - case 26: { - profilePicture_ = input.readBytes(); - bitField0_ |= 0x00000004; - break; - } // case 26 - case 34: { - profileKey_ = input.readBytes(); - bitField0_ |= 0x00000008; - break; - } // case 34 - case 40: { - isApproved_ = input.readBool(); - bitField0_ |= 0x00000010; - break; - } // case 40 - case 48: { - isBlocked_ = input.readBool(); - bitField0_ |= 0x00000020; - break; - } // case 48 - case 56: { - didApproveMe_ = input.readBool(); - bitField0_ |= 0x00000040; - break; - } // case 56 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { - onChanged(); - } // finally + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.DataMessage)other); + } else { + super.mergeFrom(other); return this; } - private int bitField0_; + } - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - *
-         * @required
-         * 
- * - * required bytes publicKey = 1; - * @return Whether the publicKey field is set. - */ - @java.lang.Override - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - *
-         * @required
-         * 
- * - * required bytes publicKey = 1; - * @return The publicKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - /** - *
-         * @required
-         * 
- * - * required bytes publicKey = 1; - * @param value The publicKey to set. - * @return This builder for chaining. - */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - publicKey_ = value; + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.DataMessage other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.DataMessage.getDefaultInstance()) return this; + if (other.hasBody()) { + body_ = other.body_; bitField0_ |= 0x00000001; onChanged(); - return this; - } - /** - *
-         * @required
-         * 
- * - * required bytes publicKey = 1; - * @return This builder for chaining. - */ - public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000001); - publicKey_ = getDefaultInstance().getPublicKey(); - onChanged(); - return this; - } - - private java.lang.Object name_ = ""; - /** - *
-         * @required
-         * 
- * - * required string name = 2; - * @return Whether the name field is set. - */ - public boolean hasName() { - return ((bitField0_ & 0x00000002) != 0); } - /** - *
-         * @required
-         * 
- * - * required string name = 2; - * @return The name. - */ - public java.lang.String getName() { - java.lang.Object ref = name_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; + if (attachmentsBuilder_ == null) { + if (!other.attachments_.isEmpty()) { + if (attachments_.isEmpty()) { + attachments_ = other.attachments_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureAttachmentsIsMutable(); + attachments_.addAll(other.attachments_); + } + onChanged(); + } + } else { + if (!other.attachments_.isEmpty()) { + if (attachmentsBuilder_.isEmpty()) { + attachmentsBuilder_.dispose(); + attachmentsBuilder_ = null; + attachments_ = other.attachments_; + bitField0_ = (bitField0_ & ~0x00000002); + attachmentsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getAttachmentsFieldBuilder() : null; + } else { + attachmentsBuilder_.addAllMessages(other.attachments_); } - return s; - } else { - return (java.lang.String) ref; } } - /** - *
-         * @required
-         * 
- * - * required string name = 2; - * @return The bytes for name. - */ - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; + if (other.hasFlags()) { + setFlags(other.getFlags()); + } + if (other.hasExpireTimer()) { + setExpireTimer(other.getExpireTimer()); + } + if (other.hasProfileKey()) { + setProfileKey(other.getProfileKey()); + } + if (other.hasTimestamp()) { + setTimestamp(other.getTimestamp()); + } + if (other.hasQuote()) { + mergeQuote(other.getQuote()); + } + if (previewBuilder_ == null) { + if (!other.preview_.isEmpty()) { + if (preview_.isEmpty()) { + preview_ = other.preview_; + bitField0_ = (bitField0_ & ~0x00000080); + } else { + ensurePreviewIsMutable(); + preview_.addAll(other.preview_); + } + onChanged(); + } + } else { + if (!other.preview_.isEmpty()) { + if (previewBuilder_.isEmpty()) { + previewBuilder_.dispose(); + previewBuilder_ = null; + preview_ = other.preview_; + bitField0_ = (bitField0_ & ~0x00000080); + previewBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getPreviewFieldBuilder() : null; + } else { + previewBuilder_.addAllMessages(other.preview_); + } } } - /** - *
-         * @required
-         * 
- * - * required string name = 2; - * @param value The name to set. - * @return This builder for chaining. - */ - public Builder setName( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - name_ = value; - bitField0_ |= 0x00000002; - onChanged(); - return this; + if (other.hasReaction()) { + mergeReaction(other.getReaction()); } - /** - *
-         * @required
-         * 
- * - * required string name = 2; - * @return This builder for chaining. - */ - public Builder clearName() { - name_ = getDefaultInstance().getName(); - bitField0_ = (bitField0_ & ~0x00000002); - onChanged(); - return this; + if (other.hasProfile()) { + mergeProfile(other.getProfile()); } - /** - *
-         * @required
-         * 
- * - * required string name = 2; - * @param value The bytes for name to set. - * @return This builder for chaining. - */ - public Builder setNameBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - name_ = value; - bitField0_ |= 0x00000002; + if (other.hasOpenGroupInvitation()) { + mergeOpenGroupInvitation(other.getOpenGroupInvitation()); + } + if (other.hasSyncTarget()) { + syncTarget_ = other.syncTarget_; + bitField0_ |= 0x00000800; onChanged(); - return this; } + if (other.hasBlocksCommunityMessageRequests()) { + setBlocksCommunityMessageRequests(other.getBlocksCommunityMessageRequests()); + } + if (other.hasGroupUpdateMessage()) { + mergeGroupUpdateMessage(other.getGroupUpdateMessage()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } - private java.lang.Object profilePicture_ = ""; - /** - * optional string profilePicture = 3; - * @return Whether the profilePicture field is set. - */ - public boolean hasProfilePicture() { - return ((bitField0_ & 0x00000004) != 0); + @java.lang.Override + public final boolean isInitialized() { + for (int i = 0; i < getAttachmentsCount(); i++) { + if (!getAttachments(i).isInitialized()) { + return false; + } } - /** - * optional string profilePicture = 3; - * @return The profilePicture. - */ - public java.lang.String getProfilePicture() { - java.lang.Object ref = profilePicture_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - profilePicture_ = s; - } - return s; - } else { - return (java.lang.String) ref; + if (hasQuote()) { + if (!getQuote().isInitialized()) { + return false; } } - /** - * optional string profilePicture = 3; - * @return The bytes for profilePicture. - */ - public com.google.protobuf.ByteString - getProfilePictureBytes() { - java.lang.Object ref = profilePicture_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - profilePicture_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; + for (int i = 0; i < getPreviewCount(); i++) { + if (!getPreview(i).isInitialized()) { + return false; } } - /** - * optional string profilePicture = 3; - * @param value The profilePicture to set. - * @return This builder for chaining. - */ - public Builder setProfilePicture( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - profilePicture_ = value; - bitField0_ |= 0x00000004; - onChanged(); - return this; + if (hasReaction()) { + if (!getReaction().isInitialized()) { + return false; + } } - /** - * optional string profilePicture = 3; - * @return This builder for chaining. - */ - public Builder clearProfilePicture() { - profilePicture_ = getDefaultInstance().getProfilePicture(); - bitField0_ = (bitField0_ & ~0x00000004); - onChanged(); - return this; + if (hasOpenGroupInvitation()) { + if (!getOpenGroupInvitation().isInitialized()) { + return false; + } } - /** - * optional string profilePicture = 3; - * @param value The bytes for profilePicture to set. - * @return This builder for chaining. - */ - public Builder setProfilePictureBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - profilePicture_ = value; - bitField0_ |= 0x00000004; - onChanged(); - return this; + if (hasGroupUpdateMessage()) { + if (!getGroupUpdateMessage().isInitialized()) { + return false; + } } + return true; + } - private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes profileKey = 4; - * @return Whether the profileKey field is set. - */ - @java.lang.Override - public boolean hasProfileKey() { - return ((bitField0_ & 0x00000008) != 0); - } - /** - * optional bytes profileKey = 4; - * @return The profileKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getProfileKey() { - return profileKey_; - } - /** - * optional bytes profileKey = 4; - * @param value The profileKey to set. - * @return This builder for chaining. - */ - public Builder setProfileKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - profileKey_ = value; - bitField0_ |= 0x00000008; - onChanged(); - return this; + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); } - /** - * optional bytes profileKey = 4; - * @return This builder for chaining. - */ - public Builder clearProfileKey() { - bitField0_ = (bitField0_ & ~0x00000008); - profileKey_ = getDefaultInstance().getProfileKey(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + body_ = input.readBytes(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: { + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer m = + input.readMessage( + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.parser(), + extensionRegistry); + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + attachments_.add(m); + } else { + attachmentsBuilder_.addMessage(m); + } + break; + } // case 18 + case 32: { + flags_ = input.readUInt32(); + bitField0_ |= 0x00000004; + break; + } // case 32 + case 40: { + expireTimer_ = input.readUInt32(); + bitField0_ |= 0x00000008; + break; + } // case 40 + case 50: { + profileKey_ = input.readBytes(); + bitField0_ |= 0x00000010; + break; + } // case 50 + case 56: { + timestamp_ = input.readUInt64(); + bitField0_ |= 0x00000020; + break; + } // case 56 + case 66: { + input.readMessage( + getQuoteFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00000040; + break; + } // case 66 + case 82: { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview m = + input.readMessage( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.parser(), + extensionRegistry); + if (previewBuilder_ == null) { + ensurePreviewIsMutable(); + preview_.add(m); + } else { + previewBuilder_.addMessage(m); + } + break; + } // case 82 + case 90: { + input.readMessage( + getReactionFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00000100; + break; + } // case 90 + case 810: { + input.readMessage( + getProfileFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00000200; + break; + } // case 810 + case 818: { + input.readMessage( + getOpenGroupInvitationFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00000400; + break; + } // case 818 + case 842: { + syncTarget_ = input.readBytes(); + bitField0_ |= 0x00000800; + break; + } // case 842 + case 848: { + blocksCommunityMessageRequests_ = input.readBool(); + bitField0_ |= 0x00001000; + break; + } // case 848 + case 962: { + input.readMessage( + getGroupUpdateMessageFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00002000; + break; + } // case 962 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { onChanged(); - return this; - } + } // finally + return this; + } + private int bitField0_; - private boolean isApproved_ ; - /** - * optional bool isApproved = 5; - * @return Whether the isApproved field is set. - */ - @java.lang.Override - public boolean hasIsApproved() { - return ((bitField0_ & 0x00000010) != 0); + private java.lang.Object body_ = ""; + /** + * optional string body = 1; + * @return Whether the body field is set. + */ + public boolean hasBody() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional string body = 1; + * @return The body. + */ + public java.lang.String getBody() { + java.lang.Object ref = body_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + body_ = s; + } + return s; + } else { + return (java.lang.String) ref; } - /** - * optional bool isApproved = 5; - * @return The isApproved. - */ - @java.lang.Override - public boolean getIsApproved() { - return isApproved_; + } + /** + * optional string body = 1; + * @return The bytes for body. + */ + public com.google.protobuf.ByteString + getBodyBytes() { + java.lang.Object ref = body_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + body_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; } - /** - * optional bool isApproved = 5; - * @param value The isApproved to set. - * @return This builder for chaining. - */ - public Builder setIsApproved(boolean value) { + } + /** + * optional string body = 1; + * @param value The body to set. + * @return This builder for chaining. + */ + public Builder setBody( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + body_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * optional string body = 1; + * @return This builder for chaining. + */ + public Builder clearBody() { + body_ = getDefaultInstance().getBody(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * optional string body = 1; + * @param value The bytes for body to set. + * @return This builder for chaining. + */ + public Builder setBodyBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + body_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } - isApproved_ = value; - bitField0_ |= 0x00000010; - onChanged(); - return this; - } - /** - * optional bool isApproved = 5; - * @return This builder for chaining. - */ - public Builder clearIsApproved() { - bitField0_ = (bitField0_ & ~0x00000010); - isApproved_ = false; - onChanged(); - return this; - } + private java.util.List attachments_ = + java.util.Collections.emptyList(); + private void ensureAttachmentsIsMutable() { + if (!((bitField0_ & 0x00000002) != 0)) { + attachments_ = new java.util.ArrayList(attachments_); + bitField0_ |= 0x00000002; + } + } - private boolean isBlocked_ ; - /** - * optional bool isBlocked = 6; - * @return Whether the isBlocked field is set. - */ - @java.lang.Override - public boolean hasIsBlocked() { - return ((bitField0_ & 0x00000020) != 0); - } - /** - * optional bool isBlocked = 6; - * @return The isBlocked. - */ - @java.lang.Override - public boolean getIsBlocked() { - return isBlocked_; - } - /** - * optional bool isBlocked = 6; - * @param value The isBlocked to set. - * @return This builder for chaining. - */ - public Builder setIsBlocked(boolean value) { + private com.google.protobuf.RepeatedFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder> attachmentsBuilder_; - isBlocked_ = value; - bitField0_ |= 0x00000020; - onChanged(); - return this; - } - /** - * optional bool isBlocked = 6; - * @return This builder for chaining. - */ - public Builder clearIsBlocked() { - bitField0_ = (bitField0_ & ~0x00000020); - isBlocked_ = false; - onChanged(); - return this; + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public java.util.List getAttachmentsList() { + if (attachmentsBuilder_ == null) { + return java.util.Collections.unmodifiableList(attachments_); + } else { + return attachmentsBuilder_.getMessageList(); } - - private boolean didApproveMe_ ; - /** - * optional bool didApproveMe = 7; - * @return Whether the didApproveMe field is set. - */ - @java.lang.Override - public boolean hasDidApproveMe() { - return ((bitField0_ & 0x00000040) != 0); + } + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public int getAttachmentsCount() { + if (attachmentsBuilder_ == null) { + return attachments_.size(); + } else { + return attachmentsBuilder_.getCount(); } - /** - * optional bool didApproveMe = 7; - * @return The didApproveMe. - */ - @java.lang.Override - public boolean getDidApproveMe() { - return didApproveMe_; + } + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAttachments(int index) { + if (attachmentsBuilder_ == null) { + return attachments_.get(index); + } else { + return attachmentsBuilder_.getMessage(index); } - /** - * optional bool didApproveMe = 7; - * @param value The didApproveMe to set. - * @return This builder for chaining. - */ - public Builder setDidApproveMe(boolean value) { - - didApproveMe_ = value; - bitField0_ |= 0x00000040; + } + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public Builder setAttachments( + int index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { + if (attachmentsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAttachmentsIsMutable(); + attachments_.set(index, value); onChanged(); - return this; + } else { + attachmentsBuilder_.setMessage(index, value); } - /** - * optional bool didApproveMe = 7; - * @return This builder for chaining. - */ - public Builder clearDidApproveMe() { - bitField0_ = (bitField0_ & ~0x00000040); - didApproveMe_ = false; + return this; + } + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public Builder setAttachments( + int index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder builderForValue) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + attachments_.set(index, builderForValue.build()); onChanged(); - return this; + } else { + attachmentsBuilder_.setMessage(index, builderForValue.build()); } - - // @@protoc_insertion_point(builder_scope:signalservice.ConfigurationMessage.Contact) - } - - // @@protoc_insertion_point(class_scope:signalservice.ConfigurationMessage.Contact) - private static final org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact getDefaultInstance() { - return DEFAULT_INSTANCE; + return this; } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public Contact parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public Builder addAttachments(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { + if (attachmentsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); } - return builder.buildPartial(); + ensureAttachmentsIsMutable(); + attachments_.add(value); + onChanged(); + } else { + attachmentsBuilder_.addMessage(value); } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact getDefaultInstanceForType() { - return DEFAULT_INSTANCE; + return this; } - - } - - private int bitField0_; - public static final int CLOSEDGROUPS_FIELD_NUMBER = 1; - @SuppressWarnings("serial") - private java.util.List closedGroups_; - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - @java.lang.Override - public java.util.List getClosedGroupsList() { - return closedGroups_; - } - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - @java.lang.Override - public java.util.List - getClosedGroupsOrBuilderList() { - return closedGroups_; - } - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - @java.lang.Override - public int getClosedGroupsCount() { - return closedGroups_.size(); - } - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup getClosedGroups(int index) { - return closedGroups_.get(index); - } - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroupOrBuilder getClosedGroupsOrBuilder( - int index) { - return closedGroups_.get(index); - } - - public static final int OPENGROUPS_FIELD_NUMBER = 2; - @SuppressWarnings("serial") - private com.google.protobuf.LazyStringArrayList openGroups_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - /** - * repeated string openGroups = 2; - * @return A list containing the openGroups. - */ - public com.google.protobuf.ProtocolStringList - getOpenGroupsList() { - return openGroups_; - } - /** - * repeated string openGroups = 2; - * @return The count of openGroups. - */ - public int getOpenGroupsCount() { - return openGroups_.size(); - } - /** - * repeated string openGroups = 2; - * @param index The index of the element to return. - * @return The openGroups at the given index. - */ - public java.lang.String getOpenGroups(int index) { - return openGroups_.get(index); - } - /** - * repeated string openGroups = 2; - * @param index The index of the value to return. - * @return The bytes of the openGroups at the given index. - */ - public com.google.protobuf.ByteString - getOpenGroupsBytes(int index) { - return openGroups_.getByteString(index); - } - - public static final int DISPLAYNAME_FIELD_NUMBER = 3; - @SuppressWarnings("serial") - private volatile java.lang.Object displayName_ = ""; - /** - * optional string displayName = 3; - * @return Whether the displayName field is set. - */ - @java.lang.Override - public boolean hasDisplayName() { - return ((bitField0_ & 0x00000001) != 0); - } - /** - * optional string displayName = 3; - * @return The displayName. - */ - @java.lang.Override - public java.lang.String getDisplayName() { - java.lang.Object ref = displayName_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - displayName_ = s; + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public Builder addAttachments( + int index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { + if (attachmentsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAttachmentsIsMutable(); + attachments_.add(index, value); + onChanged(); + } else { + attachmentsBuilder_.addMessage(index, value); } - return s; - } - } - /** - * optional string displayName = 3; - * @return The bytes for displayName. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getDisplayNameBytes() { - java.lang.Object ref = displayName_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - displayName_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; + return this; } - } - - public static final int PROFILEPICTURE_FIELD_NUMBER = 4; - @SuppressWarnings("serial") - private volatile java.lang.Object profilePicture_ = ""; - /** - * optional string profilePicture = 4; - * @return Whether the profilePicture field is set. - */ - @java.lang.Override - public boolean hasProfilePicture() { - return ((bitField0_ & 0x00000002) != 0); - } - /** - * optional string profilePicture = 4; - * @return The profilePicture. - */ - @java.lang.Override - public java.lang.String getProfilePicture() { - java.lang.Object ref = profilePicture_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - profilePicture_ = s; + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public Builder addAttachments( + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder builderForValue) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + attachments_.add(builderForValue.build()); + onChanged(); + } else { + attachmentsBuilder_.addMessage(builderForValue.build()); } - return s; - } - } - /** - * optional string profilePicture = 4; - * @return The bytes for profilePicture. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getProfilePictureBytes() { - java.lang.Object ref = profilePicture_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - profilePicture_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; + return this; } - } - - public static final int PROFILEKEY_FIELD_NUMBER = 5; - private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes profileKey = 5; - * @return Whether the profileKey field is set. - */ - @java.lang.Override - public boolean hasProfileKey() { - return ((bitField0_ & 0x00000004) != 0); - } - /** - * optional bytes profileKey = 5; - * @return The profileKey. - */ - @java.lang.Override - public com.google.protobuf.ByteString getProfileKey() { - return profileKey_; - } - - public static final int CONTACTS_FIELD_NUMBER = 6; - @SuppressWarnings("serial") - private java.util.List contacts_; - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - @java.lang.Override - public java.util.List getContactsList() { - return contacts_; - } - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - @java.lang.Override - public java.util.List - getContactsOrBuilderList() { - return contacts_; - } - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - @java.lang.Override - public int getContactsCount() { - return contacts_.size(); - } - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact getContacts(int index) { - return contacts_.get(index); - } - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ContactOrBuilder getContactsOrBuilder( - int index) { - return contacts_.get(index); - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - for (int i = 0; i < getClosedGroupsCount(); i++) { - if (!getClosedGroups(i).isInitialized()) { - memoizedIsInitialized = 0; - return false; + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public Builder addAttachments( + int index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder builderForValue) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + attachments_.add(index, builderForValue.build()); + onChanged(); + } else { + attachmentsBuilder_.addMessage(index, builderForValue.build()); } + return this; } - for (int i = 0; i < getContactsCount(); i++) { - if (!getContacts(i).isInitialized()) { - memoizedIsInitialized = 0; - return false; + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public Builder addAllAttachments( + java.lang.Iterable values) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, attachments_); + onChanged(); + } else { + attachmentsBuilder_.addAllMessages(values); } + return this; } - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - for (int i = 0; i < closedGroups_.size(); i++) { - output.writeMessage(1, closedGroups_.get(i)); + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public Builder clearAttachments() { + if (attachmentsBuilder_ == null) { + attachments_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + } else { + attachmentsBuilder_.clear(); + } + return this; } - for (int i = 0; i < openGroups_.size(); i++) { - com.google.protobuf.GeneratedMessage.writeString(output, 2, openGroups_.getRaw(i)); + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public Builder removeAttachments(int index) { + if (attachmentsBuilder_ == null) { + ensureAttachmentsIsMutable(); + attachments_.remove(index); + onChanged(); + } else { + attachmentsBuilder_.remove(index); + } + return this; } - if (((bitField0_ & 0x00000001) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 3, displayName_); + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder getAttachmentsBuilder( + int index) { + return getAttachmentsFieldBuilder().getBuilder(index); } - if (((bitField0_ & 0x00000002) != 0)) { - com.google.protobuf.GeneratedMessage.writeString(output, 4, profilePicture_); + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + int index) { + if (attachmentsBuilder_ == null) { + return attachments_.get(index); } else { + return attachmentsBuilder_.getMessageOrBuilder(index); + } } - if (((bitField0_ & 0x00000004) != 0)) { - output.writeBytes(5, profileKey_); + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public java.util.List + getAttachmentsOrBuilderList() { + if (attachmentsBuilder_ != null) { + return attachmentsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(attachments_); + } } - for (int i = 0; i < contacts_.size(); i++) { - output.writeMessage(6, contacts_.get(i)); + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder addAttachmentsBuilder() { + return getAttachmentsFieldBuilder().addBuilder( + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance()); } - getUnknownFields().writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - for (int i = 0; i < closedGroups_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(1, closedGroups_.get(i)); + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder addAttachmentsBuilder( + int index) { + return getAttachmentsFieldBuilder().addBuilder( + index, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance()); } - { - int dataSize = 0; - for (int i = 0; i < openGroups_.size(); i++) { - dataSize += computeStringSizeNoTag(openGroups_.getRaw(i)); + /** + * repeated .signalservice.AttachmentPointer attachments = 2; + */ + public java.util.List + getAttachmentsBuilderList() { + return getAttachmentsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder> + getAttachmentsFieldBuilder() { + if (attachmentsBuilder_ == null) { + attachmentsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder>( + attachments_, + ((bitField0_ & 0x00000002) != 0), + getParentForChildren(), + isClean()); + attachments_ = null; } - size += dataSize; - size += 1 * getOpenGroupsList().size(); + return attachmentsBuilder_; } - if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(3, displayName_); + + private int flags_ ; + /** + * optional uint32 flags = 4; + * @return Whether the flags field is set. + */ + @java.lang.Override + public boolean hasFlags() { + return ((bitField0_ & 0x00000004) != 0); } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(4, profilePicture_); + /** + * optional uint32 flags = 4; + * @return The flags. + */ + @java.lang.Override + public int getFlags() { + return flags_; } - if (((bitField0_ & 0x00000004) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(5, profileKey_); + /** + * optional uint32 flags = 4; + * @param value The flags to set. + * @return This builder for chaining. + */ + public Builder setFlags(int value) { + + flags_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; } - for (int i = 0; i < contacts_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(6, contacts_.get(i)); + /** + * optional uint32 flags = 4; + * @return This builder for chaining. + */ + public Builder clearFlags() { + bitField0_ = (bitField0_ & ~0x00000004); + flags_ = 0; + onChanged(); + return this; } - size += getUnknownFields().getSerializedSize(); - memoizedSize = size; - return size; - } - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; + private int expireTimer_ ; + /** + * optional uint32 expireTimer = 5; + * @return Whether the expireTimer field is set. + */ + @java.lang.Override + public boolean hasExpireTimer() { + return ((bitField0_ & 0x00000008) != 0); } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage)) { - return super.equals(obj); + /** + * optional uint32 expireTimer = 5; + * @return The expireTimer. + */ + @java.lang.Override + public int getExpireTimer() { + return expireTimer_; } - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage other = (org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage) obj; + /** + * optional uint32 expireTimer = 5; + * @param value The expireTimer to set. + * @return This builder for chaining. + */ + public Builder setExpireTimer(int value) { - if (!getClosedGroupsList() - .equals(other.getClosedGroupsList())) return false; - if (!getOpenGroupsList() - .equals(other.getOpenGroupsList())) return false; - if (hasDisplayName() != other.hasDisplayName()) return false; - if (hasDisplayName()) { - if (!getDisplayName() - .equals(other.getDisplayName())) return false; + expireTimer_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; } - if (hasProfilePicture() != other.hasProfilePicture()) return false; - if (hasProfilePicture()) { - if (!getProfilePicture() - .equals(other.getProfilePicture())) return false; + /** + * optional uint32 expireTimer = 5; + * @return This builder for chaining. + */ + public Builder clearExpireTimer() { + bitField0_ = (bitField0_ & ~0x00000008); + expireTimer_ = 0; + onChanged(); + return this; } - if (hasProfileKey() != other.hasProfileKey()) return false; - if (hasProfileKey()) { - if (!getProfileKey() - .equals(other.getProfileKey())) return false; + + private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes profileKey = 6; + * @return Whether the profileKey field is set. + */ + @java.lang.Override + public boolean hasProfileKey() { + return ((bitField0_ & 0x00000010) != 0); + } + /** + * optional bytes profileKey = 6; + * @return The profileKey. + */ + @java.lang.Override + public com.google.protobuf.ByteString getProfileKey() { + return profileKey_; + } + /** + * optional bytes profileKey = 6; + * @param value The profileKey to set. + * @return This builder for chaining. + */ + public Builder setProfileKey(com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + profileKey_ = value; + bitField0_ |= 0x00000010; + onChanged(); + return this; + } + /** + * optional bytes profileKey = 6; + * @return This builder for chaining. + */ + public Builder clearProfileKey() { + bitField0_ = (bitField0_ & ~0x00000010); + profileKey_ = getDefaultInstance().getProfileKey(); + onChanged(); + return this; } - if (!getContactsList() - .equals(other.getContactsList())) return false; - if (!getUnknownFields().equals(other.getUnknownFields())) return false; - return true; - } - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; + private long timestamp_ ; + /** + * optional uint64 timestamp = 7; + * @return Whether the timestamp field is set. + */ + @java.lang.Override + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000020) != 0); } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - if (getClosedGroupsCount() > 0) { - hash = (37 * hash) + CLOSEDGROUPS_FIELD_NUMBER; - hash = (53 * hash) + getClosedGroupsList().hashCode(); + /** + * optional uint64 timestamp = 7; + * @return The timestamp. + */ + @java.lang.Override + public long getTimestamp() { + return timestamp_; } - if (getOpenGroupsCount() > 0) { - hash = (37 * hash) + OPENGROUPS_FIELD_NUMBER; - hash = (53 * hash) + getOpenGroupsList().hashCode(); + /** + * optional uint64 timestamp = 7; + * @param value The timestamp to set. + * @return This builder for chaining. + */ + public Builder setTimestamp(long value) { + + timestamp_ = value; + bitField0_ |= 0x00000020; + onChanged(); + return this; } - if (hasDisplayName()) { - hash = (37 * hash) + DISPLAYNAME_FIELD_NUMBER; - hash = (53 * hash) + getDisplayName().hashCode(); + /** + * optional uint64 timestamp = 7; + * @return This builder for chaining. + */ + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000020); + timestamp_ = 0L; + onChanged(); + return this; } - if (hasProfilePicture()) { - hash = (37 * hash) + PROFILEPICTURE_FIELD_NUMBER; - hash = (53 * hash) + getProfilePicture().hashCode(); + + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote quote_; + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder> quoteBuilder_; + /** + * optional .signalservice.DataMessage.Quote quote = 8; + * @return Whether the quote field is set. + */ + public boolean hasQuote() { + return ((bitField0_ & 0x00000040) != 0); } - if (hasProfileKey()) { - hash = (37 * hash) + PROFILEKEY_FIELD_NUMBER; - hash = (53 * hash) + getProfileKey().hashCode(); + /** + * optional .signalservice.DataMessage.Quote quote = 8; + * @return The quote. + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote getQuote() { + if (quoteBuilder_ == null) { + return quote_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance() : quote_; + } else { + return quoteBuilder_.getMessage(); + } } - if (getContactsCount() > 0) { - hash = (37 * hash) + CONTACTS_FIELD_NUMBER; - hash = (53 * hash) + getContactsList().hashCode(); + /** + * optional .signalservice.DataMessage.Quote quote = 8; + */ + public Builder setQuote(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote value) { + if (quoteBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + quote_ = value; + } else { + quoteBuilder_.setMessage(value); + } + bitField0_ |= 0x00000040; + onChanged(); + return this; } - hash = (29 * hash) + getUnknownFields().hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input); - } - - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input); - } - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessage - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.ConfigurationMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.ConfigurationMessage) - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_descriptor; + /** + * optional .signalservice.DataMessage.Quote quote = 8; + */ + public Builder setQuote( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder builderForValue) { + if (quoteBuilder_ == null) { + quote_ = builderForValue.build(); + } else { + quoteBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000040; + onChanged(); + return this; } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.class, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Builder.class); + /** + * optional .signalservice.DataMessage.Quote quote = 8; + */ + public Builder mergeQuote(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote value) { + if (quoteBuilder_ == null) { + if (((bitField0_ & 0x00000040) != 0) && + quote_ != null && + quote_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance()) { + getQuoteBuilder().mergeFrom(value); + } else { + quote_ = value; + } + } else { + quoteBuilder_.mergeFrom(value); + } + if (quote_ != null) { + bitField0_ |= 0x00000040; + onChanged(); + } + return this; } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.newBuilder() - private Builder() { - + /** + * optional .signalservice.DataMessage.Quote quote = 8; + */ + public Builder clearQuote() { + bitField0_ = (bitField0_ & ~0x00000040); + quote_ = null; + if (quoteBuilder_ != null) { + quoteBuilder_.dispose(); + quoteBuilder_ = null; + } + onChanged(); + return this; } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - + /** + * optional .signalservice.DataMessage.Quote quote = 8; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder getQuoteBuilder() { + bitField0_ |= 0x00000040; + onChanged(); + return getQuoteFieldBuilder().getBuilder(); } - @java.lang.Override - public Builder clear() { - super.clear(); - bitField0_ = 0; - if (closedGroupsBuilder_ == null) { - closedGroups_ = java.util.Collections.emptyList(); + /** + * optional .signalservice.DataMessage.Quote quote = 8; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder getQuoteOrBuilder() { + if (quoteBuilder_ != null) { + return quoteBuilder_.getMessageOrBuilder(); } else { - closedGroups_ = null; - closedGroupsBuilder_.clear(); + return quote_ == null ? + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance() : quote_; } - bitField0_ = (bitField0_ & ~0x00000001); - openGroups_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - displayName_ = ""; - profilePicture_ = ""; - profileKey_ = com.google.protobuf.ByteString.EMPTY; - if (contactsBuilder_ == null) { - contacts_ = java.util.Collections.emptyList(); - } else { - contacts_ = null; - contactsBuilder_.clear(); + } + /** + * optional .signalservice.DataMessage.Quote quote = 8; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder> + getQuoteFieldBuilder() { + if (quoteBuilder_ == null) { + quoteBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.QuoteOrBuilder>( + getQuote(), + getParentForChildren(), + isClean()); + quote_ = null; } - bitField0_ = (bitField0_ & ~0x00000020); - return this; + return quoteBuilder_; } - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_ConfigurationMessage_descriptor; + private java.util.List preview_ = + java.util.Collections.emptyList(); + private void ensurePreviewIsMutable() { + if (!((bitField0_ & 0x00000080) != 0)) { + preview_ = new java.util.ArrayList(preview_); + bitField0_ |= 0x00000080; + } } - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.getDefaultInstance(); - } + private com.google.protobuf.RepeatedFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder> previewBuilder_; - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage build() { - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public java.util.List getPreviewList() { + if (previewBuilder_ == null) { + return java.util.Collections.unmodifiableList(preview_); + } else { + return previewBuilder_.getMessageList(); } - return result; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage result = new org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage(this); - buildPartialRepeatedFields(result); - if (bitField0_ != 0) { buildPartial0(result); } - onBuilt(); - return result; } - - private void buildPartialRepeatedFields(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage result) { - if (closedGroupsBuilder_ == null) { - if (((bitField0_ & 0x00000001) != 0)) { - closedGroups_ = java.util.Collections.unmodifiableList(closedGroups_); - bitField0_ = (bitField0_ & ~0x00000001); - } - result.closedGroups_ = closedGroups_; + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public int getPreviewCount() { + if (previewBuilder_ == null) { + return preview_.size(); } else { - result.closedGroups_ = closedGroupsBuilder_.build(); + return previewBuilder_.getCount(); } - if (contactsBuilder_ == null) { - if (((bitField0_ & 0x00000020) != 0)) { - contacts_ = java.util.Collections.unmodifiableList(contacts_); - bitField0_ = (bitField0_ & ~0x00000020); - } - result.contacts_ = contacts_; + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview getPreview(int index) { + if (previewBuilder_ == null) { + return preview_.get(index); } else { - result.contacts_ = contactsBuilder_.build(); + return previewBuilder_.getMessage(index); } } - - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage result) { - int from_bitField0_ = bitField0_; - if (((from_bitField0_ & 0x00000002) != 0)) { - openGroups_.makeImmutable(); - result.openGroups_ = openGroups_; - } - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000004) != 0)) { - result.displayName_ = displayName_; - to_bitField0_ |= 0x00000001; - } - if (((from_bitField0_ & 0x00000008) != 0)) { - result.profilePicture_ = profilePicture_; - to_bitField0_ |= 0x00000002; - } - if (((from_bitField0_ & 0x00000010) != 0)) { - result.profileKey_ = profileKey_; - to_bitField0_ |= 0x00000004; + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public Builder setPreview( + int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview value) { + if (previewBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePreviewIsMutable(); + preview_.set(index, value); + onChanged(); + } else { + previewBuilder_.setMessage(index, value); } - result.bitField0_ |= to_bitField0_; + return this; } - - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage)other); + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public Builder setPreview( + int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder builderForValue) { + if (previewBuilder_ == null) { + ensurePreviewIsMutable(); + preview_.set(index, builderForValue.build()); + onChanged(); } else { - super.mergeFrom(other); - return this; + previewBuilder_.setMessage(index, builderForValue.build()); } + return this; } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.getDefaultInstance()) return this; - if (closedGroupsBuilder_ == null) { - if (!other.closedGroups_.isEmpty()) { - if (closedGroups_.isEmpty()) { - closedGroups_ = other.closedGroups_; - bitField0_ = (bitField0_ & ~0x00000001); - } else { - ensureClosedGroupsIsMutable(); - closedGroups_.addAll(other.closedGroups_); - } - onChanged(); + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public Builder addPreview(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview value) { + if (previewBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); } + ensurePreviewIsMutable(); + preview_.add(value); + onChanged(); } else { - if (!other.closedGroups_.isEmpty()) { - if (closedGroupsBuilder_.isEmpty()) { - closedGroupsBuilder_.dispose(); - closedGroupsBuilder_ = null; - closedGroups_ = other.closedGroups_; - bitField0_ = (bitField0_ & ~0x00000001); - closedGroupsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? - getClosedGroupsFieldBuilder() : null; - } else { - closedGroupsBuilder_.addAllMessages(other.closedGroups_); - } - } + previewBuilder_.addMessage(value); } - if (!other.openGroups_.isEmpty()) { - if (openGroups_.isEmpty()) { - openGroups_ = other.openGroups_; - bitField0_ |= 0x00000002; - } else { - ensureOpenGroupsIsMutable(); - openGroups_.addAll(other.openGroups_); + return this; + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public Builder addPreview( + int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview value) { + if (previewBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); } + ensurePreviewIsMutable(); + preview_.add(index, value); onChanged(); - } - if (other.hasDisplayName()) { - displayName_ = other.displayName_; - bitField0_ |= 0x00000004; - onChanged(); - } - if (other.hasProfilePicture()) { - profilePicture_ = other.profilePicture_; - bitField0_ |= 0x00000008; - onChanged(); - } - if (other.hasProfileKey()) { - setProfileKey(other.getProfileKey()); - } - if (contactsBuilder_ == null) { - if (!other.contacts_.isEmpty()) { - if (contacts_.isEmpty()) { - contacts_ = other.contacts_; - bitField0_ = (bitField0_ & ~0x00000020); - } else { - ensureContactsIsMutable(); - contacts_.addAll(other.contacts_); - } - onChanged(); - } } else { - if (!other.contacts_.isEmpty()) { - if (contactsBuilder_.isEmpty()) { - contactsBuilder_.dispose(); - contactsBuilder_ = null; - contacts_ = other.contacts_; - bitField0_ = (bitField0_ & ~0x00000020); - contactsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? - getContactsFieldBuilder() : null; - } else { - contactsBuilder_.addAllMessages(other.contacts_); - } - } + previewBuilder_.addMessage(index, value); } - this.mergeUnknownFields(other.getUnknownFields()); - onChanged(); return this; } - - @java.lang.Override - public final boolean isInitialized() { - for (int i = 0; i < getClosedGroupsCount(); i++) { - if (!getClosedGroups(i).isInitialized()) { - return false; - } - } - for (int i = 0; i < getContactsCount(); i++) { - if (!getContacts(i).isInitialized()) { - return false; - } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public Builder addPreview( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder builderForValue) { + if (previewBuilder_ == null) { + ensurePreviewIsMutable(); + preview_.add(builderForValue.build()); + onChanged(); + } else { + previewBuilder_.addMessage(builderForValue.build()); } - return true; + return this; } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 10: { - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup m = - input.readMessage( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.parser(), - extensionRegistry); - if (closedGroupsBuilder_ == null) { - ensureClosedGroupsIsMutable(); - closedGroups_.add(m); - } else { - closedGroupsBuilder_.addMessage(m); - } - break; - } // case 10 - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - ensureOpenGroupsIsMutable(); - openGroups_.add(bs); - break; - } // case 18 - case 26: { - displayName_ = input.readBytes(); - bitField0_ |= 0x00000004; - break; - } // case 26 - case 34: { - profilePicture_ = input.readBytes(); - bitField0_ |= 0x00000008; - break; - } // case 34 - case 42: { - profileKey_ = input.readBytes(); - bitField0_ |= 0x00000010; - break; - } // case 42 - case 50: { - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact m = - input.readMessage( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.parser(), - extensionRegistry); - if (contactsBuilder_ == null) { - ensureContactsIsMutable(); - contacts_.add(m); - } else { - contactsBuilder_.addMessage(m); - } - break; - } // case 50 - default: { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.unwrapIOException(); - } finally { + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public Builder addPreview( + int index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder builderForValue) { + if (previewBuilder_ == null) { + ensurePreviewIsMutable(); + preview_.add(index, builderForValue.build()); onChanged(); - } // finally + } else { + previewBuilder_.addMessage(index, builderForValue.build()); + } return this; } - private int bitField0_; - - private java.util.List closedGroups_ = - java.util.Collections.emptyList(); - private void ensureClosedGroupsIsMutable() { - if (!((bitField0_ & 0x00000001) != 0)) { - closedGroups_ = new java.util.ArrayList(closedGroups_); - bitField0_ |= 0x00000001; - } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public Builder addAllPreview( + java.lang.Iterable values) { + if (previewBuilder_ == null) { + ensurePreviewIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, preview_); + onChanged(); + } else { + previewBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public Builder clearPreview() { + if (previewBuilder_ == null) { + preview_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000080); + onChanged(); + } else { + previewBuilder_.clear(); + } + return this; } - - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroupOrBuilder> closedGroupsBuilder_; - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * repeated .signalservice.DataMessage.Preview preview = 10; */ - public java.util.List getClosedGroupsList() { - if (closedGroupsBuilder_ == null) { - return java.util.Collections.unmodifiableList(closedGroups_); + public Builder removePreview(int index) { + if (previewBuilder_ == null) { + ensurePreviewIsMutable(); + preview_.remove(index); + onChanged(); } else { - return closedGroupsBuilder_.getMessageList(); + previewBuilder_.remove(index); + } + return this; + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder getPreviewBuilder( + int index) { + return getPreviewFieldBuilder().getBuilder(index); + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder getPreviewOrBuilder( + int index) { + if (previewBuilder_ == null) { + return preview_.get(index); } else { + return previewBuilder_.getMessageOrBuilder(index); } } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * repeated .signalservice.DataMessage.Preview preview = 10; */ - public int getClosedGroupsCount() { - if (closedGroupsBuilder_ == null) { - return closedGroups_.size(); + public java.util.List + getPreviewOrBuilderList() { + if (previewBuilder_ != null) { + return previewBuilder_.getMessageOrBuilderList(); } else { - return closedGroupsBuilder_.getCount(); + return java.util.Collections.unmodifiableList(preview_); + } + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder addPreviewBuilder() { + return getPreviewFieldBuilder().addBuilder( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.getDefaultInstance()); + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder addPreviewBuilder( + int index) { + return getPreviewFieldBuilder().addBuilder( + index, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.getDefaultInstance()); + } + /** + * repeated .signalservice.DataMessage.Preview preview = 10; + */ + public java.util.List + getPreviewBuilderList() { + return getPreviewFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder> + getPreviewFieldBuilder() { + if (previewBuilder_ == null) { + previewBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder>( + preview_, + ((bitField0_ & 0x00000080) != 0), + getParentForChildren(), + isClean()); + preview_ = null; } + return previewBuilder_; + } + + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction reaction_; + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder> reactionBuilder_; + /** + * optional .signalservice.DataMessage.Reaction reaction = 11; + * @return Whether the reaction field is set. + */ + public boolean hasReaction() { + return ((bitField0_ & 0x00000100) != 0); } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.Reaction reaction = 11; + * @return The reaction. */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup getClosedGroups(int index) { - if (closedGroupsBuilder_ == null) { - return closedGroups_.get(index); + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction getReaction() { + if (reactionBuilder_ == null) { + return reaction_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance() : reaction_; } else { - return closedGroupsBuilder_.getMessage(index); + return reactionBuilder_.getMessage(); } } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.Reaction reaction = 11; */ - public Builder setClosedGroups( - int index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup value) { - if (closedGroupsBuilder_ == null) { + public Builder setReaction(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction value) { + if (reactionBuilder_ == null) { if (value == null) { throw new NullPointerException(); } - ensureClosedGroupsIsMutable(); - closedGroups_.set(index, value); - onChanged(); + reaction_ = value; } else { - closedGroupsBuilder_.setMessage(index, value); + reactionBuilder_.setMessage(value); } + bitField0_ |= 0x00000100; + onChanged(); return this; } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.Reaction reaction = 11; */ - public Builder setClosedGroups( - int index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder builderForValue) { - if (closedGroupsBuilder_ == null) { - ensureClosedGroupsIsMutable(); - closedGroups_.set(index, builderForValue.build()); - onChanged(); + public Builder setReaction( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder builderForValue) { + if (reactionBuilder_ == null) { + reaction_ = builderForValue.build(); } else { - closedGroupsBuilder_.setMessage(index, builderForValue.build()); + reactionBuilder_.setMessage(builderForValue.build()); } + bitField0_ |= 0x00000100; + onChanged(); return this; } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.Reaction reaction = 11; */ - public Builder addClosedGroups(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup value) { - if (closedGroupsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); + public Builder mergeReaction(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction value) { + if (reactionBuilder_ == null) { + if (((bitField0_ & 0x00000100) != 0) && + reaction_ != null && + reaction_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance()) { + getReactionBuilder().mergeFrom(value); + } else { + reaction_ = value; } - ensureClosedGroupsIsMutable(); - closedGroups_.add(value); - onChanged(); } else { - closedGroupsBuilder_.addMessage(value); + reactionBuilder_.mergeFrom(value); + } + if (reaction_ != null) { + bitField0_ |= 0x00000100; + onChanged(); } return this; } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.Reaction reaction = 11; */ - public Builder addClosedGroups( - int index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup value) { - if (closedGroupsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureClosedGroupsIsMutable(); - closedGroups_.add(index, value); - onChanged(); - } else { - closedGroupsBuilder_.addMessage(index, value); + public Builder clearReaction() { + bitField0_ = (bitField0_ & ~0x00000100); + reaction_ = null; + if (reactionBuilder_ != null) { + reactionBuilder_.dispose(); + reactionBuilder_ = null; } + onChanged(); return this; } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.Reaction reaction = 11; */ - public Builder addClosedGroups( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder builderForValue) { - if (closedGroupsBuilder_ == null) { - ensureClosedGroupsIsMutable(); - closedGroups_.add(builderForValue.build()); - onChanged(); + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder getReactionBuilder() { + bitField0_ |= 0x00000100; + onChanged(); + return getReactionFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.DataMessage.Reaction reaction = 11; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder getReactionOrBuilder() { + if (reactionBuilder_ != null) { + return reactionBuilder_.getMessageOrBuilder(); } else { - closedGroupsBuilder_.addMessage(builderForValue.build()); + return reaction_ == null ? + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance() : reaction_; } - return this; } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.Reaction reaction = 11; */ - public Builder addClosedGroups( - int index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder builderForValue) { - if (closedGroupsBuilder_ == null) { - ensureClosedGroupsIsMutable(); - closedGroups_.add(index, builderForValue.build()); - onChanged(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder> + getReactionFieldBuilder() { + if (reactionBuilder_ == null) { + reactionBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.ReactionOrBuilder>( + getReaction(), + getParentForChildren(), + isClean()); + reaction_ = null; + } + return reactionBuilder_; + } + + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> profileBuilder_; + /** + * optional .signalservice.DataMessage.LokiProfile profile = 101; + * @return Whether the profile field is set. + */ + public boolean hasProfile() { + return ((bitField0_ & 0x00000200) != 0); + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 101; + * @return The profile. + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { + if (profileBuilder_ == null) { + return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; + } else { + return profileBuilder_.getMessage(); + } + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 101; + */ + public Builder setProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { + if (profileBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + profile_ = value; } else { - closedGroupsBuilder_.addMessage(index, builderForValue.build()); + profileBuilder_.setMessage(value); } + bitField0_ |= 0x00000200; + onChanged(); return this; } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.LokiProfile profile = 101; */ - public Builder addAllClosedGroups( - java.lang.Iterable values) { - if (closedGroupsBuilder_ == null) { - ensureClosedGroupsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, closedGroups_); - onChanged(); + public Builder setProfile( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder builderForValue) { + if (profileBuilder_ == null) { + profile_ = builderForValue.build(); } else { - closedGroupsBuilder_.addAllMessages(values); + profileBuilder_.setMessage(builderForValue.build()); } + bitField0_ |= 0x00000200; + onChanged(); return this; } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.LokiProfile profile = 101; */ - public Builder clearClosedGroups() { - if (closedGroupsBuilder_ == null) { - closedGroups_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000001); - onChanged(); + public Builder mergeProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { + if (profileBuilder_ == null) { + if (((bitField0_ & 0x00000200) != 0) && + profile_ != null && + profile_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance()) { + getProfileBuilder().mergeFrom(value); + } else { + profile_ = value; + } } else { - closedGroupsBuilder_.clear(); + profileBuilder_.mergeFrom(value); } - return this; - } - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - public Builder removeClosedGroups(int index) { - if (closedGroupsBuilder_ == null) { - ensureClosedGroupsIsMutable(); - closedGroups_.remove(index); + if (profile_ != null) { + bitField0_ |= 0x00000200; onChanged(); - } else { - closedGroupsBuilder_.remove(index); } return this; } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.LokiProfile profile = 101; */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder getClosedGroupsBuilder( - int index) { - return getClosedGroupsFieldBuilder().getBuilder(index); + public Builder clearProfile() { + bitField0_ = (bitField0_ & ~0x00000200); + profile_ = null; + if (profileBuilder_ != null) { + profileBuilder_.dispose(); + profileBuilder_ = null; + } + onChanged(); + return this; } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.LokiProfile profile = 101; */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroupOrBuilder getClosedGroupsOrBuilder( - int index) { - if (closedGroupsBuilder_ == null) { - return closedGroups_.get(index); } else { - return closedGroupsBuilder_.getMessageOrBuilder(index); - } + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder getProfileBuilder() { + bitField0_ |= 0x00000200; + onChanged(); + return getProfileFieldBuilder().getBuilder(); } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.LokiProfile profile = 101; */ - public java.util.List - getClosedGroupsOrBuilderList() { - if (closedGroupsBuilder_ != null) { - return closedGroupsBuilder_.getMessageOrBuilderList(); + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { + if (profileBuilder_ != null) { + return profileBuilder_.getMessageOrBuilder(); } else { - return java.util.Collections.unmodifiableList(closedGroups_); + return profile_ == null ? + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; } } /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder addClosedGroupsBuilder() { - return getClosedGroupsFieldBuilder().addBuilder( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.getDefaultInstance()); - } - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; - */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder addClosedGroupsBuilder( - int index) { - return getClosedGroupsFieldBuilder().addBuilder( - index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.getDefaultInstance()); - } - /** - * repeated .signalservice.ConfigurationMessage.ClosedGroup closedGroups = 1; + * optional .signalservice.DataMessage.LokiProfile profile = 101; */ - public java.util.List - getClosedGroupsBuilderList() { - return getClosedGroupsFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroupOrBuilder> - getClosedGroupsFieldBuilder() { - if (closedGroupsBuilder_ == null) { - closedGroupsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup.Builder, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroupOrBuilder>( - closedGroups_, - ((bitField0_ & 0x00000001) != 0), + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> + getProfileFieldBuilder() { + if (profileBuilder_ == null) { + profileBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder>( + getProfile(), getParentForChildren(), isClean()); - closedGroups_ = null; + profile_ = null; } - return closedGroupsBuilder_; + return profileBuilder_; } - private com.google.protobuf.LazyStringArrayList openGroups_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - private void ensureOpenGroupsIsMutable() { - if (!openGroups_.isModifiable()) { - openGroups_ = new com.google.protobuf.LazyStringArrayList(openGroups_); - } - bitField0_ |= 0x00000002; - } + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation openGroupInvitation_; + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder> openGroupInvitationBuilder_; /** - * repeated string openGroups = 2; - * @return A list containing the openGroups. + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; + * @return Whether the openGroupInvitation field is set. */ - public com.google.protobuf.ProtocolStringList - getOpenGroupsList() { - openGroups_.makeImmutable(); - return openGroups_; + public boolean hasOpenGroupInvitation() { + return ((bitField0_ & 0x00000400) != 0); } /** - * repeated string openGroups = 2; - * @return The count of openGroups. + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; + * @return The openGroupInvitation. */ - public int getOpenGroupsCount() { - return openGroups_.size(); + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation getOpenGroupInvitation() { + if (openGroupInvitationBuilder_ == null) { + return openGroupInvitation_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance() : openGroupInvitation_; + } else { + return openGroupInvitationBuilder_.getMessage(); + } } /** - * repeated string openGroups = 2; - * @param index The index of the element to return. - * @return The openGroups at the given index. + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ - public java.lang.String getOpenGroups(int index) { - return openGroups_.get(index); + public Builder setOpenGroupInvitation(org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation value) { + if (openGroupInvitationBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + openGroupInvitation_ = value; + } else { + openGroupInvitationBuilder_.setMessage(value); + } + bitField0_ |= 0x00000400; + onChanged(); + return this; } /** - * repeated string openGroups = 2; - * @param index The index of the value to return. - * @return The bytes of the openGroups at the given index. + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ - public com.google.protobuf.ByteString - getOpenGroupsBytes(int index) { - return openGroups_.getByteString(index); + public Builder setOpenGroupInvitation( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder builderForValue) { + if (openGroupInvitationBuilder_ == null) { + openGroupInvitation_ = builderForValue.build(); + } else { + openGroupInvitationBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000400; + onChanged(); + return this; } /** - * repeated string openGroups = 2; - * @param index The index to set the value at. - * @param value The openGroups to set. - * @return This builder for chaining. + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ - public Builder setOpenGroups( - int index, java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - ensureOpenGroupsIsMutable(); - openGroups_.set(index, value); - bitField0_ |= 0x00000002; - onChanged(); + public Builder mergeOpenGroupInvitation(org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation value) { + if (openGroupInvitationBuilder_ == null) { + if (((bitField0_ & 0x00000400) != 0) && + openGroupInvitation_ != null && + openGroupInvitation_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance()) { + getOpenGroupInvitationBuilder().mergeFrom(value); + } else { + openGroupInvitation_ = value; + } + } else { + openGroupInvitationBuilder_.mergeFrom(value); + } + if (openGroupInvitation_ != null) { + bitField0_ |= 0x00000400; + onChanged(); + } return this; } /** - * repeated string openGroups = 2; - * @param value The openGroups to add. - * @return This builder for chaining. + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ - public Builder addOpenGroups( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - ensureOpenGroupsIsMutable(); - openGroups_.add(value); - bitField0_ |= 0x00000002; + public Builder clearOpenGroupInvitation() { + bitField0_ = (bitField0_ & ~0x00000400); + openGroupInvitation_ = null; + if (openGroupInvitationBuilder_ != null) { + openGroupInvitationBuilder_.dispose(); + openGroupInvitationBuilder_ = null; + } onChanged(); return this; } /** - * repeated string openGroups = 2; - * @param values The openGroups to add. - * @return This builder for chaining. + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ - public Builder addAllOpenGroups( - java.lang.Iterable values) { - ensureOpenGroupsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, openGroups_); - bitField0_ |= 0x00000002; + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder getOpenGroupInvitationBuilder() { + bitField0_ |= 0x00000400; onChanged(); - return this; + return getOpenGroupInvitationFieldBuilder().getBuilder(); } /** - * repeated string openGroups = 2; - * @return This builder for chaining. + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ - public Builder clearOpenGroups() { - openGroups_ = - com.google.protobuf.LazyStringArrayList.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002);; - onChanged(); - return this; + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder getOpenGroupInvitationOrBuilder() { + if (openGroupInvitationBuilder_ != null) { + return openGroupInvitationBuilder_.getMessageOrBuilder(); + } else { + return openGroupInvitation_ == null ? + org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance() : openGroupInvitation_; + } } /** - * repeated string openGroups = 2; - * @param value The bytes of the openGroups to add. - * @return This builder for chaining. + * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ - public Builder addOpenGroupsBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - ensureOpenGroupsIsMutable(); - openGroups_.add(value); - bitField0_ |= 0x00000002; - onChanged(); - return this; + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder> + getOpenGroupInvitationFieldBuilder() { + if (openGroupInvitationBuilder_ == null) { + openGroupInvitationBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitationOrBuilder>( + getOpenGroupInvitation(), + getParentForChildren(), + isClean()); + openGroupInvitation_ = null; + } + return openGroupInvitationBuilder_; } - private java.lang.Object displayName_ = ""; + private java.lang.Object syncTarget_ = ""; /** - * optional string displayName = 3; - * @return Whether the displayName field is set. + * optional string syncTarget = 105; + * @return Whether the syncTarget field is set. */ - public boolean hasDisplayName() { - return ((bitField0_ & 0x00000004) != 0); + public boolean hasSyncTarget() { + return ((bitField0_ & 0x00000800) != 0); } /** - * optional string displayName = 3; - * @return The displayName. + * optional string syncTarget = 105; + * @return The syncTarget. */ - public java.lang.String getDisplayName() { - java.lang.Object ref = displayName_; + public java.lang.String getSyncTarget() { + java.lang.Object ref = syncTarget_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { - displayName_ = s; + syncTarget_ = s; } return s; } else { @@ -36062,628 +24251,764 @@ public java.lang.String getDisplayName() { } } /** - * optional string displayName = 3; - * @return The bytes for displayName. + * optional string syncTarget = 105; + * @return The bytes for syncTarget. */ public com.google.protobuf.ByteString - getDisplayNameBytes() { - java.lang.Object ref = displayName_; + getSyncTargetBytes() { + java.lang.Object ref = syncTarget_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - displayName_ = b; + syncTarget_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** - * optional string displayName = 3; - * @param value The displayName to set. + * optional string syncTarget = 105; + * @param value The syncTarget to set. * @return This builder for chaining. */ - public Builder setDisplayName( + public Builder setSyncTarget( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - displayName_ = value; - bitField0_ |= 0x00000004; + syncTarget_ = value; + bitField0_ |= 0x00000800; onChanged(); return this; } /** - * optional string displayName = 3; + * optional string syncTarget = 105; * @return This builder for chaining. */ - public Builder clearDisplayName() { - displayName_ = getDefaultInstance().getDisplayName(); - bitField0_ = (bitField0_ & ~0x00000004); + public Builder clearSyncTarget() { + syncTarget_ = getDefaultInstance().getSyncTarget(); + bitField0_ = (bitField0_ & ~0x00000800); onChanged(); return this; } /** - * optional string displayName = 3; - * @param value The bytes for displayName to set. + * optional string syncTarget = 105; + * @param value The bytes for syncTarget to set. * @return This builder for chaining. */ - public Builder setDisplayNameBytes( + public Builder setSyncTargetBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - displayName_ = value; - bitField0_ |= 0x00000004; + syncTarget_ = value; + bitField0_ |= 0x00000800; + onChanged(); + return this; + } + + private boolean blocksCommunityMessageRequests_ ; + /** + * optional bool blocksCommunityMessageRequests = 106; + * @return Whether the blocksCommunityMessageRequests field is set. + */ + @java.lang.Override + public boolean hasBlocksCommunityMessageRequests() { + return ((bitField0_ & 0x00001000) != 0); + } + /** + * optional bool blocksCommunityMessageRequests = 106; + * @return The blocksCommunityMessageRequests. + */ + @java.lang.Override + public boolean getBlocksCommunityMessageRequests() { + return blocksCommunityMessageRequests_; + } + /** + * optional bool blocksCommunityMessageRequests = 106; + * @param value The blocksCommunityMessageRequests to set. + * @return This builder for chaining. + */ + public Builder setBlocksCommunityMessageRequests(boolean value) { + + blocksCommunityMessageRequests_ = value; + bitField0_ |= 0x00001000; + onChanged(); + return this; + } + /** + * optional bool blocksCommunityMessageRequests = 106; + * @return This builder for chaining. + */ + public Builder clearBlocksCommunityMessageRequests() { + bitField0_ = (bitField0_ & ~0x00001000); + blocksCommunityMessageRequests_ = false; onChanged(); return this; } - private java.lang.Object profilePicture_ = ""; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage groupUpdateMessage_; + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder> groupUpdateMessageBuilder_; /** - * optional string profilePicture = 4; - * @return Whether the profilePicture field is set. + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; + * @return Whether the groupUpdateMessage field is set. */ - public boolean hasProfilePicture() { - return ((bitField0_ & 0x00000008) != 0); + public boolean hasGroupUpdateMessage() { + return ((bitField0_ & 0x00002000) != 0); } /** - * optional string profilePicture = 4; - * @return The profilePicture. + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; + * @return The groupUpdateMessage. */ - public java.lang.String getProfilePicture() { - java.lang.Object ref = profilePicture_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - profilePicture_ = s; - } - return s; + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage getGroupUpdateMessage() { + if (groupUpdateMessageBuilder_ == null) { + return groupUpdateMessage_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance() : groupUpdateMessage_; } else { - return (java.lang.String) ref; + return groupUpdateMessageBuilder_.getMessage(); } } /** - * optional string profilePicture = 4; - * @return The bytes for profilePicture. + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; */ - public com.google.protobuf.ByteString - getProfilePictureBytes() { - java.lang.Object ref = profilePicture_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - profilePicture_ = b; - return b; + public Builder setGroupUpdateMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage value) { + if (groupUpdateMessageBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + groupUpdateMessage_ = value; } else { - return (com.google.protobuf.ByteString) ref; + groupUpdateMessageBuilder_.setMessage(value); } + bitField0_ |= 0x00002000; + onChanged(); + return this; } /** - * optional string profilePicture = 4; - * @param value The profilePicture to set. - * @return This builder for chaining. + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; */ - public Builder setProfilePicture( - java.lang.String value) { - if (value == null) { throw new NullPointerException(); } - profilePicture_ = value; - bitField0_ |= 0x00000008; + public Builder setGroupUpdateMessage( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder builderForValue) { + if (groupUpdateMessageBuilder_ == null) { + groupUpdateMessage_ = builderForValue.build(); + } else { + groupUpdateMessageBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00002000; onChanged(); return this; } /** - * optional string profilePicture = 4; - * @return This builder for chaining. + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; */ - public Builder clearProfilePicture() { - profilePicture_ = getDefaultInstance().getProfilePicture(); - bitField0_ = (bitField0_ & ~0x00000008); - onChanged(); + public Builder mergeGroupUpdateMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage value) { + if (groupUpdateMessageBuilder_ == null) { + if (((bitField0_ & 0x00002000) != 0) && + groupUpdateMessage_ != null && + groupUpdateMessage_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance()) { + getGroupUpdateMessageBuilder().mergeFrom(value); + } else { + groupUpdateMessage_ = value; + } + } else { + groupUpdateMessageBuilder_.mergeFrom(value); + } + if (groupUpdateMessage_ != null) { + bitField0_ |= 0x00002000; + onChanged(); + } return this; } /** - * optional string profilePicture = 4; - * @param value The bytes for profilePicture to set. - * @return This builder for chaining. + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; */ - public Builder setProfilePictureBytes( - com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - profilePicture_ = value; - bitField0_ |= 0x00000008; + public Builder clearGroupUpdateMessage() { + bitField0_ = (bitField0_ & ~0x00002000); + groupUpdateMessage_ = null; + if (groupUpdateMessageBuilder_ != null) { + groupUpdateMessageBuilder_.dispose(); + groupUpdateMessageBuilder_ = null; + } onChanged(); return this; } - - private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; /** - * optional bytes profileKey = 5; - * @return Whether the profileKey field is set. + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; */ - @java.lang.Override - public boolean hasProfileKey() { - return ((bitField0_ & 0x00000010) != 0); + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder getGroupUpdateMessageBuilder() { + bitField0_ |= 0x00002000; + onChanged(); + return getGroupUpdateMessageFieldBuilder().getBuilder(); } /** - * optional bytes profileKey = 5; - * @return The profileKey. + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; */ - @java.lang.Override - public com.google.protobuf.ByteString getProfileKey() { - return profileKey_; + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder getGroupUpdateMessageOrBuilder() { + if (groupUpdateMessageBuilder_ != null) { + return groupUpdateMessageBuilder_.getMessageOrBuilder(); + } else { + return groupUpdateMessage_ == null ? + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.getDefaultInstance() : groupUpdateMessage_; + } } /** - * optional bytes profileKey = 5; - * @param value The profileKey to set. - * @return This builder for chaining. + * optional .signalservice.DataMessage.GroupUpdateMessage groupUpdateMessage = 120; */ - public Builder setProfileKey(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - profileKey_ = value; - bitField0_ |= 0x00000010; - onChanged(); - return this; + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder> + getGroupUpdateMessageFieldBuilder() { + if (groupUpdateMessageBuilder_ == null) { + groupUpdateMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessageOrBuilder>( + getGroupUpdateMessage(), + getParentForChildren(), + isClean()); + groupUpdateMessage_ = null; + } + return groupUpdateMessageBuilder_; } - /** - * optional bytes profileKey = 5; - * @return This builder for chaining. - */ - public Builder clearProfileKey() { - bitField0_ = (bitField0_ & ~0x00000010); - profileKey_ = getDefaultInstance().getProfileKey(); - onChanged(); - return this; + + // @@protoc_insertion_point(builder_scope:signalservice.DataMessage) + } + + // @@protoc_insertion_point(class_scope:signalservice.DataMessage) + private static final org.session.libsignal.protos.SignalServiceProtos.DataMessage DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.DataMessage(); + } + + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public DataMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface CallMessageOrBuilder extends + // @@protoc_insertion_point(interface_extends:signalservice.CallMessage) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * @required
+     * 
+ * + * required .signalservice.CallMessage.Type type = 1; + * @return Whether the type field is set. + */ + boolean hasType(); + /** + *
+     * @required
+     * 
+ * + * required .signalservice.CallMessage.Type type = 1; + * @return The type. + */ + org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type getType(); + + /** + * repeated string sdps = 2; + * @return A list containing the sdps. + */ + java.util.List + getSdpsList(); + /** + * repeated string sdps = 2; + * @return The count of sdps. + */ + int getSdpsCount(); + /** + * repeated string sdps = 2; + * @param index The index of the element to return. + * @return The sdps at the given index. + */ + java.lang.String getSdps(int index); + /** + * repeated string sdps = 2; + * @param index The index of the value to return. + * @return The bytes of the sdps at the given index. + */ + com.google.protobuf.ByteString + getSdpsBytes(int index); + + /** + * repeated uint32 sdpMLineIndexes = 3; + * @return A list containing the sdpMLineIndexes. + */ + java.util.List getSdpMLineIndexesList(); + /** + * repeated uint32 sdpMLineIndexes = 3; + * @return The count of sdpMLineIndexes. + */ + int getSdpMLineIndexesCount(); + /** + * repeated uint32 sdpMLineIndexes = 3; + * @param index The index of the element to return. + * @return The sdpMLineIndexes at the given index. + */ + int getSdpMLineIndexes(int index); - private java.util.List contacts_ = - java.util.Collections.emptyList(); - private void ensureContactsIsMutable() { - if (!((bitField0_ & 0x00000020) != 0)) { - contacts_ = new java.util.ArrayList(contacts_); - bitField0_ |= 0x00000020; - } - } + /** + * repeated string sdpMids = 4; + * @return A list containing the sdpMids. + */ + java.util.List + getSdpMidsList(); + /** + * repeated string sdpMids = 4; + * @return The count of sdpMids. + */ + int getSdpMidsCount(); + /** + * repeated string sdpMids = 4; + * @param index The index of the element to return. + * @return The sdpMids at the given index. + */ + java.lang.String getSdpMids(int index); + /** + * repeated string sdpMids = 4; + * @param index The index of the value to return. + * @return The bytes of the sdpMids at the given index. + */ + com.google.protobuf.ByteString + getSdpMidsBytes(int index); - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ContactOrBuilder> contactsBuilder_; + /** + *
+     * @required
+     * 
+ * + * required string uuid = 5; + * @return Whether the uuid field is set. + */ + boolean hasUuid(); + /** + *
+     * @required
+     * 
+ * + * required string uuid = 5; + * @return The uuid. + */ + java.lang.String getUuid(); + /** + *
+     * @required
+     * 
+ * + * required string uuid = 5; + * @return The bytes for uuid. + */ + com.google.protobuf.ByteString + getUuidBytes(); + } + /** + * Protobuf type {@code signalservice.CallMessage} + */ + public static final class CallMessage extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:signalservice.CallMessage) + CallMessageOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 29, + /* patch= */ 3, + /* suffix= */ "", + CallMessage.class.getName()); + } + // Use CallMessage.newBuilder() to construct. + private CallMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private CallMessage() { + type_ = 6; + sdps_ = + com.google.protobuf.LazyStringArrayList.emptyList(); + sdpMLineIndexes_ = emptyIntList(); + sdpMids_ = + com.google.protobuf.LazyStringArrayList.emptyList(); + uuid_ = ""; + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.CallMessage.class, org.session.libsignal.protos.SignalServiceProtos.CallMessage.Builder.class); + } + + /** + * Protobuf enum {@code signalservice.CallMessage.Type} + */ + public enum Type + implements com.google.protobuf.ProtocolMessageEnum { /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - public java.util.List getContactsList() { - if (contactsBuilder_ == null) { - return java.util.Collections.unmodifiableList(contacts_); - } else { - return contactsBuilder_.getMessageList(); - } - } - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * PRE_OFFER = 6; */ - public int getContactsCount() { - if (contactsBuilder_ == null) { - return contacts_.size(); - } else { - return contactsBuilder_.getCount(); - } - } + PRE_OFFER(6), /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * OFFER = 1; */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact getContacts(int index) { - if (contactsBuilder_ == null) { - return contacts_.get(index); - } else { - return contactsBuilder_.getMessage(index); - } - } + OFFER(1), /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * ANSWER = 2; */ - public Builder setContacts( - int index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact value) { - if (contactsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureContactsIsMutable(); - contacts_.set(index, value); - onChanged(); - } else { - contactsBuilder_.setMessage(index, value); - } - return this; - } + ANSWER(2), /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * PROVISIONAL_ANSWER = 3; */ - public Builder setContacts( - int index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder builderForValue) { - if (contactsBuilder_ == null) { - ensureContactsIsMutable(); - contacts_.set(index, builderForValue.build()); - onChanged(); - } else { - contactsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } + PROVISIONAL_ANSWER(3), /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * ICE_CANDIDATES = 4; */ - public Builder addContacts(org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact value) { - if (contactsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureContactsIsMutable(); - contacts_.add(value); - onChanged(); - } else { - contactsBuilder_.addMessage(value); - } - return this; - } + ICE_CANDIDATES(4), /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * END_CALL = 5; */ - public Builder addContacts( - int index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact value) { - if (contactsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureContactsIsMutable(); - contacts_.add(index, value); - onChanged(); - } else { - contactsBuilder_.addMessage(index, value); - } - return this; + END_CALL(5), + ; + + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 29, + /* patch= */ 3, + /* suffix= */ "", + Type.class.getName()); } /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * PRE_OFFER = 6; */ - public Builder addContacts( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder builderForValue) { - if (contactsBuilder_ == null) { - ensureContactsIsMutable(); - contacts_.add(builderForValue.build()); - onChanged(); - } else { - contactsBuilder_.addMessage(builderForValue.build()); - } - return this; - } + public static final int PRE_OFFER_VALUE = 6; /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * OFFER = 1; */ - public Builder addContacts( - int index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder builderForValue) { - if (contactsBuilder_ == null) { - ensureContactsIsMutable(); - contacts_.add(index, builderForValue.build()); - onChanged(); - } else { - contactsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } + public static final int OFFER_VALUE = 1; /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * ANSWER = 2; */ - public Builder addAllContacts( - java.lang.Iterable values) { - if (contactsBuilder_ == null) { - ensureContactsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, contacts_); - onChanged(); - } else { - contactsBuilder_.addAllMessages(values); - } - return this; - } + public static final int ANSWER_VALUE = 2; /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * PROVISIONAL_ANSWER = 3; */ - public Builder clearContacts() { - if (contactsBuilder_ == null) { - contacts_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000020); - onChanged(); - } else { - contactsBuilder_.clear(); - } - return this; - } + public static final int PROVISIONAL_ANSWER_VALUE = 3; /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * ICE_CANDIDATES = 4; */ - public Builder removeContacts(int index) { - if (contactsBuilder_ == null) { - ensureContactsIsMutable(); - contacts_.remove(index); - onChanged(); - } else { - contactsBuilder_.remove(index); - } - return this; - } + public static final int ICE_CANDIDATES_VALUE = 4; /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * END_CALL = 5; */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder getContactsBuilder( - int index) { - return getContactsFieldBuilder().getBuilder(index); + public static final int END_CALL_VALUE = 5; + + + public final int getNumber() { + return value; } + /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + * @deprecated Use {@link #forNumber(int)} instead. */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ContactOrBuilder getContactsOrBuilder( - int index) { - if (contactsBuilder_ == null) { - return contacts_.get(index); } else { - return contactsBuilder_.getMessageOrBuilder(index); - } + @java.lang.Deprecated + public static Type valueOf(int value) { + return forNumber(value); } + /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. */ - public java.util.List - getContactsOrBuilderList() { - if (contactsBuilder_ != null) { - return contactsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(contacts_); + public static Type forNumber(int value) { + switch (value) { + case 6: return PRE_OFFER; + case 1: return OFFER; + case 2: return ANSWER; + case 3: return PROVISIONAL_ANSWER; + case 4: return ICE_CANDIDATES; + case 5: return END_CALL; + default: return null; } } - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder addContactsBuilder() { - return getContactsFieldBuilder().addBuilder( - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.getDefaultInstance()); + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; } - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder addContactsBuilder( - int index) { - return getContactsFieldBuilder().addBuilder( - index, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.getDefaultInstance()); + private static final com.google.protobuf.Internal.EnumLiteMap< + Type> internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Type findValueByNumber(int number) { + return Type.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(ordinal()); } - /** - * repeated .signalservice.ConfigurationMessage.Contact contacts = 6; - */ - public java.util.List - getContactsBuilderList() { - return getContactsFieldBuilder().getBuilderList(); + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); } - private com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ContactOrBuilder> - getContactsFieldBuilder() { - if (contactsBuilder_ == null) { - contactsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.Contact.Builder, org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ContactOrBuilder>( - contacts_, - ((bitField0_ & 0x00000020) != 0), - getParentForChildren(), - isClean()); - contacts_ = null; - } - return contactsBuilder_; + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.CallMessage.getDescriptor().getEnumTypes().get(0); } - // @@protoc_insertion_point(builder_scope:signalservice.ConfigurationMessage) - } - - // @@protoc_insertion_point(class_scope:signalservice.ConfigurationMessage) - private static final org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage getDefaultInstance() { - return DEFAULT_INSTANCE; - } + private static final Type[] VALUES = values(); - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public ConfigurationMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); + public static Type valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); } - return builder.buildPartial(); + return VALUES[desc.getIndex()]; } - }; - public static com.google.protobuf.Parser parser() { - return PARSER; - } + private final int value; - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } + private Type(int value) { + this.value = value; + } - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage getDefaultInstanceForType() { - return DEFAULT_INSTANCE; + // @@protoc_insertion_point(enum_scope:signalservice.CallMessage.Type) } - } - - public interface MessageRequestResponseOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.MessageRequestResponse) - com.google.protobuf.MessageOrBuilder { - + private int bitField0_; + public static final int TYPE_FIELD_NUMBER = 1; + private int type_ = 6; /** *
      * @required
      * 
* - * required bool isApproved = 1; - * @return Whether the isApproved field is set. + * required .signalservice.CallMessage.Type type = 1; + * @return Whether the type field is set. */ - boolean hasIsApproved(); + @java.lang.Override public boolean hasType() { + return ((bitField0_ & 0x00000001) != 0); + } /** *
      * @required
      * 
* - * required bool isApproved = 1; - * @return The isApproved. + * required .signalservice.CallMessage.Type type = 1; + * @return The type. */ - boolean getIsApproved(); + @java.lang.Override public org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type getType() { + org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type result = org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.forNumber(type_); + return result == null ? org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PRE_OFFER : result; + } + public static final int SDPS_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private com.google.protobuf.LazyStringArrayList sdps_ = + com.google.protobuf.LazyStringArrayList.emptyList(); /** - * optional bytes profileKey = 2; - * @return Whether the profileKey field is set. + * repeated string sdps = 2; + * @return A list containing the sdps. */ - boolean hasProfileKey(); + public com.google.protobuf.ProtocolStringList + getSdpsList() { + return sdps_; + } /** - * optional bytes profileKey = 2; - * @return The profileKey. + * repeated string sdps = 2; + * @return The count of sdps. */ - com.google.protobuf.ByteString getProfileKey(); - + public int getSdpsCount() { + return sdps_.size(); + } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; - * @return Whether the profile field is set. + * repeated string sdps = 2; + * @param index The index of the element to return. + * @return The sdps at the given index. */ - boolean hasProfile(); + public java.lang.String getSdps(int index) { + return sdps_.get(index); + } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; - * @return The profile. + * repeated string sdps = 2; + * @param index The index of the value to return. + * @return The bytes of the sdps at the given index. */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile(); + public com.google.protobuf.ByteString + getSdpsBytes(int index) { + return sdps_.getByteString(index); + } + + public static final int SDPMLINEINDEXES_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private com.google.protobuf.Internal.IntList sdpMLineIndexes_ = + emptyIntList(); /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; + * repeated uint32 sdpMLineIndexes = 3; + * @return A list containing the sdpMLineIndexes. */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder(); - } - /** - * Protobuf type {@code signalservice.MessageRequestResponse} - */ - public static final class MessageRequestResponse extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.MessageRequestResponse) - MessageRequestResponseOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - MessageRequestResponse.class.getName()); - } - // Use MessageRequestResponse.newBuilder() to construct. - private MessageRequestResponse(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private MessageRequestResponse() { - profileKey_ = com.google.protobuf.ByteString.EMPTY; + @java.lang.Override + public java.util.List + getSdpMLineIndexesList() { + return sdpMLineIndexes_; } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_descriptor; + /** + * repeated uint32 sdpMLineIndexes = 3; + * @return The count of sdpMLineIndexes. + */ + public int getSdpMLineIndexesCount() { + return sdpMLineIndexes_.size(); } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.class, org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.Builder.class); + /** + * repeated uint32 sdpMLineIndexes = 3; + * @param index The index of the element to return. + * @return The sdpMLineIndexes at the given index. + */ + public int getSdpMLineIndexes(int index) { + return sdpMLineIndexes_.getInt(index); } - private int bitField0_; - public static final int ISAPPROVED_FIELD_NUMBER = 1; - private boolean isApproved_ = false; + public static final int SDPMIDS_FIELD_NUMBER = 4; + @SuppressWarnings("serial") + private com.google.protobuf.LazyStringArrayList sdpMids_ = + com.google.protobuf.LazyStringArrayList.emptyList(); /** - *
-     * @required
-     * 
- * - * required bool isApproved = 1; - * @return Whether the isApproved field is set. + * repeated string sdpMids = 4; + * @return A list containing the sdpMids. */ - @java.lang.Override - public boolean hasIsApproved() { - return ((bitField0_ & 0x00000001) != 0); + public com.google.protobuf.ProtocolStringList + getSdpMidsList() { + return sdpMids_; } /** - *
-     * @required
-     * 
- * - * required bool isApproved = 1; - * @return The isApproved. + * repeated string sdpMids = 4; + * @return The count of sdpMids. */ - @java.lang.Override - public boolean getIsApproved() { - return isApproved_; + public int getSdpMidsCount() { + return sdpMids_.size(); } - - public static final int PROFILEKEY_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; /** - * optional bytes profileKey = 2; - * @return Whether the profileKey field is set. + * repeated string sdpMids = 4; + * @param index The index of the element to return. + * @return The sdpMids at the given index. */ - @java.lang.Override - public boolean hasProfileKey() { - return ((bitField0_ & 0x00000002) != 0); + public java.lang.String getSdpMids(int index) { + return sdpMids_.get(index); } /** - * optional bytes profileKey = 2; - * @return The profileKey. + * repeated string sdpMids = 4; + * @param index The index of the value to return. + * @return The bytes of the sdpMids at the given index. */ - @java.lang.Override - public com.google.protobuf.ByteString getProfileKey() { - return profileKey_; + public com.google.protobuf.ByteString + getSdpMidsBytes(int index) { + return sdpMids_.getByteString(index); } - public static final int PROFILE_FIELD_NUMBER = 3; - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; + public static final int UUID_FIELD_NUMBER = 5; + @SuppressWarnings("serial") + private volatile java.lang.Object uuid_ = ""; /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; - * @return Whether the profile field is set. + *
+     * @required
+     * 
+ * + * required string uuid = 5; + * @return Whether the uuid field is set. */ @java.lang.Override - public boolean hasProfile() { - return ((bitField0_ & 0x00000004) != 0); + public boolean hasUuid() { + return ((bitField0_ & 0x00000002) != 0); } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; - * @return The profile. + *
+     * @required
+     * 
+ * + * required string uuid = 5; + * @return The uuid. */ @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { - return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; + public java.lang.String getUuid() { + java.lang.Object ref = uuid_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + uuid_ = s; + } + return s; + } } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; + *
+     * @required
+     * 
+ * + * required string uuid = 5; + * @return The bytes for uuid. */ @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { - return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; + public com.google.protobuf.ByteString + getUuidBytes() { + java.lang.Object ref = uuid_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + uuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } } private byte memoizedIsInitialized = -1; @@ -36693,7 +25018,11 @@ public final boolean isInitialized() { if (isInitialized == 1) return true; if (isInitialized == 0) return false; - if (!hasIsApproved()) { + if (!hasType()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasUuid()) { memoizedIsInitialized = 0; return false; } @@ -36705,13 +25034,19 @@ public final boolean isInitialized() { public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { if (((bitField0_ & 0x00000001) != 0)) { - output.writeBool(1, isApproved_); + output.writeEnum(1, type_); } - if (((bitField0_ & 0x00000002) != 0)) { - output.writeBytes(2, profileKey_); + for (int i = 0; i < sdps_.size(); i++) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, sdps_.getRaw(i)); } - if (((bitField0_ & 0x00000004) != 0)) { - output.writeMessage(3, getProfile()); + for (int i = 0; i < sdpMLineIndexes_.size(); i++) { + output.writeUInt32(3, sdpMLineIndexes_.getInt(i)); + } + for (int i = 0; i < sdpMids_.size(); i++) { + com.google.protobuf.GeneratedMessage.writeString(output, 4, sdpMids_.getRaw(i)); + } + if (((bitField0_ & 0x00000002) != 0)) { + com.google.protobuf.GeneratedMessage.writeString(output, 5, uuid_); } getUnknownFields().writeTo(output); } @@ -36724,15 +25059,35 @@ public int getSerializedSize() { size = 0; if (((bitField0_ & 0x00000001) != 0)) { size += com.google.protobuf.CodedOutputStream - .computeBoolSize(1, isApproved_); + .computeEnumSize(1, type_); } - if (((bitField0_ & 0x00000002) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, profileKey_); + { + int dataSize = 0; + for (int i = 0; i < sdps_.size(); i++) { + dataSize += computeStringSizeNoTag(sdps_.getRaw(i)); + } + size += dataSize; + size += 1 * getSdpsList().size(); } - if (((bitField0_ & 0x00000004) != 0)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(3, getProfile()); + { + int dataSize = 0; + for (int i = 0; i < sdpMLineIndexes_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeUInt32SizeNoTag(sdpMLineIndexes_.getInt(i)); + } + size += dataSize; + size += 1 * getSdpMLineIndexesList().size(); + } + { + int dataSize = 0; + for (int i = 0; i < sdpMids_.size(); i++) { + dataSize += computeStringSizeNoTag(sdpMids_.getRaw(i)); + } + size += dataSize; + size += 1 * getSdpMidsList().size(); + } + if (((bitField0_ & 0x00000002) != 0)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(5, uuid_); } size += getUnknownFields().getSerializedSize(); memoizedSize = size; @@ -36744,25 +25099,25 @@ public boolean equals(final java.lang.Object obj) { if (obj == this) { return true; } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse)) { + if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.CallMessage)) { return super.equals(obj); } - org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse other = (org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse) obj; + org.session.libsignal.protos.SignalServiceProtos.CallMessage other = (org.session.libsignal.protos.SignalServiceProtos.CallMessage) obj; - if (hasIsApproved() != other.hasIsApproved()) return false; - if (hasIsApproved()) { - if (getIsApproved() - != other.getIsApproved()) return false; - } - if (hasProfileKey() != other.hasProfileKey()) return false; - if (hasProfileKey()) { - if (!getProfileKey() - .equals(other.getProfileKey())) return false; + if (hasType() != other.hasType()) return false; + if (hasType()) { + if (type_ != other.type_) return false; } - if (hasProfile() != other.hasProfile()) return false; - if (hasProfile()) { - if (!getProfile() - .equals(other.getProfile())) return false; + if (!getSdpsList() + .equals(other.getSdpsList())) return false; + if (!getSdpMLineIndexesList() + .equals(other.getSdpMLineIndexesList())) return false; + if (!getSdpMidsList() + .equals(other.getSdpMidsList())) return false; + if (hasUuid() != other.hasUuid()) return false; + if (hasUuid()) { + if (!getUuid() + .equals(other.getUuid())) return false; } if (!getUnknownFields().equals(other.getUnknownFields())) return false; return true; @@ -36775,62 +25130,69 @@ public int hashCode() { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasIsApproved()) { - hash = (37 * hash) + ISAPPROVED_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getIsApproved()); + if (hasType()) { + hash = (37 * hash) + TYPE_FIELD_NUMBER; + hash = (53 * hash) + type_; } - if (hasProfileKey()) { - hash = (37 * hash) + PROFILEKEY_FIELD_NUMBER; - hash = (53 * hash) + getProfileKey().hashCode(); + if (getSdpsCount() > 0) { + hash = (37 * hash) + SDPS_FIELD_NUMBER; + hash = (53 * hash) + getSdpsList().hashCode(); } - if (hasProfile()) { - hash = (37 * hash) + PROFILE_FIELD_NUMBER; - hash = (53 * hash) + getProfile().hashCode(); + if (getSdpMLineIndexesCount() > 0) { + hash = (37 * hash) + SDPMLINEINDEXES_FIELD_NUMBER; + hash = (53 * hash) + getSdpMLineIndexesList().hashCode(); + } + if (getSdpMidsCount() > 0) { + hash = (37 * hash) + SDPMIDS_FIELD_NUMBER; + hash = (53 * hash) + getSdpMidsList().hashCode(); + } + if (hasUuid()) { + hash = (37 * hash) + UUID_FIELD_NUMBER; + hash = (53 * hash) + getUuid().hashCode(); } hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom(byte[] data) + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom(java.io.InputStream input) + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -36838,26 +25200,26 @@ public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestRes .parseWithIOException(PARSER, input, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseDelimitedFrom(java.io.InputStream input) + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessage .parseDelimitedWithIOException(PARSER, input); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseDelimitedFrom( + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessage .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input); } - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -36870,7 +25232,7 @@ public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestRes public static Builder newBuilder() { return DEFAULT_INSTANCE.toBuilder(); } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse prototype) { + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.CallMessage prototype) { return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); } @java.lang.Override @@ -36886,69 +25248,63 @@ protected Builder newBuilderForType( return builder; } /** - * Protobuf type {@code signalservice.MessageRequestResponse} + * Protobuf type {@code signalservice.CallMessage} */ public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.MessageRequestResponse) - org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponseOrBuilder { + // @@protoc_insertion_point(builder_implements:signalservice.CallMessage) + org.session.libsignal.protos.SignalServiceProtos.CallMessageOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_descriptor; + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_descriptor; } @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_fieldAccessorTable + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_fieldAccessorTable .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.class, org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.Builder.class); + org.session.libsignal.protos.SignalServiceProtos.CallMessage.class, org.session.libsignal.protos.SignalServiceProtos.CallMessage.Builder.class); } - // Construct using org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.newBuilder() + // Construct using org.session.libsignal.protos.SignalServiceProtos.CallMessage.newBuilder() private Builder() { - maybeForceBuilderInitialization(); + } private Builder( com.google.protobuf.GeneratedMessage.BuilderParent parent) { super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage - .alwaysUseFieldBuilders) { - getProfileFieldBuilder(); - } + } @java.lang.Override public Builder clear() { super.clear(); bitField0_ = 0; - isApproved_ = false; - profileKey_ = com.google.protobuf.ByteString.EMPTY; - profile_ = null; - if (profileBuilder_ != null) { - profileBuilder_.dispose(); - profileBuilder_ = null; - } + type_ = 6; + sdps_ = + com.google.protobuf.LazyStringArrayList.emptyList(); + sdpMLineIndexes_ = emptyIntList(); + sdpMids_ = + com.google.protobuf.LazyStringArrayList.emptyList(); + uuid_ = ""; return this; } @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_descriptor; + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_CallMessage_descriptor; } @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.getDefaultInstance(); + public org.session.libsignal.protos.SignalServiceProtos.CallMessage getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.CallMessage.getDefaultInstance(); } @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse build() { - org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse result = buildPartial(); + public org.session.libsignal.protos.SignalServiceProtos.CallMessage build() { + org.session.libsignal.protos.SignalServiceProtos.CallMessage result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } @@ -36956,53 +25312,89 @@ public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse b } @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse result = new org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse(this); + public org.session.libsignal.protos.SignalServiceProtos.CallMessage buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.CallMessage result = new org.session.libsignal.protos.SignalServiceProtos.CallMessage(this); if (bitField0_ != 0) { buildPartial0(result); } onBuilt(); return result; } - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse result) { + private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.CallMessage result) { int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) != 0)) { - result.isApproved_ = isApproved_; + result.type_ = type_; to_bitField0_ |= 0x00000001; } if (((from_bitField0_ & 0x00000002) != 0)) { - result.profileKey_ = profileKey_; - to_bitField0_ |= 0x00000002; + sdps_.makeImmutable(); + result.sdps_ = sdps_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + sdpMLineIndexes_.makeImmutable(); + result.sdpMLineIndexes_ = sdpMLineIndexes_; + } + if (((from_bitField0_ & 0x00000008) != 0)) { + sdpMids_.makeImmutable(); + result.sdpMids_ = sdpMids_; } - if (((from_bitField0_ & 0x00000004) != 0)) { - result.profile_ = profileBuilder_ == null - ? profile_ - : profileBuilder_.build(); - to_bitField0_ |= 0x00000004; + if (((from_bitField0_ & 0x00000010) != 0)) { + result.uuid_ = uuid_; + to_bitField0_ |= 0x00000002; } result.bitField0_ |= to_bitField0_; } @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse)other); + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.CallMessage) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.CallMessage)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.getDefaultInstance()) return this; - if (other.hasIsApproved()) { - setIsApproved(other.getIsApproved()); + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.CallMessage other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.CallMessage.getDefaultInstance()) return this; + if (other.hasType()) { + setType(other.getType()); } - if (other.hasProfileKey()) { - setProfileKey(other.getProfileKey()); + if (!other.sdps_.isEmpty()) { + if (sdps_.isEmpty()) { + sdps_ = other.sdps_; + bitField0_ |= 0x00000002; + } else { + ensureSdpsIsMutable(); + sdps_.addAll(other.sdps_); + } + onChanged(); } - if (other.hasProfile()) { - mergeProfile(other.getProfile()); + if (!other.sdpMLineIndexes_.isEmpty()) { + if (sdpMLineIndexes_.isEmpty()) { + sdpMLineIndexes_ = other.sdpMLineIndexes_; + sdpMLineIndexes_.makeImmutable(); + bitField0_ |= 0x00000004; + } else { + ensureSdpMLineIndexesIsMutable(); + sdpMLineIndexes_.addAll(other.sdpMLineIndexes_); + } + onChanged(); + } + if (!other.sdpMids_.isEmpty()) { + if (sdpMids_.isEmpty()) { + sdpMids_ = other.sdpMids_; + bitField0_ |= 0x00000008; + } else { + ensureSdpMidsIsMutable(); + sdpMids_.addAll(other.sdpMids_); + } + onChanged(); + } + if (other.hasUuid()) { + uuid_ = other.uuid_; + bitField0_ |= 0x00000010; + onChanged(); } this.mergeUnknownFields(other.getUnknownFields()); onChanged(); @@ -37011,7 +25403,10 @@ public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.Messag @java.lang.Override public final boolean isInitialized() { - if (!hasIsApproved()) { + if (!hasType()) { + return false; + } + if (!hasUuid()) { return false; } return true; @@ -37034,22 +25429,50 @@ public Builder mergeFrom( done = true; break; case 8: { - isApproved_ = input.readBool(); - bitField0_ |= 0x00000001; + int tmpRaw = input.readEnum(); + org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type tmpValue = + org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.forNumber(tmpRaw); + if (tmpValue == null) { + mergeUnknownVarintField(1, tmpRaw); + } else { + type_ = tmpRaw; + bitField0_ |= 0x00000001; + } break; } // case 8 case 18: { - profileKey_ = input.readBytes(); - bitField0_ |= 0x00000002; + com.google.protobuf.ByteString bs = input.readBytes(); + ensureSdpsIsMutable(); + sdps_.add(bs); break; } // case 18 + case 24: { + int v = input.readUInt32(); + ensureSdpMLineIndexesIsMutable(); + sdpMLineIndexes_.addInt(v); + break; + } // case 24 case 26: { - input.readMessage( - getProfileFieldBuilder().getBuilder(), - extensionRegistry); - bitField0_ |= 0x00000004; + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + ensureSdpMLineIndexesIsMutable(); + while (input.getBytesUntilLimit() > 0) { + sdpMLineIndexes_.addInt(input.readUInt32()); + } + input.popLimit(limit); break; } // case 26 + case 34: { + com.google.protobuf.ByteString bs = input.readBytes(); + ensureSdpMidsIsMutable(); + sdpMids_.add(bs); + break; + } // case 34 + case 42: { + uuid_ = input.readBytes(); + bitField0_ |= 0x00000010; + break; + } // case 42 default: { if (!super.parseUnknownField(input, extensionRegistry, tag)) { done = true; // was an endgroup tag @@ -37067,17 +25490,16 @@ public Builder mergeFrom( } private int bitField0_; - private boolean isApproved_ ; + private int type_ = 6; /** *
        * @required
        * 
* - * required bool isApproved = 1; - * @return Whether the isApproved field is set. + * required .signalservice.CallMessage.Type type = 1; + * @return Whether the type field is set. */ - @java.lang.Override - public boolean hasIsApproved() { + @java.lang.Override public boolean hasType() { return ((bitField0_ & 0x00000001) != 0); } /** @@ -37085,26 +25507,29 @@ public boolean hasIsApproved() { * @required * * - * required bool isApproved = 1; - * @return The isApproved. + * required .signalservice.CallMessage.Type type = 1; + * @return The type. */ @java.lang.Override - public boolean getIsApproved() { - return isApproved_; + public org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type getType() { + org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type result = org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.forNumber(type_); + return result == null ? org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PRE_OFFER : result; } /** *
        * @required
        * 
* - * required bool isApproved = 1; - * @param value The isApproved to set. + * required .signalservice.CallMessage.Type type = 1; + * @param value The type to set. * @return This builder for chaining. */ - public Builder setIsApproved(boolean value) { - - isApproved_ = value; + public Builder setType(org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type value) { + if (value == null) { + throw new NullPointerException(); + } bitField0_ |= 0x00000001; + type_ = value.getNumber(); onChanged(); return this; } @@ -37113,557 +25538,633 @@ public Builder setIsApproved(boolean value) { * @required * * - * required bool isApproved = 1; + * required .signalservice.CallMessage.Type type = 1; * @return This builder for chaining. */ - public Builder clearIsApproved() { + public Builder clearType() { bitField0_ = (bitField0_ & ~0x00000001); - isApproved_ = false; + type_ = 6; onChanged(); return this; } - private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; + private com.google.protobuf.LazyStringArrayList sdps_ = + com.google.protobuf.LazyStringArrayList.emptyList(); + private void ensureSdpsIsMutable() { + if (!sdps_.isModifiable()) { + sdps_ = new com.google.protobuf.LazyStringArrayList(sdps_); + } + bitField0_ |= 0x00000002; + } /** - * optional bytes profileKey = 2; - * @return Whether the profileKey field is set. + * repeated string sdps = 2; + * @return A list containing the sdps. */ - @java.lang.Override - public boolean hasProfileKey() { - return ((bitField0_ & 0x00000002) != 0); + public com.google.protobuf.ProtocolStringList + getSdpsList() { + sdps_.makeImmutable(); + return sdps_; } /** - * optional bytes profileKey = 2; - * @return The profileKey. + * repeated string sdps = 2; + * @return The count of sdps. */ - @java.lang.Override - public com.google.protobuf.ByteString getProfileKey() { - return profileKey_; + public int getSdpsCount() { + return sdps_.size(); } /** - * optional bytes profileKey = 2; - * @param value The profileKey to set. + * repeated string sdps = 2; + * @param index The index of the element to return. + * @return The sdps at the given index. + */ + public java.lang.String getSdps(int index) { + return sdps_.get(index); + } + /** + * repeated string sdps = 2; + * @param index The index of the value to return. + * @return The bytes of the sdps at the given index. + */ + public com.google.protobuf.ByteString + getSdpsBytes(int index) { + return sdps_.getByteString(index); + } + /** + * repeated string sdps = 2; + * @param index The index to set the value at. + * @param value The sdps to set. * @return This builder for chaining. */ - public Builder setProfileKey(com.google.protobuf.ByteString value) { + public Builder setSdps( + int index, java.lang.String value) { if (value == null) { throw new NullPointerException(); } - profileKey_ = value; + ensureSdpsIsMutable(); + sdps_.set(index, value); bitField0_ |= 0x00000002; onChanged(); return this; } /** - * optional bytes profileKey = 2; + * repeated string sdps = 2; + * @param value The sdps to add. * @return This builder for chaining. */ - public Builder clearProfileKey() { - bitField0_ = (bitField0_ & ~0x00000002); - profileKey_ = getDefaultInstance().getProfileKey(); + public Builder addSdps( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + ensureSdpsIsMutable(); + sdps_.add(value); + bitField0_ |= 0x00000002; onChanged(); return this; } - - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> profileBuilder_; - /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; - * @return Whether the profile field is set. - */ - public boolean hasProfile() { - return ((bitField0_ & 0x00000004) != 0); - } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; - * @return The profile. + * repeated string sdps = 2; + * @param values The sdps to add. + * @return This builder for chaining. */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { - if (profileBuilder_ == null) { - return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; - } else { - return profileBuilder_.getMessage(); - } + public Builder addAllSdps( + java.lang.Iterable values) { + ensureSdpsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, sdps_); + bitField0_ |= 0x00000002; + onChanged(); + return this; } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; - */ - public Builder setProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { - if (profileBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - profile_ = value; - } else { - profileBuilder_.setMessage(value); - } - bitField0_ |= 0x00000004; + * repeated string sdps = 2; + * @return This builder for chaining. + */ + public Builder clearSdps() { + sdps_ = + com.google.protobuf.LazyStringArrayList.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002);; onChanged(); return this; } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; + * repeated string sdps = 2; + * @param value The bytes of the sdps to add. + * @return This builder for chaining. */ - public Builder setProfile( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder builderForValue) { - if (profileBuilder_ == null) { - profile_ = builderForValue.build(); - } else { - profileBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000004; + public Builder addSdpsBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + ensureSdpsIsMutable(); + sdps_.add(value); + bitField0_ |= 0x00000002; onChanged(); return this; } + + private com.google.protobuf.Internal.IntList sdpMLineIndexes_ = emptyIntList(); + private void ensureSdpMLineIndexesIsMutable() { + if (!sdpMLineIndexes_.isModifiable()) { + sdpMLineIndexes_ = makeMutableCopy(sdpMLineIndexes_); + } + bitField0_ |= 0x00000004; + } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; + * repeated uint32 sdpMLineIndexes = 3; + * @return A list containing the sdpMLineIndexes. */ - public Builder mergeProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { - if (profileBuilder_ == null) { - if (((bitField0_ & 0x00000004) != 0) && - profile_ != null && - profile_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance()) { - getProfileBuilder().mergeFrom(value); - } else { - profile_ = value; - } - } else { - profileBuilder_.mergeFrom(value); - } - if (profile_ != null) { - bitField0_ |= 0x00000004; - onChanged(); - } - return this; + public java.util.List + getSdpMLineIndexesList() { + sdpMLineIndexes_.makeImmutable(); + return sdpMLineIndexes_; } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; + * repeated uint32 sdpMLineIndexes = 3; + * @return The count of sdpMLineIndexes. */ - public Builder clearProfile() { - bitField0_ = (bitField0_ & ~0x00000004); - profile_ = null; - if (profileBuilder_ != null) { - profileBuilder_.dispose(); - profileBuilder_ = null; - } - onChanged(); - return this; + public int getSdpMLineIndexesCount() { + return sdpMLineIndexes_.size(); } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; + * repeated uint32 sdpMLineIndexes = 3; + * @param index The index of the element to return. + * @return The sdpMLineIndexes at the given index. */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder getProfileBuilder() { + public int getSdpMLineIndexes(int index) { + return sdpMLineIndexes_.getInt(index); + } + /** + * repeated uint32 sdpMLineIndexes = 3; + * @param index The index to set the value at. + * @param value The sdpMLineIndexes to set. + * @return This builder for chaining. + */ + public Builder setSdpMLineIndexes( + int index, int value) { + + ensureSdpMLineIndexesIsMutable(); + sdpMLineIndexes_.setInt(index, value); bitField0_ |= 0x00000004; onChanged(); - return getProfileFieldBuilder().getBuilder(); + return this; } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; + * repeated uint32 sdpMLineIndexes = 3; + * @param value The sdpMLineIndexes to add. + * @return This builder for chaining. */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { - if (profileBuilder_ != null) { - return profileBuilder_.getMessageOrBuilder(); - } else { - return profile_ == null ? - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; - } + public Builder addSdpMLineIndexes(int value) { + + ensureSdpMLineIndexesIsMutable(); + sdpMLineIndexes_.addInt(value); + bitField0_ |= 0x00000004; + onChanged(); + return this; } /** - * optional .signalservice.DataMessage.LokiProfile profile = 3; + * repeated uint32 sdpMLineIndexes = 3; + * @param values The sdpMLineIndexes to add. + * @return This builder for chaining. */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> - getProfileFieldBuilder() { - if (profileBuilder_ == null) { - profileBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder>( - getProfile(), - getParentForChildren(), - isClean()); - profile_ = null; - } - return profileBuilder_; - } - - // @@protoc_insertion_point(builder_scope:signalservice.MessageRequestResponse) - } - - // @@protoc_insertion_point(class_scope:signalservice.MessageRequestResponse) - private static final org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public MessageRequestResponse parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); + public Builder addAllSdpMLineIndexes( + java.lang.Iterable values) { + ensureSdpMLineIndexesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, sdpMLineIndexes_); + bitField0_ |= 0x00000004; + onChanged(); + return this; } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface SharedConfigMessageOrBuilder extends - // @@protoc_insertion_point(interface_extends:signalservice.SharedConfigMessage) - com.google.protobuf.MessageOrBuilder { - - /** - *
-     * @required
-     * 
- * - * required .signalservice.SharedConfigMessage.Kind kind = 1; - * @return Whether the kind field is set. - */ - boolean hasKind(); - /** - *
-     * @required
-     * 
- * - * required .signalservice.SharedConfigMessage.Kind kind = 1; - * @return The kind. - */ - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind getKind(); - - /** - *
-     * @required
-     * 
- * - * required int64 seqno = 2; - * @return Whether the seqno field is set. - */ - boolean hasSeqno(); - /** - *
-     * @required
-     * 
- * - * required int64 seqno = 2; - * @return The seqno. - */ - long getSeqno(); - - /** - *
-     * @required
-     * 
- * - * required bytes data = 3; - * @return Whether the data field is set. - */ - boolean hasData(); - /** - *
-     * @required
-     * 
- * - * required bytes data = 3; - * @return The data. - */ - com.google.protobuf.ByteString getData(); - } - /** - * Protobuf type {@code signalservice.SharedConfigMessage} - */ - public static final class SharedConfigMessage extends - com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:signalservice.SharedConfigMessage) - SharedConfigMessageOrBuilder { - private static final long serialVersionUID = 0L; - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - SharedConfigMessage.class.getName()); - } - // Use SharedConfigMessage.newBuilder() to construct. - private SharedConfigMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - private SharedConfigMessage() { - kind_ = 1; - data_ = com.google.protobuf.ByteString.EMPTY; - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.class, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder.class); - } - - /** - * Protobuf enum {@code signalservice.SharedConfigMessage.Kind} - */ - public enum Kind - implements com.google.protobuf.ProtocolMessageEnum { /** - * USER_PROFILE = 1; + * repeated uint32 sdpMLineIndexes = 3; + * @return This builder for chaining. */ - USER_PROFILE(1), + public Builder clearSdpMLineIndexes() { + sdpMLineIndexes_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + + private com.google.protobuf.LazyStringArrayList sdpMids_ = + com.google.protobuf.LazyStringArrayList.emptyList(); + private void ensureSdpMidsIsMutable() { + if (!sdpMids_.isModifiable()) { + sdpMids_ = new com.google.protobuf.LazyStringArrayList(sdpMids_); + } + bitField0_ |= 0x00000008; + } /** - * CONTACTS = 2; + * repeated string sdpMids = 4; + * @return A list containing the sdpMids. */ - CONTACTS(2), + public com.google.protobuf.ProtocolStringList + getSdpMidsList() { + sdpMids_.makeImmutable(); + return sdpMids_; + } /** - * CONVO_INFO_VOLATILE = 3; + * repeated string sdpMids = 4; + * @return The count of sdpMids. */ - CONVO_INFO_VOLATILE(3), + public int getSdpMidsCount() { + return sdpMids_.size(); + } /** - * GROUPS = 4; + * repeated string sdpMids = 4; + * @param index The index of the element to return. + * @return The sdpMids at the given index. */ - GROUPS(4), + public java.lang.String getSdpMids(int index) { + return sdpMids_.get(index); + } /** - * CLOSED_GROUP_INFO = 5; + * repeated string sdpMids = 4; + * @param index The index of the value to return. + * @return The bytes of the sdpMids at the given index. */ - CLOSED_GROUP_INFO(5), + public com.google.protobuf.ByteString + getSdpMidsBytes(int index) { + return sdpMids_.getByteString(index); + } /** - * CLOSED_GROUP_MEMBERS = 6; + * repeated string sdpMids = 4; + * @param index The index to set the value at. + * @param value The sdpMids to set. + * @return This builder for chaining. */ - CLOSED_GROUP_MEMBERS(6), + public Builder setSdpMids( + int index, java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + ensureSdpMidsIsMutable(); + sdpMids_.set(index, value); + bitField0_ |= 0x00000008; + onChanged(); + return this; + } /** - * ENCRYPTION_KEYS = 7; + * repeated string sdpMids = 4; + * @param value The sdpMids to add. + * @return This builder for chaining. */ - ENCRYPTION_KEYS(7), - ; - - static { - com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( - com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, - /* major= */ 4, - /* minor= */ 29, - /* patch= */ 3, - /* suffix= */ "", - Kind.class.getName()); + public Builder addSdpMids( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + ensureSdpMidsIsMutable(); + sdpMids_.add(value); + bitField0_ |= 0x00000008; + onChanged(); + return this; } /** - * USER_PROFILE = 1; + * repeated string sdpMids = 4; + * @param values The sdpMids to add. + * @return This builder for chaining. */ - public static final int USER_PROFILE_VALUE = 1; + public Builder addAllSdpMids( + java.lang.Iterable values) { + ensureSdpMidsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, sdpMids_); + bitField0_ |= 0x00000008; + onChanged(); + return this; + } /** - * CONTACTS = 2; + * repeated string sdpMids = 4; + * @return This builder for chaining. */ - public static final int CONTACTS_VALUE = 2; + public Builder clearSdpMids() { + sdpMids_ = + com.google.protobuf.LazyStringArrayList.emptyList(); + bitField0_ = (bitField0_ & ~0x00000008);; + onChanged(); + return this; + } /** - * CONVO_INFO_VOLATILE = 3; + * repeated string sdpMids = 4; + * @param value The bytes of the sdpMids to add. + * @return This builder for chaining. */ - public static final int CONVO_INFO_VOLATILE_VALUE = 3; + public Builder addSdpMidsBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + ensureSdpMidsIsMutable(); + sdpMids_.add(value); + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + + private java.lang.Object uuid_ = ""; /** - * GROUPS = 4; + *
+       * @required
+       * 
+ * + * required string uuid = 5; + * @return Whether the uuid field is set. */ - public static final int GROUPS_VALUE = 4; + public boolean hasUuid() { + return ((bitField0_ & 0x00000010) != 0); + } /** - * CLOSED_GROUP_INFO = 5; + *
+       * @required
+       * 
+ * + * required string uuid = 5; + * @return The uuid. */ - public static final int CLOSED_GROUP_INFO_VALUE = 5; + public java.lang.String getUuid() { + java.lang.Object ref = uuid_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + uuid_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } /** - * CLOSED_GROUP_MEMBERS = 6; + *
+       * @required
+       * 
+ * + * required string uuid = 5; + * @return The bytes for uuid. */ - public static final int CLOSED_GROUP_MEMBERS_VALUE = 6; + public com.google.protobuf.ByteString + getUuidBytes() { + java.lang.Object ref = uuid_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + uuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } /** - * ENCRYPTION_KEYS = 7; + *
+       * @required
+       * 
+ * + * required string uuid = 5; + * @param value The uuid to set. + * @return This builder for chaining. */ - public static final int ENCRYPTION_KEYS_VALUE = 7; - - - public final int getNumber() { - return value; + public Builder setUuid( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + uuid_ = value; + bitField0_ |= 0x00000010; + onChanged(); + return this; } - /** - * @param value The numeric wire value of the corresponding enum entry. - * @return The enum associated with the given numeric wire value. - * @deprecated Use {@link #forNumber(int)} instead. + *
+       * @required
+       * 
+ * + * required string uuid = 5; + * @return This builder for chaining. */ - @java.lang.Deprecated - public static Kind valueOf(int value) { - return forNumber(value); + public Builder clearUuid() { + uuid_ = getDefaultInstance().getUuid(); + bitField0_ = (bitField0_ & ~0x00000010); + onChanged(); + return this; } - /** - * @param value The numeric wire value of the corresponding enum entry. - * @return The enum associated with the given numeric wire value. + *
+       * @required
+       * 
+ * + * required string uuid = 5; + * @param value The bytes for uuid to set. + * @return This builder for chaining. */ - public static Kind forNumber(int value) { - switch (value) { - case 1: return USER_PROFILE; - case 2: return CONTACTS; - case 3: return CONVO_INFO_VOLATILE; - case 4: return GROUPS; - case 5: return CLOSED_GROUP_INFO; - case 6: return CLOSED_GROUP_MEMBERS; - case 7: return ENCRYPTION_KEYS; - default: return null; - } + public Builder setUuidBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + uuid_ = value; + bitField0_ |= 0x00000010; + onChanged(); + return this; } - public static com.google.protobuf.Internal.EnumLiteMap - internalGetValueMap() { - return internalValueMap; - } - private static final com.google.protobuf.Internal.EnumLiteMap< - Kind> internalValueMap = - new com.google.protobuf.Internal.EnumLiteMap() { - public Kind findValueByNumber(int number) { - return Kind.forNumber(number); - } - }; + // @@protoc_insertion_point(builder_scope:signalservice.CallMessage) + } - public final com.google.protobuf.Descriptors.EnumValueDescriptor - getValueDescriptor() { - return getDescriptor().getValues().get(ordinal()); - } - public final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptorForType() { - return getDescriptor(); - } - public static final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDescriptor().getEnumTypes().get(0); - } + // @@protoc_insertion_point(class_scope:signalservice.CallMessage) + private static final org.session.libsignal.protos.SignalServiceProtos.CallMessage DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.CallMessage(); + } - private static final Kind[] VALUES = values(); + public static org.session.libsignal.protos.SignalServiceProtos.CallMessage getDefaultInstance() { + return DEFAULT_INSTANCE; + } - public static Kind valueOf( - com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException( - "EnumValueDescriptor is not for this type."); + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public CallMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); } - return VALUES[desc.getIndex()]; + return builder.buildPartial(); } + }; - private final int value; + public static com.google.protobuf.Parser parser() { + return PARSER; + } - private Kind(int value) { - this.value = value; - } + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } - // @@protoc_insertion_point(enum_scope:signalservice.SharedConfigMessage.Kind) + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.CallMessage getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - private int bitField0_; - public static final int KIND_FIELD_NUMBER = 1; - private int kind_ = 1; + } + + public interface MessageRequestResponseOrBuilder extends + // @@protoc_insertion_point(interface_extends:signalservice.MessageRequestResponse) + com.google.protobuf.MessageOrBuilder { + /** *
      * @required
      * 
* - * required .signalservice.SharedConfigMessage.Kind kind = 1; - * @return Whether the kind field is set. + * required bool isApproved = 1; + * @return Whether the isApproved field is set. */ - @java.lang.Override public boolean hasKind() { - return ((bitField0_ & 0x00000001) != 0); - } + boolean hasIsApproved(); /** *
      * @required
      * 
* - * required .signalservice.SharedConfigMessage.Kind kind = 1; - * @return The kind. + * required bool isApproved = 1; + * @return The isApproved. + */ + boolean getIsApproved(); + + /** + * optional bytes profileKey = 2; + * @return Whether the profileKey field is set. + */ + boolean hasProfileKey(); + /** + * optional bytes profileKey = 2; + * @return The profileKey. + */ + com.google.protobuf.ByteString getProfileKey(); + + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + * @return Whether the profile field is set. + */ + boolean hasProfile(); + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + * @return The profile. + */ + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile(); + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; */ - @java.lang.Override public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind getKind() { - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind result = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind.forNumber(kind_); - return result == null ? org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind.USER_PROFILE : result; + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder(); + } + /** + * Protobuf type {@code signalservice.MessageRequestResponse} + */ + public static final class MessageRequestResponse extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:signalservice.MessageRequestResponse) + MessageRequestResponseOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 29, + /* patch= */ 3, + /* suffix= */ "", + MessageRequestResponse.class.getName()); + } + // Use MessageRequestResponse.newBuilder() to construct. + private MessageRequestResponse(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private MessageRequestResponse() { + profileKey_ = com.google.protobuf.ByteString.EMPTY; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.class, org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.Builder.class); } - public static final int SEQNO_FIELD_NUMBER = 2; - private long seqno_ = 0L; + private int bitField0_; + public static final int ISAPPROVED_FIELD_NUMBER = 1; + private boolean isApproved_ = false; /** *
      * @required
      * 
* - * required int64 seqno = 2; - * @return Whether the seqno field is set. + * required bool isApproved = 1; + * @return Whether the isApproved field is set. */ @java.lang.Override - public boolean hasSeqno() { - return ((bitField0_ & 0x00000002) != 0); + public boolean hasIsApproved() { + return ((bitField0_ & 0x00000001) != 0); } /** *
      * @required
      * 
* - * required int64 seqno = 2; - * @return The seqno. + * required bool isApproved = 1; + * @return The isApproved. */ @java.lang.Override - public long getSeqno() { - return seqno_; + public boolean getIsApproved() { + return isApproved_; } - public static final int DATA_FIELD_NUMBER = 3; - private com.google.protobuf.ByteString data_ = com.google.protobuf.ByteString.EMPTY; + public static final int PROFILEKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; /** - *
-     * @required
-     * 
- * - * required bytes data = 3; - * @return Whether the data field is set. + * optional bytes profileKey = 2; + * @return Whether the profileKey field is set. + */ + @java.lang.Override + public boolean hasProfileKey() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional bytes profileKey = 2; + * @return The profileKey. + */ + @java.lang.Override + public com.google.protobuf.ByteString getProfileKey() { + return profileKey_; + } + + public static final int PROFILE_FIELD_NUMBER = 3; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + * @return Whether the profile field is set. */ @java.lang.Override - public boolean hasData() { + public boolean hasProfile() { return ((bitField0_ & 0x00000004) != 0); } /** - *
-     * @required
-     * 
- * - * required bytes data = 3; - * @return The data. + * optional .signalservice.DataMessage.LokiProfile profile = 3; + * @return The profile. + */ + @java.lang.Override + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { + return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; */ @java.lang.Override - public com.google.protobuf.ByteString getData() { - return data_; + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { + return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; } private byte memoizedIsInitialized = -1; @@ -37673,15 +26174,7 @@ public final boolean isInitialized() { if (isInitialized == 1) return true; if (isInitialized == 0) return false; - if (!hasKind()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasSeqno()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasData()) { + if (!hasIsApproved()) { memoizedIsInitialized = 0; return false; } @@ -37693,13 +26186,13 @@ public final boolean isInitialized() { public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { if (((bitField0_ & 0x00000001) != 0)) { - output.writeEnum(1, kind_); + output.writeBool(1, isApproved_); } if (((bitField0_ & 0x00000002) != 0)) { - output.writeInt64(2, seqno_); + output.writeBytes(2, profileKey_); } if (((bitField0_ & 0x00000004) != 0)) { - output.writeBytes(3, data_); + output.writeMessage(3, getProfile()); } getUnknownFields().writeTo(output); } @@ -37712,15 +26205,15 @@ public int getSerializedSize() { size = 0; if (((bitField0_ & 0x00000001) != 0)) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(1, kind_); + .computeBoolSize(1, isApproved_); } if (((bitField0_ & 0x00000002) != 0)) { size += com.google.protobuf.CodedOutputStream - .computeInt64Size(2, seqno_); + .computeBytesSize(2, profileKey_); } if (((bitField0_ & 0x00000004) != 0)) { size += com.google.protobuf.CodedOutputStream - .computeBytesSize(3, data_); + .computeMessageSize(3, getProfile()); } size += getUnknownFields().getSerializedSize(); memoizedSize = size; @@ -37732,24 +26225,25 @@ public boolean equals(final java.lang.Object obj) { if (obj == this) { return true; } - if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage)) { + if (!(obj instanceof org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse)) { return super.equals(obj); } - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage other = (org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage) obj; + org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse other = (org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse) obj; - if (hasKind() != other.hasKind()) return false; - if (hasKind()) { - if (kind_ != other.kind_) return false; + if (hasIsApproved() != other.hasIsApproved()) return false; + if (hasIsApproved()) { + if (getIsApproved() + != other.getIsApproved()) return false; } - if (hasSeqno() != other.hasSeqno()) return false; - if (hasSeqno()) { - if (getSeqno() - != other.getSeqno()) return false; + if (hasProfileKey() != other.hasProfileKey()) return false; + if (hasProfileKey()) { + if (!getProfileKey() + .equals(other.getProfileKey())) return false; } - if (hasData() != other.hasData()) return false; - if (hasData()) { - if (!getData() - .equals(other.getData())) return false; + if (hasProfile() != other.hasProfile()) return false; + if (hasProfile()) { + if (!getProfile() + .equals(other.getProfile())) return false; } if (!getUnknownFields().equals(other.getUnknownFields())) return false; return true; @@ -37762,62 +26256,62 @@ public int hashCode() { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasKind()) { - hash = (37 * hash) + KIND_FIELD_NUMBER; - hash = (53 * hash) + kind_; + if (hasIsApproved()) { + hash = (37 * hash) + ISAPPROVED_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getIsApproved()); } - if (hasSeqno()) { - hash = (37 * hash) + SEQNO_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getSeqno()); + if (hasProfileKey()) { + hash = (37 * hash) + PROFILEKEY_FIELD_NUMBER; + hash = (53 * hash) + getProfileKey().hashCode(); } - if (hasData()) { - hash = (37 * hash) + DATA_FIELD_NUMBER; - hash = (53 * hash) + getData().hashCode(); + if (hasProfile()) { + hash = (37 * hash) + PROFILE_FIELD_NUMBER; + hash = (53 * hash) + getProfile().hashCode(); } hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom(byte[] data) + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom(java.io.InputStream input) + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -37825,26 +26319,26 @@ public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessa .parseWithIOException(PARSER, input, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseDelimitedFrom(java.io.InputStream input) + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessage .parseDelimitedWithIOException(PARSER, input); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseDelimitedFrom( + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessage .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -37857,7 +26351,7 @@ public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessa public static Builder newBuilder() { return DEFAULT_INSTANCE.toBuilder(); } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage prototype) { + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse prototype) { return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); } @java.lang.Override @@ -37873,59 +26367,69 @@ protected Builder newBuilderForType( return builder; } /** - * Protobuf type {@code signalservice.SharedConfigMessage} + * Protobuf type {@code signalservice.MessageRequestResponse} */ public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements - // @@protoc_insertion_point(builder_implements:signalservice.SharedConfigMessage) - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBuilder { + // @@protoc_insertion_point(builder_implements:signalservice.MessageRequestResponse) + org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponseOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_descriptor; + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_descriptor; } @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_fieldAccessorTable + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_fieldAccessorTable .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.class, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder.class); + org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.class, org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.Builder.class); } - // Construct using org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.newBuilder() + // Construct using org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.newBuilder() private Builder() { - + maybeForceBuilderInitialization(); } private Builder( com.google.protobuf.GeneratedMessage.BuilderParent parent) { super(parent); - + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage + .alwaysUseFieldBuilders) { + getProfileFieldBuilder(); + } } @java.lang.Override public Builder clear() { super.clear(); bitField0_ = 0; - kind_ = 1; - seqno_ = 0L; - data_ = com.google.protobuf.ByteString.EMPTY; + isApproved_ = false; + profileKey_ = com.google.protobuf.ByteString.EMPTY; + profile_ = null; + if (profileBuilder_ != null) { + profileBuilder_.dispose(); + profileBuilder_ = null; + } return this; } @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_descriptor; + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_MessageRequestResponse_descriptor; } @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance(); + public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.getDefaultInstance(); } @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage build() { - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage result = buildPartial(); + public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse build() { + org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } @@ -37933,26 +26437,28 @@ public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage buil } @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage result = new org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage(this); + public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse result = new org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse(this); if (bitField0_ != 0) { buildPartial0(result); } onBuilt(); return result; } - private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage result) { + private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse result) { int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) != 0)) { - result.kind_ = kind_; + result.isApproved_ = isApproved_; to_bitField0_ |= 0x00000001; } if (((from_bitField0_ & 0x00000002) != 0)) { - result.seqno_ = seqno_; + result.profileKey_ = profileKey_; to_bitField0_ |= 0x00000002; } if (((from_bitField0_ & 0x00000004) != 0)) { - result.data_ = data_; + result.profile_ = profileBuilder_ == null + ? profile_ + : profileBuilder_.build(); to_bitField0_ |= 0x00000004; } result.bitField0_ |= to_bitField0_; @@ -37960,39 +26466,33 @@ private void buildPartial0(org.session.libsignal.protos.SignalServiceProtos.Shar @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage)other); + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance()) return this; - if (other.hasKind()) { - setKind(other.getKind()); + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse.getDefaultInstance()) return this; + if (other.hasIsApproved()) { + setIsApproved(other.getIsApproved()); } - if (other.hasSeqno()) { - setSeqno(other.getSeqno()); + if (other.hasProfileKey()) { + setProfileKey(other.getProfileKey()); } - if (other.hasData()) { - setData(other.getData()); + if (other.hasProfile()) { + mergeProfile(other.getProfile()); } this.mergeUnknownFields(other.getUnknownFields()); onChanged(); return this; } - @java.lang.Override - public final boolean isInitialized() { - if (!hasKind()) { - return false; - } - if (!hasSeqno()) { - return false; - } - if (!hasData()) { + @java.lang.Override + public final boolean isInitialized() { + if (!hasIsApproved()) { return false; } return true; @@ -38015,24 +26515,19 @@ public Builder mergeFrom( done = true; break; case 8: { - int tmpRaw = input.readEnum(); - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind tmpValue = - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind.forNumber(tmpRaw); - if (tmpValue == null) { - mergeUnknownVarintField(1, tmpRaw); - } else { - kind_ = tmpRaw; - bitField0_ |= 0x00000001; - } + isApproved_ = input.readBool(); + bitField0_ |= 0x00000001; break; } // case 8 - case 16: { - seqno_ = input.readInt64(); + case 18: { + profileKey_ = input.readBytes(); bitField0_ |= 0x00000002; break; - } // case 16 + } // case 18 case 26: { - data_ = input.readBytes(); + input.readMessage( + getProfileFieldBuilder().getBuilder(), + extensionRegistry); bitField0_ |= 0x00000004; break; } // case 26 @@ -38053,16 +26548,17 @@ public Builder mergeFrom( } private int bitField0_; - private int kind_ = 1; + private boolean isApproved_ ; /** *
        * @required
        * 
* - * required .signalservice.SharedConfigMessage.Kind kind = 1; - * @return Whether the kind field is set. + * required bool isApproved = 1; + * @return Whether the isApproved field is set. */ - @java.lang.Override public boolean hasKind() { + @java.lang.Override + public boolean hasIsApproved() { return ((bitField0_ & 0x00000001) != 0); } /** @@ -38070,29 +26566,26 @@ public Builder mergeFrom( * @required * * - * required .signalservice.SharedConfigMessage.Kind kind = 1; - * @return The kind. + * required bool isApproved = 1; + * @return The isApproved. */ @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind getKind() { - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind result = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind.forNumber(kind_); - return result == null ? org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind.USER_PROFILE : result; + public boolean getIsApproved() { + return isApproved_; } /** *
        * @required
        * 
* - * required .signalservice.SharedConfigMessage.Kind kind = 1; - * @param value The kind to set. + * required bool isApproved = 1; + * @param value The isApproved to set. * @return This builder for chaining. */ - public Builder setKind(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind value) { - if (value == null) { - throw new NullPointerException(); - } + public Builder setIsApproved(boolean value) { + + isApproved_ = value; bitField0_ |= 0x00000001; - kind_ = value.getNumber(); onChanged(); return this; } @@ -38101,145 +26594,194 @@ public Builder setKind(org.session.libsignal.protos.SignalServiceProtos.SharedCo * @required * * - * required .signalservice.SharedConfigMessage.Kind kind = 1; + * required bool isApproved = 1; * @return This builder for chaining. */ - public Builder clearKind() { + public Builder clearIsApproved() { bitField0_ = (bitField0_ & ~0x00000001); - kind_ = 1; + isApproved_ = false; onChanged(); return this; } - private long seqno_ ; + private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; /** - *
-       * @required
-       * 
- * - * required int64 seqno = 2; - * @return Whether the seqno field is set. + * optional bytes profileKey = 2; + * @return Whether the profileKey field is set. */ @java.lang.Override - public boolean hasSeqno() { + public boolean hasProfileKey() { return ((bitField0_ & 0x00000002) != 0); } /** - *
-       * @required
-       * 
- * - * required int64 seqno = 2; - * @return The seqno. + * optional bytes profileKey = 2; + * @return The profileKey. */ @java.lang.Override - public long getSeqno() { - return seqno_; + public com.google.protobuf.ByteString getProfileKey() { + return profileKey_; } /** - *
-       * @required
-       * 
- * - * required int64 seqno = 2; - * @param value The seqno to set. + * optional bytes profileKey = 2; + * @param value The profileKey to set. * @return This builder for chaining. */ - public Builder setSeqno(long value) { - - seqno_ = value; + public Builder setProfileKey(com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + profileKey_ = value; bitField0_ |= 0x00000002; onChanged(); return this; } /** - *
-       * @required
-       * 
- * - * required int64 seqno = 2; + * optional bytes profileKey = 2; * @return This builder for chaining. */ - public Builder clearSeqno() { + public Builder clearProfileKey() { bitField0_ = (bitField0_ & ~0x00000002); - seqno_ = 0L; + profileKey_ = getDefaultInstance().getProfileKey(); onChanged(); return this; } - private com.google.protobuf.ByteString data_ = com.google.protobuf.ByteString.EMPTY; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> profileBuilder_; /** - *
-       * @required
-       * 
- * - * required bytes data = 3; - * @return Whether the data field is set. + * optional .signalservice.DataMessage.LokiProfile profile = 3; + * @return Whether the profile field is set. */ - @java.lang.Override - public boolean hasData() { + public boolean hasProfile() { return ((bitField0_ & 0x00000004) != 0); } /** - *
-       * @required
-       * 
- * - * required bytes data = 3; - * @return The data. + * optional .signalservice.DataMessage.LokiProfile profile = 3; + * @return The profile. */ - @java.lang.Override - public com.google.protobuf.ByteString getData() { - return data_; + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { + if (profileBuilder_ == null) { + return profile_ == null ? org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; + } else { + return profileBuilder_.getMessage(); + } } /** - *
-       * @required
-       * 
- * - * required bytes data = 3; - * @param value The data to set. - * @return This builder for chaining. + * optional .signalservice.DataMessage.LokiProfile profile = 3; */ - public Builder setData(com.google.protobuf.ByteString value) { - if (value == null) { throw new NullPointerException(); } - data_ = value; + public Builder setProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { + if (profileBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + profile_ = value; + } else { + profileBuilder_.setMessage(value); + } bitField0_ |= 0x00000004; onChanged(); return this; } /** - *
-       * @required
-       * 
- * - * required bytes data = 3; - * @return This builder for chaining. + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder setProfile( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder builderForValue) { + if (profileBuilder_ == null) { + profile_ = builderForValue.build(); + } else { + profileBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder mergeProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { + if (profileBuilder_ == null) { + if (((bitField0_ & 0x00000004) != 0) && + profile_ != null && + profile_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance()) { + getProfileBuilder().mergeFrom(value); + } else { + profile_ = value; + } + } else { + profileBuilder_.mergeFrom(value); + } + if (profile_ != null) { + bitField0_ |= 0x00000004; + onChanged(); + } + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; */ - public Builder clearData() { + public Builder clearProfile() { bitField0_ = (bitField0_ & ~0x00000004); - data_ = getDefaultInstance().getData(); + profile_ = null; + if (profileBuilder_ != null) { + profileBuilder_.dispose(); + profileBuilder_ = null; + } onChanged(); return this; } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder getProfileBuilder() { + bitField0_ |= 0x00000004; + onChanged(); + return getProfileFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { + if (profileBuilder_ != null) { + return profileBuilder_.getMessageOrBuilder(); + } else { + return profile_ == null ? + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance() : profile_; + } + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> + getProfileFieldBuilder() { + if (profileBuilder_ == null) { + profileBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder>( + getProfile(), + getParentForChildren(), + isClean()); + profile_ = null; + } + return profileBuilder_; + } - // @@protoc_insertion_point(builder_scope:signalservice.SharedConfigMessage) + // @@protoc_insertion_point(builder_scope:signalservice.MessageRequestResponse) } - // @@protoc_insertion_point(class_scope:signalservice.SharedConfigMessage) - private static final org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage DEFAULT_INSTANCE; + // @@protoc_insertion_point(class_scope:signalservice.MessageRequestResponse) + private static final org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse DEFAULT_INSTANCE; static { - DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage(); + DEFAULT_INSTANCE = new org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse(); } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage getDefaultInstance() { + public static org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse getDefaultInstance() { return DEFAULT_INSTANCE; } - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override - public SharedConfigMessage parsePartialFrom( + public MessageRequestResponse parsePartialFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { @@ -38258,17 +26800,17 @@ public SharedConfigMessage parsePartialFrom( } }; - public static com.google.protobuf.Parser parser() { + public static com.google.protobuf.Parser parser() { return PARSER; } @java.lang.Override - public com.google.protobuf.Parser getParserForType() { + public com.google.protobuf.Parser getParserForType() { return PARSER; } @java.lang.Override - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage getDefaultInstanceForType() { + public org.session.libsignal.protos.SignalServiceProtos.MessageRequestResponse getDefaultInstanceForType() { return DEFAULT_INSTANCE; } @@ -41185,71 +29727,21 @@ public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getDef private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_DataMessage_GroupUpdateMemberLeftNotificationMessage_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_DataMessage_Reaction_descriptor; private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_GroupDeleteMessage_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_GroupMemberLeftMessage_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_GroupInviteMessage_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_GroupInviteMessage_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_GroupPromoteMessage_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_CallMessage_descriptor; private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_CallMessage_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_ConfigurationMessage_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_ConfigurationMessage_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_ConfigurationMessage_ClosedGroup_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_ConfigurationMessage_ClosedGroup_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_ConfigurationMessage_Contact_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_ConfigurationMessage_Contact_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_MessageRequestResponse_descriptor; private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_MessageRequestResponse_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_SharedConfigMessage_descriptor; - private static final - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_SharedConfigMessage_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_ReceiptMessage_descriptor; private static final @@ -41279,31 +29771,28 @@ public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getDef "\002(\004\0223\n\006action\030\002 \002(\0162#.signalservice.Typi" + "ngMessage.Action\"\"\n\006Action\022\013\n\007STARTED\020\000\022" + "\013\n\007STOPPED\020\001\"2\n\rUnsendRequest\022\021\n\ttimesta" + - "mp\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\"\347\005\n\007Content\022/\n\013" + + "mp\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\"\357\004\n\007Content\022/\n\013" + "dataMessage\030\001 \001(\0132\032.signalservice.DataMe" + "ssage\022/\n\013callMessage\030\003 \001(\0132\032.signalservi" + "ce.CallMessage\0225\n\016receiptMessage\030\005 \001(\0132\035" + ".signalservice.ReceiptMessage\0223\n\rtypingM" + "essage\030\006 \001(\0132\034.signalservice.TypingMessa" + - "ge\022A\n\024configurationMessage\030\007 \001(\0132#.signa" + - "lservice.ConfigurationMessage\022M\n\032dataExt" + - "ractionNotification\030\010 \001(\0132).signalservic" + - "e.DataExtractionNotification\0223\n\runsendRe" + - "quest\030\t \001(\0132\034.signalservice.UnsendReques" + - "t\022E\n\026messageRequestResponse\030\n \001(\0132%.sign" + - "alservice.MessageRequestResponse\022?\n\023shar" + - "edConfigMessage\030\013 \001(\0132\".signalservice.Sh" + - "aredConfigMessage\022=\n\016expirationType\030\014 \001(" + - "\0162%.signalservice.Content.ExpirationType" + - "\022\027\n\017expirationTimer\030\r \001(\r\022\024\n\014sigTimestam" + - "p\030\017 \001(\004\"K\n\016ExpirationType\022\013\n\007UNKNOWN\020\000\022\025" + - "\n\021DELETE_AFTER_READ\020\001\022\025\n\021DELETE_AFTER_SE" + - "ND\020\002J\004\010\016\020\017\"0\n\007KeyPair\022\021\n\tpublicKey\030\001 \002(\014" + + "ge\022M\n\032dataExtractionNotification\030\010 \001(\0132)" + + ".signalservice.DataExtractionNotificatio" + + "n\0223\n\runsendRequest\030\t \001(\0132\034.signalservice" + + ".UnsendRequest\022E\n\026messageRequestResponse" + + "\030\n \001(\0132%.signalservice.MessageRequestRes" + + "ponse\022=\n\016expirationType\030\014 \001(\0162%.signalse" + + "rvice.Content.ExpirationType\022\027\n\017expirati" + + "onTimer\030\r \001(\r\022\024\n\014sigTimestamp\030\017 \001(\004\"K\n\016E" + + "xpirationType\022\013\n\007UNKNOWN\020\000\022\025\n\021DELETE_AFT" + + "ER_READ\020\001\022\025\n\021DELETE_AFTER_SEND\020\002J\004\010\016\020\017J\004" + + "\010\013\020\014J\004\010\007\020\010\"0\n\007KeyPair\022\021\n\tpublicKey\030\001 \002(\014" + "\022\022\n\nprivateKey\030\002 \002(\014\"\226\001\n\032DataExtractionN" + "otification\022<\n\004type\030\001 \002(\0162..signalservic" + "e.DataExtractionNotification.Type\022\021\n\ttim" + "estamp\030\002 \001(\004\"\'\n\004Type\022\016\n\nSCREENSHOT\020\001\022\017\n\013" + - "MEDIA_SAVED\020\002\"\342\034\n\013DataMessage\022\014\n\004body\030\001 " + + "MEDIA_SAVED\020\002\"\211\027\n\013DataMessage\022\014\n\004body\030\001 " + "\001(\t\0225\n\013attachments\030\002 \003(\0132 .signalservice" + ".AttachmentPointer\022\r\n\005flags\030\004 \001(\r\022\023\n\013exp" + "ireTimer\030\005 \001(\r\022\022\n\nprofileKey\030\006 \001(\014\022\021\n\tti" + @@ -41314,134 +29803,89 @@ public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getDef "e.Reaction\0227\n\007profile\030e \001(\0132&.signalserv" + "ice.DataMessage.LokiProfile\022K\n\023openGroup" + "Invitation\030f \001(\0132..signalservice.DataMes" + - "sage.OpenGroupInvitation\022W\n\031closedGroupC" + - "ontrolMessage\030h \001(\01324.signalservice.Data" + - "Message.ClosedGroupControlMessage\022\022\n\nsyn" + - "cTarget\030i \001(\t\022&\n\036blocksCommunityMessageR" + - "equests\030j \001(\010\022I\n\022groupUpdateMessage\030x \001(" + - "\0132-.signalservice.DataMessage.GroupUpdat" + - "eMessage\032\225\002\n\005Quote\022\n\n\002id\030\001 \002(\004\022\016\n\006author" + - "\030\002 \002(\t\022\014\n\004text\030\003 \001(\t\022F\n\013attachments\030\004 \003(" + - "\01321.signalservice.DataMessage.Quote.Quot" + - "edAttachment\032\231\001\n\020QuotedAttachment\022\023\n\013con" + - "tentType\030\001 \001(\t\022\020\n\010fileName\030\002 \001(\t\0223\n\tthum" + - "bnail\030\003 \001(\0132 .signalservice.AttachmentPo" + - "inter\022\r\n\005flags\030\004 \001(\r\"\032\n\005Flags\022\021\n\rVOICE_M" + - "ESSAGE\020\001\032V\n\007Preview\022\013\n\003url\030\001 \002(\t\022\r\n\005titl" + - "e\030\002 \001(\t\022/\n\005image\030\003 \001(\0132 .signalservice.A" + - "ttachmentPointer\032:\n\013LokiProfile\022\023\n\013displ" + - "ayName\030\001 \001(\t\022\026\n\016profilePicture\030\002 \001(\t\0320\n\023" + - "OpenGroupInvitation\022\013\n\003url\030\001 \002(\t\022\014\n\004name" + - "\030\003 \002(\t\032\316\005\n\022GroupUpdateMessage\022J\n\rinviteM" + - "essage\030\001 \001(\01323.signalservice.DataMessage" + - ".GroupUpdateInviteMessage\022R\n\021infoChangeM" + - "essage\030\002 \001(\01327.signalservice.DataMessage" + - ".GroupUpdateInfoChangeMessage\022V\n\023memberC" + - "hangeMessage\030\003 \001(\01329.signalservice.DataM" + - "essage.GroupUpdateMemberChangeMessage\022L\n" + - "\016promoteMessage\030\004 \001(\01324.signalservice.Da" + - "taMessage.GroupUpdatePromoteMessage\022R\n\021m" + - "emberLeftMessage\030\005 \001(\01327.signalservice.D" + - "ataMessage.GroupUpdateMemberLeftMessage\022" + - "S\n\016inviteResponse\030\006 \001(\0132;.signalservice." + - "DataMessage.GroupUpdateInviteResponseMes" + - "sage\022]\n\023deleteMemberContent\030\007 \001(\0132@.sign" + - "alservice.DataMessage.GroupUpdateDeleteM" + - "emberContentMessage\022j\n\035memberLeftNotific" + - "ationMessage\030\010 \001(\0132C.signalservice.DataM" + - "essage.GroupUpdateMemberLeftNotification" + - "Message\032p\n\030GroupUpdateInviteMessage\022\026\n\016g" + - "roupSessionId\030\001 \002(\t\022\014\n\004name\030\002 \002(\t\022\026\n\016mem" + - "berAuthData\030\003 \002(\014\022\026\n\016adminSignature\030\004 \002(" + - "\014\032L\n\030GroupUpdateDeleteMessage\022\030\n\020memberS" + - "essionIds\030\001 \003(\t\022\026\n\016adminSignature\030\002 \002(\014\032" + - "D\n\031GroupUpdatePromoteMessage\022\031\n\021groupIde" + - "ntitySeed\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\032\353\001\n\034GroupU" + - "pdateInfoChangeMessage\022J\n\004type\030\001 \002(\0162<.s" + - "ignalservice.DataMessage.GroupUpdateInfo" + - "ChangeMessage.Type\022\023\n\013updatedName\030\002 \001(\t\022" + - "\031\n\021updatedExpiration\030\003 \001(\r\022\026\n\016adminSigna" + - "ture\030\004 \002(\014\"7\n\004Type\022\010\n\004NAME\020\001\022\n\n\006AVATAR\020\002" + - "\022\031\n\025DISAPPEARING_MESSAGES\020\003\032\345\001\n\036GroupUpd" + - "ateMemberChangeMessage\022L\n\004type\030\001 \002(\0162>.s" + - "ignalservice.DataMessage.GroupUpdateMemb" + - "erChangeMessage.Type\022\030\n\020memberSessionIds" + - "\030\002 \003(\t\022\025\n\rhistoryShared\030\003 \001(\010\022\026\n\016adminSi" + - "gnature\030\004 \002(\014\",\n\004Type\022\t\n\005ADDED\020\001\022\013\n\007REMO" + - "VED\020\002\022\014\n\010PROMOTED\020\003\032\036\n\034GroupUpdateMember" + - "LeftMessage\0326\n GroupUpdateInviteResponse" + - "Message\022\022\n\nisApproved\030\001 \002(\010\032p\n%GroupUpda" + - "teDeleteMemberContentMessage\022\030\n\020memberSe" + - "ssionIds\030\001 \003(\t\022\025\n\rmessageHashes\030\002 \003(\t\022\026\n" + - "\016adminSignature\030\003 \001(\014\032*\n(GroupUpdateMemb" + - "erLeftNotificationMessage\032\203\005\n\031ClosedGrou" + - "pControlMessage\022G\n\004type\030\001 \002(\01629.signalse" + - "rvice.DataMessage.ClosedGroupControlMess" + - "age.Type\022\021\n\tpublicKey\030\002 \001(\014\022\014\n\004name\030\003 \001(" + - "\t\0221\n\021encryptionKeyPair\030\004 \001(\0132\026.signalser" + - "vice.KeyPair\022\017\n\007members\030\005 \003(\014\022\016\n\006admins\030" + - "\006 \003(\014\022U\n\010wrappers\030\007 \003(\0132C.signalservice." + - "DataMessage.ClosedGroupControlMessage.Ke" + - "yPairWrapper\022\027\n\017expirationTimer\030\010 \001(\r\022\030\n" + - "\020memberPrivateKey\030\t \001(\014\022\022\n\nprivateKey\030\n " + - "\001(\014\032=\n\016KeyPairWrapper\022\021\n\tpublicKey\030\001 \002(\014" + - "\022\030\n\020encryptedKeyPair\030\002 \002(\014\"\312\001\n\004Type\022\007\n\003N" + - "EW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022\017\n\013NAME_CH" + - "ANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017MEMBERS_REM" + - "OVED\020\006\022\017\n\013MEMBER_LEFT\020\007\022\n\n\006INVITE\020\t\022\013\n\007P" + - "ROMOTE\020\n\022\020\n\014DELETE_GROUP\020\013\022\023\n\017DELETE_MES" + - "SAGES\020\014\022\026\n\022DELETE_ATTACHMENTS\020\r\032\222\001\n\010Reac" + - "tion\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\r\n\005emoj" + - "i\030\003 \001(\t\022:\n\006action\030\004 \002(\0162*.signalservice." + - "DataMessage.Reaction.Action\"\037\n\006Action\022\t\n" + - "\005REACT\020\000\022\n\n\006REMOVE\020\001\"$\n\005Flags\022\033\n\027EXPIRAT" + - "ION_TIMER_UPDATE\020\002\"B\n\022GroupDeleteMessage" + - "\022\021\n\tpublicKey\030\001 \002(\014\022\031\n\021lastEncryptionKey" + - "\030\002 \002(\014\"\030\n\026GroupMemberLeftMessage\"O\n\022Grou" + - "pInviteMessage\022\021\n\tpublicKey\030\001 \002(\014\022\014\n\004nam" + - "e\030\002 \002(\t\022\030\n\020memberPrivateKey\030\003 \002(\014\"E\n\023Gro" + - "upPromoteMessage\022\021\n\tpublicKey\030\001 \002(\014\022\033\n\023e" + - "ncryptedPrivateKey\030\002 \002(\014\"\352\001\n\013CallMessage" + - "\022-\n\004type\030\001 \002(\0162\037.signalservice.CallMessa" + - "ge.Type\022\014\n\004sdps\030\002 \003(\t\022\027\n\017sdpMLineIndexes" + - "\030\003 \003(\r\022\017\n\007sdpMids\030\004 \003(\t\022\014\n\004uuid\030\005 \002(\t\"f\n" + - "\004Type\022\r\n\tPRE_OFFER\020\006\022\t\n\005OFFER\020\001\022\n\n\006ANSWE" + - "R\020\002\022\026\n\022PROVISIONAL_ANSWER\020\003\022\022\n\016ICE_CANDI" + - "DATES\020\004\022\014\n\010END_CALL\020\005\"\245\004\n\024ConfigurationM" + - "essage\022E\n\014closedGroups\030\001 \003(\0132/.signalser" + - "vice.ConfigurationMessage.ClosedGroup\022\022\n" + - "\nopenGroups\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022\026" + - "\n\016profilePicture\030\004 \001(\t\022\022\n\nprofileKey\030\005 \001" + - "(\014\022=\n\010contacts\030\006 \003(\0132+.signalservice.Con" + - "figurationMessage.Contact\032\233\001\n\013ClosedGrou" + - "p\022\021\n\tpublicKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021en" + - "cryptionKeyPair\030\003 \001(\0132\026.signalservice.Ke" + - "yPair\022\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\022\027" + - "\n\017expirationTimer\030\006 \001(\r\032\223\001\n\007Contact\022\021\n\tp" + - "ublicKey\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016profileP" + - "icture\030\003 \001(\t\022\022\n\nprofileKey\030\004 \001(\014\022\022\n\nisAp" + - "proved\030\005 \001(\010\022\021\n\tisBlocked\030\006 \001(\010\022\024\n\014didAp" + - "proveMe\030\007 \001(\010\"y\n\026MessageRequestResponse\022" + - "\022\n\nisApproved\030\001 \002(\010\022\022\n\nprofileKey\030\002 \001(\014\022" + - "7\n\007profile\030\003 \001(\0132&.signalservice.DataMes" + - "sage.LokiProfile\"\375\001\n\023SharedConfigMessage" + - "\0225\n\004kind\030\001 \002(\0162\'.signalservice.SharedCon" + - "figMessage.Kind\022\r\n\005seqno\030\002 \002(\003\022\014\n\004data\030\003" + - " \002(\014\"\221\001\n\004Kind\022\020\n\014USER_PROFILE\020\001\022\014\n\010CONTA" + - "CTS\020\002\022\027\n\023CONVO_INFO_VOLATILE\020\003\022\n\n\006GROUPS" + - "\020\004\022\025\n\021CLOSED_GROUP_INFO\020\005\022\030\n\024CLOSED_GROU" + - "P_MEMBERS\020\006\022\023\n\017ENCRYPTION_KEYS\020\007\"u\n\016Rece" + - "iptMessage\0220\n\004type\030\001 \002(\0162\".signalservice" + - ".ReceiptMessage.Type\022\021\n\ttimestamp\030\002 \003(\004\"" + - "\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010\n\004READ\020\001\"\354\001\n\021Atta" + - "chmentPointer\022\n\n\002id\030\001 \002(\006\022\023\n\013contentType" + - "\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004size\030\004 \001(\r\022\021\n\tthu" + - "mbnail\030\005 \001(\014\022\016\n\006digest\030\006 \001(\014\022\020\n\010fileName" + - "\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005width\030\t \001(\r\022\016\n\006" + - "height\030\n \001(\r\022\017\n\007caption\030\013 \001(\t\022\013\n\003url\030e \001" + - "(\t\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001B3\n\034org.se" + - "ssion.libsignal.protosB\023SignalServicePro" + - "tos" + "sage.OpenGroupInvitation\022\022\n\nsyncTarget\030i" + + " \001(\t\022&\n\036blocksCommunityMessageRequests\030j" + + " \001(\010\022I\n\022groupUpdateMessage\030x \001(\0132-.signa" + + "lservice.DataMessage.GroupUpdateMessage\032" + + "\225\002\n\005Quote\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\014\n" + + "\004text\030\003 \001(\t\022F\n\013attachments\030\004 \003(\01321.signa" + + "lservice.DataMessage.Quote.QuotedAttachm" + + "ent\032\231\001\n\020QuotedAttachment\022\023\n\013contentType\030" + + "\001 \001(\t\022\020\n\010fileName\030\002 \001(\t\0223\n\tthumbnail\030\003 \001" + + "(\0132 .signalservice.AttachmentPointer\022\r\n\005" + + "flags\030\004 \001(\r\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\032" + + "V\n\007Preview\022\013\n\003url\030\001 \002(\t\022\r\n\005title\030\002 \001(\t\022/" + + "\n\005image\030\003 \001(\0132 .signalservice.Attachment" + + "Pointer\032:\n\013LokiProfile\022\023\n\013displayName\030\001 " + + "\001(\t\022\026\n\016profilePicture\030\002 \001(\t\0320\n\023OpenGroup" + + "Invitation\022\013\n\003url\030\001 \002(\t\022\014\n\004name\030\003 \002(\t\032\316\005" + + "\n\022GroupUpdateMessage\022J\n\rinviteMessage\030\001 " + + "\001(\01323.signalservice.DataMessage.GroupUpd" + + "ateInviteMessage\022R\n\021infoChangeMessage\030\002 " + + "\001(\01327.signalservice.DataMessage.GroupUpd" + + "ateInfoChangeMessage\022V\n\023memberChangeMess" + + "age\030\003 \001(\01329.signalservice.DataMessage.Gr" + + "oupUpdateMemberChangeMessage\022L\n\016promoteM" + + "essage\030\004 \001(\01324.signalservice.DataMessage" + + ".GroupUpdatePromoteMessage\022R\n\021memberLeft" + + "Message\030\005 \001(\01327.signalservice.DataMessag" + + "e.GroupUpdateMemberLeftMessage\022S\n\016invite" + + "Response\030\006 \001(\0132;.signalservice.DataMessa" + + "ge.GroupUpdateInviteResponseMessage\022]\n\023d" + + "eleteMemberContent\030\007 \001(\0132@.signalservice" + + ".DataMessage.GroupUpdateDeleteMemberCont" + + "entMessage\022j\n\035memberLeftNotificationMess" + + "age\030\010 \001(\0132C.signalservice.DataMessage.Gr" + + "oupUpdateMemberLeftNotificationMessage\032p" + + "\n\030GroupUpdateInviteMessage\022\026\n\016groupSessi" + + "onId\030\001 \002(\t\022\014\n\004name\030\002 \002(\t\022\026\n\016memberAuthDa" + + "ta\030\003 \002(\014\022\026\n\016adminSignature\030\004 \002(\014\032L\n\030Grou" + + "pUpdateDeleteMessage\022\030\n\020memberSessionIds" + + "\030\001 \003(\t\022\026\n\016adminSignature\030\002 \002(\014\032D\n\031GroupU" + + "pdatePromoteMessage\022\031\n\021groupIdentitySeed" + + "\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\032\353\001\n\034GroupUpdateInfo" + + "ChangeMessage\022J\n\004type\030\001 \002(\0162<.signalserv" + + "ice.DataMessage.GroupUpdateInfoChangeMes" + + "sage.Type\022\023\n\013updatedName\030\002 \001(\t\022\031\n\021update" + + "dExpiration\030\003 \001(\r\022\026\n\016adminSignature\030\004 \002(" + + "\014\"7\n\004Type\022\010\n\004NAME\020\001\022\n\n\006AVATAR\020\002\022\031\n\025DISAP" + + "PEARING_MESSAGES\020\003\032\345\001\n\036GroupUpdateMember" + + "ChangeMessage\022L\n\004type\030\001 \002(\0162>.signalserv" + + "ice.DataMessage.GroupUpdateMemberChangeM" + + "essage.Type\022\030\n\020memberSessionIds\030\002 \003(\t\022\025\n" + + "\rhistoryShared\030\003 \001(\010\022\026\n\016adminSignature\030\004" + + " \002(\014\",\n\004Type\022\t\n\005ADDED\020\001\022\013\n\007REMOVED\020\002\022\014\n\010" + + "PROMOTED\020\003\032\036\n\034GroupUpdateMemberLeftMessa" + + "ge\0326\n GroupUpdateInviteResponseMessage\022\022" + + "\n\nisApproved\030\001 \002(\010\032p\n%GroupUpdateDeleteM" + + "emberContentMessage\022\030\n\020memberSessionIds\030" + + "\001 \003(\t\022\025\n\rmessageHashes\030\002 \003(\t\022\026\n\016adminSig" + + "nature\030\003 \001(\014\032*\n(GroupUpdateMemberLeftNot" + + "ificationMessage\032\222\001\n\010Reaction\022\n\n\002id\030\001 \002(" + + "\004\022\016\n\006author\030\002 \002(\t\022\r\n\005emoji\030\003 \001(\t\022:\n\006acti" + + "on\030\004 \002(\0162*.signalservice.DataMessage.Rea" + + "ction.Action\"\037\n\006Action\022\t\n\005REACT\020\000\022\n\n\006REM" + + "OVE\020\001\"$\n\005Flags\022\033\n\027EXPIRATION_TIMER_UPDAT" + + "E\020\002J\004\010h\020i\"\352\001\n\013CallMessage\022-\n\004type\030\001 \002(\0162" + + "\037.signalservice.CallMessage.Type\022\014\n\004sdps" + + "\030\002 \003(\t\022\027\n\017sdpMLineIndexes\030\003 \003(\r\022\017\n\007sdpMi" + + "ds\030\004 \003(\t\022\014\n\004uuid\030\005 \002(\t\"f\n\004Type\022\r\n\tPRE_OF" + + "FER\020\006\022\t\n\005OFFER\020\001\022\n\n\006ANSWER\020\002\022\026\n\022PROVISIO" + + "NAL_ANSWER\020\003\022\022\n\016ICE_CANDIDATES\020\004\022\014\n\010END_" + + "CALL\020\005\"y\n\026MessageRequestResponse\022\022\n\nisAp" + + "proved\030\001 \002(\010\022\022\n\nprofileKey\030\002 \001(\014\0227\n\007prof" + + "ile\030\003 \001(\0132&.signalservice.DataMessage.Lo" + + "kiProfile\"u\n\016ReceiptMessage\0220\n\004type\030\001 \002(" + + "\0162\".signalservice.ReceiptMessage.Type\022\021\n" + + "\ttimestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010" + + "\n\004READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001 \002" + + "(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004" + + "size\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006digest\030" + + "\006 \001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r" + + "\n\005width\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007caption" + + "\030\013 \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_ME" + + "SSAGE\020\001B3\n\034org.session.libsignal.protosB" + + "\023SignalServiceProtos" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, @@ -41470,7 +29914,7 @@ public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getDef internal_static_signalservice_Content_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_Content_descriptor, - new java.lang.String[] { "DataMessage", "CallMessage", "ReceiptMessage", "TypingMessage", "ConfigurationMessage", "DataExtractionNotification", "UnsendRequest", "MessageRequestResponse", "SharedConfigMessage", "ExpirationType", "ExpirationTimer", "SigTimestamp", }); + new java.lang.String[] { "DataMessage", "CallMessage", "ReceiptMessage", "TypingMessage", "DataExtractionNotification", "UnsendRequest", "MessageRequestResponse", "ExpirationType", "ExpirationTimer", "SigTimestamp", }); internal_static_signalservice_KeyPair_descriptor = getDescriptor().getMessageTypes().get(4); internal_static_signalservice_KeyPair_fieldAccessorTable = new @@ -41488,7 +29932,7 @@ public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getDef internal_static_signalservice_DataMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_descriptor, - new java.lang.String[] { "Body", "Attachments", "Flags", "ExpireTimer", "ProfileKey", "Timestamp", "Quote", "Preview", "Reaction", "Profile", "OpenGroupInvitation", "ClosedGroupControlMessage", "SyncTarget", "BlocksCommunityMessageRequests", "GroupUpdateMessage", }); + new java.lang.String[] { "Body", "Attachments", "Flags", "ExpireTimer", "ProfileKey", "Timestamp", "Quote", "Preview", "Reaction", "Profile", "OpenGroupInvitation", "SyncTarget", "BlocksCommunityMessageRequests", "GroupUpdateMessage", }); internal_static_signalservice_DataMessage_Quote_descriptor = internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(0); internal_static_signalservice_DataMessage_Quote_fieldAccessorTable = new @@ -41579,92 +30023,32 @@ public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getDef com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_GroupUpdateMemberLeftNotificationMessage_descriptor, new java.lang.String[] { }); - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor = - internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(14); - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor, - new java.lang.String[] { "Type", "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "Wrappers", "ExpirationTimer", "MemberPrivateKey", "PrivateKey", }); - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor = - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor.getNestedTypes().get(0); - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor, - new java.lang.String[] { "PublicKey", "EncryptedKeyPair", }); internal_static_signalservice_DataMessage_Reaction_descriptor = - internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(15); + internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(14); internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_Reaction_descriptor, new java.lang.String[] { "Id", "Author", "Emoji", "Action", }); - internal_static_signalservice_GroupDeleteMessage_descriptor = - getDescriptor().getMessageTypes().get(7); - internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_GroupDeleteMessage_descriptor, - new java.lang.String[] { "PublicKey", "LastEncryptionKey", }); - internal_static_signalservice_GroupMemberLeftMessage_descriptor = - getDescriptor().getMessageTypes().get(8); - internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_GroupMemberLeftMessage_descriptor, - new java.lang.String[] { }); - internal_static_signalservice_GroupInviteMessage_descriptor = - getDescriptor().getMessageTypes().get(9); - internal_static_signalservice_GroupInviteMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_GroupInviteMessage_descriptor, - new java.lang.String[] { "PublicKey", "Name", "MemberPrivateKey", }); - internal_static_signalservice_GroupPromoteMessage_descriptor = - getDescriptor().getMessageTypes().get(10); - internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_GroupPromoteMessage_descriptor, - new java.lang.String[] { "PublicKey", "EncryptedPrivateKey", }); internal_static_signalservice_CallMessage_descriptor = - getDescriptor().getMessageTypes().get(11); + getDescriptor().getMessageTypes().get(7); internal_static_signalservice_CallMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_CallMessage_descriptor, new java.lang.String[] { "Type", "Sdps", "SdpMLineIndexes", "SdpMids", "Uuid", }); - internal_static_signalservice_ConfigurationMessage_descriptor = - getDescriptor().getMessageTypes().get(12); - internal_static_signalservice_ConfigurationMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_ConfigurationMessage_descriptor, - new java.lang.String[] { "ClosedGroups", "OpenGroups", "DisplayName", "ProfilePicture", "ProfileKey", "Contacts", }); - internal_static_signalservice_ConfigurationMessage_ClosedGroup_descriptor = - internal_static_signalservice_ConfigurationMessage_descriptor.getNestedTypes().get(0); - internal_static_signalservice_ConfigurationMessage_ClosedGroup_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_ConfigurationMessage_ClosedGroup_descriptor, - new java.lang.String[] { "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "ExpirationTimer", }); - internal_static_signalservice_ConfigurationMessage_Contact_descriptor = - internal_static_signalservice_ConfigurationMessage_descriptor.getNestedTypes().get(1); - internal_static_signalservice_ConfigurationMessage_Contact_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_ConfigurationMessage_Contact_descriptor, - new java.lang.String[] { "PublicKey", "Name", "ProfilePicture", "ProfileKey", "IsApproved", "IsBlocked", "DidApproveMe", }); internal_static_signalservice_MessageRequestResponse_descriptor = - getDescriptor().getMessageTypes().get(13); + getDescriptor().getMessageTypes().get(8); internal_static_signalservice_MessageRequestResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_MessageRequestResponse_descriptor, new java.lang.String[] { "IsApproved", "ProfileKey", "Profile", }); - internal_static_signalservice_SharedConfigMessage_descriptor = - getDescriptor().getMessageTypes().get(14); - internal_static_signalservice_SharedConfigMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_SharedConfigMessage_descriptor, - new java.lang.String[] { "Kind", "Seqno", "Data", }); internal_static_signalservice_ReceiptMessage_descriptor = - getDescriptor().getMessageTypes().get(15); + getDescriptor().getMessageTypes().get(9); internal_static_signalservice_ReceiptMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ReceiptMessage_descriptor, new java.lang.String[] { "Type", "Timestamp", }); internal_static_signalservice_AttachmentPointer_descriptor = - getDescriptor().getMessageTypes().get(16); + getDescriptor().getMessageTypes().get(10); internal_static_signalservice_AttachmentPointer_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_AttachmentPointer_descriptor, diff --git a/app/src/main/java/org/session/libsignal/streams/PaddingInputStream.java b/app/src/main/java/org/session/libsignal/streams/PaddingInputStream.java index 8e4c52c3cb..e9bf434054 100644 --- a/app/src/main/java/org/session/libsignal/streams/PaddingInputStream.java +++ b/app/src/main/java/org/session/libsignal/streams/PaddingInputStream.java @@ -53,6 +53,15 @@ public int available() throws IOException { } public static long getPaddedSize(long size) { - return (int) Math.max(541, Math.floor(Math.pow(1.05, Math.ceil(Math.log(size) / Math.log(1.05))))); + return Math.max( + 541L, + Math.min( + 10_000_000L, + (long) Math.floor(Math.pow( + 1.05, + Math.ceil(Math.log(Math.max(1, size)) / Math.log(1.05)) + )) + ) + ); } } diff --git a/app/src/main/java/org/session/libsignal/streams/StreamDetails.java b/app/src/main/java/org/session/libsignal/streams/StreamDetails.java deleted file mode 100644 index 529e16beec..0000000000 --- a/app/src/main/java/org/session/libsignal/streams/StreamDetails.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.session.libsignal.streams; - -import java.io.InputStream; - -public class StreamDetails { - - private final InputStream stream; - private final String contentType; - private final long length; - - public StreamDetails(InputStream stream, String contentType, long length) { - this.stream = stream; - this.contentType = contentType; - this.length = length; - } - - public InputStream getStream() { - return stream; - } - - public String getContentType() { - return contentType; - } - - public long getLength() { - return length; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt index 27f39add90..7de5d80e83 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt @@ -37,7 +37,8 @@ import dagger.hilt.EntryPoints import dagger.hilt.android.HiltAndroidApp import network.loki.messenger.BuildConfig import network.loki.messenger.R -import network.loki.messenger.libsession_util.util.Logger.initLogger +import network.loki.messenger.libsession_util.util.LogLevel +import network.loki.messenger.libsession_util.util.Logger import nl.komponents.kovenant.android.startKovenant import nl.komponents.kovenant.android.stopKovenant import org.conscrypt.Conscrypt @@ -71,6 +72,7 @@ import org.thoughtcrime.securesms.configs.ConfigUploader import org.thoughtcrime.securesms.database.EmojiSearchDatabase import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.EmojiSearchData import org.thoughtcrime.securesms.debugmenu.DebugActivity import org.thoughtcrime.securesms.dependencies.AppComponent @@ -93,7 +95,8 @@ import org.thoughtcrime.securesms.migration.DatabaseMigrationManager import org.thoughtcrime.securesms.notifications.BackgroundPollManager import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.notifications.PushRegistrationHandler -import org.thoughtcrime.securesms.providers.BlobProvider +import org.thoughtcrime.securesms.pro.ProStatusManager +import org.thoughtcrime.securesms.providers.BlobUtils import org.thoughtcrime.securesms.service.ExpiringMessageManager import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager @@ -137,7 +140,6 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, private set private var conversationListHandlerThread: HandlerThread? = null private var conversationListHandler: Handler? = null - lateinit var persistentLogger: PersistentLogger @Inject lateinit var workerFactory: Lazy @Inject lateinit var lokiAPIDatabase: Lazy @@ -162,6 +164,7 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, @Inject lateinit var snodeClock: Lazy @Inject lateinit var migrationManager: Lazy @Inject lateinit var appDisguiseManager: Lazy + @Inject lateinit var persistentLogger: Lazy @get:Deprecated(message = "Use proper DI to inject this component") @Inject @@ -187,6 +190,7 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, @Inject lateinit var cleanupInvitationHandler: Lazy @Inject lateinit var usernameUtils: Lazy @Inject lateinit var pollerManager: Lazy + @Inject lateinit var proStatusManager: Lazy @Inject lateinit var backgroundPollManager: Lazy // Exists here only to start upon app starts @@ -203,6 +207,9 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, @Inject lateinit var openGroupPollerManager: Lazy + @Inject + lateinit var threadDatabase: Lazy + @Volatile var isAppVisible: Boolean = false @@ -284,7 +291,8 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, clock = snodeClock.get(), preferences = textSecurePreferences.get(), deprecationManager = legacyGroupDeprecationManager.get(), - usernameUtils = usernameUtils.get() + usernameUtils = usernameUtils.get(), + proStatusManager = proStatusManager.get() ) startKovenant() @@ -377,6 +385,8 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, groupPollerManager.get() expiredGroupManager.get() openGroupPollerManager.get() + + threadDatabase.get().onAppCreated() } override fun onStart(owner: LifecycleOwner) { @@ -420,9 +430,22 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, } private fun initializeLogging() { - persistentLogger = PersistentLogger(this) - Log.initialize(AndroidLogger(), persistentLogger) - initLogger() + Log.initialize(AndroidLogger(), persistentLogger.get()) + Logger.addLogger(object : Logger { + private val tag = "LibSession" + + override fun log(message: String, category: String, level: LogLevel) { + when (level) { + Logger.LOG_LEVEL_INFO -> Log.i(tag, "$category: $message") + Logger.LOG_LEVEL_DEBUG -> Log.d(tag, "$category: $message") + Logger.LOG_LEVEL_WARN -> Log.w(tag, "$category: $message") + Logger.LOG_LEVEL_ERROR -> Log.e(tag, "$category: $message") + Logger.LOG_LEVEL_CRITICAL -> Log.wtf(tag, "$category: $message") + Logger.LOG_LEVEL_OFF -> {} + else -> Log.v(tag, "$category: $message") + } + } + }) } private fun initializeCrashHandling() { @@ -442,7 +465,7 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, private fun initializeBlobProvider() { AsyncTask.THREAD_POOL_EXECUTOR.execute { - BlobProvider.getInstance().onSessionStart(this) + BlobUtils.getInstance().onSessionStart(this) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/InputBarDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/InputBarDialogs.kt new file mode 100644 index 0000000000..9aef8e5ad6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/InputBarDialogs.kt @@ -0,0 +1,80 @@ +package org.thoughtcrime.securesms + + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import network.loki.messenger.R +import org.thoughtcrime.securesms.ui.AlertDialog +import org.thoughtcrime.securesms.ui.CTAFeature +import org.thoughtcrime.securesms.ui.DialogButtonData +import org.thoughtcrime.securesms.ui.GetString +import org.thoughtcrime.securesms.ui.SimpleSessionProCTA +import org.thoughtcrime.securesms.ui.components.annotatedStringResource +import org.thoughtcrime.securesms.ui.theme.LocalColors +import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun InputBarDialogs( + inputBarDialogsState: InputbarViewModel.InputBarDialogsState, + sendCommand: (InputbarViewModel.Commands) -> Unit +){ + SessionMaterialTheme { + // Simple dialogs + if (inputBarDialogsState.showSimpleDialog != null) { + val buttons = mutableListOf() + if(inputBarDialogsState.showSimpleDialog.positiveText != null) { + buttons.add( + DialogButtonData( + text = GetString(inputBarDialogsState.showSimpleDialog.positiveText), + color = if (inputBarDialogsState.showSimpleDialog.positiveStyleDanger) LocalColors.current.danger + else LocalColors.current.text, + qaTag = inputBarDialogsState.showSimpleDialog.positiveQaTag, + onClick = inputBarDialogsState.showSimpleDialog.onPositive + ) + ) + } + if(inputBarDialogsState.showSimpleDialog.negativeText != null){ + buttons.add( + DialogButtonData( + text = GetString(inputBarDialogsState.showSimpleDialog.negativeText), + qaTag = inputBarDialogsState.showSimpleDialog.negativeQaTag, + onClick = inputBarDialogsState.showSimpleDialog.onNegative + ) + ) + } + + AlertDialog( + onDismissRequest = { + // hide dialog + sendCommand(InputbarViewModel.Commands.HideSimpleDialog) + }, + title = annotatedStringResource(inputBarDialogsState.showSimpleDialog.title), + text = annotatedStringResource(inputBarDialogsState.showSimpleDialog.message), + showCloseButton = inputBarDialogsState.showSimpleDialog.showXIcon, + buttons = buttons + ) + } + + // Pro CTA + if (inputBarDialogsState.sessionProCharLimitCTA) { + SimpleSessionProCTA( + heroImage = R.drawable.cta_hero_char_limit, + text = stringResource(R.string.proCallToActionLongerMessages), + features = listOf( + CTAFeature.Icon(stringResource(R.string.proFeatureListLongerMessages)), + CTAFeature.Icon(stringResource(R.string.proFeatureListLargerGroups)), + CTAFeature.RainbowIcon(stringResource(R.string.proFeatureListLoadsMore)), + ), + onUpgrade = { + sendCommand(InputbarViewModel.Commands.HideSessionProCTA) + //todo PRO go to screen once it exists + }, + onCancel = { + sendCommand(InputbarViewModel.Commands.HideSessionProCTA) + } + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt new file mode 100644 index 0000000000..8e38d7b685 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt @@ -0,0 +1,199 @@ +package org.thoughtcrime.securesms + +import android.app.Application +import androidx.lifecycle.ViewModel +import com.squareup.phrase.Phrase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.LIMIT_KEY +import org.thoughtcrime.securesms.pro.ProStatusManager +import org.thoughtcrime.securesms.ui.SimpleDialogData +import org.thoughtcrime.securesms.util.NumberUtil + +// the amount of character left at which point we should show an indicator +private const val CHARACTER_LIMIT_THRESHOLD = 200 + +abstract class InputbarViewModel( + private val application: Application, + private val proStatusManager: ProStatusManager +): ViewModel() { + protected val _inputBarState = MutableStateFlow(InputBarState()) + val inputBarState: StateFlow get() = _inputBarState + + private val _inputBarStateDialogsState = MutableStateFlow(InputBarDialogsState()) + val inputBarStateDialogsState: StateFlow = _inputBarStateDialogsState + + fun onTextChanged(text: CharSequence) { + // check the character limit + val maxChars = proStatusManager.getCharacterLimit() + val charsLeft = maxChars - text.length + + // update the char limit state based on characters left + val charLimitState = if(charsLeft <= CHARACTER_LIMIT_THRESHOLD){ + InputBarCharLimitState( + count = charsLeft, + countFormatted = NumberUtil.getFormattedNumber(charsLeft.toLong()), + danger = charsLeft < 0, + showProBadge = proStatusManager.isPostPro() && !proStatusManager.isCurrentUserPro() // only show the badge for non pro users POST pro launch + ) + } else { + null + } + + _inputBarState.update { it.copy(charLimitState = charLimitState) } + } + + fun validateMessageLength(): Boolean { + // the message is too long if we have a negative char left in the input state + val charsLeft = _inputBarState.value.charLimitState?.count ?: 0 + return if(charsLeft < 0){ + // the user is trying to send a message that is too long - we should display a dialog + // we currently have different logic for PRE and POST Pro launch + // which we can remove once Pro is out - currently we can switch this fro the debug menu + if(!proStatusManager.isPostPro() || proStatusManager.isCurrentUserPro()){ + showMessageTooLongSendDialog() + } else { + showSessionProCTA() + } + + false + } else { + true + } + } + + fun onCharLimitTapped(){ + // we currently have different logic for PRE and POST Pro launch + // which we can remove once Pro is out - currently we can switch this fro the debug menu + if(!proStatusManager.isPostPro() || proStatusManager.isCurrentUserPro()){ + handleCharLimitTappedForProUser() + } else { + handleCharLimitTappedForRegularUser() + } + } + + private fun handleCharLimitTappedForProUser(){ + if((_inputBarState.value.charLimitState?.count ?: 0) < 0){ + showMessageTooLongDialog() + } else { + showMessageLengthDialog() + } + } + + private fun handleCharLimitTappedForRegularUser(){ + showSessionProCTA() + } + + fun showSessionProCTA(){ + _inputBarStateDialogsState.update { + it.copy(sessionProCharLimitCTA = true) + } + } + + fun showMessageLengthDialog(){ + _inputBarStateDialogsState.update { + val charsLeft = _inputBarState.value.charLimitState?.count ?: 0 + it.copy( + showSimpleDialog = SimpleDialogData( + title = application.getString(R.string.modalMessageCharacterDisplayTitle), + message = application.resources.getQuantityString( + R.plurals.modalMessageCharacterDisplayDescription, + charsLeft, // quantity for plural + proStatusManager.getCharacterLimit(), // 1st arg: total character limit + charsLeft, // 2nd arg: chars left + ), + positiveStyleDanger = false, + positiveText = application.getString(R.string.okay), + onPositive = ::hideSimpleDialog + + ) + ) + } + } + + fun showMessageTooLongDialog(){ + _inputBarStateDialogsState.update { + it.copy( + showSimpleDialog = SimpleDialogData( + title = application.getString(R.string.modalMessageTooLongTitle), + message = Phrase.from(application.getString(R.string.modalMessageCharacterTooLongDescription)) + .put(LIMIT_KEY, proStatusManager.getCharacterLimit()) + .format(), + positiveStyleDanger = false, + positiveText = application.getString(R.string.okay), + onPositive = ::hideSimpleDialog + ) + ) + } + } + + fun showMessageTooLongSendDialog(){ + _inputBarStateDialogsState.update { + it.copy( + showSimpleDialog = SimpleDialogData( + title = application.getString(R.string.modalMessageTooLongTitle), + message = Phrase.from(application.getString(R.string.modalMessageTooLongDescription)) + .put(LIMIT_KEY, proStatusManager.getCharacterLimit()) + .format(), + positiveStyleDanger = false, + positiveText = application.getString(R.string.okay), + onPositive = ::hideSimpleDialog + ) + ) + } + } + + private fun hideSimpleDialog(){ + _inputBarStateDialogsState.update { + it.copy(showSimpleDialog = null) + } + } + + fun onInputBarCommand(command: Commands) { + when (command) { + is Commands.HideSimpleDialog -> { + hideSimpleDialog() + } + + is Commands.HideSessionProCTA -> { + _inputBarStateDialogsState.update { + it.copy(sessionProCharLimitCTA = false) + } + } + } + } + + data class InputBarCharLimitState( + val count: Int, + val countFormatted: String, + val danger: Boolean, + val showProBadge: Boolean + ) + + sealed interface InputBarContentState { + data object Hidden : InputBarContentState + data object Visible : InputBarContentState + data class Disabled(val text: String, val onClick: (() -> Unit)? = null) : InputBarContentState + } + + data class InputBarState( + val contentState: InputBarContentState = InputBarContentState.Visible, + // Note: These input media controls are with regard to whether the user can attach multimedia files + // or record voice messages to be sent to a recipient - they are NOT things like video or audio + // playback controls. + val enableAttachMediaControls: Boolean = true, + val charLimitState: InputBarCharLimitState? = null, + ) + + data class InputBarDialogsState( + val showSimpleDialog: SimpleDialogData? = null, + val sessionProCharLimitCTA: Boolean = false + ) + + sealed interface Commands { + data object HideSimpleDialog : Commands + data object HideSessionProCTA : Commands + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java deleted file mode 100644 index b96c6996b7..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ /dev/null @@ -1,785 +0,0 @@ -/* - * Copyright (C) 2014 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms; - -import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.database.CursorIndexOutOfBoundsException; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.view.GestureDetector; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.core.util.Pair; -import androidx.core.view.WindowCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.lifecycle.ViewModelProvider; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.viewpager2.widget.ViewPager2; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.RequestManager; -import com.squareup.phrase.Phrase; - -import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager; -import org.session.libsession.messaging.messages.control.DataExtractionNotification; -import org.session.libsession.messaging.sending_receiving.MessageSender; -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; -import org.session.libsession.snode.SnodeAPI; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.components.MediaView; -import org.thoughtcrime.securesms.components.dialogs.DeleteMediaPreviewDialog; -import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; -import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.media.MediaOverviewActivity; -import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel; -import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.AttachmentUtil; -import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.FilenameUtils; -import org.thoughtcrime.securesms.util.SaveAttachmentTask; -import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; - -import java.io.IOException; -import java.util.Locale; -import java.util.WeakHashMap; - -import javax.inject.Inject; - -import dagger.hilt.android.AndroidEntryPoint; -import kotlin.Unit; -import network.loki.messenger.R; -import network.loki.messenger.databinding.MediaPreviewActivityBinding; -import network.loki.messenger.databinding.MediaViewPageBinding; - - -/** - * Activity for displaying media attachments in-app - */ -@AndroidEntryPoint -public class MediaPreviewActivity extends ScreenLockActionBarActivity implements RecipientModifiedListener, - LoaderManager.LoaderCallbacks>, - MediaRailAdapter.RailItemListener -{ - private final static String TAG = MediaPreviewActivity.class.getSimpleName(); - - private static final int UI_ANIMATION_DELAY = 300; - - public static final String ADDRESS_EXTRA = "address"; - public static final String DATE_EXTRA = "date"; - public static final String SIZE_EXTRA = "size"; - public static final String CAPTION_EXTRA = "caption"; - public static final String OUTGOING_EXTRA = "outgoing"; - public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent"; - - private MediaPreviewActivityBinding binding; - private Uri initialMediaUri; - private String initialMediaType; - private long initialMediaSize; - private String initialCaption; - private Recipient conversationRecipient; - private boolean leftIsRecent; - private MediaPreviewViewModel viewModel; - private ViewPagerListener viewPagerListener; - - @Inject - LegacyGroupDeprecationManager deprecationManager; - - private int restartItem = -1; - - private boolean isFullscreen = false; - private final Handler hideHandler = new Handler(Looper.myLooper()); - - @Inject DateUtils dateUtils; - - private final Runnable showRunnable = () -> { - getSupportActionBar().show(); - }; - private final Runnable hideRunnable = () -> { - WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView()) - .hide(WindowInsetsCompat.Type.systemBars()); - }; - private MediaItemAdapter adapter; - private MediaRailAdapter albumRailAdapter; - - public static Intent getPreviewIntent(Context context, MediaPreviewArgs args) { - return getPreviewIntent(context, args.getSlide(), args.getMmsRecord(), args.getThread()); - } - - public static Intent getPreviewIntent(Context context, Slide slide, MmsMessageRecord mms, Recipient threadRecipient) { - Intent previewIntent = null; - if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { - previewIntent = new Intent(context, MediaPreviewActivity.class); - previewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .setDataAndType(slide.getUri(), slide.getContentType()) - .putExtra(ADDRESS_EXTRA, threadRecipient.getAddress()) - .putExtra(OUTGOING_EXTRA, mms.isOutgoing()) - .putExtra(DATE_EXTRA, mms.getTimestamp()) - .putExtra(SIZE_EXTRA, slide.asAttachment().getSize()) - .putExtra(CAPTION_EXTRA, slide.getCaption().orNull()) - .putExtra(LEFT_IS_RECENT_EXTRA, false); - } - return previewIntent; - } - - - @SuppressWarnings("ConstantConditions") - @Override - protected void onCreate(Bundle bundle, boolean ready) { - viewModel = new ViewModelProvider(this).get(MediaPreviewViewModel.class); - binding = MediaPreviewActivityBinding.inflate(getLayoutInflater()); - - setContentView(binding.getRoot()); - - initializeViews(); - initializeResources(); - initializeObservers(); - } - - private void toggleFullscreen() { - if (isFullscreen) { - exitFullscreen(); - } else { - enterFullscreen(); - } - } - - private void enterFullscreen() { - getSupportActionBar().hide(); - isFullscreen = true; - hideHandler.removeCallbacks(showRunnable); - hideHandler.postDelayed(hideRunnable, UI_ANIMATION_DELAY); - } - - private void exitFullscreen() { - WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView()) - .show(WindowInsetsCompat.Type.systemBars()); - - isFullscreen = false; - hideHandler.removeCallbacks(hideRunnable); - hideHandler.postDelayed(showRunnable, UI_ANIMATION_DELAY); - } - - @SuppressLint("MissingSuperCall") - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); - } - - @Override - public void onModified(Recipient recipient) { - Util.runOnMain(this::updateActionBar); - } - - @Override - public void onRailItemClicked(int distanceFromActive) { - binding.mediaPager.setCurrentItem(binding.mediaPager.getCurrentItem() + distanceFromActive); - } - - @Override - public void onRailItemDeleteClicked(int distanceFromActive) { - throw new UnsupportedOperationException("Callback unsupported."); - } - - @SuppressWarnings("ConstantConditions") - private void updateActionBar() { - MediaItem mediaItem = getCurrentMediaItem(); - - if (mediaItem != null) { - CharSequence relativeTimeSpan; - - if (mediaItem.date > 0) { - relativeTimeSpan = dateUtils.getDisplayFormattedTimeSpanString(Locale.getDefault(), mediaItem.date); - } else { - relativeTimeSpan = getString(R.string.draft); - } - - if (mediaItem.outgoing) getSupportActionBar().setTitle(getString(R.string.you)); - else if (mediaItem.recipient != null) getSupportActionBar().setTitle(mediaItem.recipient.getName()); - else getSupportActionBar().setTitle(""); - - getSupportActionBar().setSubtitle(relativeTimeSpan); - } - } - - @Override - public void onResume() { - super.onResume(); - initializeMedia(); - } - - @Override - public void onPause() { - super.onPause(); - restartItem = cleanupMedia(); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - initializeResources(); - } - - private void initializeViews() { - binding.mediaPager.setOffscreenPageLimit(1); - - albumRailAdapter = new MediaRailAdapter(Glide.with(this), this, false); - - binding.mediaPreviewAlbumRail.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); - binding.mediaPreviewAlbumRail.setAdapter(albumRailAdapter); - - setSupportActionBar(findViewById(R.id.search_toolbar)); - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setHomeButtonEnabled(true); - - binding.mediaPager.setOnClickListener((v) -> { - toggleFullscreen(); - }); - } - - private void initializeResources() { - Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA); - - initialMediaUri = getIntent().getData(); - initialMediaType = getIntent().getType(); - initialMediaSize = getIntent().getLongExtra(SIZE_EXTRA, 0); - initialCaption = getIntent().getStringExtra(CAPTION_EXTRA); - leftIsRecent = getIntent().getBooleanExtra(LEFT_IS_RECENT_EXTRA, false); - restartItem = -1; - - if (address != null) { - conversationRecipient = Recipient.from(this, address, true); - } else { - conversationRecipient = null; - } - } - - private void initializeObservers() { - viewModel.getPreviewData().observe(this, previewData -> { - if (previewData == null || binding == null || binding.mediaPager.getAdapter() == null) { - return; - } - - View playbackControls = ((MediaItemAdapter) binding.mediaPager.getAdapter()).getPlaybackControls(binding.mediaPager.getCurrentItem()); - - if (previewData.getAlbumThumbnails().isEmpty() && previewData.getCaption() == null && playbackControls == null) { - binding.mediaPreviewDetailsContainer.setVisibility(View.GONE); - } else { - binding.mediaPreviewDetailsContainer.setVisibility(View.VISIBLE); - } - - binding.mediaPreviewAlbumRail.setVisibility(previewData.getAlbumThumbnails().isEmpty() ? View.GONE : View.VISIBLE); - albumRailAdapter.setMedia(previewData.getAlbumThumbnails(), previewData.getActivePosition()); - binding.mediaPreviewAlbumRail.smoothScrollToPosition(previewData.getActivePosition()); - - binding.mediaPreviewCaptionContainer.setVisibility(previewData.getCaption() == null ? View.GONE : View.VISIBLE); - binding.mediaPreviewCaption.setText(previewData.getCaption()); - - if (playbackControls != null) { - ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - playbackControls.setLayoutParams(params); - - binding.mediaPreviewPlaybackControlsContainer.removeAllViews(); - binding.mediaPreviewPlaybackControlsContainer.addView(playbackControls); - } else { - binding.mediaPreviewPlaybackControlsContainer.removeAllViews(); - } - }); - } - - private void initializeMedia() { - if (!isContentTypeSupported(initialMediaType)) { - Log.w(TAG, "Unsupported media type sent to MediaPreviewActivity, finishing."); - Toast.makeText(getApplicationContext(), R.string.attachmentsErrorNotSupported, Toast.LENGTH_LONG).show(); - finish(); - } - - Log.i(TAG, "Loading Part URI: " + initialMediaUri); - - if (conversationRecipient != null) { - getSupportLoaderManager().restartLoader(0, null, this); - } else { - adapter = new SingleItemPagerAdapter(Glide.with(this), getWindow(), initialMediaUri, initialMediaType, initialMediaSize); - binding.mediaPager.setAdapter(adapter); - - if (initialCaption != null) { - binding.mediaPreviewDetailsContainer.setVisibility(View.VISIBLE); - binding.mediaPreviewCaptionContainer.setVisibility(View.VISIBLE); - binding.mediaPreviewCaption.setText(initialCaption); - } - } - } - - private int cleanupMedia() { - int restartItem = binding.mediaPager.getCurrentItem(); - - binding.mediaPager.removeAllViews(); - binding.mediaPager.setAdapter(null); - - return restartItem; - } - - private void showOverview() { - startActivity(MediaOverviewActivity.createIntent(this, conversationRecipient.getAddress())); - } - - private void forward() { - MediaItem mediaItem = getCurrentMediaItem(); - - if (mediaItem != null) { - Intent composeIntent = new Intent(this, ShareActivity.class); - composeIntent.putExtra(Intent.EXTRA_STREAM, mediaItem.uri); - composeIntent.setType(mediaItem.mimeType); - startActivity(composeIntent); - } - } - - @SuppressWarnings("CodeBlock2Expr") - @SuppressLint("InlinedApi") - private void saveToDisk() { - MediaItem mediaItem = getCurrentMediaItem(); - if (mediaItem == null) { - Log.w(TAG, "Cannot save a null MediaItem to disk - bailing."); - return; - } - - // If we have an attachment then we can take the filename from it, otherwise we have to take the - // more expensive route of looking up or synthesizing a filename from the MediaItem's Uri. - String mediaFilename = ""; - if (mediaItem.attachment != null) { - mediaFilename = mediaItem.attachment.getFilename(); - } - - if(mediaFilename == null || mediaFilename.isEmpty()){ - mediaFilename = FilenameUtils.getFilenameFromUri(MediaPreviewActivity.this, mediaItem.uri, mediaItem.mimeType); - } - - final String outputFilename = mediaFilename; // We need a `final` value for the saveTask, below - Log.i(TAG, "About to save media as: " + outputFilename); - - SaveAttachmentTask.showOneTimeWarningDialogOrSave(this, 1, () -> { - Permissions.with(this) - .request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - .maxSdkVersion(Build.VERSION_CODES.P) // Note: P is API 28 - .withPermanentDenialDialog(getPermanentlyDeniedStorageText()) - .onAnyDenied(() -> { - Toast.makeText(this, getPermanentlyDeniedStorageText(), Toast.LENGTH_LONG).show(); - }) - .onAllGranted(() -> { - SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this); - long saveDate = (mediaItem.date > 0) ? mediaItem.date : SnodeAPI.getNowWithOffset(); - saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.mimeType, saveDate, outputFilename)); - if (!mediaItem.outgoing) { sendMediaSavedNotificationIfNeeded(); } - }) - .execute(); - return Unit.INSTANCE; - }); - } - - private String getPermanentlyDeniedStorageText(){ - return Phrase.from(getApplicationContext(), R.string.permissionsStorageDeniedLegacy) - .put(APP_NAME_KEY, getString(R.string.app_name)) - .format().toString(); - } - - private void sendMediaSavedNotificationIfNeeded() { - if (conversationRecipient.isGroupOrCommunityRecipient()) return; - DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(SnodeAPI.getNowWithOffset())); - MessageSender.send(message, conversationRecipient.getAddress()); - } - - @SuppressLint("StaticFieldLeak") - private void deleteMedia() { - MediaItem mediaItem = getCurrentMediaItem(); - if (mediaItem == null || mediaItem.attachment == null) { - return; - } - - DeleteMediaPreviewDialog.show(this, () -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... voids) { - DatabaseAttachment attachment = mediaItem.attachment; - if (attachment != null) { - AttachmentUtil.deleteAttachment(getApplicationContext(), attachment); - } - return null; - } - }.execute(); - - finish(); - }); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - - menu.clear(); - MenuInflater inflater = this.getMenuInflater(); - inflater.inflate(R.menu.media_preview, menu); - - final boolean isDeprecatedLegacyGroup = conversationRecipient != null && - conversationRecipient.isLegacyGroupRecipient() && - deprecationManager.getDeprecationState().getValue() == LegacyGroupDeprecationManager.DeprecationState.DEPRECATED; - - if (!isMediaInDb() || isDeprecatedLegacyGroup) { - menu.findItem(R.id.media_preview__overview).setVisible(false); - menu.findItem(R.id.delete).setVisible(false); - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - - switch (item.getItemId()) { - // TODO / WARNING: R.id values are NON-CONSTANT in Gradle 8.0+ - what would be the best way to address this?! -AL 2024/08/26 - case R.id.media_preview__overview: showOverview(); return true; - case R.id.media_preview__forward: forward(); return true; - case R.id.save: saveToDisk(); return true; - case R.id.delete: deleteMedia(); return true; - case android.R.id.home: finish(); return true; - } - - return false; - } - - private boolean isMediaInDb() { - return conversationRecipient != null; - } - - private @Nullable MediaItem getCurrentMediaItem() { - if (adapter == null) return null; - try { - return adapter.getMediaItemFor(binding.mediaPager.getCurrentItem()); - } catch (Exception e) { - Log.w(TAG, "Error getting current media item", e); - return null; - } - } - - public static boolean isContentTypeSupported(final String contentType) { - return contentType != null && (contentType.startsWith("image/") || contentType.startsWith("video/")); - } - - @Override - public @NonNull Loader> onCreateLoader(int id, Bundle args) { - return new PagingMediaLoader(this, conversationRecipient, initialMediaUri, leftIsRecent); - } - - @Override - public void onLoadFinished(@NonNull Loader> loader, @Nullable Pair data) { - if (data == null) return; - - binding.mediaPager.unregisterOnPageChangeCallback(viewPagerListener); - - adapter = new CursorPagerAdapter(this, Glide.with(this), getWindow(), data.first, data.second, leftIsRecent); - binding.mediaPager.setAdapter(adapter); - - final GestureDetector detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onSingleTapConfirmed(@NonNull MotionEvent e) { - toggleFullscreen(); - return super.onSingleTapConfirmed(e); - } - }); - - binding.observeTouchEventFrame.setOnTouchDispatchListener((view, event) -> { - detector.onTouchEvent(event); - return false; - }); - - viewModel.setCursor(this, data.first, leftIsRecent); - - int item = restartItem >= 0 && restartItem < adapter.getItemCount() ? restartItem : Math.max(Math.min(data.second, adapter.getItemCount() - 1), 0); - - viewPagerListener = new ViewPagerListener(); - binding.mediaPager.registerOnPageChangeCallback(viewPagerListener); - - try { - binding.mediaPager.setCurrentItem(item, false); - } catch (CursorIndexOutOfBoundsException e) { - throw new RuntimeException("restartItem = " + restartItem + ", data.second = " + data.second + " leftIsRecent = " + leftIsRecent, e); - } - - if (item == 0) { viewPagerListener.onPageSelected(0); } - } - - @Override - public void onLoaderReset(@NonNull Loader> loader) { /* Do nothing */ } - - private class ViewPagerListener extends ViewPager2.OnPageChangeCallback { - - private int currentPage = -1; - - @Override - public void onPageSelected(int position) { - if (currentPage != -1 && currentPage != position) onPageUnselected(currentPage); - currentPage = position; - - if (adapter == null) return; - - try { - MediaItem item = adapter.getMediaItemFor(position); - if (item.recipient != null) item.recipient.addListener(MediaPreviewActivity.this); - viewModel.setActiveAlbumRailItem(MediaPreviewActivity.this, position); - updateActionBar(); - } catch (Exception e) { - finish(); - } - } - - - public void onPageUnselected(int position) { - if (adapter == null) return; - - try { - MediaItem item = adapter.getMediaItemFor(position); - if (item.recipient != null) item.recipient.removeListener(MediaPreviewActivity.this); - } catch (CursorIndexOutOfBoundsException e) { - throw new RuntimeException("position = " + position + " leftIsRecent = " + leftIsRecent, e); - } catch (Exception e){ - finish(); - } - - adapter.pause(position); - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - /* Do nothing */ - } - - @Override - public void onPageScrollStateChanged(int state) { /* Do nothing */ } - } - - private static class SingleItemPagerAdapter extends MediaItemAdapter { - - private final RequestManager glideRequests; - private final Window window; - private final Uri uri; - private final String mediaType; - private final long size; - - - SingleItemPagerAdapter(@NonNull RequestManager glideRequests, - @NonNull Window window, @NonNull Uri uri, @NonNull String mediaType, - long size) - { - this.glideRequests = glideRequests; - this.window = window; - this.uri = uri; - this.mediaType = mediaType; - this.size = size; - } - - @NonNull - @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new RecyclerView.ViewHolder( - MediaViewPageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false).getRoot() - ) {}; - } - - @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - final MediaViewPageBinding binding = MediaViewPageBinding.bind(holder.itemView); - - try { - binding.mediaView.set(glideRequests, window, uri, mediaType, size, true); - } catch (IOException e) { - Log.w(TAG, e); - } - } - - @Override - public int getItemCount() { - return 1; - } - - @Override - public MediaItem getMediaItemFor(int position) { - return new MediaItem(null, null, uri, mediaType, -1, true); - } - - @Override - public void pause(int position) { /* Do nothing */ } - - @Override - public @Nullable View getPlaybackControls(int position) { - return null; - } - } - - private static class CursorPagerAdapter extends MediaItemAdapter { - - private final WeakHashMap mediaViews = new WeakHashMap<>(); - - private final Context context; - private final RequestManager glideRequests; - private final Window window; - private final Cursor cursor; - private final boolean leftIsRecent; - - private int autoPlayPosition; - - CursorPagerAdapter(@NonNull Context context, @NonNull RequestManager glideRequests, - @NonNull Window window, @NonNull Cursor cursor, int autoPlayPosition, - boolean leftIsRecent) - { - this.context = context.getApplicationContext(); - this.glideRequests = glideRequests; - this.window = window; - this.cursor = cursor; - this.autoPlayPosition = autoPlayPosition; - this.leftIsRecent = leftIsRecent; - } - - @NonNull - @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new RecyclerView.ViewHolder(MediaViewPageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false).getRoot()) {}; - } - - @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - final MediaViewPageBinding binding = MediaViewPageBinding.bind(holder.itemView); - - boolean autoplay = position == autoPlayPosition; - int cursorPosition = getCursorPosition(position); - - autoPlayPosition = -1; - - cursor.moveToPosition(cursorPosition); - - MediaRecord mediaRecord = MediaRecord.from(context, cursor); - - try { - //noinspection ConstantConditions - binding.mediaView.set(glideRequests, window, mediaRecord.getAttachment().getDataUri(), - mediaRecord.getAttachment().getContentType(), mediaRecord.getAttachment().getSize(), autoplay); - } catch (IOException e) { - Log.w(TAG, e); - } - - mediaViews.put(position, binding.mediaView); - } - - @Override - public int getItemCount() { - return cursor.getCount(); - } - - public MediaItem getMediaItemFor(int position) { - cursor.moveToPosition(getCursorPosition(position)); - MediaRecord mediaRecord = MediaRecord.from(context, cursor); - Address address = mediaRecord.getAddress(); - - if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError(); - - return new MediaItem(address != null ? Recipient.from(context, address,true) : null, - mediaRecord.getAttachment(), - mediaRecord.getAttachment().getDataUri(), - mediaRecord.getContentType(), - mediaRecord.getDate(), - mediaRecord.isOutgoing()); - } - - @Override - public void pause(int position) { - MediaView mediaView = mediaViews.get(position); - if (mediaView != null) mediaView.pause(); - } - - @Override - public @Nullable View getPlaybackControls(int position) { - MediaView mediaView = mediaViews.get(position); - if (mediaView != null) return mediaView.getPlaybackControls(); - return null; - } - - private int getCursorPosition(int position) { - int unclamped = leftIsRecent ? position : cursor.getCount() - 1 - position; - return Math.max(Math.min(unclamped, cursor.getCount() - 1), 0); - } - } - - private static class MediaItem { - private final @Nullable Recipient recipient; - private final @Nullable DatabaseAttachment attachment; - private final @NonNull Uri uri; - private final @NonNull String mimeType; - private final long date; - private final boolean outgoing; - - private MediaItem(@Nullable Recipient recipient, - @Nullable DatabaseAttachment attachment, - @NonNull Uri uri, - @NonNull String mimeType, - long date, - boolean outgoing) - { - this.recipient = recipient; - this.attachment = attachment; - this.uri = uri; - this.mimeType = mimeType; - this.date = date; - this.outgoing = outgoing; - } - } - - abstract static class MediaItemAdapter extends RecyclerView.Adapter { - abstract MediaItem getMediaItemFor(int position); - abstract void pause(int position); - @Nullable abstract View getPlaybackControls(int position); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt new file mode 100644 index 0000000000..a9c0d0aa03 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt @@ -0,0 +1,856 @@ +/* + * Copyright (C) 2014 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import android.database.Cursor +import android.database.CursorIndexOutOfBoundsException +import android.net.Uri +import android.os.AsyncTask +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.view.ViewTreeObserver +import android.view.Window +import android.widget.Toast +import androidx.activity.viewModels +import androidx.core.graphics.ColorUtils +import androidx.core.graphics.drawable.toDrawable +import androidx.core.util.Pair +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import androidx.lifecycle.Observer +import androidx.loader.app.LoaderManager +import androidx.loader.content.Loader +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager +import com.squareup.phrase.Phrase +import dagger.hilt.android.AndroidEntryPoint +import network.loki.messenger.R +import network.loki.messenger.databinding.MediaPreviewActivityBinding +import network.loki.messenger.databinding.MediaViewPageBinding +import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager +import org.session.libsession.messaging.messages.control.DataExtractionNotification +import org.session.libsession.messaging.messages.control.DataExtractionNotification.Kind.MediaSaved +import org.session.libsession.messaging.sending_receiving.MessageSender.send +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsession.snode.SnodeAPI.nowWithOffset +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY +import org.session.libsession.utilities.Util.runOnMain +import org.session.libsession.utilities.getColorFromAttr +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientModifiedListener +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.ShareActivity +import org.thoughtcrime.securesms.components.MediaView +import org.thoughtcrime.securesms.components.dialogs.DeleteMediaPreviewDialog +import org.thoughtcrime.securesms.conversation.v2.DimensionUnit +import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord +import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader +import org.thoughtcrime.securesms.database.model.MmsMessageRecord +import org.thoughtcrime.securesms.media.MediaOverviewActivity.Companion.createIntent +import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel +import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel.PreviewData +import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter +import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter.RailItemListener +import org.thoughtcrime.securesms.mms.Slide +import org.thoughtcrime.securesms.permissions.Permissions +import org.thoughtcrime.securesms.util.AttachmentUtil +import org.thoughtcrime.securesms.util.DateUtils +import org.thoughtcrime.securesms.util.FilenameUtils.getFilenameFromUri +import org.thoughtcrime.securesms.util.SaveAttachmentTask +import org.thoughtcrime.securesms.util.SaveAttachmentTask.Companion.showOneTimeWarningDialogOrSave +import java.io.IOException +import java.util.Locale +import java.util.WeakHashMap +import javax.inject.Inject +import kotlin.math.max +import kotlin.math.min + +/** + * Activity for displaying media attachments in-app + */ +@AndroidEntryPoint +class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedListener, + LoaderManager.LoaderCallbacks?>, + RailItemListener, MediaView.FullscreenToggleListener { + private lateinit var binding: MediaPreviewActivityBinding + private var initialMediaUri: Uri? = null + private var initialMediaType: String? = null + private var initialMediaSize: Long = 0 + private var initialCaption: String? = null + private var conversationRecipient: Recipient? = null + private var leftIsRecent = false + private val viewModel: MediaPreviewViewModel by viewModels() + private var viewPagerListener: ViewPagerListener? = null + + @Inject + lateinit var deprecationManager: LegacyGroupDeprecationManager + + private var isFullscreen = false + + @Inject + lateinit var dateUtils: DateUtils + + override val applyDefaultWindowInsets: Boolean + get() = false + + private var adapter: CursorPagerAdapter? = null + private var albumRailAdapter: MediaRailAdapter? = null + + private var windowInsetBottom = 0 + private var railHeight = 0 + + override fun onCreate(bundle: Bundle?, ready: Boolean) { + binding = MediaPreviewActivityBinding.inflate( + layoutInflater + ) + + setContentView(binding.root) + + initializeViews() + initializeResources() + initializeObservers() + initializeMedia() + + // make the toolbar translucent so that the video can be seen below in landscape - 70% of regular toolbar color + supportActionBar?.setBackgroundDrawable( + ColorUtils.setAlphaComponent( + getColorFromAttr( + android.R.attr.colorPrimary + ), (0.7f * 255).toInt() + ).toDrawable()) + + // handle edge to edge display + ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content)) { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()) + windowInsetBottom = insets.bottom + + binding.toolbar.updatePadding(top = insets.top) + binding.mediaPreviewAlbumRailContainer.updatePadding(bottom = max(insets.bottom, binding.mediaPreviewAlbumRailContainer.paddingBottom)) + + updateControlsPosition() + + // on older android version this acts as a safety when the system intercepts the first tap and only + // shows the system bars but ignores our code to show the toolbar and rail back + val systemBarsVisible: Boolean = windowInsets.isVisible(WindowInsetsCompat.Type.systemBars()) + if (systemBarsVisible && isFullscreen) { + exitFullscreen() + } + + windowInsets.inset(insets) + } + + + // Set up system UI visibility listener + window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> + // Check if system bars became visible + val systemBarsVisible = (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 + if (systemBarsVisible && isFullscreen) { + // System bars appeared - exit fullscreen and show our UI + exitFullscreen() + } + } + } + + /** + * Updates the media controls' position based on the rail's position + */ + private fun updateControlsPosition() { + // the ypos of the controls is either the window bottom inset, or the rail height if there is a rail + // since the rail height takes the window inset into account with its padding + val totalBottomPadding = max( + windowInsetBottom, + railHeight + resources.getDimensionPixelSize(R.dimen.medium_spacing) + ) + + adapter?.setControlsYPosition(totalBottomPadding) + } + + override fun toggleFullscreen() { + if (isFullscreen) exitFullscreen() else enterFullscreen() + } + + override fun setFullscreen(displayFullscreen: Boolean) { + if (displayFullscreen) enterFullscreen() else exitFullscreen() + } + + private fun enterFullscreen() { + supportActionBar?.hide() + hideAlbumRail() + isFullscreen = true + WindowCompat.getInsetsController(window, window.decorView) + .hide(WindowInsetsCompat.Type.systemBars()) + } + + private fun exitFullscreen() { + supportActionBar?.show() + showAlbumRail() + WindowCompat.getInsetsController(window, window.decorView) + .show(WindowInsetsCompat.Type.systemBars()) + isFullscreen = false + } + + private fun hideAlbumRail() { + val rail = binding.mediaPreviewAlbumRailContainer + rail.animate().cancel() + rail.animate() + .translationY(rail.height.toFloat()) + .alpha(0f) + .setDuration(200) + .withEndAction { rail.visibility = View.GONE } + .start() + } + + private fun showAlbumRail() { + // never show the rail in landscape + if(isLandscape()) return + + val rail = binding.mediaPreviewAlbumRailContainer + rail.animate().cancel() + rail.visibility = View.VISIBLE + rail.animate() + .translationY(0f) + .alpha(1f) + .setDuration(200) + .start() + } + + @SuppressLint("MissingSuperCall") + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults) + } + + override fun onModified(recipient: Recipient) { + runOnMain { this.updateActionBar() } + } + + override fun onRailItemClicked(distanceFromActive: Int) { + binding.mediaPager.currentItem = binding.mediaPager.currentItem + distanceFromActive + } + + override fun onRailItemDeleteClicked(distanceFromActive: Int) { + throw UnsupportedOperationException("Callback unsupported.") + } + + private fun updateActionBar() { + val mediaItem = currentMediaItem + + if (mediaItem != null) { + val relativeTimeSpan: CharSequence = if (mediaItem.date > 0) { + dateUtils.getDisplayFormattedTimeSpanString(mediaItem.date) + } else { + getString(R.string.draft) + } + + if (mediaItem.outgoing) supportActionBar?.title = getString(R.string.you) + else if (mediaItem.recipient != null) supportActionBar?.title = + mediaItem.recipient.name + else supportActionBar?.title = "" + + supportActionBar?.subtitle = relativeTimeSpan + } + } + + public override fun onPause() { + super.onPause() + + adapter?.pause(binding.mediaPager.currentItem) + } + + override fun onDestroy() { + adapter?.cleanUp() + super.onDestroy() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + initializeResources() + } + + fun onMediaPaused(videoUri: Uri, position: Long){ + viewModel.savePlaybackPosition(videoUri, position) + } + + fun getLastPlaybackPosition(videoUri: Uri): Long { + return viewModel.getSavedPlaybackPosition(videoUri) + } + + private fun initializeViews() { + binding.mediaPager.offscreenPageLimit = 1 + + albumRailAdapter = MediaRailAdapter(Glide.with(this), this, false) + + binding.mediaPreviewAlbumRail.layoutManager = + LinearLayoutManager( + this, + LinearLayoutManager.HORIZONTAL, + false + ) + binding.mediaPreviewAlbumRail.adapter = albumRailAdapter + + setSupportActionBar(findViewById(R.id.toolbar)) + + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setHomeButtonEnabled(true) + } + + private fun initializeResources() { + val address = intent.getParcelableExtra
( + ADDRESS_EXTRA + ) + + initialMediaUri = intent.data + initialMediaType = intent.type + initialMediaSize = intent.getLongExtra(SIZE_EXTRA, 0) + initialCaption = intent.getStringExtra(CAPTION_EXTRA) + leftIsRecent = intent.getBooleanExtra(LEFT_IS_RECENT_EXTRA, false) + + conversationRecipient = if (address != null) { + Recipient.from( + this, + address, + true + ) + } else { + null + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + // always hide the rail in landscape + if (isLandscape()) { + hideAlbumRail() + } else { + if (!isFullscreen) { + showAlbumRail() + } + } + + // Re-apply fullscreen if we were already in it + if (isFullscreen) { + enterFullscreen() + } else { + exitFullscreen() + } + } + + private fun isLandscape(): Boolean { + return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + } + + private fun initializeObservers() { + viewModel.previewData.observe( + this, + Observer { previewData: PreviewData? -> + if (previewData == null || binding == null || binding.mediaPager.adapter == null) { + return@Observer + } + + binding.mediaPreviewAlbumRailContainer.visibility = + if (previewData.albumThumbnails.isEmpty()) View.GONE else View.VISIBLE + albumRailAdapter?.setMedia(previewData.albumThumbnails, previewData.activePosition) + + // recalculate controls position if we have rail data + if(previewData.albumThumbnails.isNotEmpty()) { + binding.mediaPreviewAlbumRailContainer.viewTreeObserver.addOnGlobalLayoutListener( + object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + binding.mediaPreviewAlbumRailContainer.viewTreeObserver.removeOnGlobalLayoutListener( + this + ) + railHeight = binding.mediaPreviewAlbumRailContainer.height + updateControlsPosition() + } + } + ) + } + + binding.mediaPreviewAlbumRail.smoothScrollToPosition(previewData.activePosition) + }) + } + + private fun initializeMedia() { + if (!isContentTypeSupported(initialMediaType)) { + Log.w(TAG, "Unsupported media type sent to MediaPreviewActivity, finishing.") + Toast.makeText( + applicationContext, + R.string.attachmentsErrorNotSupported, + Toast.LENGTH_LONG + ).show() + finish() + } + + Log.i( + TAG, + "Loading Part URI: $initialMediaUri" + ) + + if (conversationRecipient != null) { + LoaderManager.getInstance(this).restartLoader(0, null, this) + } else { + finish() + } + } + + private fun showOverview() { + conversationRecipient?.address?.let { startActivity(createIntent(this, it)) } + } + + private fun forward() { + val mediaItem = currentMediaItem + + if (mediaItem != null) { + val composeIntent = Intent( + this, + ShareActivity::class.java + ) + composeIntent.putExtra(Intent.EXTRA_STREAM, mediaItem.uri) + composeIntent.setType(mediaItem.mimeType) + startActivity(composeIntent) + } + } + + @SuppressLint("InlinedApi") + private fun saveToDisk() { + val mediaItem = currentMediaItem + if (mediaItem == null) { + Log.w(TAG, "Cannot save a null MediaItem to disk - bailing.") + return + } + + // If we have an attachment then we can take the filename from it, otherwise we have to take the + // more expensive route of looking up or synthesizing a filename from the MediaItem's Uri. + var mediaFilename = "" + if (mediaItem.attachment != null) { + mediaFilename = mediaItem.attachment.filename + } + + if (mediaFilename == null || mediaFilename.isEmpty()) { + mediaFilename = + getFilenameFromUri(this@MediaPreviewActivity, mediaItem.uri, mediaItem.mimeType) + } + + val outputFilename = mediaFilename // We need a final value for the saveTask, below + Log.i( + TAG, + "About to save media as: $outputFilename" + ) + + showOneTimeWarningDialogOrSave(this, 1) { + Permissions.with(this) + .request(Manifest.permission.WRITE_EXTERNAL_STORAGE) + .maxSdkVersion(Build.VERSION_CODES.P) // Note: P is API 28 + .withPermanentDenialDialog(permanentlyDeniedStorageText) + .onAnyDenied { + Toast.makeText( + this, + permanentlyDeniedStorageText, Toast.LENGTH_LONG + ).show() + } + .onAllGranted { + val saveTask = SaveAttachmentTask(this@MediaPreviewActivity) + val saveDate = if (mediaItem.date > 0) mediaItem.date else nowWithOffset + saveTask.executeOnExecutor( + AsyncTask.THREAD_POOL_EXECUTOR, + SaveAttachmentTask.Attachment( + mediaItem.uri, + mediaItem.mimeType, + saveDate, + outputFilename + ) + ) + if (!mediaItem.outgoing) { + sendMediaSavedNotificationIfNeeded() + } + } + .execute() + Unit + } + } + + private val permanentlyDeniedStorageText: String + get() = Phrase.from( + applicationContext, + R.string.permissionsStorageDeniedLegacy + ) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString() + + private fun sendMediaSavedNotificationIfNeeded() { + if (conversationRecipient == null || conversationRecipient?.isGroupOrCommunityRecipient == true) return + val message = DataExtractionNotification( + MediaSaved( + nowWithOffset + ) + ) + send(message, conversationRecipient!!.address) + } + + @SuppressLint("StaticFieldLeak") + private fun deleteMedia() { + val mediaItem = currentMediaItem + if (mediaItem?.attachment == null) { + return + } + + DeleteMediaPreviewDialog.show(this){ + AttachmentUtil.deleteAttachment(applicationContext, mediaItem.attachment) + finish() + } + } + + override fun onPrepareOptionsMenu(menu: Menu): Boolean { + super.onPrepareOptionsMenu(menu) + + menu.clear() + val inflater = this.menuInflater + inflater.inflate(R.menu.media_preview, menu) + + val isDeprecatedLegacyGroup = conversationRecipient != null && + conversationRecipient?.isLegacyGroupRecipient == true && + deprecationManager.deprecationState.value == LegacyGroupDeprecationManager.DeprecationState.DEPRECATED + + if (!isMediaInDb || isDeprecatedLegacyGroup) { + menu.findItem(R.id.media_preview__overview).setVisible(false) + menu.findItem(R.id.delete).setVisible(false) + } + + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + super.onOptionsItemSelected(item) + + when (item.itemId) { + R.id.media_preview__overview -> { + showOverview() + return true + } + + R.id.media_preview__forward -> { + forward() + return true + } + + R.id.save -> { + saveToDisk() + return true + } + + R.id.delete -> { + deleteMedia() + return true + } + + android.R.id.home -> { + finish() + return true + } + } + + return false + } + + private val isMediaInDb: Boolean + get() = conversationRecipient != null + + private val currentMediaItem: MediaItem? + get() { + if (adapter == null) return null + + try { + return adapter!!.getMediaItemFor(binding.mediaPager.currentItem) + } catch (e: Exception) { + Log.w(TAG, "Error getting current media item", e) + return null + } + } + + override fun onCreateLoader(id: Int, args: Bundle?): Loader?> { + return PagingMediaLoader( + this, + conversationRecipient!!, initialMediaUri!!, leftIsRecent + ) + } + + override fun onLoadFinished(loader: Loader?>, data: Pair?) { + if (data == null) return + + viewPagerListener?.let{ binding.mediaPager.unregisterOnPageChangeCallback(it) } + + adapter = CursorPagerAdapter( + this, Glide.with(this), + window, data.first, data.second, leftIsRecent + ) + binding.mediaPager.adapter = adapter + + updateControlsPosition() + + viewModel.setCursor(this, data.first, leftIsRecent) + + val item = max(min(data.second, adapter!!.itemCount - 1), 0) + + viewPagerListener = ViewPagerListener() + binding.mediaPager.registerOnPageChangeCallback(viewPagerListener!!) + + try { + binding.mediaPager.setCurrentItem(item, false) + } catch (e: CursorIndexOutOfBoundsException) { + throw RuntimeException( + "data.second = " + data.second + " leftIsRecent = " + leftIsRecent, e + ) + } + + if (item == 0) { + viewPagerListener?.onPageSelected(0) + } + } + + override fun onLoaderReset(loader: Loader?>) { /* Do nothing */ + } + + private inner class ViewPagerListener : ViewPager2.OnPageChangeCallback() { + private var currentPage = -1 + + override fun onPageSelected(position: Int) { + if (currentPage != -1 && currentPage != position) onPageUnselected(currentPage) + currentPage = position + + if (adapter == null) return + + try { + val item = adapter!!.getMediaItemFor(position) + if (item.recipient != null) item.recipient.addListener(this@MediaPreviewActivity) + viewModel.setActiveAlbumRailItem(this@MediaPreviewActivity, position) + updateActionBar() + } catch (e: Exception){ + finish() + } + } + + + fun onPageUnselected(position: Int) { + if (adapter == null) return + + try { + val item = adapter!!.getMediaItemFor(position) + if (item.recipient != null) item.recipient.removeListener(this@MediaPreviewActivity) + } catch (e: CursorIndexOutOfBoundsException) { + throw RuntimeException("position = $position leftIsRecent = $leftIsRecent", e) + } catch (e: Exception){ + finish() + } + + adapter!!.pause(position) + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + /* Do nothing */ + } + + override fun onPageScrollStateChanged(state: Int) { /* Do nothing */ + } + } + + private class CursorPagerAdapter( + context: Context, private val glideRequests: RequestManager, + private val window: Window, private val cursor: Cursor, private var autoPlayPosition: Int, + private val leftIsRecent: Boolean + ) : MediaItemAdapter() { + private val mediaViews = WeakHashMap() + + private val context: Context = context + + private var controlsYPosition: Int = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return object : RecyclerView.ViewHolder( + MediaViewPageBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ).root + ) {} + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val binding = MediaViewPageBinding.bind(holder.itemView) + + val autoplay = position == autoPlayPosition + val cursorPosition = getCursorPosition(position) + + autoPlayPosition = -1 + + cursor.moveToPosition(cursorPosition) + + val mediaRecord = MediaRecord.from(context, cursor) + + // Set fullscreen toggle listener + (context as? MediaPreviewActivity)?.let { binding.mediaView.setFullscreenToggleListener(it) } + + try { + if (mediaRecord.attachment.dataUri == null) throw AssertionError() + binding.mediaView.set(glideRequests, window, mediaRecord.attachment.dataUri!!, mediaRecord.attachment.contentType, mediaRecord.attachment.size, autoplay) + binding.mediaView.setControlsYPosition(controlsYPosition) + + // try to resume where we were if we have a saved playback position + val playbackPosition = (context as? MediaPreviewActivity)?.getLastPlaybackPosition(mediaRecord.attachment.dataUri!!) + if(playbackPosition != 0L) binding.mediaView.seek(playbackPosition) + } catch (e: IOException) { + Log.w(TAG, e) + } + + mediaViews[position] = binding.mediaView + } + + override fun getItemCount(): Int { + return cursor.count + } + + override fun getMediaItemFor(position: Int): MediaItem { + cursor.moveToPosition(getCursorPosition(position)) + val mediaRecord = MediaRecord.from(context, cursor) + val address = mediaRecord.address + + if (mediaRecord.attachment.dataUri == null) throw AssertionError() + + return MediaItem( + if (address != null) Recipient.from(context, address, true) else null, + mediaRecord.attachment, + mediaRecord.attachment.dataUri!!, + mediaRecord.contentType, + mediaRecord.date, + mediaRecord.isOutgoing + ) + } + + override fun pause(position: Int) { + val mediaView = mediaViews[position] + val playbackPosition = mediaView?.pause() ?: 0L + // save the last playback position on pause + (context as? MediaPreviewActivity)?.onMediaPaused(getMediaItemFor(position).uri, playbackPosition) + } + + override fun cleanUp() { + mediaViews.forEach{ + it.value.cleanup() + } + } + + fun getCursorPosition(position: Int): Int { + val unclamped = if (leftIsRecent) position else cursor.count - 1 - position + return max(min(unclamped, cursor.count - 1), 0) + } + + override fun setControlsYPosition(position: Int){ + controlsYPosition = position + + // Update all existing MediaViews immediately + mediaViews.values.forEach { mediaView -> + mediaView.setControlsYPosition(position) + } + } + } + + class MediaItem( + val recipient: Recipient?, + val attachment: DatabaseAttachment?, + val uri: Uri, + val mimeType: String, + val date: Long, + val outgoing: Boolean + ) + + internal abstract class MediaItemAdapter : + RecyclerView.Adapter() { + abstract fun getMediaItemFor(position: Int): MediaItem + abstract fun pause(position: Int) + abstract fun cleanUp() + abstract fun setControlsYPosition(position: Int) + } + + companion object { + private val TAG: String = MediaPreviewActivity::class.java.simpleName + + const val ADDRESS_EXTRA: String = "address" + const val DATE_EXTRA: String = "date" + const val SIZE_EXTRA: String = "size" + const val CAPTION_EXTRA: String = "caption" + const val OUTGOING_EXTRA: String = "outgoing" + const val LEFT_IS_RECENT_EXTRA: String = "left_is_recent" + + fun getPreviewIntent(context: Context?, args: MediaPreviewArgs): Intent? { + return getPreviewIntent( + context, args.slide, + args.mmsRecord, args.thread + ) + } + + fun getPreviewIntent( + context: Context?, + slide: Slide, + mms: MmsMessageRecord, + threadRecipient: Recipient + ): Intent? { + var previewIntent: Intent? = null + if (isContentTypeSupported(slide.contentType) && slide.uri != null) { + previewIntent = Intent(context, MediaPreviewActivity::class.java) + previewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .setDataAndType(slide.uri, slide.contentType) + .putExtra(ADDRESS_EXTRA, threadRecipient.address) + .putExtra(OUTGOING_EXTRA, mms.isOutgoing) + .putExtra(DATE_EXTRA, mms.timestamp) + .putExtra(SIZE_EXTRA, slide.asAttachment().size) + .putExtra(CAPTION_EXTRA, slide.caption.orNull()) + .putExtra(LEFT_IS_RECENT_EXTRA, false) + } + return previewIntent + } + + + fun isContentTypeSupported(contentType: String?): Boolean { + return contentType != null && (contentType.startsWith("image/") || contentType.startsWith( + "video/" + )) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt index 00e2c3d6d8..3a812cbc6d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt @@ -6,6 +6,6 @@ import org.thoughtcrime.securesms.mms.Slide data class MediaPreviewArgs( val slide: Slide, - val mmsRecord: MmsMessageRecord?, - val thread: Recipient?, + val mmsRecord: MmsMessageRecord, + val thread: Recipient, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ScreenLockActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ScreenLockActivity.kt index 281258e158..1c5ba1e8b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ScreenLockActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ScreenLockActivity.kt @@ -43,7 +43,6 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.setScree import org.session.libsession.utilities.TextSecurePreferences.Companion.setScreenLockTimeout import org.session.libsession.utilities.ThemeUtil import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.components.AnimatingToggle import org.thoughtcrime.securesms.crypto.BiometricSecretProvider import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.service.KeyCachingService.KeySetBinder @@ -52,7 +51,6 @@ class ScreenLockActivity : BaseActionBarActivity() { private val TAG: String = ScreenLockActivity::class.java.simpleName private lateinit var fingerprintPrompt: ImageView - private lateinit var visibilityToggle: AnimatingToggle private var biometricPrompt: BiometricPrompt? = null private var promptInfo: BiometricPrompt.PromptInfo? = null @@ -308,7 +306,6 @@ class ScreenLockActivity : BaseActionBarActivity() { .put(APP_NAME_KEY, getString(R.string.app_name)) .format().toString() - visibilityToggle = findViewById(R.id.button_toggle) fingerprintPrompt = findViewById(R.id.fingerprint_auth_container) fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt index efe800d81d..03126f64e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt @@ -45,7 +45,7 @@ import org.thoughtcrime.securesms.contacts.ShareContactListFragment.OnContactSel import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.mms.PartAuthority -import org.thoughtcrime.securesms.providers.BlobProvider +import org.thoughtcrime.securesms.providers.BlobUtils import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.applySafeInsetsPaddings import java.io.FileInputStream @@ -106,7 +106,7 @@ class ShareActivity : ScreenLockActionBarActivity(), OnContactSelectedListener { public override fun onPause() { super.onPause() if (!isPassingAlongMedia && resolvedExtra != null) { - BlobProvider.getInstance().delete(this, resolvedExtra!!) + BlobUtils.getInstance().delete(this, resolvedExtra!!) if (!isFinishing) { finish() } } } @@ -295,11 +295,11 @@ class ShareActivity : ScreenLockActionBarActivity(), OnContactSelectedListener { cursor?.close() } - return BlobProvider.getInstance() + return BlobUtils.getInstance() .forData(inputStream, if (fileSize == null) 0 else fileSize) .withMimeType(mimeType!!) .withFileName(fileName!!) - .createForMultipleSessionsOnDisk(context, BlobProvider.ErrorListener { e: IOException? -> Log.w(TAG, "Failed to write to disk.", e) }) + .createForMultipleSessionsOnDisk(context, BlobUtils.ErrorListener { e: IOException? -> Log.w(TAG, "Failed to write to disk.", e) }) .get() } catch (ioe: Exception) { Log.w(TAG, ioe) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentServer.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentServer.java deleted file mode 100644 index 176a8c290f..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentServer.java +++ /dev/null @@ -1,372 +0,0 @@ -package org.thoughtcrime.securesms.attachments; - - -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; - -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.mms.PartAuthority; - -import org.session.libsignal.utilities.Hex; -import org.session.libsession.utilities.Util; -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; - -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.security.MessageDigest; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.StringTokenizer; - -/** - * @author Stefan "frostymarvelous" Froelich - */ -public class AttachmentServer implements Runnable { - - private static final String TAG = AttachmentServer.class.getSimpleName(); - - private final Context context; - private final Attachment attachment; - private final ServerSocket socket; - private final int port; - private final String auth; - - private volatile boolean isRunning; - - public AttachmentServer(Context context, Attachment attachment) - throws IOException - { - try { - this.context = context.getApplicationContext(); - this.attachment = attachment; - this.socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[]{127, 0, 0, 1})); - this.port = socket.getLocalPort(); - this.auth = Hex.toStringCondensed(Util.getSecretBytes(16)); - - this.socket.setSoTimeout(5000); - } catch (UnknownHostException e) { - throw new AssertionError(e); - } - } - - public Uri getUri() { - return Uri.parse(String.format(Locale.ROOT, "http://127.0.0.1:%d/%s", port, auth)); - } - - public void start() { - isRunning = true; - new Thread(this).start(); - } - - public void stop() { - isRunning = false; - } - - @Override - public void run() { - while (isRunning) { - Socket client = null; - - try { - client = socket.accept(); - - if (client != null) { - StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client, "/" + auth); - - if (task.processRequest()) { - task.execute(); - } - } - - } catch (SocketTimeoutException e) { - Log.w(TAG, e); - } catch (IOException e) { - Log.e(TAG, "Error connecting to client", e); - } finally { - try {if (client != null) client.close();} catch (IOException e) {} - } - } - - Log.d(TAG, "Proxy interrupted. Shutting down."); - } - - - private class StreamToMediaPlayerTask { - - private final @NonNull Socket client; - private final @NonNull String auth; - - private long cbSkip; - private Properties parameters; - private Properties request; - private Properties requestHeaders; -// private String filePath; - - public StreamToMediaPlayerTask(@NonNull Socket client, @NonNull String auth) { - this.client = client; - this.auth = auth; - } - - public boolean processRequest() throws IOException { - InputStream is = client.getInputStream(); - final int bufferSize = 8192; - byte[] buffer = new byte[bufferSize]; - int splitByte = 0; - int readLength = 0; - - { - int read = is.read(buffer, 0, bufferSize); - while (read > 0) { - readLength += read; - splitByte = findHeaderEnd(buffer, readLength); - if (splitByte > 0) - break; - read = is.read(buffer, readLength, bufferSize - readLength); - } - } - - // Create a BufferedReader for parsing the header. - ByteArrayInputStream hbis = new ByteArrayInputStream(buffer, 0, readLength); - BufferedReader hin = new BufferedReader(new InputStreamReader(hbis)); - - request = new Properties(); - parameters = new Properties(); - requestHeaders = new Properties(); - - try { - decodeHeader(hin, request, parameters, requestHeaders); - } catch (InterruptedException e1) { - Log.e(TAG, "Exception: " + e1.getMessage()); - e1.printStackTrace(); - } - - for (Map.Entry e : requestHeaders.entrySet()) { - Log.i(TAG, "Header: " + e.getKey() + " : " + e.getValue()); - } - - String range = requestHeaders.getProperty("range"); - - if (range != null) { - Log.i(TAG, "range is: " + range); - range = range.substring(6); - int charPos = range.indexOf('-'); - if (charPos > 0) { - range = range.substring(0, charPos); - } - cbSkip = Long.parseLong(range); - Log.i(TAG, "range found!! " + cbSkip); - } - - if (!"GET".equals(request.get("method"))) { - Log.e(TAG, "Only GET is supported: " + request.get("method")); - return false; - } - - String receivedAuth = request.getProperty("uri"); - - if (receivedAuth == null || !MessageDigest.isEqual(receivedAuth.getBytes(), auth.getBytes())) { - Log.w(TAG, "Bad auth token!"); - return false; - } - -// filePath = request.getProperty("uri"); - - return true; - } - - protected void execute() throws IOException { - InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.getDataUri()); - long fileSize = attachment.getSize(); - - String headers = ""; - if (cbSkip > 0) {// It is a seek or skip request if there's a Range - // header - headers += "HTTP/1.1 206 Partial Content\r\n"; - headers += "Content-Type: " + attachment.getContentType() + "\r\n"; - headers += "Accept-Ranges: bytes\r\n"; - headers += "Content-Length: " + (fileSize - cbSkip) + "\r\n"; - headers += "Content-Range: bytes " + cbSkip + "-" + (fileSize - 1) + "/" + fileSize + "\r\n"; - headers += "Connection: Keep-Alive\r\n"; - headers += "\r\n"; - } else { - headers += "HTTP/1.1 200 OK\r\n"; - headers += "Content-Type: " + attachment.getContentType() + "\r\n"; - headers += "Accept-Ranges: bytes\r\n"; - headers += "Content-Length: " + fileSize + "\r\n"; - headers += "Connection: Keep-Alive\r\n"; - headers += "\r\n"; - } - - Log.i(TAG, "headers: " + headers); - - OutputStream output = null; - byte[] buff = new byte[64 * 1024]; - try { - output = new BufferedOutputStream(client.getOutputStream(), 32 * 1024); - output.write(headers.getBytes()); - - inputStream.skip(cbSkip); -// dataSource.skipFully(data, cbSkip);//try to skip as much as possible - - // Loop as long as there's stuff to send and client has not closed - int cbRead; - while (!client.isClosed() && (cbRead = inputStream.read(buff, 0, buff.length)) != -1) { - output.write(buff, 0, cbRead); - } - } - catch (SocketException socketException) { - Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly"); - } - catch (Exception e) { - Log.e(TAG, "Exception thrown from streaming task:"); - Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage()); - } - - // Cleanup - try { - if (output != null) { - output.close(); - } - client.close(); - } - catch (IOException e) { - Log.e(TAG, "IOException while cleaning up streaming task:"); - Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage()); - e.printStackTrace(); - } - } - - /** - * Find byte index separating header from body. It must be the last byte of - * the first two sequential new lines. - **/ - private int findHeaderEnd(final byte[] buf, int rlen) { - int splitbyte = 0; - while (splitbyte + 3 < rlen) { - if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' - && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') - return splitbyte + 4; - splitbyte++; - } - return 0; - } - - - /** - * Decodes the sent headers and loads the data into java Properties' key - - * value pairs - **/ - private void decodeHeader(BufferedReader in, Properties pre, - Properties parms, Properties header) throws InterruptedException { - try { - // Read the request line - String inLine = in.readLine(); - if (inLine == null) - return; - StringTokenizer st = new StringTokenizer(inLine); - if (!st.hasMoreTokens()) - Log.e(TAG, "BAD REQUEST: Syntax error. Usage: GET /example/file.html"); - - String method = st.nextToken(); - pre.put("method", method); - - if (!st.hasMoreTokens()) - Log.e(TAG, "BAD REQUEST: Missing URI. Usage: GET /example/file.html"); - - String uri = st.nextToken(); - - // Decode parameters from the URI - int qmi = uri.indexOf('?'); - if (qmi >= 0) { - decodeParms(uri.substring(qmi + 1), parms); - uri = decodePercent(uri.substring(0, qmi)); - } else - uri = decodePercent(uri); - - // If there's another token, it's protocol version, - // followed by HTTP headers. Ignore version but parse headers. - // NOTE: this now forces header names lowercase since they are - // case insensitive and vary by client. - if (st.hasMoreTokens()) { - String line = in.readLine(); - while (line != null && line.trim().length() > 0) { - int p = line.indexOf(':'); - if (p >= 0) - header.put(line.substring(0, p).trim().toLowerCase(), - line.substring(p + 1).trim()); - line = in.readLine(); - } - } - - pre.put("uri", uri); - } catch (IOException ioe) { - Log.e(TAG, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); - } - } - - /** - * Decodes parameters in percent-encoded URI-format ( e.g. - * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given - * Properties. NOTE: this doesn't support multiple identical keys due to the - * simplicity of Properties -- if you need multiples, you might want to - * replace the Properties with a Hashtable of Vectors or such. - */ - private void decodeParms(String parms, Properties p) - throws InterruptedException { - if (parms == null) - return; - - StringTokenizer st = new StringTokenizer(parms, "&"); - while (st.hasMoreTokens()) { - String e = st.nextToken(); - int sep = e.indexOf('='); - if (sep >= 0) - p.put(decodePercent(e.substring(0, sep)).trim(), - decodePercent(e.substring(sep + 1))); - } - } - - /** - * Decodes the percent encoding scheme.
- * For example: "an+example%20string" -> "an example string" - */ - private String decodePercent(String str) throws InterruptedException { - try { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - switch (c) { - case '+': - sb.append(' '); - break; - case '%': - sb.append((char) Integer.parseInt( - str.substring(i + 1, i + 3), 16)); - i += 2; - break; - default: - sb.append(c); - break; - } - } - return sb.toString(); - } catch (Exception e) { - Log.e(TAG, "BAD REQUEST: Bad percent-encoding."); - return null; - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.kt b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.kt index 41ba508602..91c469d027 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.kt @@ -125,14 +125,14 @@ fun recordAudio( try { val file by lazy { - File.createTempFile("audio_recording_", ".aac", context.cacheDir) + File.createTempFile("audio_recording_", ".m4a", context.cacheDir) } recorder.setAudioSource(MediaRecorder.AudioSource.MIC) recorder.setAudioChannels(1) recorder.setAudioSamplingRate(44100) recorder.setAudioEncodingBitRate(32000) - recorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS) + recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) recorder.setOutputFile(file) recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC) recorder.setOnErrorListener { _, what, extra -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java index ac70a6024a..0238fd0a9b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java @@ -29,7 +29,6 @@ import org.session.libsession.utilities.Util; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.attachments.AttachmentServer; import org.thoughtcrime.securesms.mms.AudioSlide; public class AudioSlidePlayer implements SensorEventListener { @@ -48,7 +47,6 @@ public class AudioSlidePlayer implements SensorEventListener { private @NonNull WeakReference listener; private @Nullable ExoPlayer mediaPlayer; - private @Nullable AttachmentServer audioAttachmentServer; private long startTime; public synchronized static AudioSlidePlayer createFor(@NonNull Context context, @@ -92,12 +90,9 @@ private void play(final double progress, boolean earpiece) throws IOException { if (this.mediaPlayer != null) { stop(); } this.mediaPlayer = new ExoPlayer.Builder(context).build(); - this.audioAttachmentServer = new AttachmentServer(context, slide.asAttachment()); this.startTime = System.currentTimeMillis(); - audioAttachmentServer.start(); - - MediaItem mediaItem = MediaItem.fromUri(audioAttachmentServer.getUri()); + MediaItem mediaItem = MediaItem.fromUri(slide.asAttachment().getDataUri()); mediaPlayer.setMediaItem(mediaItem); mediaPlayer.setAudioAttributes(new AudioAttributes.Builder() @@ -149,11 +144,6 @@ public void onPlaybackStateChanged(int playbackState) { mediaPlayer.release(); mediaPlayer = null; - if (audioAttachmentServer != null) { - audioAttachmentServer.stop(); - audioAttachmentServer = null; - } - sensorManager.unregisterListener(AudioSlidePlayer.this); if (wakeLock != null && wakeLock.isHeld()) { @@ -174,11 +164,6 @@ public void onPlayerError(PlaybackException error) { synchronized (AudioSlidePlayer.this) { mediaPlayer = null; - if (audioAttachmentServer != null) { - audioAttachmentServer.stop(); - audioAttachmentServer = null; - } - sensorManager.unregisterListener(AudioSlidePlayer.this); if (wakeLock != null && wakeLock.isHeld()) { @@ -205,12 +190,9 @@ public synchronized void stop() { this.mediaPlayer.release(); } - if (this.audioAttachmentServer != null) { this.audioAttachmentServer.stop(); } - sensorManager.unregisterListener(AudioSlidePlayer.this); this.mediaPlayer = null; - this.audioAttachmentServer = null; } public synchronized static void stopAll() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ControllableTabLayout.java b/app/src/main/java/org/thoughtcrime/securesms/components/ControllableTabLayout.java deleted file mode 100644 index 969945621f..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ControllableTabLayout.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import com.google.android.material.tabs.TabLayout; -import android.util.AttributeSet; -import android.view.View; - -import java.util.List; - -/** - * An implementation of {@link TabLayout} that disables taps when the view is disabled. - */ -public class ControllableTabLayout extends TabLayout { - - private List touchables; - - public ControllableTabLayout(Context context) { - super(context); - } - - public ControllableTabLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ControllableTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - public void setEnabled(boolean enabled) { - if (isEnabled() && !enabled) { - touchables = getTouchables(); - } - - for (View touchable : touchables) { - touchable.setClickable(enabled); - } - - super.setEnabled(enabled); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/HidingLinearLayout.java b/app/src/main/java/org/thoughtcrime/securesms/components/HidingLinearLayout.java deleted file mode 100644 index bdb7c2fdf0..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/HidingLinearLayout.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.ScaleAnimation; -import android.widget.LinearLayout; - -import androidx.interpolator.view.animation.FastOutSlowInInterpolator; - -public class HidingLinearLayout extends LinearLayout { - - public HidingLinearLayout(Context context) { - super(context); - } - - public HidingLinearLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public HidingLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public void hide() { - if (!isEnabled() || getVisibility() == GONE) return; - - AnimationSet animation = new AnimationSet(true); - animation.addAnimation(new ScaleAnimation(1, 0.5f, 1, 1, Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF, 0.5f)); - animation.addAnimation(new AlphaAnimation(1, 0)); - animation.setDuration(100); - - animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - setVisibility(GONE); - } - }); - - animateWith(animation); - } - - public void show() { - if (!isEnabled() || getVisibility() == VISIBLE) return; - - setVisibility(VISIBLE); - - AnimationSet animation = new AnimationSet(true); - animation.addAnimation(new ScaleAnimation(0.5f, 1, 1, 1, Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF, 0.5f)); - animation.addAnimation(new AlphaAnimation(0, 1)); - animation.setDuration(100); - - animateWith(animation); - } - - private void animateWith(Animation animation) { - animation.setDuration(150); - animation.setInterpolator(new FastOutSlowInInterpolator()); - startAnimation(animation); - } - - public void disable() { - setVisibility(GONE); - setEnabled(false); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java b/app/src/main/java/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java deleted file mode 100644 index bebc12a7e5..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java +++ /dev/null @@ -1,269 +0,0 @@ -/** - * Copyright (C) 2014 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.graphics.Rect; -import android.preference.PreferenceManager; -import android.util.AttributeSet; -import android.view.Surface; -import android.view.View; - -import androidx.appcompat.widget.LinearLayoutCompat; - -import org.session.libsession.utilities.ServiceUtil; -import org.session.libsession.utilities.Util; -import org.session.libsignal.utilities.Log; - -import java.lang.reflect.Field; -import java.util.HashSet; -import java.util.Set; - -import network.loki.messenger.R; - -/** - * LinearLayout that, when a view container, will report back when it thinks a soft keyboard - * has been opened and what its height would be. - */ -public class KeyboardAwareLinearLayout extends LinearLayoutCompat { - private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName(); - - private final Rect rect = new Rect(); - private final Set hiddenListeners = new HashSet<>(); - private final Set shownListeners = new HashSet<>(); - private final int minKeyboardSize; - private final int minCustomKeyboardSize; - private final int defaultCustomKeyboardSize; - private final int minCustomKeyboardTopMarginPortrait; - private final int minCustomKeyboardTopMarginLandscape; - private final int statusBarHeight; - - private int viewInset; - - private boolean keyboardOpen = false; - private int rotation = -1; - private boolean isFullscreen = false; - - public KeyboardAwareLinearLayout(Context context) { - this(context, null); - } - - public KeyboardAwareLinearLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public KeyboardAwareLinearLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - final int statusBarRes = getResources().getIdentifier("status_bar_height", "dimen", "android"); - minKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_keyboard_size); - minCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_size); - defaultCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.default_custom_keyboard_size); - minCustomKeyboardTopMarginPortrait = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin_portrait); - minCustomKeyboardTopMarginLandscape = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin_portrait); - statusBarHeight = statusBarRes > 0 ? getResources().getDimensionPixelSize(statusBarRes) : 0; - viewInset = getViewInset(); - } - - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - updateRotation(); - updateKeyboardState(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private void updateRotation() { - int oldRotation = rotation; - rotation = getDeviceRotation(); - if (oldRotation != rotation) { - Log.i(TAG, "rotation changed"); - onKeyboardClose(); - } - } - - private void updateKeyboardState() { - if (viewInset == 0) viewInset = getViewInset(); - - getWindowVisibleDisplayFrame(rect); - - final int availableHeight = getAvailableHeight(); - final int keyboardHeight = availableHeight - (rect.bottom - rect.top); - - if (keyboardHeight > minKeyboardSize) { - if (getKeyboardHeight() != keyboardHeight) { - if (isLandscape()) { - setKeyboardLandscapeHeight(keyboardHeight); - } else { - setKeyboardPortraitHeight(keyboardHeight); - } - } - if (!keyboardOpen) { - onKeyboardOpen(keyboardHeight); - } - } else if (keyboardOpen) { - onKeyboardClose(); - } - } - - private int getViewInset() { - try { - Field attachInfoField = View.class.getDeclaredField("mAttachInfo"); - attachInfoField.setAccessible(true); - Object attachInfo = attachInfoField.get(this); - if (attachInfo != null) { - Field stableInsetsField = attachInfo.getClass().getDeclaredField("mStableInsets"); - stableInsetsField.setAccessible(true); - Rect insets = (Rect)stableInsetsField.get(attachInfo); - return insets.bottom; - } - } catch (NoSuchFieldException nsfe) { - Log.w(TAG, "field reflection error when measuring view inset, NoSuchFieldException"); - } catch (IllegalAccessException iae) { - Log.w(TAG, "access reflection error when measuring view inset", iae); - } - return 0; - } - - private int getAvailableHeight() { - final int availableHeight = this.getRootView().getHeight() - viewInset - (!isFullscreen ? statusBarHeight : 0); - final int availableWidth = this.getRootView().getWidth() - (!isFullscreen ? statusBarHeight : 0); - - if (isLandscape() && availableHeight > availableWidth) { - //noinspection SuspiciousNameCombination - return availableWidth; - } - - return availableHeight; - } - - protected void onKeyboardOpen(int keyboardHeight) { - Log.i(TAG, "onKeyboardOpen(" + keyboardHeight + ")"); - keyboardOpen = true; - - notifyShownListeners(); - } - - protected void onKeyboardClose() { - Log.i(TAG, "onKeyboardClose()"); - keyboardOpen = false; - notifyHiddenListeners(); - } - - public boolean isKeyboardOpen() { - return keyboardOpen; - } - - public int getKeyboardHeight() { - return isLandscape() ? getKeyboardLandscapeHeight() : getKeyboardPortraitHeight(); - } - - public boolean isLandscape() { - int rotation = getDeviceRotation(); - return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270; - } - private int getDeviceRotation() { - return ServiceUtil.getWindowManager(getContext()).getDefaultDisplay().getRotation(); - } - - private int getKeyboardLandscapeHeight() { - int keyboardHeight = PreferenceManager.getDefaultSharedPreferences(getContext()) - .getInt("keyboard_height_landscape", defaultCustomKeyboardSize); - return Util.clamp(keyboardHeight, minCustomKeyboardSize, getRootView().getHeight() - minCustomKeyboardTopMarginLandscape); - } - - private int getKeyboardPortraitHeight() { - int keyboardHeight = PreferenceManager.getDefaultSharedPreferences(getContext()) - .getInt("keyboard_height_portrait", defaultCustomKeyboardSize); - return Util.clamp(keyboardHeight, minCustomKeyboardSize, getRootView().getHeight() - minCustomKeyboardTopMarginPortrait); - } - - private void setKeyboardPortraitHeight(int height) { - PreferenceManager.getDefaultSharedPreferences(getContext()) - .edit().putInt("keyboard_height_portrait", height).apply(); - } - - private void setKeyboardLandscapeHeight(int height) { - PreferenceManager.getDefaultSharedPreferences(getContext()) - .edit().putInt("keyboard_height_landscape", height).apply(); - } - - public void postOnKeyboardClose(final Runnable runnable) { - if (keyboardOpen) { - addOnKeyboardHiddenListener(new OnKeyboardHiddenListener() { - @Override public void onKeyboardHidden() { - removeOnKeyboardHiddenListener(this); - runnable.run(); - } - }); - } else { - runnable.run(); - } - } - - public void postOnKeyboardOpen(final Runnable runnable) { - if (!keyboardOpen) { - addOnKeyboardShownListener(new OnKeyboardShownListener() { - @Override public void onKeyboardShown() { - removeOnKeyboardShownListener(this); - runnable.run(); - } - }); - } else { - runnable.run(); - } - } - - public void addOnKeyboardHiddenListener(OnKeyboardHiddenListener listener) { - hiddenListeners.add(listener); - } - - public void removeOnKeyboardHiddenListener(OnKeyboardHiddenListener listener) { - hiddenListeners.remove(listener); - } - - public void addOnKeyboardShownListener(OnKeyboardShownListener listener) { - shownListeners.add(listener); - } - - public void removeOnKeyboardShownListener(OnKeyboardShownListener listener) { - shownListeners.remove(listener); - } - - public void setFullscreen(boolean isFullscreen) { - this.isFullscreen = isFullscreen; - } - - private void notifyHiddenListeners() { - final Set listeners = new HashSet<>(hiddenListeners); - for (OnKeyboardHiddenListener listener : listeners) { - listener.onKeyboardHidden(); - } - } - - private void notifyShownListeners() { - final Set listeners = new HashSet<>(shownListeners); - for (OnKeyboardShownListener listener : listeners) { - listener.onKeyboardShown(); - } - } - - public interface OnKeyboardHiddenListener { - void onKeyboardHidden(); - } - - public interface OnKeyboardShownListener { - void onKeyboardShown(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/MaxHeightScrollView.java b/app/src/main/java/org/thoughtcrime/securesms/components/MaxHeightScrollView.java deleted file mode 100644 index 744e1a35e5..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/MaxHeightScrollView.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.content.res.TypedArray; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.widget.ScrollView; - -import network.loki.messenger.R; - -public class MaxHeightScrollView extends ScrollView { - - private int maxHeight = -1; - - public MaxHeightScrollView(Context context) { - super(context); - initialize(null); - } - - public MaxHeightScrollView(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(attrs); - } - - private void initialize(@Nullable AttributeSet attrs) { - if (attrs != null) { - TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView, 0, 0); - - maxHeight = typedArray.getDimensionPixelOffset(R.styleable.MaxHeightScrollView_scrollView_maxHeight, -1); - - typedArray.recycle(); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (maxHeight >= 0) { - heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/MediaView.java b/app/src/main/java/org/thoughtcrime/securesms/components/MediaView.java index 28e65dad74..8a0d5a56e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/MediaView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/MediaView.java @@ -6,21 +6,36 @@ import android.view.View; import android.view.Window; import android.widget.FrameLayout; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; +import androidx.media3.common.util.UnstableApi; + import com.bumptech.glide.RequestManager; -import java.io.IOException; -import network.loki.messenger.R; + import org.session.libsession.utilities.Stub; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.util.FilenameUtils; import org.thoughtcrime.securesms.video.VideoPlayer; +import java.io.IOException; + +import network.loki.messenger.R; + public class MediaView extends FrameLayout { private ZoomingImageView imageView; private Stub videoView; + public interface FullscreenToggleListener { + void toggleFullscreen(); + void setFullscreen(boolean displayFullscreen); + } + + @Nullable + private FullscreenToggleListener fullscreenToggleListener = null; + public MediaView(@NonNull Context context) { super(context); initialize(); @@ -49,17 +64,35 @@ public void set(@NonNull RequestManager glideRequests, @NonNull String mediaType, long size, boolean autoplay) - throws IOException + throws IOException { if (mediaType.startsWith("image/")) { imageView.setVisibility(View.VISIBLE); if (videoView.resolved()) videoView.get().setVisibility(View.GONE); imageView.setImageUri(glideRequests, sourceUri, mediaType); + + // handle fullscreen toggle based on image tap + imageView.setInteractor(new ZoomingImageView.ZoomImageInteractions() { + @Override + public void onImageTapped() { + if (fullscreenToggleListener != null) fullscreenToggleListener.toggleFullscreen(); + } + }); } else if (mediaType.startsWith("video/")) { imageView.setVisibility(View.GONE); videoView.get().setVisibility(View.VISIBLE); videoView.get().setWindow(window); + // react to callbacks from video players and pass it on to the fullscreen handling + videoView.get().setInteractor(new VideoPlayer.VideoPlayerInteractions() { + @Override + public void onControllerVisibilityChanged(boolean visible) { + // go fullscreen once the controls are hidden + if(fullscreenToggleListener != null) fullscreenToggleListener.setFullscreen(!visible); + } + }); + + Context context = getContext(); String filename = FilenameUtils.getFilenameFromUri(context, sourceUri); @@ -69,23 +102,28 @@ public void set(@NonNull RequestManager glideRequests, } } - public void pause() { + public void setControlsYPosition(int yPosition){ if (this.videoView.resolved()){ - this.videoView.get().pause(); + this.videoView.get().setControlsYPosition(yPosition); } } - public void hideControls() { + public Long pause() { if (this.videoView.resolved()){ - this.videoView.get().hideControls(); + return this.videoView.get().pause(); } + + return 0L; } - public @Nullable View getPlaybackControls() { + public void seek(Long position){ if (this.videoView.resolved()){ - return this.videoView.get().getControlView(); + this.videoView.get().seek(position); } - return null; + } + + public void setFullscreenToggleListener(FullscreenToggleListener listener) { + this.fullscreenToggleListener = listener; } public void cleanup() { @@ -94,4 +132,4 @@ public void cleanup() { this.videoView.get().cleanup(); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/NestedScrollableHost.kt b/app/src/main/java/org/thoughtcrime/securesms/components/NestedScrollableHost.kt deleted file mode 100644 index ef27c307c7..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/NestedScrollableHost.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.thoughtcrime.securesms.components - -import android.content.Context -import android.util.AttributeSet -import android.view.MotionEvent -import android.view.View -import android.view.ViewConfiguration -import android.widget.FrameLayout -import androidx.viewpager2.widget.ViewPager2 -import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL -import kotlin.math.absoluteValue -import kotlin.math.sign - -/** - * Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem - * where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as - * ViewPager2. The scrollable element needs to be the immediate and only child of this host layout. - * - * This solution has limitations when using multiple levels of nested scrollable elements - * (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2). - */ -class NestedScrollableHost : FrameLayout { - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - - private var touchSlop = 0 - private var initialX = 0f - private var initialY = 0f - private val parentViewPager: ViewPager2? - get() { - var v: View? = parent as? View - while (v != null && v !is ViewPager2) { - v = v.parent as? View - } - return v as? ViewPager2 - } - - private val child: View? get() = if (childCount > 0) getChildAt(0) else null - - init { - touchSlop = ViewConfiguration.get(context).scaledTouchSlop - } - - private fun canChildScroll(orientation: Int, delta: Float): Boolean { - val direction = -delta.sign.toInt() - return when (orientation) { - 0 -> child?.canScrollHorizontally(direction) ?: false - 1 -> child?.canScrollVertically(direction) ?: false - else -> throw IllegalArgumentException() - } - } - - override fun onInterceptTouchEvent(e: MotionEvent): Boolean { - handleInterceptTouchEvent(e) - return super.onInterceptTouchEvent(e) - } - - private fun handleInterceptTouchEvent(e: MotionEvent) { - val orientation = parentViewPager?.orientation ?: return - - // Early return if child can't scroll in same direction as parent - if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) { - return - } - - if (e.action == MotionEvent.ACTION_DOWN) { - initialX = e.x - initialY = e.y - parent.requestDisallowInterceptTouchEvent(true) - } else if (e.action == MotionEvent.ACTION_MOVE) { - val dx = e.x - initialX - val dy = e.y - initialY - val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL - - // assuming ViewPager2 touch-slop is 2x touch-slop of child - val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f - val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f - - if (scaledDx > touchSlop || scaledDy > touchSlop) { - if (isVpHorizontal == (scaledDy > scaledDx)) { - // Gesture is perpendicular, allow all parents to intercept - parent.requestDisallowInterceptTouchEvent(false) - } else { - // Gesture is parallel, query child if movement in that direction is possible - if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { - // Child can scroll, disallow all parents to intercept - parent.requestDisallowInterceptTouchEvent(true) - } else { - // Child cannot scroll, allow all parents to intercept - parent.requestDisallowInterceptTouchEvent(false) - } - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ObservableTouchEventFrameLayout.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ObservableTouchEventFrameLayout.kt deleted file mode 100644 index 48f22609f9..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ObservableTouchEventFrameLayout.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.thoughtcrime.securesms.components - -import android.content.Context -import android.util.AttributeSet -import android.view.MotionEvent -import android.widget.FrameLayout - -/** - * A FrameLayout that allows you to observe touch events dispatched to it. - * - * Note: this is different from [android.view.View.setOnTouchListener] as it allows you to observe the touch events - * that flow through this parent regardless of whether they are consumed by child views. - */ -class ObservableTouchEventFrameLayout @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - var onTouchDispatchListener: OnTouchListener? = null - - override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { - onTouchDispatchListener?.onTouch(this, ev) - return super.dispatchTouchEvent(ev) - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java b/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java deleted file mode 100644 index 98bce61010..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.thoughtcrime.securesms.components; - - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.MediaStore; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.Key; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.signature.MediaStoreSignature; - -import org.session.libsession.utilities.ViewUtil; -import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; -import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader; - -import network.loki.messenger.R; - -public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.LoaderCallbacks { - - @NonNull private final RecyclerView recyclerView; - @Nullable private OnItemClickedListener listener; - - public RecentPhotoViewRail(Context context) { - this(context, null); - } - - public RecentPhotoViewRail(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public RecentPhotoViewRail(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - inflate(context, R.layout.recent_photo_view, this); - - this.recyclerView = ViewUtil.findById(this, R.id.photo_list); - this.recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)); - this.recyclerView.setItemAnimator(new DefaultItemAnimator()); - } - - public void setListener(@Nullable OnItemClickedListener listener) { - this.listener = listener; - - if (this.recyclerView.getAdapter() != null) { - ((RecentPhotoAdapter)this.recyclerView.getAdapter()).setListener(listener); - } - } - - @Override - public @NonNull Loader onCreateLoader(int id, Bundle args) { - return new RecentPhotosLoader(getContext()); - } - - @Override - public void onLoadFinished(@NonNull Loader loader, Cursor data) { - this.recyclerView.setAdapter(new RecentPhotoAdapter(getContext(), data, RecentPhotosLoader.BASE_URL, listener)); - } - - @Override - public void onLoaderReset(@NonNull Loader loader) { - ((CursorRecyclerViewAdapter)this.recyclerView.getAdapter()).changeCursor(null); - } - - private static class RecentPhotoAdapter extends CursorRecyclerViewAdapter { - - @SuppressWarnings("unused") - private static final String TAG = RecentPhotoAdapter.class.getSimpleName(); - - @NonNull private final Uri baseUri; - @Nullable private OnItemClickedListener clickedListener; - - private RecentPhotoAdapter(@NonNull Context context, @NonNull Cursor cursor, @NonNull Uri baseUri, @Nullable OnItemClickedListener listener) { - super(context, cursor); - this.baseUri = baseUri; - this.clickedListener = listener; - } - - @Override - public RecentPhotoViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { - View itemView = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.recent_photo_view_item, parent, false); - - return new RecentPhotoViewHolder(itemView); - } - - @Override - public void onBindItemViewHolder(RecentPhotoViewHolder viewHolder, @NonNull Cursor cursor) { - viewHolder.imageView.setImageDrawable(null); - - long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)); - long dateTaken = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_TAKEN)); - long dateModified = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_MODIFIED)); - String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.MIME_TYPE)); - String bucketId = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_ID)); - int orientation = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION)); - long size = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.SIZE)); - int width = cursor.getInt(cursor.getColumnIndexOrThrow(getWidthColumn(orientation))); - int height = cursor.getInt(cursor.getColumnIndexOrThrow(getHeightColumn(orientation))); - - final Uri uri = Uri.withAppendedPath(baseUri, Long.toString(id)); - - Key signature = new MediaStoreSignature(mimeType, dateModified, orientation); - - Glide.with(getContext().getApplicationContext()) - .load(uri) - .signature(signature) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .into(viewHolder.imageView); - - viewHolder.imageView.setOnClickListener(v -> { - if (clickedListener != null) clickedListener.onItemClicked(uri, mimeType, bucketId, dateTaken, width, height, size); - }); - - } - - @SuppressWarnings("SuspiciousNameCombination") - private String getWidthColumn(int orientation) { - if (orientation == 0 || orientation == 180) return MediaStore.Images.ImageColumns.WIDTH; - else return MediaStore.Images.ImageColumns.HEIGHT; - } - - @SuppressWarnings("SuspiciousNameCombination") - private String getHeightColumn(int orientation) { - if (orientation == 0 || orientation == 180) return MediaStore.Images.ImageColumns.HEIGHT; - else return MediaStore.Images.ImageColumns.WIDTH; - } - - public void setListener(@Nullable OnItemClickedListener listener) { - this.clickedListener = listener; - } - - static class RecentPhotoViewHolder extends RecyclerView.ViewHolder { - - ImageView imageView; - - RecentPhotoViewHolder(View itemView) { - super(itemView); - - this.imageView = ViewUtil.findById(itemView, R.id.thumbnail); - } - } - } - - public interface OnItemClickedListener { - void onItemClicked(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java deleted file mode 100644 index b246bca4d3..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.Uri; -import android.os.AsyncTask; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.AttributeSet; - - -import org.session.libsignal.utilities.Log; -import android.util.Pair; -import android.view.View; -import android.widget.FrameLayout; - -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.request.target.Target; -import com.davemorrissey.labs.subscaleview.ImageSource; -import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; -import com.davemorrissey.labs.subscaleview.decoder.DecoderFactory; -import com.github.chrisbanes.photoview.PhotoView; - -import network.loki.messenger.R; -import org.thoughtcrime.securesms.components.subsampling.AttachmentBitmapDecoder; -import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder; -import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; -import com.bumptech.glide.RequestManager; -import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.util.BitmapDecodingException; -import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.MediaUtil; - -import java.io.IOException; -import java.io.InputStream; - - -public class ZoomingImageView extends FrameLayout { - - private static final String TAG = ZoomingImageView.class.getSimpleName(); - - private final PhotoView photoView; - private final SubsamplingScaleImageView subsamplingImageView; - - public ZoomingImageView(Context context) { - this(context, null); - } - - public ZoomingImageView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ZoomingImageView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - inflate(context, R.layout.zooming_image_view, this); - - this.photoView = findViewById(R.id.image_view); - this.subsamplingImageView = findViewById(R.id.subsampling_image_view); - - this.subsamplingImageView.setOrientation(SubsamplingScaleImageView.ORIENTATION_USE_EXIF); - } - - @SuppressLint("StaticFieldLeak") - public void setImageUri(@NonNull RequestManager glideRequests, @NonNull Uri uri, @NonNull String contentType) - { - final Context context = getContext(); - final int maxTextureSize = BitmapUtil.getMaxTextureSize(); - - Log.i(TAG, "Max texture size: " + maxTextureSize); - - new AsyncTask>() { - @Override - protected @Nullable Pair doInBackground(Void... params) { - if (MediaUtil.isGif(contentType)) return null; - - try { - InputStream inputStream = PartAuthority.getAttachmentStream(context, uri); - return BitmapUtil.getDimensions(inputStream); - } catch (IOException | BitmapDecodingException e) { - Log.w(TAG, e); - return null; - } - } - - protected void onPostExecute(@Nullable Pair dimensions) { - Log.i(TAG, "Dimensions: " + (dimensions == null ? "(null)" : dimensions.first + ", " + dimensions.second)); - - if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) { - Log.i(TAG, "Loading in standard image view..."); - setImageViewUri(glideRequests, uri); - } else { - Log.i(TAG, "Loading in subsampling image view..."); - setSubsamplingImageViewUri(uri); - } - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void setImageViewUri(@NonNull RequestManager glideRequests, @NonNull Uri uri) { - photoView.setVisibility(View.VISIBLE); - subsamplingImageView.setVisibility(View.GONE); - - glideRequests.load(new DecryptableUri(uri)) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .dontTransform() - .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .into(photoView); - } - - private void setSubsamplingImageViewUri(@NonNull Uri uri) { - subsamplingImageView.setBitmapDecoderFactory(new AttachmentBitmapDecoderFactory()); - subsamplingImageView.setRegionDecoderFactory(new AttachmentRegionDecoderFactory()); - - subsamplingImageView.setVisibility(View.VISIBLE); - photoView.setVisibility(View.GONE); - - subsamplingImageView.setImage(ImageSource.uri(uri)); - } - - public void cleanup() { - photoView.setImageDrawable(null); - subsamplingImageView.recycle(); - } - - private static class AttachmentBitmapDecoderFactory implements DecoderFactory { - @Override - public AttachmentBitmapDecoder make() throws IllegalAccessException, InstantiationException { - return new AttachmentBitmapDecoder(); - } - } - - private static class AttachmentRegionDecoderFactory implements DecoderFactory { - @Override - public AttachmentRegionDecoder make() throws IllegalAccessException, InstantiationException { - return new AttachmentRegionDecoder(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.kt new file mode 100644 index 0000000000..7c8ec46a5d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.kt @@ -0,0 +1,164 @@ +package org.thoughtcrime.securesms.components + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.PointF +import android.net.Uri +import android.os.AsyncTask +import android.util.AttributeSet +import android.util.Pair +import android.view.GestureDetector +import android.view.MotionEvent +import android.widget.FrameLayout +import com.bumptech.glide.RequestManager +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.request.target.Target +import com.davemorrissey.labs.subscaleview.ImageSource +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView +import com.davemorrissey.labs.subscaleview.decoder.DecoderFactory +import com.github.chrisbanes.photoview.PhotoView +import network.loki.messenger.R +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.components.subsampling.AttachmentBitmapDecoder +import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder +import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri +import org.thoughtcrime.securesms.mms.PartAuthority +import org.thoughtcrime.securesms.util.BitmapDecodingException +import org.thoughtcrime.securesms.util.BitmapUtil +import org.thoughtcrime.securesms.util.MediaUtil +import java.io.IOException + +class ZoomingImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : + FrameLayout(context, attrs, defStyleAttr) { + private val photoView: PhotoView + private val subsamplingImageView: SubsamplingScaleImageView + + interface ZoomImageInteractions { + fun onImageTapped() + } + + private var interactor: ZoomImageInteractions? = null + + init { + inflate(context, R.layout.zooming_image_view, this) + + this.photoView = findViewById(R.id.image_view) + + this.subsamplingImageView = findViewById(R.id.subsampling_image_view) + + subsamplingImageView.orientation = SubsamplingScaleImageView.ORIENTATION_USE_EXIF + } + + fun setInteractor(interactor: ZoomImageInteractions?) { + this.interactor = interactor + } + + @SuppressLint("StaticFieldLeak") + fun setImageUri(glideRequests: RequestManager, uri: Uri, contentType: String) { + val context = context + val maxTextureSize = BitmapUtil.getMaxTextureSize() + + Log.i( + TAG, + "Max texture size: $maxTextureSize" + ) + + object : AsyncTask?>() { + override fun doInBackground(vararg params: Void?): Pair? { + if (MediaUtil.isGif(contentType)) return null + + try { + val inputStream = PartAuthority.getAttachmentStream(context, uri) + return BitmapUtil.getDimensions(inputStream) + } catch (e: IOException) { + Log.w(TAG, e) + return null + } catch (e: BitmapDecodingException) { + Log.w(TAG, e) + return null + } + } + + override fun onPostExecute(dimensions: Pair?) { + Log.i( + TAG, + "Dimensions: " + (if (dimensions == null) "(null)" else dimensions.first.toString() + ", " + dimensions.second) + ) + + if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) { + Log.i(TAG, "Loading in standard image view...") + setImageViewUri(glideRequests, uri) + } else { + Log.i(TAG, "Loading in subsampling image view...") + setSubsamplingImageViewUri(uri) + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } + + private fun setImageViewUri(glideRequests: RequestManager, uri: Uri) { + photoView.visibility = VISIBLE + subsamplingImageView.visibility = GONE + + photoView.setOnViewTapListener { _, _, _ -> + if (interactor != null) interactor!!.onImageTapped() + } + + glideRequests.load(DecryptableUri(uri)) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .dontTransform() + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .into(photoView) + } + + @SuppressLint("ClickableViewAccessibility") + private fun setSubsamplingImageViewUri(uri: Uri) { + subsamplingImageView.setBitmapDecoderFactory(AttachmentBitmapDecoderFactory()) + subsamplingImageView.setRegionDecoderFactory(AttachmentRegionDecoderFactory()) + + subsamplingImageView.visibility = VISIBLE + photoView.visibility = GONE + + val gestureDetector = GestureDetector( + context, + object : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + interactor?.onImageTapped() + return true + } + } + ) + + subsamplingImageView.setImage(ImageSource.uri(uri)) + subsamplingImageView.setOnTouchListener { v, event -> + gestureDetector.onTouchEvent(event) + } + } + + fun cleanup() { + photoView.setImageDrawable(null) + subsamplingImageView.recycle() + } + + private class AttachmentBitmapDecoderFactory : DecoderFactory { + @Throws(IllegalAccessException::class, InstantiationException::class) + override fun make(): AttachmentBitmapDecoder { + return AttachmentBitmapDecoder() + } + } + + private class AttachmentRegionDecoderFactory : DecoderFactory { + @Throws(IllegalAccessException::class, InstantiationException::class) + override fun make(): AttachmentRegionDecoder { + return AttachmentRegionDecoder() + } + } + + companion object { + private val TAG: String = ZoomingImageView::class.java.simpleName + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java b/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java deleted file mode 100644 index 7a991eae5c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.thoughtcrime.securesms.components.camera; - -import android.content.Context; -import android.view.SurfaceHolder; -import android.view.SurfaceView; - -public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback { - private boolean ready; - - @SuppressWarnings("deprecation") - public CameraSurfaceView(Context context) { - super(context); - getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - getHolder().addCallback(this); - } - - public boolean isReady() { - return ready; - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - ready = true; - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - ready = false; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraUtils.java b/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraUtils.java deleted file mode 100644 index 1aea994a98..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraUtils.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.thoughtcrime.securesms.components.camera; - -import android.app.Activity; -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; -import android.hardware.Camera.Parameters; -import android.hardware.Camera.Size; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.DisplayMetrics; -import org.session.libsignal.utilities.Log; -import android.view.Surface; - -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; - -@SuppressWarnings("deprecation") -public class CameraUtils { - private static final String TAG = CameraUtils.class.getSimpleName(); - /* - * modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java - */ - public static @Nullable Size getPreferredPreviewSize(int displayOrientation, - int width, - int height, - @NonNull Parameters parameters) { - final int targetWidth = displayOrientation % 180 == 90 ? height : width; - final int targetHeight = displayOrientation % 180 == 90 ? width : height; - final double targetRatio = (double) targetWidth / targetHeight; - - Log.d(TAG, String.format("getPreferredPreviewSize(%d, %d, %d) -> target %dx%d, AR %.02f", - displayOrientation, width, height, - targetWidth, targetHeight, targetRatio)); - - List sizes = parameters.getSupportedPreviewSizes(); - List ideals = new LinkedList<>(); - List bigEnough = new LinkedList<>(); - - for (Size size : sizes) { - Log.d(TAG, String.format(" %dx%d (%.02f)", size.width, size.height, (float)size.width / size.height)); - - if (size.height == size.width * targetRatio && size.height >= targetHeight && size.width >= targetWidth) { - ideals.add(size); - Log.d(TAG, " (ideal ratio)"); - } else if (size.width >= targetWidth && size.height >= targetHeight) { - bigEnough.add(size); - Log.d(TAG, " (good size, suboptimal ratio)"); - } - } - - if (!ideals.isEmpty()) return Collections.min(ideals, new AreaComparator()); - else if (!bigEnough.isEmpty()) return Collections.min(bigEnough, new AspectRatioComparator(targetRatio)); - else return Collections.max(sizes, new AreaComparator()); - } - - // based on - // http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int) - // and http://stackoverflow.com/a/10383164/115145 - public static int getCameraDisplayOrientation(@NonNull Activity activity, - @NonNull CameraInfo info) - { - int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); - int degrees = 0; - DisplayMetrics dm = new DisplayMetrics(); - - activity.getWindowManager().getDefaultDisplay().getMetrics(dm); - - switch (rotation) { - case Surface.ROTATION_0: degrees = 0; break; - case Surface.ROTATION_90: degrees = 90; break; - case Surface.ROTATION_180: degrees = 180; break; - case Surface.ROTATION_270: degrees = 270; break; - } - - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - return (360 - ((info.orientation + degrees) % 360)) % 360; - } else { - return (info.orientation - degrees + 360) % 360; - } - } - - private static class AreaComparator implements Comparator { - @Override - public int compare(Size lhs, Size rhs) { - return Long.signum(lhs.width * lhs.height - rhs.width * rhs.height); - } - } - - private static class AspectRatioComparator extends AreaComparator { - private final double target; - public AspectRatioComparator(double target) { - this.target = target; - } - - @Override - public int compare(Size lhs, Size rhs) { - final double lhsDiff = Math.abs(target - (double) lhs.width / lhs.height); - final double rhsDiff = Math.abs(target - (double) rhs.width / rhs.height); - if (lhsDiff < rhsDiff) return -1; - else if (lhsDiff > rhsDiff) return 1; - else return super.compare(lhs, rhs); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java b/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java deleted file mode 100644 index 5b04e39289..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java +++ /dev/null @@ -1,587 +0,0 @@ -/*** - Copyright (c) 2013-2014 CommonsWare, LLC - Portions Copyright (C) 2007 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -package org.thoughtcrime.securesms.components.camera; - -import android.app.Activity; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Rect; -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; -import android.hardware.Camera.Parameters; -import android.hardware.Camera.Size; -import android.os.AsyncTask; -import android.os.Build; -import android.util.AttributeSet; -import android.view.OrientationEventListener; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.util.BitmapUtil; - -import java.io.IOException; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import network.loki.messenger.R; - -@SuppressWarnings("deprecation") -public class CameraView extends ViewGroup { - private static final String TAG = CameraView.class.getSimpleName(); - - private final CameraSurfaceView surface; - private final OnOrientationChange onOrientationChange; - - private volatile Optional camera = Optional.absent(); - private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK; - private volatile int displayOrientation = -1; - - private @NonNull State state = State.PAUSED; - private @Nullable Size previewSize; - private @NonNull List listeners = Collections.synchronizedList(new LinkedList()); - private int outputOrientation = -1; - - public CameraView(Context context) { - this(context, null); - } - - public CameraView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public CameraView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setBackgroundColor(Color.BLACK); - - if (attrs != null) { - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraView); - int camera = typedArray.getInt(R.styleable.CameraView_camera, -1); - - if (camera != -1) cameraId = camera; - else if (isMultiCamera()) cameraId = TextSecurePreferences.getDirectCaptureCameraId(context); - - typedArray.recycle(); - } - - surface = new CameraSurfaceView(getContext()); - onOrientationChange = new OnOrientationChange(context.getApplicationContext()); - addView(surface); - } - - public void onResume() { - if (state != State.PAUSED) return; - state = State.RESUMED; - Log.i(TAG, "onResume() queued"); - enqueueTask(new SerialAsyncTask() { - @Override - protected - @Nullable - Void onRunBackground() { - try { - long openStartMillis = System.currentTimeMillis(); - camera = Optional.fromNullable(Camera.open(cameraId)); - Log.i(TAG, "camera.open() -> " + (System.currentTimeMillis() - openStartMillis) + "ms"); - synchronized (CameraView.this) { - CameraView.this.notifyAll(); - } - if (camera.isPresent()) onCameraReady(camera.get()); - } catch (Exception e) { - Log.w(TAG, e); - } - return null; - } - - @Override - protected void onPostMain(Void avoid) { - if (!camera.isPresent()) { - Log.w(TAG, "tried to open camera but got null"); - for (CameraViewListener listener : listeners) { - listener.onCameraFail(); - } - return; - } - - if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { - onOrientationChange.enable(); - } - Log.i(TAG, "onResume() completed"); - } - }); - } - - public void onPause() { - if (state == State.PAUSED) return; - state = State.PAUSED; - Log.i(TAG, "onPause() queued"); - - enqueueTask(new SerialAsyncTask() { - private Optional cameraToDestroy; - - @Override - protected void onPreMain() { - cameraToDestroy = camera; - camera = Optional.absent(); - } - - @Override - protected Void onRunBackground() { - if (cameraToDestroy.isPresent()) { - try { - stopPreview(); - cameraToDestroy.get().setPreviewCallback(null); - cameraToDestroy.get().release(); - Log.w(TAG, "released old camera instance"); - } catch (Exception e) { - Log.w(TAG, e); - } - } - return null; - } - - @Override protected void onPostMain(Void avoid) { - onOrientationChange.disable(); - displayOrientation = -1; - outputOrientation = -1; - removeView(surface); - addView(surface); - Log.i(TAG, "onPause() completed"); - } - }); - - for (CameraViewListener listener : listeners) { - listener.onCameraStop(); - } - } - - public boolean isStarted() { - return state != State.PAUSED; - } - - @SuppressWarnings("SuspiciousNameCombination") - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - final int width = r - l; - final int height = b - t; - final int previewWidth; - final int previewHeight; - - if (camera.isPresent() && previewSize != null) { - if (displayOrientation == 90 || displayOrientation == 270) { - previewWidth = previewSize.height; - previewHeight = previewSize.width; - } else { - previewWidth = previewSize.width; - previewHeight = previewSize.height; - } - } else { - previewWidth = width; - previewHeight = height; - } - - if (previewHeight == 0 || previewWidth == 0) { - Log.w(TAG, "skipping layout due to zero-width/height preview size"); - return; - } - - if (width * previewHeight > height * previewWidth) { - final int scaledChildHeight = previewHeight * width / previewWidth; - surface.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2); - } else { - final int scaledChildWidth = previewWidth * height / previewHeight; - surface.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height); - } - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - Log.i(TAG, "onSizeChanged(" + oldw + "x" + oldh + " -> " + w + "x" + h + ")"); - super.onSizeChanged(w, h, oldw, oldh); - if (camera.isPresent()) startPreview(camera.get().getParameters()); - } - - public void addListener(@NonNull CameraViewListener listener) { - listeners.add(listener); - } - - public void setPreviewCallback(final @NonNull PreviewCallback previewCallback) { - enqueueTask(new PostInitializationTask() { - @Override - protected void onPostMain(Void avoid) { - if (camera.isPresent()) { - camera.get().setPreviewCallback(new Camera.PreviewCallback() { - @Override - public void onPreviewFrame(byte[] data, Camera camera) { - if (!CameraView.this.camera.isPresent()) { - return; - } - - final int rotation = getCameraPictureOrientation(); - final Size previewSize = camera.getParameters().getPreviewSize(); - if (data != null) { - previewCallback.onPreviewFrame(new PreviewFrame(data, previewSize.width, previewSize.height, rotation)); - } - } - }); - } - } - }); - } - - public boolean isMultiCamera() { - return Camera.getNumberOfCameras() > 1; - } - - private void onCameraReady(final @NonNull Camera camera) { - final Parameters parameters = camera.getParameters(); - - parameters.setRecordingHint(true); - final List focusModes = parameters.getSupportedFocusModes(); - if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { - parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); - } else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { - parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); - } - - displayOrientation = CameraUtils.getCameraDisplayOrientation(getActivity(), getCameraInfo()); - camera.setDisplayOrientation(displayOrientation); - camera.setParameters(parameters); - enqueueTask(new PostInitializationTask() { - @Override - protected Void onRunBackground() { - try { - camera.setPreviewDisplay(surface.getHolder()); - startPreview(parameters); - } catch (Exception e) { - Log.w(TAG, "couldn't set preview display", e); - } - return null; - } - }); - } - - private void startPreview(final @NonNull Parameters parameters) { - if (this.camera.isPresent()) { - try { - final Camera camera = this.camera.get(); - final Size preferredPreviewSize = getPreferredPreviewSize(parameters); - - if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) { - Log.i(TAG, "starting preview with size " + preferredPreviewSize.width + "x" + preferredPreviewSize.height); - if (state == State.ACTIVE) stopPreview(); - previewSize = preferredPreviewSize; - parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height); - camera.setParameters(parameters); - } else { - previewSize = parameters.getPreviewSize(); - } - long previewStartMillis = System.currentTimeMillis(); - camera.startPreview(); - Log.i(TAG, "camera.startPreview() -> " + (System.currentTimeMillis() - previewStartMillis) + "ms"); - state = State.ACTIVE; - Util.runOnMain(new Runnable() { - @Override - public void run() { - requestLayout(); - for (CameraViewListener listener : listeners) { - listener.onCameraStart(); - } - } - }); - } catch (Exception e) { - Log.w(TAG, e); - } - } - } - - private void stopPreview() { - if (camera.isPresent()) { - try { - camera.get().stopPreview(); - state = State.RESUMED; - } catch (Exception e) { - Log.w(TAG, e); - } - } - } - - - private Size getPreferredPreviewSize(@NonNull Parameters parameters) { - return CameraUtils.getPreferredPreviewSize(displayOrientation, - getMeasuredWidth(), - getMeasuredHeight(), - parameters); - } - - private int getCameraPictureOrientation() { - if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { - outputOrientation = getCameraPictureRotation(getActivity().getWindowManager() - .getDefaultDisplay() - .getOrientation()); - } else if (getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT) { - outputOrientation = (360 - displayOrientation) % 360; - } else { - outputOrientation = displayOrientation; - } - - return outputOrientation; - } - - // https://github.com/signalapp/Signal-Android/issues/4715 - private boolean isTroublemaker() { - return getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT && - "JWR66Y".equals(Build.DISPLAY) && - "yakju".equals(Build.PRODUCT); - } - - private @NonNull CameraInfo getCameraInfo() { - final CameraInfo info = new Camera.CameraInfo(); - Camera.getCameraInfo(cameraId, info); - return info; - } - - // XXX this sucks - private Activity getActivity() { - return (Activity)getContext(); - } - - public int getCameraPictureRotation(int orientation) { - final CameraInfo info = getCameraInfo(); - final int rotation; - - orientation = (orientation + 45) / 90 * 90; - - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - rotation = (info.orientation - orientation + 360) % 360; - } else { - rotation = (info.orientation + orientation) % 360; - } - - return rotation; - } - - private class OnOrientationChange extends OrientationEventListener { - public OnOrientationChange(Context context) { - super(context); - disable(); - } - - @Override - public void onOrientationChanged(int orientation) { - if (camera.isPresent() && orientation != ORIENTATION_UNKNOWN) { - int newOutputOrientation = getCameraPictureRotation(orientation); - - if (newOutputOrientation != outputOrientation) { - outputOrientation = newOutputOrientation; - - Camera.Parameters params = camera.get().getParameters(); - - params.setRotation(outputOrientation); - - try { - camera.get().setParameters(params); - } - catch (Exception e) { - Log.e(TAG, "Exception updating camera parameters in orientation change", e); - } - } - } - } - } - - public void takePicture(final Rect previewRect) { - if (!camera.isPresent() || camera.get().getParameters() == null) { - Log.w(TAG, "camera not in capture-ready state"); - return; - } - - camera.get().setOneShotPreviewCallback(new Camera.PreviewCallback() { - @Override - public void onPreviewFrame(byte[] data, final Camera camera) { - final int rotation = getCameraPictureOrientation(); - final Size previewSize = camera.getParameters().getPreviewSize(); - final Rect croppingRect = getCroppedRect(previewSize, previewRect, rotation); - - Log.i(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height); - Log.i(TAG, "data bytes: " + data.length); - Log.i(TAG, "previewFormat: " + camera.getParameters().getPreviewFormat()); - Log.i(TAG, "croppingRect: " + croppingRect.toString()); - Log.i(TAG, "rotation: " + rotation); - new CaptureTask(previewSize, rotation, croppingRect).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, data); - } - }); - } - - private Rect getCroppedRect(Size cameraPreviewSize, Rect visibleRect, int rotation) { - final int previewWidth = cameraPreviewSize.width; - final int previewHeight = cameraPreviewSize.height; - - if (rotation % 180 > 0) rotateRect(visibleRect); - - float scale = (float) previewWidth / visibleRect.width(); - if (visibleRect.height() * scale > previewHeight) { - scale = (float) previewHeight / visibleRect.height(); - } - final float newWidth = visibleRect.width() * scale; - final float newHeight = visibleRect.height() * scale; - final float centerX = (isTroublemaker()) ? previewWidth - newWidth / 2 : previewWidth / 2; - final float centerY = previewHeight / 2; - - visibleRect.set((int) (centerX - newWidth / 2), - (int) (centerY - newHeight / 2), - (int) (centerX + newWidth / 2), - (int) (centerY + newHeight / 2)); - - if (rotation % 180 > 0) rotateRect(visibleRect); - return visibleRect; - } - - @SuppressWarnings("SuspiciousNameCombination") - private void rotateRect(Rect rect) { - rect.set(rect.top, rect.left, rect.bottom, rect.right); - } - - private void enqueueTask(SerialAsyncTask job) { - AsyncTask.SERIAL_EXECUTOR.execute(job); - } - - public static abstract class SerialAsyncTask implements Runnable { - - @Override - public final void run() { - if (!onWait()) { - Log.w(TAG, "skipping task, preconditions not met in onWait()"); - return; - } - - Util.runOnMainSync(this::onPreMain); - final Result result = onRunBackground(); - Util.runOnMainSync(() -> onPostMain(result)); - } - - protected boolean onWait() { return true; } - protected void onPreMain() {} - protected Result onRunBackground() { return null; } - protected void onPostMain(Result result) {} - } - - private abstract class PostInitializationTask extends SerialAsyncTask { - @Override protected boolean onWait() { - synchronized (CameraView.this) { - if (!camera.isPresent()) { - return false; - } - while (getMeasuredHeight() <= 0 || getMeasuredWidth() <= 0 || !surface.isReady()) { - Log.i(TAG, String.format("waiting. surface ready? %s", surface.isReady())); - Util.wait(CameraView.this, 0); - } - return true; - } - } - } - - private class CaptureTask extends AsyncTask { - private final Size previewSize; - private final int rotation; - private final Rect croppingRect; - - public CaptureTask(Size previewSize, int rotation, Rect croppingRect) { - this.previewSize = previewSize; - this.rotation = rotation; - this.croppingRect = croppingRect; - } - - @Override - protected byte[] doInBackground(byte[]... params) { - final byte[] data = params[0]; - try { - return BitmapUtil.createFromNV21(data, - previewSize.width, - previewSize.height, - rotation, - croppingRect, - cameraId == CameraInfo.CAMERA_FACING_FRONT); - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - } - - @Override - protected void onPostExecute(byte[] imageBytes) { - if (imageBytes != null) { - for (CameraViewListener listener : listeners) { - listener.onImageCapture(imageBytes); - } - } - } - } - - private static class PreconditionsNotMetException extends Exception {} - - public interface CameraViewListener { - void onImageCapture(@NonNull final byte[] imageBytes); - void onCameraFail(); - void onCameraStart(); - void onCameraStop(); - } - - public interface PreviewCallback { - void onPreviewFrame(@NonNull PreviewFrame frame); - } - - public static class PreviewFrame { - private final @NonNull byte[] data; - private final int width; - private final int height; - private final int orientation; - - private PreviewFrame(@NonNull byte[] data, int width, int height, int orientation) { - this.data = data; - this.width = width; - this.height = height; - this.orientation = orientation; - } - - public @NonNull byte[] getData() { - return data; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public int getOrientation() { - return orientation; - } - } - - private enum State { - PAUSED, RESUMED, ACTIVE - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java index 4ccb16ed5e..ea23ca13d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java @@ -20,13 +20,10 @@ public class EmojiTextView extends AppCompatTextView { private final boolean scaleEmojis; - private static final char ELLIPSIS = '…'; - private CharSequence previousText; private BufferType previousBufferType = BufferType.NORMAL; private float originalFontSize; private boolean sizeChangeInProgress; - private int maxLength; private CharSequence overflowText; private CharSequence previousOverflowText; @@ -42,7 +39,6 @@ public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); scaleEmojis = true; - maxLength = 1000; originalFontSize = getResources().getDimension(R.dimen.medium_font_size); } @@ -82,78 +78,12 @@ public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr) { if (candidates == null || candidates.size() == 0) { super.setText(new SpannableStringBuilder(Optional.fromNullable(text).or("")).append(Optional.fromNullable(overflowText).or("")), BufferType.NORMAL); - - if (getEllipsize() == TextUtils.TruncateAt.END && maxLength > 0) { - ellipsizeAnyTextForMaxLength(); - } } else { CharSequence emojified = EmojiProvider.emojify(candidates, text, this, false); super.setText(new SpannableStringBuilder(emojified).append(Optional.fromNullable(overflowText).or("")), BufferType.SPANNABLE); - - // Android fails to ellipsize spannable strings. (https://issuetracker.google.com/issues/36991688) - // We ellipsize them ourselves by manually truncating the appropriate section. - if (getEllipsize() == TextUtils.TruncateAt.END) { - if (maxLength > 0) { - ellipsizeAnyTextForMaxLength(); - } else { - ellipsizeEmojiTextForMaxLines(); - } - } - } - } - - public void setOverflowText(@Nullable CharSequence overflowText) { - this.overflowText = overflowText; - setText(previousText, BufferType.SPANNABLE); - } - - private void ellipsizeAnyTextForMaxLength() { - if (maxLength > 0 && getText().length() > maxLength + 1) { - SpannableStringBuilder newContent = new SpannableStringBuilder(); - newContent.append(getText().subSequence(0, maxLength)).append(ELLIPSIS).append(Optional.fromNullable(overflowText).or("")); - - EmojiParser.CandidateList newCandidates = EmojiProvider.getCandidates(newContent); - - if (newCandidates == null || newCandidates.size() == 0) { - super.setText(newContent, BufferType.NORMAL); - } else { - CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, false); - super.setText(emojified, BufferType.SPANNABLE); - } } } - private void ellipsizeEmojiTextForMaxLines() { - post(() -> { - if (getLayout() == null) { - ellipsizeEmojiTextForMaxLines(); - return; - } - - int maxLines = TextViewCompat.getMaxLines(EmojiTextView.this); - if (maxLines <= 0 && maxLength < 0) { - return; - } - - int lineCount = getLineCount(); - if (lineCount > maxLines) { - int overflowStart = getLayout().getLineStart(maxLines - 1); - CharSequence overflow = getText().subSequence(overflowStart, getText().length()); - CharSequence ellipsized = TextUtils.ellipsize(overflow, getPaint(), getWidth(), TextUtils.TruncateAt.END); - - SpannableStringBuilder newContent = new SpannableStringBuilder(); - newContent.append(getText().subSequence(0, overflowStart)) - .append(ellipsized.subSequence(0, ellipsized.length())) - .append(Optional.fromNullable(overflowText).or("")); - - EmojiParser.CandidateList newCandidates = EmojiProvider.getCandidates(newContent); - CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, false); - - super.setText(emojified, BufferType.SPANNABLE); - } - }); - } - private boolean unchanged(CharSequence text, CharSequence overflowText, BufferType bufferType) { CharSequence finalPrevText = (previousText == null || previousText.length() == 0 ? "" : previousText); CharSequence finalText = (text == null || text.length() == 0 ? "" : text); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt index ec938d39e6..8fd103003d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -1,10 +1,7 @@ package org.thoughtcrime.securesms.conversation.disappearingmessages import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import androidx.annotation.StringRes import network.loki.messenger.R import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.database.StorageProtocol @@ -12,7 +9,6 @@ import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.MessageSender -import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address import org.session.libsession.utilities.ExpirationUtil @@ -20,13 +16,12 @@ import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerP import org.session.libsession.utilities.StringSubstitutionConstants.DISAPPEARING_MESSAGES_TYPE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.getExpirationTypeDisplayValue +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId -import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.ui.getSubbedCharSequence import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds class DisappearingMessages @Inject constructor( private val textSecurePreferences: TextSecurePreferences, @@ -55,26 +50,60 @@ class DisappearingMessages @Inject constructor( } } - fun showFollowSettingDialog(context: Context, message: MessageRecord) = context.showSessionDialog { + fun showFollowSettingDialog(context: Context, + threadId: Long, + recipient: Recipient, + content: DisappearingMessageUpdate) = context.showSessionDialog { title(R.string.disappearingMessagesFollowSetting) - text(if (message.expiresIn == 0L) { - context.getText(R.string.disappearingMessagesFollowSettingOff) - } else { - context.getSubbedCharSequence(R.string.disappearingMessagesFollowSettingOn, - TIME_KEY to ExpirationUtil.getExpirationDisplayValue(context, message.expiresIn.milliseconds), - DISAPPEARING_MESSAGES_TYPE_KEY to context.getExpirationTypeDisplayValue(message.isNotDisappearAfterRead)) - }) + + val bodyText: CharSequence + @StringRes + val dangerButtonText: Int + @StringRes + val dangerButtonContentDescription: Int + + when (content.expiryMode) { + ExpiryMode.NONE -> { + bodyText = context.getText(R.string.disappearingMessagesFollowSettingOff) + dangerButtonText = R.string.confirm + dangerButtonContentDescription = R.string.AccessibilityId_confirm + } + is ExpiryMode.AfterSend -> { + bodyText = context.getSubbedCharSequence( + R.string.disappearingMessagesFollowSettingOn, + TIME_KEY to ExpirationUtil.getExpirationDisplayValue( + context, + content.expiryMode.duration + ), + DISAPPEARING_MESSAGES_TYPE_KEY to context.getString(R.string.disappearingMessagesTypeSent) + ) + + dangerButtonText = R.string.set + dangerButtonContentDescription = R.string.AccessibilityId_setButton + } + is ExpiryMode.AfterRead -> { + bodyText = context.getSubbedCharSequence( + R.string.disappearingMessagesFollowSettingOn, + TIME_KEY to ExpirationUtil.getExpirationDisplayValue( + context, + content.expiryMode.duration + ), + DISAPPEARING_MESSAGES_TYPE_KEY to context.getString(R.string.disappearingMessagesTypeRead) + ) + + dangerButtonText = R.string.set + dangerButtonContentDescription = R.string.AccessibilityId_setButton + } + } + + text(bodyText) dangerButton( - text = if (message.expiresIn == 0L) R.string.confirm else R.string.set, - contentDescriptionRes = if (message.expiresIn == 0L) R.string.AccessibilityId_confirm else R.string.AccessibilityId_setButton + text = dangerButtonText, + contentDescriptionRes = dangerButtonContentDescription, ) { - set(message.threadId, message.recipient.address, message.expiryMode, message.recipient.isGroupRecipient) + set(threadId, recipient.address, content.expiryMode, recipient.isGroupRecipient) } cancelButton() } } - -val MessageRecord.expiryMode get() = if (expiresIn <= 0) ExpiryMode.NONE - else if (expireStarted == timestamp) ExpiryMode.AfterSend(expiresIn / 1000) - else ExpiryMode.AfterRead(expiresIn / 1000) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt index 0ae2336549..fb58e0a48f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt @@ -25,7 +25,7 @@ import org.thoughtcrime.securesms.ui.OptionsCard import org.thoughtcrime.securesms.ui.RadioOption import org.thoughtcrime.securesms.ui.components.AppBarBackIcon import org.thoughtcrime.securesms.ui.components.AppBarText -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.components.appBarColors import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalColors @@ -110,7 +110,7 @@ fun DisappearingMessages( } if (state.showSetButton) { - PrimaryOutlineButton( + AccentOutlineButton( stringResource(R.string.set), modifier = Modifier .qaTag(R.string.AccessibilityId_setButton) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt index d42124fb74..c6f3a5f809 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt @@ -92,9 +92,7 @@ class StartConversationFragment : BottomSheetDialogFragment(), StartConversation } override fun onCreateGroupSelected() { - val fragment = CreateGroupFragment() - - replaceFragment(fragment) + replaceFragment(CreateGroupFragment()) } override fun onJoinCommunitySelected() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/invitefriend/InviteFriend.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/invitefriend/InviteFriend.kt index d8c4db07b6..5491e172ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/invitefriend/InviteFriend.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/invitefriend/InviteFriend.kt @@ -21,11 +21,11 @@ import androidx.compose.ui.tooling.preview.Preview import com.squareup.phrase.Phrase import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY +import org.thoughtcrime.securesms.ui.border import org.thoughtcrime.securesms.ui.components.AppBarCloseIcon import org.thoughtcrime.securesms.ui.components.BackAppBar import org.thoughtcrime.securesms.ui.components.SlimOutlineButton import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton -import org.thoughtcrime.securesms.ui.components.border import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessage.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessage.kt index 3fdaa2c3d5..af6b3b4740 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessage.kt @@ -33,7 +33,7 @@ import org.thoughtcrime.securesms.ui.LoadingArcOr import org.thoughtcrime.securesms.ui.components.AppBarCloseIcon import org.thoughtcrime.securesms.ui.components.BackAppBar import org.thoughtcrime.securesms.ui.components.BorderlessButtonWithIcon -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.components.QRScannerScreen import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField import org.thoughtcrime.securesms.ui.components.SessionTabRow @@ -129,7 +129,7 @@ private fun EnterAccountId( Spacer(Modifier.weight(1f).heightIn(min = LocalDimensions.current.smallSpacing)) - PrimaryOutlineButton( + AccentOutlineButton( modifier = Modifier .align(Alignment.CenterHorizontally) .padding(horizontal = LocalDimensions.current.xlargeSpacing) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 569386de58..4030fe9e29 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -64,6 +64,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -207,17 +208,14 @@ import org.thoughtcrime.securesms.util.drawToBitmap import org.thoughtcrime.securesms.util.fadeIn import org.thoughtcrime.securesms.util.fadeOut import org.thoughtcrime.securesms.util.isFullyScrolled -import org.thoughtcrime.securesms.util.isScrolledToBottom -import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom +import org.thoughtcrime.securesms.util.isNearBottom import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.toPx import org.thoughtcrime.securesms.webrtc.WebRtcCallActivity import org.thoughtcrime.securesms.webrtc.WebRtcCallActivity.Companion.ACTION_START_CALL import org.thoughtcrime.securesms.webrtc.WebRtcCallBridge.Companion.EXTRA_RECIPIENT_ADDRESS import java.io.File -import java.lang.ref.WeakReference import java.util.LinkedList -import java.util.Locale import java.util.concurrent.ExecutionException import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong @@ -327,6 +325,9 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } private val mentionCandidateAdapter = MentionCandidateAdapter { mentionViewModel.onCandidateSelected(it.member.publicKey) + + // make sure to reverify text length here as the onTextChanged happens before this step + viewModel.onTextChanged(mentionViewModel.deconstructMessageMentions()) } // Search val searchViewModel: SearchViewModel by viewModels() @@ -343,6 +344,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, private val EMOJI_REACTIONS_ALLOWED_PER_MINUTE = 20 private val ONE_MINUTE_IN_MILLISECONDS = 1.minutes.inWholeMilliseconds + private var conversationLoadAnimationJob: Job? = null + private val layoutManager: LinearLayoutManager? get() { return binding.conversationRecyclerView.layoutManager as LinearLayoutManager? } @@ -359,18 +362,15 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, MnemonicCodec(loadFileContents).encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.english) } - // There is a bug when initially joining a community where all messages will immediately be marked - // as read if we reverse the message list so this is now hard-coded to false - private val reverseMessageList = false + private var firstCursorLoad = false private val adapter by lazy { - val cursor = mmsSmsDb.getConversation(viewModel.threadId, reverseMessageList) val adapter = ConversationAdapter( this, - cursor, + null, viewModel.recipient, storage.getLastSeen(viewModel.threadId), - reverseMessageList, + false, onItemPress = { message, position, view, event -> handlePress(message, position, view, event) }, @@ -419,6 +419,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, private val messageToScrollAuthor = AtomicReference(null) private val firstLoad = AtomicBoolean(true) + private var isKeyboardVisible = false + private lateinit var reactionDelegate: ConversationReactionDelegate private val reactWithAnyEmojiStartPage = -1 @@ -469,13 +471,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } } - // Properties related to the conversation recycler view's scroll state and position - private var previousLastVisibleRecyclerViewIndex: Int = RecyclerView.NO_POSITION - private var currentLastVisibleRecyclerViewIndex: Int = RecyclerView.NO_POSITION - private var recyclerScrollState: Int = RecyclerView.SCROLL_STATE_IDLE - private val isScrolledToBottom: Boolean - get() = binding.conversationRecyclerView.isScrolledToBottom + get() = binding.conversationRecyclerView.isNearBottom // When the user clicks on the original message in a reply then we scroll to and highlight that original // message. To do this we keep track of the replied-to message's location in the recycler view. @@ -526,14 +523,21 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, binding = ActivityConversationV2Binding.inflate(layoutInflater) setContentView(binding.root) + setupWindowInsets() + + startConversationLoaderWithDelay() + // set the compose dialog content binding.dialogOpenUrl.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setThemedContent { val dialogsState by viewModel.dialogsState.collectAsState() + val inputBarDialogState by viewModel.inputBarStateDialogsState.collectAsState() ConversationV2Dialogs( dialogsState = dialogsState, - sendCommand = viewModel::onCommand + inputBarDialogsState = inputBarDialogState, + sendCommand = viewModel::onCommand, + sendInputBarCommand = viewModel::onInputBarCommand ) } } @@ -556,7 +560,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, binding.scrollToBottomButton.setOnClickListener { val layoutManager = binding.conversationRecyclerView.layoutManager as LinearLayoutManager - val targetPosition = if (reverseMessageList) 0 else adapter.itemCount + val targetPosition = adapter.itemCount // If we are currently in the process of smooth scrolling then we'll use `scrollToPosition` to quick-jump.. if (layoutManager.isSmoothScrolling) { @@ -572,49 +576,16 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, startActivity(WebRtcCallActivity.getCallActivityIntent(this)) } - updateUnreadCountIndicator() - updatePlaceholder() setUpExpiredGroupBanner() binding.searchBottomBar.setEventListener(this) updateSendAfterApprovalText() setUpMessageRequests() - val weakActivity = WeakReference(this) - - lifecycleScope.launch(Dispatchers.IO) { - // Note: We are accessing the `adapter` property because we want it to be loaded on - // the background thread to avoid blocking the UI thread and potentially hanging when - // transitioning to the activity - weakActivity.get()?.adapter ?: return@launch - - // 'Get' instead of 'GetAndSet' here because we want to trigger the highlight in 'onFirstLoad' - // by triggering 'jumpToMessage' using these values - val messageTimestamp = messageToScrollTimestamp.get() - val author = messageToScrollAuthor.get() - - val targetPosition = if (author != null && messageTimestamp >= 0) { - mmsSmsDb.getMessagePositionInConversation(viewModel.threadId, messageTimestamp, author, reverseMessageList) - } else { - -1 - } - - withContext(Dispatchers.Main) { - setUpRecyclerView() - setUpTypingObserver() - setUpRecipientObserver() - setUpSearchResultObserver() - scrollToFirstUnreadMessageIfNeeded() - setUpOutdatedClientBanner() - setUpLegacyGroupUI() - - if (author != null && messageTimestamp >= 0 && targetPosition >= 0) { - binding.conversationRecyclerView.scrollToPosition(targetPosition) - } - else { - scrollToFirstUnreadMessageIfNeeded(true) - } - } - } + setUpRecyclerView() + setUpTypingObserver() + setUpRecipientObserver() + setUpSearchResultObserver() + setUpLegacyGroupUI() val reactionOverlayStub: Stub = ViewUtil.findStubById(this, R.id.conversation_reaction_scrubber_stub) @@ -640,7 +611,21 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, setupMentionView() setupUiEventsObserver() - setupWindowInsets() + } + + private fun startConversationLoaderWithDelay() { + conversationLoadAnimationJob = lifecycleScope.launch { + delay(700) + binding.conversationLoader.isVisible = true + } + } + + private fun stopConversationLoader() { + // Cancel the delayed load animation + conversationLoadAnimationJob?.cancel() + conversationLoadAnimationJob = null + + binding.conversationLoader.isVisible = false } private fun setupWindowInsets() { @@ -648,6 +633,20 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.ime()) + val imeHeight = windowInsets.getInsets(WindowInsetsCompat.Type.ime()).bottom + val keyboardVisible = imeHeight > 0 + + if (keyboardVisible != isKeyboardVisible) { + isKeyboardVisible = keyboardVisible + if (keyboardVisible) { + // when showing the keyboard, we should auto scroll the conversation recyclerview + // if we are near the bottom (within 50dp of bottom) + if (binding.conversationRecyclerView.isNearBottom) { + binding.conversationRecyclerView.smoothScrollToPosition(adapter.itemCount) + } + } + } + binding.bottomSpacer.updateLayoutParams { height = systemBarsInsets.bottom } @@ -697,9 +696,9 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } private fun setupMentionView() { - binding?.conversationMentionCandidates?.let { view -> - view.adapter = mentionCandidateAdapter - view.itemAnimator = null + binding.conversationMentionCandidates.apply { + adapter = mentionCandidateAdapter + itemAnimator = null } lifecycleScope.launch { @@ -712,7 +711,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } } - binding?.inputBar?.setInputBarEditableFactory(mentionViewModel.editableFactory) + binding.inputBar.setInputBarEditableFactory(mentionViewModel.editableFactory) } override fun onResume() { @@ -747,7 +746,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } override fun onCreateLoader(id: Int, bundle: Bundle?): Loader { - return ConversationLoader(viewModel.threadId, reverseMessageList, this@ConversationActivityV2) + return ConversationLoader(viewModel.threadId, false, this@ConversationActivityV2) } override fun onLoadFinished(loader: Loader, cursor: Cursor?) { @@ -756,6 +755,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, adapter.changeCursor(cursor) if (cursor != null) { + firstCursorLoad = true val messageTimestamp = messageToScrollTimestamp.getAndSet(-1) val author = messageToScrollAuthor.getAndSet(null) val initialUnreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId) @@ -772,11 +772,15 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, if (author != null && messageTimestamp >= 0) { jumpToMessage(author, messageTimestamp, firstLoad.get(), null) } else { - if (firstLoad.getAndSet(false)) scrollToFirstUnreadMessageIfNeeded(true) + if (firstLoad.getAndSet(false)) scrollToFirstUnreadMessageOrBottom() handleRecyclerViewScrolled() } + + updatePlaceholder() } - updatePlaceholder() + + stopConversationLoader() + viewModel.recipient?.let { setUpOutdatedClientBanner() } @@ -787,23 +791,17 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // called from onCreate private fun setUpRecyclerView() { binding.conversationRecyclerView.adapter = adapter - val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, reverseMessageList) + val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) binding.conversationRecyclerView.layoutManager = layoutManager // Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will) LoaderManager.getInstance(this).restartLoader(0, null, this) binding.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - // The unreadCount check is to prevent us scrolling to the bottom when we first enter a conversation. - if (recyclerScrollState == RecyclerView.SCROLL_STATE_IDLE && unreadCount != Int.MAX_VALUE) { - scrollToMostRecentMessageIfWeShould() - } handleRecyclerViewScrolled() } override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { - recyclerScrollState = newState - // If we were scrolling towards a specific message to highlight when scrolling stops then do so if (newState == RecyclerView.SCROLL_STATE_IDLE) { pendingHighlightMessagePosition?.let { position -> @@ -830,39 +828,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } } - private fun scrollToMostRecentMessageIfWeShould() { - //don't do anything during search - it still needs to happen when starting the search when the keyboard opens - // so we check the state of the query as an indication that we are scrolling due to search - if(!searchViewModel.searchQuery.value.isNullOrEmpty()) return - - val lm = layoutManager ?: return Log.w(TAG, "Cannot scroll recycler view without a layout manager - bailing.") - - // Grab an initial 'previous' last visible message.. - if (previousLastVisibleRecyclerViewIndex == RecyclerView.NO_POSITION) { - previousLastVisibleRecyclerViewIndex = lm.findLastVisibleItemPosition() - } - - // ..and grab the 'current' last visible message. - currentLastVisibleRecyclerViewIndex = lm.findLastVisibleItemPosition() - - // If the current last visible message index is less than the previous one (i.e. we've - // lost visibility of one or more messages due to showing the IME keyboard) AND we're - // at the bottom of the message feed.. - val atBottomAndTrueLastNoLongerVisible = currentLastVisibleRecyclerViewIndex <= previousLastVisibleRecyclerViewIndex && - !binding.scrollToBottomButton.isVisible - - // ..OR we're at the last message or have received a new message.. - val atLastOrReceivedNewMessage = currentLastVisibleRecyclerViewIndex == (adapter.itemCount - 1) - - // ..then scroll the recycler view to the last message on resize. - if (atBottomAndTrueLastNoLongerVisible || atLastOrReceivedNewMessage) { - binding.conversationRecyclerView.smoothScrollToPosition(adapter.itemCount) - } - - // Update our previous last visible view index to the current one - previousLastVisibleRecyclerViewIndex = currentLastVisibleRecyclerViewIndex - } - // called from onCreate private fun setUpToolBar() { binding.conversationAppBar.applySafeInsetsPaddings(WindowInsetsCompat.Type.statusBars()) @@ -925,7 +890,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, AttachmentManager.MediaType.VIDEO == mediaType) ) { val media = Media(mediaURI, filename, mimeType, 0, 0, 0, 0, null, null) - startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient!!, ""), PICK_FROM_LIBRARY) + startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient!!, threadId, getMessageBody()), PICK_FROM_LIBRARY) return } else { prepMediaForSending(mediaURI, mediaType).addListener(object : ListenableFuture.Listener { @@ -962,7 +927,9 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } if (textSecurePreferences.isTypingIndicatorsEnabled()) { binding.inputBar.addTextChangedListener { - typingStatusSender.onTypingStarted(viewModel.threadId) + if(it.isNotEmpty()) { + typingStatusSender.onTypingStarted(viewModel.threadId) + } } } } @@ -1100,8 +1067,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> - binding.inputBar.setState(state.inputBarState) - binding.root.requestApplyInsets() // show or hide loading indicator @@ -1112,6 +1077,14 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } } + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.inputBarState.collect { state -> + binding.inputBar.setState(state) + } + } + } + lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.callBanner.collect { callBanner -> @@ -1127,22 +1100,17 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } } - private fun scrollToFirstUnreadMessageIfNeeded(isFirstLoad: Boolean = false, shouldHighlight: Boolean = false): Int { - val lastSeenTimestamp = threadDb.getLastSeenAndHasSent(viewModel.threadId).first() - val lastSeenItemPosition = adapter.findLastSeenItemPosition(lastSeenTimestamp) ?: return -1 - - // If this is triggered when first opening a conversation then we want to position the top - // of the first unread message in the middle of the screen - if (isFirstLoad && !reverseMessageList) { - layoutManager?.scrollToPositionWithOffset(lastSeenItemPosition, ((layoutManager?.height ?: 0) / 2)) - if (shouldHighlight) { highlightViewAtPosition(lastSeenItemPosition) } - return lastSeenItemPosition + private fun scrollToFirstUnreadMessageOrBottom() { + // if there are no unread messages, go straight to the very bottom of the list + if(unreadCount == 0){ + layoutManager?.scrollToPositionWithOffset(adapter.itemCount -1, Int.MIN_VALUE) + return } - if (lastSeenItemPosition <= 3) { return lastSeenItemPosition } + val lastSeenTimestamp = threadDb.getLastSeenAndHasSent(viewModel.threadId).first() + val lastSeenItemPosition = adapter.findLastSeenItemPosition(lastSeenTimestamp) ?: return - binding.conversationRecyclerView.scrollToPosition(lastSeenItemPosition) - return lastSeenItemPosition + layoutManager?.scrollToPositionWithOffset(lastSeenItemPosition, ((layoutManager?.height ?: 0) / 2)) } private fun highlightViewAtPosition(position: Int) { @@ -1168,8 +1136,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // region Animation & Updating override fun onModified(recipient: Recipient) { - viewModel.updateRecipient() - runOnUiThread { invalidateOptionsMenu() updateSendAfterApprovalText() @@ -1235,6 +1201,10 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, }.show(supportFragmentManager, "Link Preview Dialog") textSecurePreferences.setHasSeenLinkPreviewSuggestionDialog() } + + // use the normalised version of the text's body to get the characters amount with the + // mentions as their account id + viewModel.onTextChanged(mentionViewModel.deconstructMessageMentions()) } override fun toggleAttachmentOptions() { @@ -1332,7 +1302,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, binding.typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom showScrollToBottomButtonIfApplicable() - val maybeTargetVisiblePosition = if (reverseMessageList) layoutManager?.findFirstVisibleItemPosition() else layoutManager?.findLastVisibleItemPosition() + val maybeTargetVisiblePosition = layoutManager?.findLastVisibleItemPosition() val targetVisiblePosition = maybeTargetVisiblePosition ?: RecyclerView.NO_POSITION if (!firstLoad.get() && targetVisiblePosition != RecyclerView.NO_POSITION) { adapter.getTimestampForItemAt(targetVisiblePosition)?.let { visibleItemTimestamp -> @@ -1342,18 +1312,16 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } } - if (reverseMessageList) { - unreadCount = min(unreadCount, targetVisiblePosition).coerceAtLeast(0) - } else { - val layoutUnreadCount = layoutManager?.let { (it.itemCount - 1) - it.findLastVisibleItemPosition() } - ?: RecyclerView.NO_POSITION - unreadCount = min(unreadCount, layoutUnreadCount).coerceAtLeast(0) - } + val layoutUnreadCount = layoutManager?.let { (it.itemCount - 1) - it.findLastVisibleItemPosition() } + ?: RecyclerView.NO_POSITION + unreadCount = min(unreadCount, layoutUnreadCount).coerceAtLeast(0) + updateUnreadCountIndicator() } // Update placeholder / control messages in a conversation private fun updatePlaceholder() { + if(!firstCursorLoad) return val recipient = viewModel.recipient ?: return Log.w("Loki", "recipient was null in placeholder update") val blindedRecipient = viewModel.blindedRecipient val openGroup = viewModel.openGroup @@ -1710,7 +1678,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, dateSent = emojiTimestamp, dateReceived = emojiTimestamp ) - reactionDb.addReaction(reaction, false) + reactionDb.addReaction(reaction) val originalAuthor = if (originalMessage.isOutgoing) { fromSerialized(viewModel.blindedPublicKey ?: textSecurePreferences.getLocalNumber()!!) @@ -1747,7 +1715,11 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, Log.w(TAG, "Unable to locate local number when removing emoji reaction - aborting.") return } else { - reactionDb.deleteReaction(emoji, MessageId(originalMessage.id, originalMessage.isMms), author, false) + reactionDb.deleteReaction( + emoji, + MessageId(originalMessage.id, originalMessage.isMms), + author + ) val originalAuthor = if (originalMessage.isOutgoing) { fromSerialized(viewModel.blindedPublicKey ?: textSecurePreferences.getLocalNumber()!!) @@ -1887,6 +1859,10 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } } + override fun onCharLimitTapped() { + viewModel.onCharLimitTapped() + } + private fun isValidLockViewLocation(x: Int, y: Int): Boolean { // We can be anywhere above the lock view and a bit to the side of it (at most `lockViewHitMargin` // to the side) @@ -1958,10 +1934,16 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, override fun sendMessage() { val recipient = viewModel.recipient ?: return + + // show the unblock dialog when trying to send a message to a blocked contact if (recipient.isContactRecipient && recipient.isBlocked) { BlockedDialog(recipient, viewModel.getUsername(recipient.address.toString())).show(supportFragmentManager, "Blocked Dialog") return } + + // validate message length before sending + if(!viewModel.validateMessageLength()) return + val sentMessageInfo = if (binding.inputBar.linkPreview != null || binding.inputBar.quote != null) { sendAttachments(listOf(), getMessageBody(), binding.inputBar.quote, binding.inputBar.linkPreview) } else { @@ -1980,7 +1962,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val mimeType = MediaUtil.getMimeType(this, contentUri)!! val filename = FilenameUtils.getFilenameFromUri(this, contentUri, mimeType) val media = Media(contentUri, filename, mimeType, 0, 0, 0, 0, null, null) - startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), recipient, getMessageBody()), PICK_FROM_LIBRARY) + startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), recipient, threadId, getMessageBody()), PICK_FROM_LIBRARY) } // If we previously approve this recipient, either implicitly or explicitly, we need to wait for @@ -2015,10 +1997,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, message.sentTimestamp = sentTimestamp message.text = text val expiresInMillis = viewModel.expirationConfiguration?.expiryMode?.expiryMillis ?: 0 - val expireStartedAt = if (viewModel.expirationConfiguration?.expiryMode is ExpiryMode.AfterSend) { - message.sentTimestamp - } else 0 - val outgoingTextMessage = OutgoingTextMessage.from(message, recipient, expiresInMillis, expireStartedAt!!) + val outgoingTextMessage = OutgoingTextMessage.from(message, recipient, expiresInMillis, 0) // Clear the input bar binding.inputBar.text = "" @@ -2153,12 +2132,19 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, private fun pickFromLibrary() { val recipient = viewModel.recipient ?: return - binding.inputBar.text?.trim()?.let { text -> - AttachmentManager.selectGallery(this, PICK_FROM_LIBRARY, recipient, text) - } + AttachmentManager.selectGallery(this, PICK_FROM_LIBRARY, recipient, threadId, + getMessageBody()) } - private fun showCamera() { attachmentManager.capturePhoto(this, TAKE_PHOTO, viewModel.recipient) } + private fun showCamera() { + attachmentManager.capturePhoto( + this, + TAKE_PHOTO, + viewModel.recipient, + threadId, + getMessageBody() + ) + } override fun onAttachmentChanged() { /* Do nothing */ } @@ -2335,7 +2321,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, Uri.fromFile(result.file), voiceMessageFilename, result.length, - MediaTypes.AUDIO_AAC, + MediaTypes.AUDIO_MP4, true, result.duration.inWholeMilliseconds) @@ -2411,7 +2397,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, if (TextUtils.isEmpty(body)) { continue } if (messageSize > 1) { val formattedTimestamp = dateUtils.getDisplayFormattedTimeSpanString( - Locale.getDefault(), message.timestamp ) builder.append("$formattedTimestamp: ") @@ -2678,7 +2663,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, private fun jumpToMessage(author: Address, timestamp: Long, highlight: Boolean, onMessageNotFound: Runnable?) { SimpleTask.run(lifecycle, { - mmsSmsDb.getMessagePositionInConversation(viewModel.threadId, timestamp, author, reverseMessageList) + mmsSmsDb.getMessagePositionInConversation(viewModel.threadId, timestamp, author, false) }) { p: Int -> moveToMessagePosition(p, highlight, onMessageNotFound) } } @@ -2722,10 +2707,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, inner class ConversationAdapterDataObserver(val recyclerView: ConversationRecyclerView, val adapter: ConversationAdapter) : RecyclerView.AdapterDataObserver() { override fun onChanged() { super.onChanged() - if (recyclerView.isScrolledToWithin30dpOfBottom && !recyclerView.isFullyScrolled) { - // Note: The adapter itemCount is zero based - so calling this with the itemCount in - // a non-zero based manner scrolls us to the bottom of the last message (including - // to the bottom of long messages as required by Jira SES-789 / GitHub 1364). + // scrolled to bottom has a leniency of 50dp, so if we are within the 50dp but not fully at the bottom, scroll down + if (recyclerView.isNearBottom && !recyclerView.isFullyScrolled) { recyclerView.smoothScrollToPosition(adapter.itemCount) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 4c4b6311fa..7f6ec668ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -31,10 +31,11 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import kotlin.math.max class ConversationAdapter( context: Context, - cursor: Cursor, + cursor: Cursor?, conversation: Recipient?, originalLastSeen: Long, private val isReversed: Boolean, @@ -71,6 +72,8 @@ class ConversationAdapter( AccountId(conversation.address.toString()) else null + private val expandedMessageIds = mutableSetOf() + init { lifecycleCoroutineScope.launch(IO) { while (isActive) { @@ -140,6 +143,7 @@ class ConversationAdapter( } } val contact = contactCache[senderIdHash] + val isExpanded = expandedMessageIds.contains(message.messageId) visibleMessageView.bind( message = message, @@ -155,7 +159,11 @@ class ConversationAdapter( lastSentMessageId = lastSentMessageId, delegate = visibleMessageViewDelegate, downloadPendingAttachment = downloadPendingAttachment, - retryFailedAttachments = retryFailedAttachments + retryFailedAttachments = retryFailedAttachments, + isTextExpanded = isExpanded, + onTextExpanded = { messageId -> + expandedMessageIds.add(messageId) + } ) if (!message.isDeleted) { @@ -295,6 +303,13 @@ class ConversationAdapter( val cursor = this.cursor ?: return null if (!cursor.moveToPosition(firstVisiblePosition)) return null val message = messageDB.readerFor(cursor).current ?: return null - return message.timestamp + if (message.reactions.isEmpty()) { + // If the message has no reactions, we can use the timestamp directly + return message.timestamp + } + + // Otherwise, we will need to take the reaction timestamp into account + val maxReactionTimestamp = message.reactions.maxOf { it.dateReceived } + return max(message.timestamp, maxReactionTimestamp) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt index 3ada7066c2..01ce8e6368 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt @@ -20,6 +20,8 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat +import androidx.core.graphics.Insets +import androidx.core.view.WindowInsetsCompat import androidx.core.view.doOnLayout import androidx.core.view.isVisible import androidx.vectordrawable.graphics.drawable.AnimatorInflaterCompat @@ -58,6 +60,7 @@ import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.util.AnimationCompleteListener import org.thoughtcrime.securesms.util.DateUtils +import org.thoughtcrime.securesms.util.applySafeInsetsPaddings @AndroidEntryPoint class ConversationReactionOverlay : FrameLayout { @@ -114,6 +117,8 @@ class ConversationReactionOverlay : FrameLayout { d } + private var systemInsets: Insets = Insets.NONE + constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) @@ -134,6 +139,27 @@ class ConversationReactionOverlay : FrameLayout { scrubberHorizontalMargin = resources.getDimensionPixelOffset(R.dimen.conversation_reaction_scrub_horizontal_margin) animationEmojiStartDelayFactor = resources.getInteger(R.integer.reaction_scrubber_emoji_reveal_duration_start_delay_factor) initAnimators() + + // Use your existing utility to handle insets + applySafeInsetsPaddings( + typeMask = WindowInsetsCompat.Type.systemBars(), + consumeInsets = false, // Don't consume so children can also access them + applyTop = false, // Don't apply as padding, just capture the values + applyBottom = false + ) { insets -> + // Store the insets for our layout calculations + systemInsets = insets + } + } + + private fun getAvailableScreenHeight(): Int { + val displayMetrics = resources.displayMetrics + return displayMetrics.heightPixels - systemInsets.top - systemInsets.bottom + } + + private fun getAvailableScreenWidth(): Int { + val displayMetrics = resources.displayMetrics + return displayMetrics.widthPixels - systemInsets.left - systemInsets.right } fun show(activity: Activity, @@ -155,10 +181,7 @@ class ConversationReactionOverlay : FrameLayout { val conversationItemSnapshot = selectedConversationModel.bitmap conversationBubble.layoutParams = LinearLayout.LayoutParams(conversationItemSnapshot.width, conversationItemSnapshot.height) conversationBubble.background = BitmapDrawable(resources, conversationItemSnapshot) - conversationTimestamp.text = dateUtils.getDisplayFormattedTimeSpanString( - Locale.getDefault(), - messageRecord.timestamp - ) + conversationTimestamp.text = dateUtils.getDisplayFormattedTimeSpanString(messageRecord.timestamp) updateConversationTimestamp(messageRecord) val isMessageOnLeft = selectedConversationModel.isOutgoing xor ViewUtil.isLtr(this) conversationItem.scaleX = LONG_PRESS_SCALE_FACTOR @@ -188,13 +211,18 @@ class ConversationReactionOverlay : FrameLayout { val recipient = threadDatabase.getRecipientForThreadId(messageRecord.threadId) val contextMenu = ConversationContextMenu(dropdownAnchor, recipient?.let { getMenuActionItems(messageRecord, it) }.orEmpty()) this.contextMenu = contextMenu + var endX = if (isMessageOnLeft) scrubberHorizontalMargin.toFloat() else selectedConversationModel.bubbleX - conversationItem.width + selectedConversationModel.bubbleWidth var endY = selectedConversationModel.bubbleY - statusBarHeight conversationItem.x = endX conversationItem.y = endY + val conversationItemSnapshot = selectedConversationModel.bitmap val isWideLayout = contextMenu.getMaxWidth() + scrubberWidth < width - val overlayHeight = height + + // Use our own available height calculation + val availableHeight = getAvailableScreenHeight() + val bubbleWidth = selectedConversationModel.bubbleWidth var endApparentTop = endY var endScale = 1f @@ -202,8 +230,12 @@ class ConversationReactionOverlay : FrameLayout { val reactionBarTopPadding = DimensionUnit.DP.toPixels(32f) val reactionBarHeight = backgroundView.height var reactionBarBackgroundY: Float + + // Use actual content height from context menu + val actualMenuHeight = contextMenu.getMaxHeight() + if (isWideLayout) { - val everythingFitsVertically = reactionBarHeight + menuPadding + reactionBarTopPadding + conversationItemSnapshot.height < overlayHeight + val everythingFitsVertically = reactionBarHeight + menuPadding + reactionBarTopPadding + conversationItemSnapshot.height < availableHeight if (everythingFitsVertically) { val reactionBarFitsAboveItem = conversationItem.y > reactionBarHeight + menuPadding + reactionBarTopPadding if (reactionBarFitsAboveItem) { @@ -213,7 +245,7 @@ class ConversationReactionOverlay : FrameLayout { reactionBarBackgroundY = reactionBarTopPadding } } else { - val spaceAvailableForItem = overlayHeight - reactionBarHeight - menuPadding - reactionBarTopPadding + val spaceAvailableForItem = availableHeight - reactionBarHeight - menuPadding - reactionBarTopPadding endScale = spaceAvailableForItem / conversationItem.height endX += Util.halfOffsetFromScale(conversationItemSnapshot.width, endScale) * if (isMessageOnLeft) -1 else 1 endY = reactionBarHeight + menuPadding + reactionBarTopPadding - Util.halfOffsetFromScale(conversationItemSnapshot.height, endScale) @@ -222,62 +254,68 @@ class ConversationReactionOverlay : FrameLayout { } else { val reactionBarOffset = DimensionUnit.DP.toPixels(48f) val spaceForReactionBar = Math.max(reactionBarHeight + reactionBarOffset, 0f) - val everythingFitsVertically = contextMenu.getMaxHeight() + conversationItemSnapshot.height + menuPadding + spaceForReactionBar < overlayHeight + val everythingFitsVertically = actualMenuHeight + conversationItemSnapshot.height + menuPadding + spaceForReactionBar < availableHeight + if (everythingFitsVertically) { val bubbleBottom = selectedConversationModel.bubbleY + conversationItemSnapshot.height - val menuFitsBelowItem = bubbleBottom + menuPadding + contextMenu.getMaxHeight() <= overlayHeight + statusBarHeight + val menuFitsBelowItem = bubbleBottom + menuPadding + actualMenuHeight <= availableHeight + statusBarHeight + if (menuFitsBelowItem) { - if (conversationItem.y < 0) { - endY = 0f + if (conversationItem.y < systemInsets.top) { + endY = systemInsets.top.toFloat() } val contextMenuTop = endY + conversationItemSnapshot.height - reactionBarBackgroundY = getReactionBarOffsetForTouch(selectedConversationModel.bubbleY, contextMenuTop, menuPadding, reactionBarOffset, reactionBarHeight, reactionBarTopPadding, endY) + reactionBarBackgroundY = getReactionBarOffsetForTouch( + selectedConversationModel.bubbleY, + contextMenuTop, + menuPadding, + reactionBarOffset, + reactionBarHeight, + reactionBarTopPadding, + endY + ) if (reactionBarBackgroundY <= reactionBarTopPadding) { endY = backgroundView.height + menuPadding + reactionBarTopPadding } } else { - endY = overlayHeight - contextMenu.getMaxHeight() - 2*menuPadding - conversationItemSnapshot.height + endY = availableHeight - actualMenuHeight - 2*menuPadding - conversationItemSnapshot.height reactionBarBackgroundY = endY - reactionBarHeight - menuPadding } endApparentTop = endY - } else if (reactionBarOffset + reactionBarHeight + contextMenu.getMaxHeight() + menuPadding < overlayHeight) { - val spaceAvailableForItem = overlayHeight.toFloat() - contextMenu.getMaxHeight() - menuPadding - spaceForReactionBar + } else if (reactionBarOffset + reactionBarHeight + actualMenuHeight + menuPadding < availableHeight) { + val spaceAvailableForItem = availableHeight.toFloat() - actualMenuHeight - menuPadding - spaceForReactionBar endScale = spaceAvailableForItem / conversationItemSnapshot.height endX += Util.halfOffsetFromScale(conversationItemSnapshot.width, endScale) * if (isMessageOnLeft) -1 else 1 endY = spaceForReactionBar - Util.halfOffsetFromScale(conversationItemSnapshot.height, endScale) - reactionBarBackgroundY = reactionBarTopPadding //getReactionBarOffsetForTouch(selectedConversationModel.getBubbleY(), contextMenuTop + Util.halfOffsetFromScale(conversationItemSnapshot.getHeight(), endScale), menuPadding, reactionBarOffset, reactionBarHeight, reactionBarTopPadding, endY); + reactionBarBackgroundY = reactionBarTopPadding endApparentTop = endY + Util.halfOffsetFromScale(conversationItemSnapshot.height, endScale) } else { - contextMenu.height = contextMenu.getMaxHeight() / 2 - val menuHeight = contextMenu.height - val fitsVertically = menuHeight + conversationItem.height + menuPadding * 2 + reactionBarHeight + reactionBarTopPadding < overlayHeight - if (fitsVertically) { - val bubbleBottom = selectedConversationModel.bubbleY + conversationItemSnapshot.height - val menuFitsBelowItem = bubbleBottom + menuPadding + menuHeight <= overlayHeight + statusBarHeight - if (menuFitsBelowItem) { - reactionBarBackgroundY = conversationItem.y - menuPadding - reactionBarHeight - if (reactionBarBackgroundY < reactionBarTopPadding) { - endY = reactionBarTopPadding + reactionBarHeight + menuPadding - reactionBarBackgroundY = reactionBarTopPadding - } - } else { - endY = overlayHeight - menuHeight - menuPadding - conversationItemSnapshot.height - reactionBarBackgroundY = endY - reactionBarHeight - menuPadding - } - endApparentTop = endY - } else { - val spaceAvailableForItem = overlayHeight.toFloat() - menuHeight - menuPadding * 2 - reactionBarHeight - reactionBarTopPadding + // Calculate how much we need to scale the bubble to fit everything + val spaceAvailableForItem = availableHeight.toFloat() - actualMenuHeight - menuPadding * 2 - reactionBarHeight - reactionBarTopPadding + + if (spaceAvailableForItem > 0) { endScale = spaceAvailableForItem / conversationItemSnapshot.height endX += Util.halfOffsetFromScale(conversationItemSnapshot.width, endScale) * if (isMessageOnLeft) -1 else 1 endY = reactionBarHeight - Util.halfOffsetFromScale(conversationItemSnapshot.height, endScale) + menuPadding + reactionBarTopPadding reactionBarBackgroundY = reactionBarTopPadding endApparentTop = reactionBarHeight + menuPadding + reactionBarTopPadding + } else { + // If we can't fit everything even with scaling, use a minimum scale + val minScale = 0.2f // Minimum readable scale + endScale = minScale + endX += Util.halfOffsetFromScale(conversationItemSnapshot.width, endScale) * if (isMessageOnLeft) -1 else 1 + endY = reactionBarHeight - Util.halfOffsetFromScale(conversationItemSnapshot.height, endScale) + menuPadding + reactionBarTopPadding + reactionBarBackgroundY = reactionBarTopPadding + endApparentTop = reactionBarHeight + menuPadding + reactionBarTopPadding } } } - reactionBarBackgroundY = Math.max(reactionBarBackgroundY, -statusBarHeight.toFloat()) + + // Adjust for system insets + reactionBarBackgroundY = maxOf(reactionBarBackgroundY, systemInsets.top.toFloat() - statusBarHeight) hideAnimatorSet.end() visibility = VISIBLE + val scrubberX = if (isMessageOnLeft) { scrubberHorizontalMargin.toFloat() } else { @@ -288,17 +326,21 @@ class ConversationReactionOverlay : FrameLayout { foregroundView.y = reactionBarBackgroundY + reactionBarHeight / 2f - foregroundView.height / 2f backgroundView.x = scrubberX backgroundView.y = reactionBarBackgroundY + verticalScrubBoundary.update(reactionBarBackgroundY, - lastSeenDownPoint.y + distanceFromTouchDownPointToBottomOfScrubberDeadZone) + lastSeenDownPoint.y + distanceFromTouchDownPointToBottomOfScrubberDeadZone) updateBoundsOnLayoutChanged() revealAnimatorSet.start() + if (isWideLayout) { val scrubberRight = scrubberX + scrubberWidth val offsetX = when { isMessageOnLeft -> scrubberRight + menuPadding else -> scrubberX - contextMenu.getMaxWidth() - menuPadding } - contextMenu.show(offsetX.toInt(), Math.min(backgroundView.y, (overlayHeight - contextMenu.getMaxHeight()).toFloat()).toInt()) + // Adjust Y position to account for insets + val adjustedY = minOf(backgroundView.y, (availableHeight - actualMenuHeight).toFloat()).toInt() + contextMenu.show(offsetX.toInt(), adjustedY) } else { val contentX = if (isMessageOnLeft) scrubberHorizontalMargin.toFloat() else selectedConversationModel.bubbleX val offsetX = when { @@ -308,6 +350,7 @@ class ConversationReactionOverlay : FrameLayout { val menuTop = endApparentTop + conversationItemSnapshot.height * endScale contextMenu.show(offsetX.toInt(), (menuTop + menuPadding).toInt()) } + val revealDuration = context.resources.getInteger(R.integer.reaction_scrubber_reveal_duration) conversationBubble.animate() .scaleX(endScale) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt index 81926bc991..3a665debc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt @@ -18,6 +18,8 @@ import androidx.compose.ui.tooling.preview.Preview import com.squareup.phrase.Phrase import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY +import org.thoughtcrime.securesms.InputBarDialogs +import org.thoughtcrime.securesms.InputbarViewModel import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.ClearEmoji import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.ConfirmRecreateGroup import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.HideClearEmoji @@ -28,11 +30,14 @@ import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.ShowOpenUrlDialog import org.thoughtcrime.securesms.groups.compose.CreateGroupScreen import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.CTAFeature +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.OpenURLAlertDialog import org.thoughtcrime.securesms.ui.RadioOption +import org.thoughtcrime.securesms.ui.SimpleSessionProCTA import org.thoughtcrime.securesms.ui.components.DialogTitledRadioButton +import org.thoughtcrime.securesms.ui.components.annotatedStringResource import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType @@ -43,9 +48,17 @@ import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme @Composable fun ConversationV2Dialogs( dialogsState: ConversationViewModel.DialogsState, - sendCommand: (ConversationViewModel.Commands) -> Unit + inputBarDialogsState: InputbarViewModel.InputBarDialogsState, + sendCommand: (ConversationViewModel.Commands) -> Unit, + sendInputBarCommand: (InputbarViewModel.Commands) -> Unit ){ SessionMaterialTheme { + // inputbar dialogs + InputBarDialogs( + inputBarDialogsState = inputBarDialogsState, + sendCommand = sendInputBarCommand + ) + // open link confirmation if(!dialogsState.openLinkDialogUrl.isNullOrEmpty()){ OpenURLAlertDialog( @@ -116,7 +129,7 @@ fun ConversationV2Dialogs( } }, buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(id = R.string.delete)), color = LocalColors.current.danger, onClick = { @@ -129,7 +142,7 @@ fun ConversationV2Dialogs( ) } ), - DialogButtonModel( + DialogButtonData( GetString(stringResource(R.string.cancel)) ) ) @@ -147,7 +160,7 @@ fun ConversationV2Dialogs( Phrase.from(txt).put(EMOJI_KEY, dialogsState.clearAllEmoji.emoji).format().toString() }, buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(id = R.string.clear)), color = LocalColors.current.danger, onClick = { @@ -157,7 +170,7 @@ fun ConversationV2Dialogs( ) } ), - DialogButtonModel( + DialogButtonData( GetString(stringResource(R.string.cancel)) ) ) @@ -173,14 +186,14 @@ fun ConversationV2Dialogs( title = stringResource(R.string.recreateGroup), text = stringResource(R.string.legacyGroupChatHistory), buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(id = R.string.theContinue)), color = LocalColors.current.danger, onClick = { sendCommand(ConfirmRecreateGroup) } ), - DialogButtonModel( + DialogButtonData( GetString(stringResource(R.string.cancel)) ) ) @@ -222,7 +235,9 @@ fun PreviewURLDialog(){ dialogsState = ConversationViewModel.DialogsState( openLinkDialogUrl = "https://google.com" ), - sendCommand = {} + inputBarDialogsState = InputbarViewModel.InputBarDialogsState(), + sendCommand = {}, + sendInputBarCommand = {} ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 67a911f2f1..282e808249 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -56,6 +56,7 @@ import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.InputbarViewModel import org.thoughtcrime.securesms.audio.AudioSlidePlayer import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.LokiAPIDatabase @@ -71,6 +72,7 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.groups.ExpiredGroupManager import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.mms.AudioSlide +import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.ui.components.ConversationAppBarData import org.thoughtcrime.securesms.ui.components.ConversationAppBarPagerData @@ -85,7 +87,6 @@ import org.thoughtcrime.securesms.webrtc.data.State import java.time.ZoneId import java.util.UUID - class ConversationViewModel( val threadId: Long, val edKeyPair: KeyPair?, @@ -109,7 +110,11 @@ class ConversationViewModel( private val avatarUtils: AvatarUtils, private val recipientChangeSource: RecipientChangeSource, private val openGroupManager: OpenGroupManager, -) : ViewModel() { + private val proStatusManager: ProStatusManager, +) : InputbarViewModel( + application = application, + proStatusManager = proStatusManager +) { val showSendAfterApprovalText: Boolean get() = recipient?.run { @@ -336,10 +341,13 @@ class ConversationViewModel( _uiState.update { it.copy( shouldExit = recipient == null, - inputBarState = getInputBarState(recipient, community, deprecationState), messageRequestState = buildMessageRequestState(recipient), ) } + + _inputBarState.update { + getInputBarState(recipient, community, deprecationState) + } } } @@ -350,9 +358,12 @@ class ConversationViewModel( _uiState.update { it.copy( shouldExit = recipient == null, - inputBarState = getInputBarState(recipient, _openGroup.value, legacyGroupDeprecationManager.deprecationState.value), ) } + + _inputBarState.update { + getInputBarState(recipient, _openGroup.value, legacyGroupDeprecationManager.deprecationState.value) + } } } @@ -379,11 +390,13 @@ class ConversationViewModel( community: OpenGroup?, deprecationState: LegacyGroupDeprecationManager.DeprecationState ): InputBarState { + val currentCharLimitState = _inputBarState.value.charLimitState return when { // prioritise cases that demand the input to be hidden !shouldShowInput(recipient, community, deprecationState) -> InputBarState( contentState = InputBarContentState.Hidden, - enableAttachMediaControls = false + enableAttachMediaControls = false, + charLimitState = currentCharLimitState ) // next are cases where the input is visible but disabled @@ -395,7 +408,8 @@ class ConversationViewModel( _uiEvents.tryEmit(ConversationUiEvent.ShowUnblockConfirmation) } ), - enableAttachMediaControls = false + enableAttachMediaControls = false, + charLimitState = currentCharLimitState ) // the user does not have write access in the community @@ -403,13 +417,15 @@ class ConversationViewModel( contentState = InputBarContentState.Disabled( text = application.getString(R.string.permissionsWriteCommunity), ), - enableAttachMediaControls = false + enableAttachMediaControls = false, + charLimitState = currentCharLimitState ) // other cases the input is visible, and the buttons might be disabled based on some criteria else -> InputBarState( contentState = InputBarContentState.Visible, - enableAttachMediaControls = shouldEnableInputMediaControls(recipient) + enableAttachMediaControls = shouldEnableInputMediaControls(recipient), + charLimitState = currentCharLimitState ) } } @@ -1330,6 +1346,7 @@ class ConversationViewModel( private val avatarUtils: AvatarUtils, private val recipientChangeSource: RecipientChangeSource, private val openGroupManager: OpenGroupManager, + private val proStatusManager: ProStatusManager, ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { @@ -1356,6 +1373,7 @@ class ConversationViewModel( avatarUtils = avatarUtils, recipientChangeSource = recipientChangeSource, openGroupManager = openGroupManager, + proStatusManager = proStatusManager, ) as T } } @@ -1411,26 +1429,9 @@ data class ConversationUiState( val uiMessages: List = emptyList(), val messageRequestState: MessageRequestUiState = MessageRequestUiState.Invisible, val shouldExit: Boolean = false, - val inputBarState: InputBarState = InputBarState(), - val showLoader: Boolean = false, ) -data class InputBarState( - val contentState: InputBarContentState = InputBarContentState.Visible, - // Note: These input media controls are with regard to whether the user can attach multimedia files - // or record voice messages to be sent to a recipient - they are NOT things like video or audio - // playback controls. - val enableAttachMediaControls: Boolean = true, -) - -sealed interface InputBarContentState { - data object Hidden : InputBarContentState - data object Visible : InputBarContentState - data class Disabled(val text: String, val onClick: (() -> Unit)? = null) : InputBarContentState -} - - sealed interface ConversationUiEvent { data class NavigateToConversation(val threadId: Long) : ConversationUiEvent data class ShowDisappearingMessages(val threadId: Long) : ConversationUiEvent diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index c611268702..ee7d8d5d24 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -64,7 +64,7 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment -import org.thoughtcrime.securesms.MediaPreviewActivity.getPreviewIntent +import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader @@ -127,7 +127,7 @@ class MessageDetailActivity : ScreenLockActionBarActivity(), ActivityDispatcher when (it) { Event.Finish -> finish() is Event.StartMediaPreview -> startActivity( - getPreviewIntent(this@MessageDetailActivity, it.args) + MediaPreviewActivity.getPreviewIntent(this@MessageDetailActivity, it.args) ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 1374a277d7..8d1035cca2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.TitledText import org.thoughtcrime.securesms.util.AvatarUIData import org.thoughtcrime.securesms.util.AvatarUtils +import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.observeChanges import java.util.Date import java.util.Locale @@ -61,6 +62,7 @@ class MessageDetailsViewModel @AssistedInject constructor( private val deprecationManager: LegacyGroupDeprecationManager, private val context: ApplicationContext, private val avatarUtils: AvatarUtils, + private val dateUtils: DateUtils, messageDataProvider: MessageDataProvider, storage: Storage ) : ViewModel() { @@ -149,15 +151,15 @@ class MessageDetailsViewModel @AssistedInject constructor( sent = if (messageRecord.isSending && errorString == null) { val sendingWithEllipsisString = context.getString(R.string.sending) + ellipsis // e.g., "Sending…" TitledText(sendingWithEllipsisString, null) - } else if (messageRecord.isSent && errorString == null) { - dateReceived.let(::Date).toString().let { TitledText(R.string.sent, it) } + } else if (dateSent > 0L && errorString == null) { + TitledText(R.string.sent, dateUtils.getMessageDateTimeFormattedString(dateSent)) } else { null // Not sending or sent? Don't display anything for the "Sent" element. }, // Set the "Received" message info TitledText appropriately - received = if (messageRecord.isIncoming && errorString == null) { - dateReceived.let(::Date).toString().let { TitledText(R.string.received, it) } + received = if (dateReceived > 0L && messageRecord.isIncoming && errorString == null) { + TitledText(R.string.received, dateUtils.getMessageDateTimeFormattedString(dateReceived)) } else { null // Not incoming? Then don't display anything for the "Received" element. }, @@ -218,6 +220,7 @@ class MessageDetailsViewModel @AssistedInject constructor( val slide = mmsRecord.slideDeck.slides[index] ?: return // only open to downloaded images if (slide.isInProgress || slide.isFailed) return + if(state.thread == null) return viewModelScope.launch { MediaPreviewArgs(slide, state.mmsRecord, state.thread) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index f0ab330b73..5f59f85653 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -20,9 +20,10 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ViewInputBarBinding import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.recipients.Recipient -import org.thoughtcrime.securesms.conversation.v2.InputBarContentState -import org.thoughtcrime.securesms.conversation.v2.InputBarState +import org.thoughtcrime.securesms.InputbarViewModel +import org.thoughtcrime.securesms.InputbarViewModel.InputBarContentState import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.QuoteView @@ -31,7 +32,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.util.addTextChangedListener import org.thoughtcrime.securesms.util.contains -import org.thoughtcrime.securesms.util.setSafeOnClickListener // TODO: A lot of the logic regarding voice messages is currently performed in the ConversationActivity // TODO: and here - it would likely be best to move this into the CA's ViewModel. @@ -84,6 +84,10 @@ class InputBar @JvmOverloads constructor( get() = binding.inputBarEditText.text?.toString() ?: "" set(value) { binding.inputBarEditText.setText(value) } + fun setText(text: CharSequence, type: TextView.BufferType){ + binding.inputBarEditText.setText(text, type) + } + var voiceRecorderState = VoiceRecorderState.Idle private val attachmentsButton = InputBarButton(context, R.drawable.ic_plus).apply { @@ -98,54 +102,30 @@ class InputBar @JvmOverloads constructor( contentDescription = context.getString(R.string.AccessibilityId_send) } - init { - // Attachments button - binding.attachmentsButtonContainer.addView(attachmentsButton) - attachmentsButton.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - attachmentsButton.onPress = { toggleAttachmentOptions() } - - // Microphone button - binding.microphoneOrSendButtonContainer.addView(microphoneButton) - microphoneButton.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - - microphoneButton.onMove = { delegate?.onMicrophoneButtonMove(it) } - microphoneButton.onCancel = { delegate?.onMicrophoneButtonCancel(it) } - - // Use a separate 'raw' OnTouchListener to record the microphone button down/up timestamps because - // they don't get delayed by any multi-threading or delegates which throw off the timestamp accuracy. - // For example: If we bind something to `microphoneButton.onPress` and also log something in - // `microphoneButton.onUp` and tap the button then the logged output order is onUp and THEN onPress! - microphoneButton.setOnTouchListener(object : OnTouchListener { - override fun onTouch(v: View, event: MotionEvent): Boolean { - - // We only handle single finger touch events so just consume the event and bail if there are more - if (event.pointerCount > 1) return true - - when (event.action) { - MotionEvent.ACTION_DOWN -> { - - // Only start spinning up the voice recorder if we're not already recording, setting up, or tearing down - if (voiceRecorderState == VoiceRecorderState.Idle) { - startRecordingVoiceMessage() - } - } - MotionEvent.ACTION_UP -> { + private val textColor: Int by lazy { + context.getColorFromAttr(android.R.attr.textColorPrimary) + } - // Handle the pointer up event appropriately, whether that's to keep recording if recording was locked - // on, or finishing recording if just hold-to-record. - delegate?.onMicrophoneButtonUp(event) - } - } + private val dangerColor: Int by lazy { + context.getColorFromAttr(R.attr.danger) + } + + var sendOnly: Boolean = false - // Return false to propagate the event rather than consuming it - return false + init { + // Parse custom attributes + attrs?.let { attributeSet -> + val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.InputBar) + try { + sendOnly = typedArray.getBoolean(R.styleable.InputBar_sendOnly, false) + } finally { + typedArray.recycle() } - }) + } // Send button binding.microphoneOrSendButtonContainer.addView(sendButton) sendButton.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - sendButton.isVisible = false sendButton.onUp = { e -> if (sendButton.contains(PointF(e.x, e.y))) { delegate?.sendMessage() @@ -162,6 +142,60 @@ class InputBar @JvmOverloads constructor( val incognitoFlag = if (TextSecurePreferences.isIncognitoKeyboardEnabled(context)) 16777216 else 0 binding.inputBarEditText.imeOptions = binding.inputBarEditText.imeOptions or incognitoFlag // Always use incognito keyboard if setting enabled binding.inputBarEditText.delegate = this + + if(sendOnly){ + sendButton.isVisible = true + binding.attachmentsButtonContainer.isVisible = false + microphoneButton.isVisible = false + } else { + sendButton.isVisible = false + + // Attachments button + binding.attachmentsButtonContainer.addView(attachmentsButton) + attachmentsButton.layoutParams = + LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + attachmentsButton.onPress = { toggleAttachmentOptions() } + + // Microphone button + binding.microphoneOrSendButtonContainer.addView(microphoneButton) + microphoneButton.layoutParams = + LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + + microphoneButton.onMove = { delegate?.onMicrophoneButtonMove(it) } + microphoneButton.onCancel = { delegate?.onMicrophoneButtonCancel(it) } + + // Use a separate 'raw' OnTouchListener to record the microphone button down/up timestamps because + // they don't get delayed by any multi-threading or delegates which throw off the timestamp accuracy. + // For example: If we bind something to `microphoneButton.onPress` and also log something in + // `microphoneButton.onUp` and tap the button then the logged output order is onUp and THEN onPress! + microphoneButton.setOnTouchListener(object : OnTouchListener { + override fun onTouch(v: View, event: MotionEvent): Boolean { + + // We only handle single finger touch events so just consume the event and bail if there are more + if (event.pointerCount > 1) return true + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + + // Only start spinning up the voice recorder if we're not already recording, setting up, or tearing down + if (voiceRecorderState == VoiceRecorderState.Idle) { + startRecordingVoiceMessage() + } + } + + MotionEvent.ACTION_UP -> { + + // Handle the pointer up event appropriately, whether that's to keep recording if recording was locked + // on, or finishing recording if just hold-to-record. + delegate?.onMicrophoneButtonUp(event) + } + } + + // Return false to propagate the event rather than consuming it + return false + } + }) + } } override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { @@ -174,13 +208,11 @@ class InputBar @JvmOverloads constructor( } override fun inputBarEditTextContentChanged(text: CharSequence) { - microphoneButton.isVisible = text.trim().isEmpty() - sendButton.isVisible = microphoneButton.isGone + microphoneButton.isVisible = text.trim().isEmpty() && !sendOnly + sendButton.isVisible = microphoneButton.isGone || sendOnly delegate?.inputBarEditTextContentChanged(text) } - override fun inputBarEditTextHeightChanged(newValue: Int) { } - override fun commitInputContent(contentUri: Uri) { delegate?.commitInputContent(contentUri) } private fun toggleAttachmentOptions() { delegate?.toggleAttachmentOptions() } @@ -260,9 +292,9 @@ class InputBar @JvmOverloads constructor( } binding.inputBarEditText.isVisible = showInput - attachmentsButton.isVisible = showInput - microphoneButton.isVisible = showInput && text.isEmpty() - sendButton.isVisible = showInput && text.isNotEmpty() + attachmentsButton.isVisible = showInput && !sendOnly + microphoneButton.isVisible = showInput && text.isEmpty() && !sendOnly + sendButton.isVisible = showInput && text.isNotEmpty() || sendOnly } private fun updateMultimediaButtonsState() { @@ -279,7 +311,7 @@ class InputBar @JvmOverloads constructor( binding.inputBarEditText.setEditableFactory(factory) } - fun setState(state: InputBarState){ + fun setState(state: InputbarViewModel.InputBarState){ // handle content state when(state.contentState){ is InputBarContentState.Hidden ->{ @@ -309,6 +341,22 @@ class InputBar @JvmOverloads constructor( // handle buttons state allowAttachMultimediaButtons = state.enableAttachMediaControls + + // handle char limit + if(state.charLimitState != null){ + binding.characterLimitText.text = state.charLimitState.countFormatted + binding.characterLimitText.setTextColor(if(state.charLimitState.danger) dangerColor else textColor) + binding.characterLimitContainer.setOnClickListener { + delegate?.onCharLimitTapped() + } + + binding.badgePro.isVisible = state.charLimitState.showProBadge + + binding.characterLimitContainer.isVisible = true + } else { + binding.characterLimitContainer.setOnClickListener(null) + binding.characterLimitContainer.isVisible = false + } } } @@ -322,4 +370,5 @@ interface InputBarDelegate { fun onMicrophoneButtonUp(event: MotionEvent) fun sendMessage() fun commitInputContent(contentUri: Uri) + fun onCharLimitTapped() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarEditText.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarEditText.kt index 161768ed6f..f70dab9f55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarEditText.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarEditText.kt @@ -22,8 +22,6 @@ class InputBarEditText : AppCompatEditText { var allowMultimediaInput: Boolean = true - private val snMinHeight = toPx(40.0f, resources) - private val snMaxHeight = toPx(80.0f, resources) constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) @@ -37,13 +35,6 @@ class InputBarEditText : AppCompatEditText { // edit text. val width = (screenWidth - 2 * toPx(64.0f, resources)).roundToInt() if (width < 0) { return } // screenWidth initially evaluates to 0 - val height = TextUtilities.getIntrinsicHeight(text, paint, width).toFloat() - val constrainedHeight = min(max(height, snMinHeight), snMaxHeight) - if (constrainedHeight.roundToInt() == this.height) { return } - val layoutParams = this.layoutParams as? RelativeLayout.LayoutParams ?: return - layoutParams.height = constrainedHeight.roundToInt() - this.layoutParams = layoutParams - delegate?.inputBarEditTextHeightChanged(constrainedHeight.roundToInt()) } override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection? { @@ -78,6 +69,5 @@ class InputBarEditText : AppCompatEditText { interface InputBarEditTextDelegate { fun inputBarEditTextContentChanged(text: CharSequence) - fun inputBarEditTextHeightChanged(newValue: Int) fun commitInputContent(contentUri: Uri) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt index 45dc18ba3e..bc0e12f3f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt @@ -17,15 +17,20 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.libsession_util.allWithStatus @@ -33,6 +38,7 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix +import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.database.DatabaseContentProviders.Conversation import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.GroupMemberDatabase @@ -234,6 +240,10 @@ class MentionViewModel( * As "@123456" is the standard format for mentioning a user, this method will replace "@Alice" with "@123456" */ fun normalizeMessageBody(): String { + return deconstructMessageMentions().trim() + } + + fun deconstructMessageMentions(): String { val spansWithRanges = editable.getSpans() .mapTo(mutableListOf()) { span -> span to (editable.getSpanStart(span)..editable.getSpanEnd(span)) @@ -244,22 +254,40 @@ class MentionViewModel( val sb = StringBuilder() var offset = 0 for ((span, range) in spansWithRanges) { - // Add content before the mention span. There's a possibility of overlapping spans so we need to - // safe guard the start offset here to not go over our span's start. + // Add content before the mention span val thisMentionStart = range.first val lastMentionEnd = offset.coerceAtMost(thisMentionStart) sb.append(editable, lastMentionEnd, thisMentionStart) // Replace the mention span with "@public key" - sb.append('@').append(span.member.publicKey).append(' ') + sb.append('@').append(span.member.publicKey) - // Safe guard offset to not go over the end of the editable. + // Check if the original mention span ended with a space + // The span includes the space, so we need to preserve it in the deconstructed version + if (range.last < editable.length && editable[range.last] == ' ') { + sb.append(' ') + } + + // Move offset to after the mention span (including the space) offset = (range.last + 1).coerceAtMost(editable.length) } // Add the remaining content sb.append(editable, offset, editable.length) - return sb.toString().trim() + return sb.toString() + } + + suspend fun reconstructMentions(raw: String): Editable { + editable.replace(0, editable.length, raw) + + val memberList = members.filterNotNull().first() + + MentionUtilities.substituteIdsInPlace( + editable, + memberList.associateBy { it.publicKey } + ) + + return editable } data class Member( diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index d0f2b6d47f..1876bc5a97 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -10,7 +10,6 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout -import androidx.compose.ui.graphics.Color import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.toBitmap import androidx.core.view.isGone @@ -22,15 +21,14 @@ import network.loki.messenger.databinding.ViewControlMessageBinding import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.ExpirationConfiguration -import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED import org.session.libsession.utilities.getColorFromAttr import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages -import org.thoughtcrime.securesms.conversation.disappearingmessages.expiryMode import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity @@ -84,8 +82,9 @@ class ControlMessageView : LinearLayout { binding.root.contentDescription = null binding.textView.text = messageBody + val messageContent = message.messageContent when { - message.isExpirationTimerUpdate -> { + messageContent is DisappearingMessageUpdate -> { binding.apply { expirationTimerView.isVisible = true @@ -98,12 +97,14 @@ class ControlMessageView : LinearLayout { } followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled - && !message.isOutgoing - && message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) - && threadRecipient?.isGroupOrCommunityRecipient != true + && !message.isOutgoing + && messageContent.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) + && threadRecipient?.isGroupOrCommunityRecipient != true if (followSetting.isVisible) { - binding.controlContentView.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) } + binding.controlContentView.setOnClickListener { + disappearingMessages.showFollowSettingDialog(context, threadId = message.threadId, recipient = message.recipient, messageContent) + } } else { binding.controlContentView.setOnClickListener(null) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/MessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/MessageUtilities.kt index a6068886c1..21866a0637 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/MessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/MessageUtilities.kt @@ -13,7 +13,6 @@ fun TextView.showDateBreak(message: MessageRecord, previous: MessageRecord?, dat val showDateBreak = (previous == null || message.timestamp - previous.timestamp > maxTimeBetweenBreaksMS) isVisible = showDateBreak text = if (showDateBreak) dateUtils.getDisplayFormattedTimeSpanString( - Locale.getDefault(), message.timestamp ) else "" } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index c2d1ba568d..fb3fb851f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -4,9 +4,10 @@ import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.content.res.ColorStateList -import android.graphics.Color import android.graphics.Rect +import android.text.Layout import android.text.Spannable +import android.text.StaticLayout import android.text.style.BackgroundColorSpan import android.text.style.ForegroundColorSpan import android.text.style.URLSpan @@ -15,6 +16,7 @@ import android.util.AttributeSet import android.util.Log import android.view.MotionEvent import android.view.View +import android.view.ViewTreeObserver import android.widget.Toast import androidx.annotation.ColorInt import androidx.constraintlayout.widget.ConstraintLayout @@ -22,6 +24,9 @@ import androidx.core.graphics.ColorUtils import androidx.core.text.getSpans import androidx.core.text.toSpannable import androidx.core.view.children +import androidx.core.view.doOnAttach +import androidx.core.view.doOnLayout +import androidx.core.view.doOnPreDraw import androidx.core.view.isVisible import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager @@ -33,6 +38,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.modifyLayoutParams +import org.session.libsession.utilities.needsCollapsing import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.messages.AttachmentControlView.AttachmentType.AUDIO @@ -43,6 +49,7 @@ import org.thoughtcrime.securesms.conversation.v2.messages.AttachmentControlView import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans +import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.PartAuthority @@ -58,6 +65,8 @@ class VisibleMessageContentView : ConstraintLayout { var delegate: VisibleMessageViewDelegate? = null var indexInAdapter: Int = -1 + private val MAX_COLLAPSED_LINE_COUNT = 25 + // region Lifecycle constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) @@ -74,7 +83,9 @@ class VisibleMessageContentView : ConstraintLayout { searchQuery: String? = null, downloadPendingAttachment: (DatabaseAttachment) -> Unit, retryFailedAttachments: (List) -> Unit, - suppressThumbnails: Boolean = false + suppressThumbnails: Boolean = false, + isTextExpanded: Boolean = false, + onTextExpanded: ((MessageId) -> Unit)? = null ) { // Background val color = if (message.isOutgoing) context.getAccentColor() @@ -115,6 +126,7 @@ class VisibleMessageContentView : ConstraintLayout { binding.deletedMessageView.root.isVisible = true binding.deletedMessageView.root.bind(message, getTextColor(context, message)) binding.bodyTextView.isVisible = false + binding.readMore.isVisible = false binding.quoteView.root.isVisible = false binding.linkPreviewView.root.isVisible = false binding.voiceMessageView.root.isVisible = false @@ -324,6 +336,11 @@ class VisibleMessageContentView : ConstraintLayout { } binding.bodyTextView.isVisible = message.body.isNotEmpty() && !hideBody + // set a max lines + binding.bodyTextView.maxLines = if(isTextExpanded) Int.MAX_VALUE else MAX_COLLAPSED_LINE_COUNT + + binding.readMore.isVisible = false + binding.contentParent.apply { isVisible = children.any { it.isVisible } } if (message.body.isNotEmpty() && !hideBody) { @@ -337,6 +354,30 @@ class VisibleMessageContentView : ConstraintLayout { span.onClick(binding.bodyTextView) } } + + // if the text was already manually expanded, we can skip this logic + if(!isTextExpanded && binding.bodyTextView.needsCollapsing( + availableWidthPx = context.resources.getDimensionPixelSize(R.dimen.max_bubble_width), + maxLines = MAX_COLLAPSED_LINE_COUNT) + ){ + // show the "Read mode" button + binding.readMore.setTextColor(color) + binding.readMore.isVisible = true + + // add read more click listener + val readMoreClickHandler: (MotionEvent) -> Unit = { event -> + val r = Rect() + binding.readMore.getGlobalVisibleRect(r) + if (r.contains(event.rawX.roundToInt(), event.rawY.roundToInt())) { + binding.bodyTextView.maxLines = Int.MAX_VALUE + binding.readMore.isVisible = false + onTextExpanded?.invoke(message.messageId) // Notify that text was expanded + } + } + onContentClick.add(readMoreClickHandler) + } else { + binding.readMore.isVisible = false + } } // handle bias for our views diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index c24520b0fc..734dbcbfc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -171,6 +171,8 @@ class VisibleMessageView : FrameLayout { delegate: VisibleMessageViewDelegate? = null, downloadPendingAttachment: (DatabaseAttachment) -> Unit, retryFailedAttachments: (List) -> Unit, + isTextExpanded: Boolean = false, + onTextExpanded: ((MessageId) -> Unit)? = null ) { clipToPadding = false clipChildren = false @@ -274,7 +276,6 @@ class VisibleMessageView : FrameLayout { // Date break val showDateBreak = isStartOfMessageCluster || snIsSelected binding.dateBreakTextView.text = if (showDateBreak) dateUtils.getDisplayFormattedTimeSpanString( - Locale.getDefault(), message.timestamp ) else null binding.dateBreakTextView.isVisible = showDateBreak @@ -311,7 +312,9 @@ class VisibleMessageView : FrameLayout { thread, searchQuery, downloadPendingAttachment = downloadPendingAttachment, - retryFailedAttachments = retryFailedAttachments + retryFailedAttachments = retryFailedAttachments, + isTextExpanded = isTextExpanded, + onTextExpanded = onTextExpanded ) binding.messageContentView.root.delegate = delegate onDoubleTap = { binding.messageContentView.root.onContentDoubleTap?.invoke() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt index 8db07bc0fa..4e4166105d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt @@ -62,6 +62,7 @@ class VoiceMessageView @JvmOverloads constructor( if (attachment.audioDurationMs > 0) { val formattedVoiceMessageDuration = MediaUtil.getFormattedVoiceMessageDuration(attachment.audioDurationMs) binding.voiceMessageViewDurationTextView.text = formattedVoiceMessageDuration + durationMS = attachment.audioDurationMs } else { Log.w(TAG, "For some reason attachment.audioDurationMs was NOT greater than zero!") binding.voiceMessageViewDurationTextView.text = "--:--" @@ -79,6 +80,7 @@ class VoiceMessageView @JvmOverloads constructor( override fun onPlayerStart(player: AudioSlidePlayer) { isPlaying = true + if (player.duration != C.TIME_UNSET) { durationMS = player.duration } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsDialogs.kt index b436d0aef7..bab4a319a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsDialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsDialogs.kt @@ -22,8 +22,9 @@ import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_K import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsViewModel.Commands.* import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString +import org.thoughtcrime.securesms.ui.PinProCTA import org.thoughtcrime.securesms.ui.RadioOption import org.thoughtcrime.securesms.ui.components.DialogTitledRadioButton import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField @@ -42,25 +43,37 @@ fun ConversationSettingsDialogs( // Simple dialogs if (dialogsState.showSimpleDialog != null) { - AlertDialog( - onDismissRequest = { - // hide dialog - sendCommand(HideSimpleDialog) - }, - title = annotatedStringResource(dialogsState.showSimpleDialog.title), - text = annotatedStringResource(dialogsState.showSimpleDialog.message), - buttons = listOf( - DialogButtonModel( + val buttons = mutableListOf() + if(dialogsState.showSimpleDialog.positiveText != null) { + buttons.add( + DialogButtonData( text = GetString(dialogsState.showSimpleDialog.positiveText), - color = if(dialogsState.showSimpleDialog.positiveStyleDanger) LocalColors.current.danger + color = if (dialogsState.showSimpleDialog.positiveStyleDanger) LocalColors.current.danger else LocalColors.current.text, + qaTag = dialogsState.showSimpleDialog.positiveQaTag, onClick = dialogsState.showSimpleDialog.onPositive - ), - DialogButtonModel( + ) + ) + } + if(dialogsState.showSimpleDialog.negativeText != null){ + buttons.add( + DialogButtonData( text = GetString(dialogsState.showSimpleDialog.negativeText), + qaTag = dialogsState.showSimpleDialog.negativeQaTag, onClick = dialogsState.showSimpleDialog.onNegative ) ) + } + + AlertDialog( + onDismissRequest = { + // hide dialog + sendCommand(HideSimpleDialog) + }, + title = annotatedStringResource(dialogsState.showSimpleDialog.title), + text = annotatedStringResource(dialogsState.showSimpleDialog.message), + showCloseButton = dialogsState.showSimpleDialog.showXIcon, + buttons = buttons ) } @@ -108,13 +121,13 @@ fun ConversationSettingsDialogs( ) }, buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(id = R.string.save)), enabled = dialogsState.nicknameDialog.setEnabled, qaTag = stringResource(R.string.qa_conversation_settings_dialog_nickname_set), onClick = { sendCommand(SetNickname) } ), - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(R.string.remove)), color = LocalColors.current.danger, enabled = dialogsState.nicknameDialog.removeEnabled, @@ -179,13 +192,13 @@ fun ConversationSettingsDialogs( } }, buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(id = R.string.save)), enabled = dialogsState.groupEditDialog.saveEnabled, qaTag = stringResource(R.string.qa_conversation_settings_dialog_groupname_save), onClick = { sendCommand(SetGroupText) } ), - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(R.string.cancel)), color = LocalColors.current.danger, qaTag = stringResource(R.string.qa_conversation_settings_dialog_groupname_cancel), @@ -193,6 +206,20 @@ fun ConversationSettingsDialogs( ) ) } + + // pin CTA + if(dialogsState.pinCTA != null){ + PinProCTA( + overTheLimit = dialogsState.pinCTA.overTheLimit, + onUpgrade = { + sendCommand(GoToProUpgradeScreen) + }, + + onCancel = { + sendCommand(HidePinCTADialog) + } + ) + } } @Composable @@ -239,7 +266,7 @@ fun GroupAdminClearMessagesDialog( } }, buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(id = R.string.clear)), color = LocalColors.current.danger, onClick = { @@ -250,7 +277,7 @@ fun GroupAdminClearMessagesDialog( ) } ), - DialogButtonModel( + DialogButtonData( GetString(stringResource(R.string.cancel)) ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 0c3b397ccc..1d5a1a514a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -70,7 +70,9 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory.Companion.MAX_GROUP import org.thoughtcrime.securesms.dependencies.ConfigFactory.Companion.MAX_NAME_BYTES import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.home.HomeActivity +import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.repository.ConversationRepository +import org.thoughtcrime.securesms.ui.SimpleDialogData import org.thoughtcrime.securesms.ui.getSubbedString import org.thoughtcrime.securesms.util.AvatarUIData import org.thoughtcrime.securesms.util.AvatarUtils @@ -97,6 +99,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private val lokiThreadDatabase: LokiThreadDatabase, private val groupManager: GroupManagerV2, private val openGroupManager: OpenGroupManager, + private val proStatusManager: ProStatusManager, ) : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow( @@ -772,8 +775,18 @@ class ConversationSettingsViewModel @AssistedInject constructor( } private fun pinConversation(){ - viewModelScope.launch { - storage.setPinned(threadId, true) + // check the pin limit before continuing + val totalPins = storage.getTotalPinned() + val maxPins = proStatusManager.getPinnedConversationLimit() + if(totalPins >= maxPins){ + // the user has reached the pin limit, show the CTA + _dialogState.update { + it.copy(pinCTA = PinProCTA(overTheLimit = totalPins > maxPins)) + } + } else { + viewModelScope.launch { + storage.setPinned(threadId, true) + } } } @@ -786,7 +799,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private fun confirmBlockUser(){ _dialogState.update { it.copy( - showSimpleDialog = Dialog( + showSimpleDialog = SimpleDialogData( title = context.getString(R.string.block), message = Phrase.from(context, R.string.blockDescription) .put(NAME_KEY, recipient?.name ?: "") @@ -805,7 +818,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private fun confirmUnblockUser(){ _dialogState.update { it.copy( - showSimpleDialog = Dialog( + showSimpleDialog = SimpleDialogData( title = context.getString(R.string.blockUnblock), message = Phrase.from(context, R.string.blockUnblockName) .put(NAME_KEY, recipient?.name ?: "") @@ -844,7 +857,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private fun confirmHideNTS(){ _dialogState.update { it.copy( - showSimpleDialog = Dialog( + showSimpleDialog = SimpleDialogData( title = context.getString(R.string.noteToSelfHide), message = context.getText(R.string.hideNoteToSelfDescription), positiveText = context.getString(R.string.hide), @@ -861,7 +874,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private fun confirmShowNTS(){ _dialogState.update { it.copy( - showSimpleDialog = Dialog( + showSimpleDialog = SimpleDialogData( title = context.getString(R.string.showNoteToSelf), message = context.getText(R.string.showNoteToSelfDescription), positiveText = context.getString(R.string.show), @@ -901,7 +914,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private fun confirmDeleteContact(){ _dialogState.update { it.copy( - showSimpleDialog = Dialog( + showSimpleDialog = SimpleDialogData( title = context.getString(R.string.contactDelete), message = Phrase.from(context, R.string.deleteContactDescription) .put(NAME_KEY, recipient?.name ?: "") @@ -934,7 +947,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private fun confirmDeleteConversation(){ _dialogState.update { it.copy( - showSimpleDialog = Dialog( + showSimpleDialog = SimpleDialogData( title = context.getString(R.string.conversationsDelete), message = Phrase.from(context, R.string.deleteConversationDescription) .put(NAME_KEY, recipient?.name ?: "") @@ -965,7 +978,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private fun confirmLeaveCommunity(){ _dialogState.update { it.copy( - showSimpleDialog = Dialog( + showSimpleDialog = SimpleDialogData( title = context.getString(R.string.communityLeave), message = Phrase.from(context, R.string.groupLeaveDescription) .put(GROUP_NAME_KEY, recipient?.name ?: "") @@ -1031,7 +1044,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( _dialogState.update { it.copy( - showSimpleDialog = Dialog( + showSimpleDialog = SimpleDialogData( title = context.getString(R.string.clearMessages), message = message, positiveText = context.getString(R.string.clear), @@ -1091,7 +1104,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( ) ?: return state.copy( - showSimpleDialog = Dialog( + showSimpleDialog = SimpleDialogData( title = dialogData.title, message = dialogData.message, positiveText = context.getString(dialogData.positiveText), @@ -1182,6 +1195,10 @@ class ConversationSettingsViewModel @AssistedInject constructor( is Commands.HideGroupEditDialog -> hideGroupEditDialog() + is Commands.HidePinCTADialog -> { + _dialogState.update { it.copy(pinCTA = null) } + } + is Commands.RemoveNickname -> { setNickname(null) @@ -1292,6 +1309,14 @@ class ConversationSettingsViewModel @AssistedInject constructor( hideLoading() } } + + is Commands.GoToProUpgradeScreen -> { + // hide dialog + _dialogState.update { it.copy(pinCTA = null) } + + // to go Pro upgrade screen + //todo PRO go to screen once it exists + } } } @@ -1423,6 +1448,10 @@ class ConversationSettingsViewModel @AssistedInject constructor( data object ShowGroupEditDialog : Commands data object HideGroupEditDialog : Commands + + data object HidePinCTADialog: Commands + + data object GoToProUpgradeScreen: Commands } @AssistedFactory @@ -1442,21 +1471,6 @@ class ConversationSettingsViewModel @AssistedInject constructor( val categories: List = emptyList() ) - /** - * Data to display a simple dialog - */ - data class Dialog( - val title: String, - val message: CharSequence, - val positiveText: String, - val positiveStyleDanger: Boolean = true, - val negativeText: String, - val positiveQaTag: String?, - val negativeQaTag: String?, - val onPositive: () -> Unit, - val onNegative: () -> Unit - ) - data class OptionsCategory( val name: String? = null, val items: List = emptyList() @@ -1478,12 +1492,17 @@ class ConversationSettingsViewModel @AssistedInject constructor( ) data class DialogsState( - val showSimpleDialog: Dialog? = null, + val pinCTA: PinProCTA? = null, + val showSimpleDialog: SimpleDialogData? = null, val nicknameDialog: NicknameDialogData? = null, val groupEditDialog: GroupEditDialog? = null, val groupAdminClearMessagesDialog: GroupAdminClearMessageDialog? = null, ) + data class PinProCTA( + val overTheLimit: Boolean + ) + data class NicknameDialogData( val name: String, val currentNickname: String?, // the currently saved nickname, if any diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsScreen.kt index d66595cac8..9aad4006cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsScreen.kt @@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.ui.OptionsCard import org.thoughtcrime.securesms.ui.OptionsCardData import org.thoughtcrime.securesms.ui.RadioOption import org.thoughtcrime.securesms.ui.components.BackAppBar -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.PreviewTheme @@ -91,7 +91,7 @@ fun NotificationSettings( } val coroutineScope = rememberCoroutineScope() - PrimaryOutlineButton( + AccentOutlineButton( stringResource(R.string.set), modifier = Modifier .qaTag(R.string.AccessibilityId_setButton) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java index 6c15b68c5f..5e92dd913f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java @@ -46,7 +46,6 @@ import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.SettableFuture; import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.giph.ui.GiphyActivity; import org.thoughtcrime.securesms.mediasend.MediaSendActivity; import org.thoughtcrime.securesms.mms.AudioSlide; @@ -59,7 +58,7 @@ import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.providers.BlobUtils; import org.thoughtcrime.securesms.util.FilenameUtils; import org.thoughtcrime.securesms.util.MediaUtil; @@ -104,13 +103,13 @@ public void cleanup() { } private void cleanup(final @Nullable Uri uri) { - if (uri != null && BlobProvider.isAuthority(uri)) { - BlobProvider.getInstance().delete(context, uri); + if (uri != null && BlobUtils.isAuthority(uri)) { + BlobUtils.getInstance().delete(context, uri); } } private void markGarbage(@Nullable Uri uri) { - if (uri != null && BlobProvider.isAuthority(uri)) { + if (uri != null && BlobUtils.isAuthority(uri)) { Log.d(TAG, "Marking garbage that needs cleaning: " + uri); garbage.add(uri); } @@ -240,9 +239,9 @@ public static void selectDocument(Activity activity, int requestCode) { // The READ_EXTERNAL_STORAGE permission is deprecated (and will AUTO-FAIL if requested!) on // Android 13 and above (API 33 - 'Tiramisu') we must ask for READ_MEDIA_VIDEO/IMAGES/AUDIO instead. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - builder = builder.request(Manifest.permission.READ_MEDIA_VIDEO) - .request(Manifest.permission.READ_MEDIA_IMAGES) - .request(Manifest.permission.READ_MEDIA_AUDIO) + builder = builder.request(Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_AUDIO) .withRationaleDialog( Phrase.from(c, R.string.permissionsMusicAudio) .put(APP_NAME_KEY, c.getString(R.string.app_name)).format().toString() @@ -265,14 +264,24 @@ public static void selectDocument(Activity activity, int requestCode) { .execute(); } - public static void selectGallery(Activity activity, int requestCode, @NonNull Recipient recipient, @NonNull String body) { + public static void selectGallery(Activity activity, int requestCode, @NonNull Recipient recipient, @NonNull long threadId, @NonNull String body) { Context c = activity.getApplicationContext(); Permissions.PermissionsBuilder builder = Permissions.with(activity); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - builder = builder.request(Manifest.permission.READ_MEDIA_VIDEO) - .request(Manifest.permission.READ_MEDIA_IMAGES) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // API 34+ + builder = builder.request(Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) + .withPermanentDenialDialog( + Phrase.from(c, R.string.permissionsStorageDenied) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString() + ); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // API 33 + builder = builder.request(Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_IMAGES) .withPermanentDenialDialog( Phrase.from(c, R.string.permissionsStorageDenied) .put(APP_NAME_KEY, c.getString(R.string.app_name)) @@ -286,7 +295,8 @@ public static void selectGallery(Activity activity, int requestCode, @NonNull Re .format().toString() ); } - builder.onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body), requestCode)) + + builder.onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, threadId, body), requestCode)) .execute(); } @@ -308,7 +318,7 @@ public static void selectGif(Activity activity, int requestCode) { return captureUri; } - public void capturePhoto(Activity activity, int requestCode, Recipient recipient) { + public void capturePhoto(Activity activity, int requestCode, Recipient recipient, @NonNull long threadId, @NonNull String body) { String cameraPermissionDeniedTxt = Phrase.from(context, R.string.permissionsCameraDenied) .put(APP_NAME_KEY, context.getString(R.string.app_name)) @@ -318,7 +328,7 @@ public void capturePhoto(Activity activity, int requestCode, Recipient recipient .request(Manifest.permission.CAMERA) .withPermanentDenialDialog(cameraPermissionDeniedTxt) .onAllGranted(() -> { - Intent captureIntent = MediaSendActivity.buildCameraIntent(activity, recipient); + Intent captureIntent = MediaSendActivity.buildCameraIntent(activity, recipient, threadId, body); if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { activity.startActivityForResult(captureIntent, requestCode); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt index fe3cd91c1a..4d5f995456 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Typeface import android.text.Spannable import android.text.SpannableString +import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan import android.util.Range @@ -16,6 +17,8 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.truncateIdForDisplay +import org.thoughtcrime.securesms.conversation.v2.mention.MentionEditable +import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.RoundedBackgroundSpan import org.thoughtcrime.securesms.util.getAccentColor @@ -23,7 +26,35 @@ import java.util.regex.Pattern object MentionUtilities { - private val pattern by lazy { Pattern.compile("@[0-9a-fA-F]*") } + private val ACCOUNT_ID = Regex("@([0-9a-fA-F]{66})") + private val pattern by lazy { Pattern.compile(ACCOUNT_ID.pattern) } + + /** + * In-place replacement on the *live* MentionEditable that the + * input-bar is already using. + * + * It swaps every "@<64-hex>" token for "@DisplayName" **and** + * attaches a MentionSpan so later normalisation still works. + */ + fun substituteIdsInPlace( + editable: MentionEditable, + membersById: Map + ) { + ACCOUNT_ID.findAll(editable) + .toList() // avoid index shifts + .asReversed() // back-to-front replacement + .forEach { m -> + val id = m.groupValues[1] + val member = membersById[id] ?: return@forEach + + val start = m.range.first + val end = m.range.last + 1 // inclusive ➜ exclusive + + editable.replace(start, end, "@${member.name}") + editable.addMention(member, start .. start + member.name.length + 1) + } + } + /** * Highlights mentions in a given text. @@ -65,7 +96,7 @@ object MentionUtilities { } else { val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithAccountID(publicKey) @Suppress("NAME_SHADOWING") val context = if (openGroup != null) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR - contact?.displayName(context) ?: truncateIdForDisplay(publicKey) + contact?.displayName(context) } if (userDisplayName != null) { val mention = "@$userDisplayName" diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt index 4d4e21d5f3..1df19790f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt @@ -178,7 +178,7 @@ open class ThumbnailView @JvmOverloads constructor( .diskCacheStrategy(DiskCacheStrategy.NONE) .overrideDimensions() .transition(DrawableTransitionOptions.withCrossFade()) - .transform(CenterCrop()) + .optionalTransform(CenterCrop()) .missingThumbnailPicture(slide.isInProgress, errorDrawable) private fun buildPlaceholderGlideRequest( diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 4f83384ef6..841a57ea01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -155,7 +155,7 @@ public class AttachmentDatabase extends Database { "CREATE INDEX IF NOT EXISTS part_sticker_pack_id_index ON " + TABLE_NAME + " (" + STICKER_PACK_ID + ");", }; - private final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor(); + final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor(); private final AttachmentSecret attachmentSecret; @@ -462,21 +462,24 @@ public void handleFailedAttachmentUpload(@NonNull AttachmentId id) { database.update(TABLE_NAME, values, PART_ID_WHERE, id.toStrings()); } - @NonNull Map insertAttachmentsForMessage(long mmsId, @NonNull List attachments, @NonNull List quoteAttachment) - throws MmsException - { + @NonNull Map insertAttachmentsForMessage( + long mmsId, + @NonNull List attachments, + @NonNull List quoteAttachment, + @NonNull List thumbnailJobsCollector + ) throws MmsException { Log.d(TAG, "insertParts(" + attachments.size() + ")"); Map insertedAttachments = new HashMap<>(); for (Attachment attachment : attachments) { - AttachmentId attachmentId = insertAttachment(mmsId, attachment, attachment.isQuote()); + AttachmentId attachmentId = insertAttachment(mmsId, attachment, attachment.isQuote(), thumbnailJobsCollector); insertedAttachments.put(attachment, attachmentId); Log.i(TAG, "Inserted attachment at ID: " + attachmentId); } for (Attachment attachment : quoteAttachment) { - AttachmentId attachmentId = insertAttachment(mmsId, attachment, true); + AttachmentId attachmentId = insertAttachment(mmsId, attachment, true, thumbnailJobsCollector); insertedAttachments.put(attachment, attachmentId); Log.i(TAG, "Inserted quoted attachment at ID: " + attachmentId); } @@ -723,9 +726,12 @@ public List getAttachment(@NonNull Cursor cursor) { } - private AttachmentId insertAttachment(long mmsId, Attachment attachment, boolean quote) - throws MmsException - { + private AttachmentId insertAttachment( + long mmsId, + Attachment attachment, + boolean quote, + @NonNull List thumbnailJobsCollector + ) throws MmsException { Log.d(TAG, "Inserting attachment for mms id: " + mmsId); SQLiteDatabase database = getWritableDatabase(); @@ -780,28 +786,30 @@ private AttachmentId insertAttachment(long mmsId, Attachment attachment, boolean dimens = BitmapUtil.getDimensions(attachmentStream); } updateAttachmentThumbnail(attachmentId, - PartAuthority.getAttachmentStream(context, thumbnailUri), - (float) dimens.first / (float) dimens.second); + PartAuthority.getAttachmentStream(context, thumbnailUri), + (float) dimens.first / (float) dimens.second); hasThumbnail = true; } catch (IOException | BitmapDecodingException e) { Log.w(TAG, "Failed to save existing thumbnail.", e); } } + // collect the job if (!hasThumbnail && dataInfo != null) { if (MediaUtil.hasVideoThumbnail(attachment.getDataUri())) { Bitmap bitmap = MediaUtil.getVideoThumbnail(context, attachment.getDataUri()); - if (bitmap != null) { ThumbnailData thumbnailData = new ThumbnailData(bitmap); updateAttachmentThumbnail(attachmentId, thumbnailData.toDataStream(), thumbnailData.getAspectRatio()); } else { Log.w(TAG, "Retrieving video thumbnail failed, submitting thumbnail generation job..."); - thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId)); + // Collect for later processing instead of immediate submission + thumbnailJobsCollector.add(attachmentId); } } else { Log.i(TAG, "Submitting thumbnail generation job..."); - thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId)); + // Collect for later processing + thumbnailJobsCollector.add(attachmentId); } } @@ -885,7 +893,6 @@ public boolean setAttachmentAudioExtras(@NonNull DatabaseAttachmentAudioExtras e return alteredRows > 0; } - @VisibleForTesting class ThumbnailFetchCallable implements Callable { private final AttachmentId attachmentId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt index 00b16e8c44..47d1cfcfb7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt @@ -5,7 +5,6 @@ import androidx.core.content.contentValuesOf import androidx.core.database.getBlobOrNull import androidx.core.database.getLongOrNull import androidx.sqlite.db.transaction -import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import javax.inject.Provider @@ -28,14 +27,14 @@ class ConfigDatabase(context: Context, helper: Provider): D private const val VARIANT_AND_PUBKEY_WHERE = "$VARIANT = ? AND $PUBKEY = ?" private const val VARIANT_IN_AND_PUBKEY_WHERE = "$VARIANT in (?) AND $PUBKEY = ?" - val CONTACTS_VARIANT: ConfigVariant = SharedConfigMessage.Kind.CONTACTS.name - val USER_GROUPS_VARIANT: ConfigVariant = SharedConfigMessage.Kind.GROUPS.name - val USER_PROFILE_VARIANT: ConfigVariant = SharedConfigMessage.Kind.USER_PROFILE.name - val CONVO_INFO_VARIANT: ConfigVariant = SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name + const val CONTACTS_VARIANT: ConfigVariant = "CONTACTS" + const val USER_GROUPS_VARIANT: ConfigVariant = "GROUPS" + const val USER_PROFILE_VARIANT: ConfigVariant = "USER_PROFILE" + const val CONVO_INFO_VARIANT: ConfigVariant = "CONVO_INFO_VOLATILE" - val KEYS_VARIANT: ConfigVariant = SharedConfigMessage.Kind.ENCRYPTION_KEYS.name - val INFO_VARIANT: ConfigVariant = SharedConfigMessage.Kind.CLOSED_GROUP_INFO.name - val MEMBER_VARIANT: ConfigVariant = SharedConfigMessage.Kind.CLOSED_GROUP_MEMBERS.name + const val KEYS_VARIANT: ConfigVariant = "ENCRYPTION_KEYS" + const val INFO_VARIANT: ConfigVariant = "CLOSED_GROUP_INFO" + const val MEMBER_VARIANT: ConfigVariant = "CLOSED_GROUP_MEMBERS" } fun storeConfig(variant: ConfigVariant, publicKey: String, data: ByteArray, timestamp: Long) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java index 0f4d7c9f25..cf5d86907d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java @@ -19,10 +19,12 @@ public static class ConversationList extends NoopContentProvider { } public static class Conversation extends NoopContentProvider { - private static final String CONTENT_URI_STRING = "content://network.loki.securesms.database.conversation/"; + public static final Uri CONTENT_URI = Uri.parse("content://network.loki.securesms.database.conversation"); public static Uri getUriForThread(long threadId) { - return Uri.parse(CONTENT_URI_STRING + threadId); + return CONTENT_URI.buildUpon() + .appendPath(String.valueOf(threadId)) + .build(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java deleted file mode 100644 index 76fa8c5c0b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ /dev/null @@ -1,33 +0,0 @@ - -/* - * Copyright (C) 2018 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.database; - -import android.content.Context; - -import net.zetetic.database.sqlcipher.SQLiteDatabase; - -import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; - -public class DatabaseFactory { - public static void upgradeRestored(Context context, SQLiteDatabase database){ - SQLCipherOpenHelper databaseHelper = DatabaseComponent.get(context).openHelper(); - databaseHelper.onUpgrade(database, database.getVersion(), -1); - databaseHelper.markCurrent(database); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 8b4e859056..867f29e17d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -279,7 +279,12 @@ public void updateTitle(String groupID, String newValue) { new String[] {groupID}); Recipient recipient = Recipient.from(context, Address.fromSerialized(groupID), false); + final boolean nameChanged = !newValue.equals(recipient.getName()); recipient.setName(newValue); + + if (nameChanged) { + notifyConversationListListeners(); + } } public void updateProfilePicture(String groupID, Bitmap newValue) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java index d141bdaa20..eb803a80d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -16,7 +16,6 @@ import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.util.SqlUtil; import java.io.IOException; import java.util.ArrayList; @@ -48,6 +47,10 @@ public MessagingDatabase(Context context, Provider database public abstract void markAsDeleted(long messageId, boolean isOutgoing, String displayedMessage); + public abstract List getExpiredMessageIDs(long nowMills); + + public abstract long getNextExpiringTimestamp(); + public abstract boolean deleteMessage(long messageId); public abstract boolean deleteMessages(long[] messageId, long threadId); @@ -77,34 +80,6 @@ public void removeMismatchedIdentity(long messageId, Address address, IdentityKe } } - void updateReactionsUnread(SQLiteDatabase db, long messageId, boolean hasReactions, boolean isRemoval, boolean notifyUnread) { - try { - MessageRecord message = getMessageRecord(messageId); - ContentValues values = new ContentValues(); - - if (notifyUnread) { - if (!hasReactions) { - values.put(REACTIONS_UNREAD, 0); - } else if (!isRemoval) { - values.put(REACTIONS_UNREAD, 1); - } - } else { - values.put(REACTIONS_UNREAD, 0); - } - - if (message.isOutgoing() && hasReactions) { - values.put(NOTIFIED, 0); - } - - if (values.size() > 0) { - db.update(getTableName(), values, ID_WHERE, SqlUtil.buildArgs(messageId)); - } - notifyConversationListeners(message.getThreadId()); - } catch (NoSuchMessageException e) { - Log.w(TAG, "Failed to find message " + messageId); - } - } - protected , I> void removeFromDocument(long messageId, String column, I object, Class clazz) throws IOException { SQLiteDatabase database = getWritableDatabase(); database.beginTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index f52070a90f..11d1413145 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -20,6 +20,9 @@ import android.content.ContentValues import android.content.Context import android.database.Cursor import com.annimon.stream.Stream +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.apache.commons.lang3.StringUtils import org.json.JSONArray import org.json.JSONException @@ -57,6 +60,8 @@ import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.Quote +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate +import org.thoughtcrime.securesms.database.model.content.MessageContent import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.mms.MmsException import org.thoughtcrime.securesms.mms.SlideDeck @@ -64,9 +69,17 @@ import org.thoughtcrime.securesms.util.asSequence import java.io.Closeable import java.io.IOException import java.util.LinkedList +import javax.inject.Inject import javax.inject.Provider - -class MmsDatabase(context: Context, databaseHelper: Provider) : MessagingDatabase(context, databaseHelper) { +import javax.inject.Singleton + +@Singleton +class MmsDatabase + @Inject constructor( + @ApplicationContext context: Context, + databaseHelper: Provider, + private val json: Json + ) : MessagingDatabase(context, databaseHelper) { private val earlyDeliveryReceiptCache = EarlyReceiptCache() private val earlyReadReceiptCache = EarlyReceiptCache() override fun getTableName() = TABLE_NAME @@ -201,7 +214,7 @@ class MmsDatabase(context: Context, databaseHelper: Provider { + val query = "SELECT " + ID + " FROM " + TABLE_NAME + + " WHERE " + EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " > 0 AND " + EXPIRE_STARTED + " + " + EXPIRES_IN + " <= ?" + + return readableDatabase.rawQuery(query, nowMills).use { cursor -> + cursor.asSequence() + .map { it.getLong(0) } + .toList() } + } - val expireNotStartedMessages: Reader - get() { - val where = "$EXPIRES_IN > 0 AND $EXPIRE_STARTED = 0" - return readerFor(rawQuery(where, null))!! + /** + * @return the next expiring timestamp for messages that have started expiring. 0 if no messages are expiring. + */ + override fun getNextExpiringTimestamp(): Long { + val query = + "SELECT MIN(" + EXPIRE_STARTED + " + " + EXPIRES_IN + ") FROM " + TABLE_NAME + + " WHERE " + EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " > 0" + + return readableDatabase.rawQuery(query).use { cursor -> + if (cursor.moveToFirst()) { + cursor.getLong(0) + } else { + 0L + } } + } private fun updateMailboxBitmask( id: Long, @@ -334,6 +367,7 @@ class MmsDatabase(context: Context, databaseHelper: Provider { return setMessagesRead( - THREAD_ID + " = ? AND (" + READ + " = 0 OR " + REACTIONS_UNREAD + " = 1) AND " + DATE_SENT + " <= ?", + THREAD_ID + " = ? AND (" + READ + " = 0) AND " + DATE_SENT + " <= ?", arrayOf(threadId.toString(), beforeTime.toString()) ) } fun setMessagesRead(threadId: Long): List { return setMessagesRead( - THREAD_ID + " = ? AND (" + READ + " = 0 OR " + REACTIONS_UNREAD + " = 1)", + THREAD_ID + " = ? AND (" + READ + " = 0)", arrayOf(threadId.toString()) ) } @@ -490,6 +524,15 @@ class MmsDatabase(context: Context, databaseHelper: Provider(messageContentJson) + }.onFailure { + Log.w(TAG, "Failed to decode message content for message ID $messageId", it) + }.getOrNull() + val message = OutgoingMediaMessage( recipient, body, @@ -503,7 +546,8 @@ class MmsDatabase(context: Context, databaseHelper: Provider { if (threadId < 0 ) throw MmsException("No thread ID supplied!") - if (retrieved.isExpirationUpdate) deleteExpirationTimerMessages(threadId, false.takeUnless { retrieved.groupId != null }) + if (retrieved.messageContent is DisappearingMessageUpdate) deleteExpirationTimerMessages(threadId, false.takeUnless { retrieved.groupId != null }) val contentValues = ContentValues() contentValues.put(DATE_SENT, retrieved.sentTimeMillis) contentValues.put(ADDRESS, retrieved.from.toString()) @@ -640,14 +684,15 @@ class MmsDatabase(context: Context, databaseHelper: Provider { if (threadId < 0 ) throw MmsException("No thread ID supplied!") - if (retrieved.isExpirationUpdate) deleteExpirationTimerMessages(threadId, true.takeUnless { retrieved.isGroup }) + if (retrieved.messageContent is DisappearingMessageUpdate) deleteExpirationTimerMessages(threadId, true.takeUnless { retrieved.isGroup }) val messageId = insertMessageOutbox( retrieved, threadId, @@ -692,9 +737,6 @@ class MmsDatabase(context: Context, databaseHelper: Provider, quoteAttachments: List, sharedContacts: List, @@ -815,6 +856,8 @@ class MmsDatabase(context: Context, databaseHelper: Provider = LinkedList() + val thumbnailJobs: MutableList = ArrayList() // Collector for thumbnail jobs + val contactAttachments = Stream.of(sharedContacts).map { obj: Contact -> obj.avatarAttachment } .filter { a: Attachment? -> a != null } @@ -823,22 +866,31 @@ class MmsDatabase(context: Context, databaseHelper: Provider lp.getThumbnail().isPresent } .map { lp: LinkPreview -> lp.getThumbnail().get() } .toList() + allAttachments.addAll(attachments) allAttachments.addAll(contactAttachments) allAttachments.addAll(previewAttachments) + contentValues.put(BODY, body) contentValues.put(PART_COUNT, allAttachments.size) + contentValues.put(MESSAGE_CONTENT, messageContent?.let { json.encodeToString(it) }) + db.beginTransaction() return try { val messageId = db.insert(TABLE_NAME, null, contentValues) + + // Pass thumbnailJobs collector to attachment insertion val insertedAttachments = partsDatabase.insertAttachmentsForMessage( messageId, allAttachments, - quoteAttachments + quoteAttachments, + thumbnailJobs // This will collect all attachment IDs that need thumbnails ) + val serializedContacts = getSerializedSharedContacts(insertedAttachments, sharedContacts) val serializedPreviews = getSerializedLinkPreviews(insertedAttachments, linkPreviews) + if (!serializedContacts.isNullOrEmpty()) { val contactValues = ContentValues() contactValues.put(SHARED_CONTACTS, serializedContacts) @@ -853,6 +905,7 @@ class MmsDatabase(context: Context, databaseHelper: Provider + Log.i(TAG, "Submitting thumbnail generation job for attachment: $attachmentId") + attachmentDatabase.thumbnailExecutor.submit( + attachmentDatabase.ThumbnailFetchCallable(attachmentId) + ) + } + notifyConversationListeners(contentValues.getAsLong(THREAD_ID)) } } @@ -1257,7 +1321,7 @@ class MmsDatabase(context: Context, databaseHelper: Provider(it) } + }.onFailure { + Log.e(TAG, "Failed to decode message content", it) + }.getOrNull() + return MediaMmsMessageRecord( id, recipient, recipient, addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId, body, slideDeck!!, partCount, box, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, - readReceiptCount, quote, contacts, previews, reactions, hasMention + readReceiptCount, quote, contacts, previews, reactions, hasMention, + messageContent ) } @@ -1446,6 +1520,17 @@ class MmsDatabase(context: Context, databaseHelper: Provider = arrayOf( "$TABLE_NAME.$ID AS $ID", THREAD_ID, + MESSAGE_CONTENT, "$DATE_SENT AS $NORMALIZED_DATE_SENT", "$DATE_RECEIVED AS $NORMALIZED_DATE_RECEIVED", MESSAGE_BOX, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 3d8f17af14..35da0238df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -9,6 +9,7 @@ public interface MmsSmsColumns { public static final String THREAD_ID = "thread_id"; public static final String READ = "read"; public static final String BODY = "body"; + public static final String MESSAGE_CONTENT = "message_content"; // This is the address of the message recipient, which may be a single user, a group, or a community! // It is NOT the address of the sender of any given message! @@ -29,6 +30,7 @@ public interface MmsSmsColumns { public static final String UNIDENTIFIED = "unidentified"; public static final String MESSAGE_REQUEST_RESPONSE = "message_request_response"; + @Deprecated(forRemoval = true) public static final String REACTIONS_UNREAD = "reactions_unread"; public static final String REACTIONS_LAST_SEEN = "reactions_last_seen"; @@ -98,6 +100,7 @@ public static class Types { // Group Message Information protected static final long GROUP_UPDATE_BIT = 0x10000; protected static final long GROUP_QUIT_BIT = 0x20000; + @Deprecated(forRemoval = true) protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000; protected static final long GROUP_UPDATE_MESSAGE_BIT = 0x80000; @@ -253,9 +256,6 @@ public static boolean isCallLog(long type) { baseType == MISSED_CALL_TYPE || baseType == FIRST_MISSED_CALL_TYPE; } - public static boolean isExpirationTimerUpdate(long type) { - return (type & EXPIRATION_TIMER_UPDATE_BIT) != 0; - } public static boolean isMediaSavedExtraction(long type) { return (type & MEDIA_SAVED_EXTRACTION_BIT) != 0; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index bce66d518d..6d072eb3a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -17,6 +17,10 @@ package org.thoughtcrime.securesms.database; import static org.thoughtcrime.securesms.database.MmsDatabase.MESSAGE_BOX; +import static org.thoughtcrime.securesms.database.MmsSmsColumns.ID; +import static org.thoughtcrime.securesms.database.MmsSmsColumns.NOTIFIED; +import static org.thoughtcrime.securesms.database.MmsSmsColumns.READ; +import static org.thoughtcrime.securesms.database.MmsSmsColumns.UNIQUE_ROW_ID; import android.content.Context; import android.database.Cursor; @@ -59,7 +63,7 @@ public class MmsSmsDatabase extends Database { public static final String SMS_TRANSPORT = "sms"; private static final String[] PROJECTION = {MmsSmsColumns.ID, MmsSmsColumns.UNIQUE_ROW_ID, - SmsDatabase.BODY, SmsDatabase.TYPE, + SmsDatabase.BODY, SmsDatabase.TYPE, MmsSmsColumns.MESSAGE_CONTENT, MmsSmsColumns.THREAD_ID, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsSmsColumns.NORMALIZED_DATE_SENT, @@ -77,7 +81,7 @@ public class MmsSmsDatabase extends Database { MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED, - MmsSmsColumns.NOTIFIED, + NOTIFIED, TRANSPORT, AttachmentDatabase.ATTACHMENT_JSON_ALIAS, MmsDatabase.QUOTE_ID, @@ -377,15 +381,72 @@ public MessageRecord getLastMessage(long threadId) { } } - public Cursor getUnread() { - String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC"; - String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0"; - + /** + * Get all incoming unread + unnotified messages + * or + * all outgoing messages with unread reactions + */ + public Cursor getUnreadOrUnseenReactions() { + + // ────────────────────────────────────────────────────────────── + // 1) Build “is-outgoing” condition that works for both MMS & SMS + // ────────────────────────────────────────────────────────────── + // MMS rows → use MESSAGE_BOX + // SMS rows → use TYPE + // + // TRANSPORT lets us be sure we’re looking at the right column, + // so an incoming MMS/SMS can never be mistaken for outgoing. + // + String outgoingCondition = + /* MMS */ + "(" + TRANSPORT + " = '" + MMS_TRANSPORT + "' AND " + + "(" + MESSAGE_BOX + " & " + + MmsSmsColumns.Types.BASE_TYPE_MASK + ") IN (" + + buildOutgoingTypesList() + "))" + + + " OR " + + + /* SMS */ + "(" + TRANSPORT + " = '" + SMS_TRANSPORT + "' AND " + + "(" + SmsDatabase.TYPE + " & " + + MmsSmsColumns.Types.BASE_TYPE_MASK + ") IN (" + + buildOutgoingTypesList() + "))"; + + final String lastSeenQuery = "SELECT " + ThreadDatabase.LAST_SEEN + + " FROM " + ThreadDatabase.TABLE_NAME + + " WHERE " + ThreadDatabase.ID + " = " + MmsSmsColumns.THREAD_ID; + + // ────────────────────────────────────────────────────────────── + // 2) Selection: + // A) incoming unread+un-notified, NOT outgoing + // B) outgoing with unseen reactions, IS outgoing + // To query unseen reactions, we compare the date received on the reaction with the "last seen timestamp" on this thread + // ────────────────────────────────────────────────────────────── + String selection = + "(" + READ + " = 0 AND " + + NOTIFIED + " = 0 AND NOT (" + outgoingCondition + "))" + // A + " OR (" + + ReactionDatabase.TABLE_NAME + "." + ReactionDatabase.DATE_SENT + " > (" + lastSeenQuery +") AND (" + + outgoingCondition + "))"; // B + + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC"; return queryTables(PROJECTION, selection, order, null); } + /** Builds the comma-separated list of base types that represent + * *outgoing* messages (same helper as before). */ + private String buildOutgoingTypesList() { + long[] types = MmsSmsColumns.Types.OUTGOING_MESSAGE_TYPES; + StringBuilder sb = new StringBuilder(types.length * 3); + for (int i = 0; i < types.length; i++) { + if (i > 0) sb.append(','); + sb.append(types[i]); + } + return sb.toString(); + } + public int getUnreadCount(long threadId) { - String selection = MmsSmsColumns.READ + " = 0 AND " + MmsSmsColumns.NOTIFIED + " = 0 AND " + MmsSmsColumns.THREAD_ID + " = " + threadId; + String selection = READ + " = 0 AND " + NOTIFIED + " = 0 AND " + MmsSmsColumns.THREAD_ID + " = " + threadId; Cursor cursor = queryTables(PROJECTION, selection, null, null); try { @@ -489,7 +550,9 @@ private Cursor queryTables(String[] projection, String selection, String order, "'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, reactionsColumn, - SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID, + SmsDatabase.BODY, + MmsDatabase.MESSAGE_CONTENT, + READ, MmsSmsColumns.THREAD_ID, SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, @@ -497,7 +560,7 @@ private Cursor queryTables(String[] projection, String selection, String order, MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED, - MmsSmsColumns.NOTIFIED, + NOTIFIED, MmsDatabase.NETWORK_FAILURE, TRANSPORT, MmsDatabase.QUOTE_ID, MmsDatabase.QUOTE_AUTHOR, @@ -518,7 +581,9 @@ private Cursor queryTables(String[] projection, String selection, String order, + " AS " + MmsSmsColumns.UNIQUE_ROW_ID, "NULL AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, reactionsColumn, - SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID, + SmsDatabase.BODY, + MmsSmsColumns.MESSAGE_CONTENT, + READ, MmsSmsColumns.THREAD_ID, SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, @@ -526,7 +591,7 @@ private Cursor queryTables(String[] projection, String selection, String order, MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED, - MmsSmsColumns.NOTIFIED, + NOTIFIED, MmsDatabase.NETWORK_FAILURE, TRANSPORT, MmsDatabase.QUOTE_ID, MmsDatabase.QUOTE_AUTHOR, @@ -561,8 +626,9 @@ private Cursor queryTables(String[] projection, String selection, String order, Set mmsColumnsPresent = new HashSet<>(); mmsColumnsPresent.add(MmsSmsColumns.ID); - mmsColumnsPresent.add(MmsSmsColumns.READ); + mmsColumnsPresent.add(READ); mmsColumnsPresent.add(MmsSmsColumns.THREAD_ID); + mmsColumnsPresent.add(MmsSmsColumns.MESSAGE_CONTENT); mmsColumnsPresent.add(MmsSmsColumns.BODY); mmsColumnsPresent.add(MmsSmsColumns.ADDRESS); mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); @@ -581,7 +647,7 @@ private Cursor queryTables(String[] projection, String selection, String order, mmsColumnsPresent.add(MmsDatabase.TRANSACTION_ID); mmsColumnsPresent.add(MmsDatabase.MESSAGE_SIZE); mmsColumnsPresent.add(MmsDatabase.EXPIRY); - mmsColumnsPresent.add(MmsDatabase.NOTIFIED); + mmsColumnsPresent.add(NOTIFIED); mmsColumnsPresent.add(MmsDatabase.STATUS); mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE); mmsColumnsPresent.add(MmsSmsColumns.HAS_MENTION); @@ -633,7 +699,7 @@ private Cursor queryTables(String[] projection, String selection, String order, smsColumnsPresent.add(MmsSmsColumns.BODY); smsColumnsPresent.add(MmsSmsColumns.ADDRESS); smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); - smsColumnsPresent.add(MmsSmsColumns.READ); + smsColumnsPresent.add(READ); smsColumnsPresent.add(MmsSmsColumns.THREAD_ID); smsColumnsPresent.add(MmsSmsColumns.DELIVERY_RECEIPT_COUNT); smsColumnsPresent.add(MmsSmsColumns.READ_RECEIPT_COUNT); @@ -641,7 +707,7 @@ private Cursor queryTables(String[] projection, String selection, String order, smsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID); smsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN); smsColumnsPresent.add(MmsSmsColumns.EXPIRE_STARTED); - smsColumnsPresent.add(MmsSmsColumns.NOTIFIED); + smsColumnsPresent.add(NOTIFIED); smsColumnsPresent.add(SmsDatabase.TYPE); smsColumnsPresent.add(SmsDatabase.SUBJECT); smsColumnsPresent.add(SmsDatabase.DATE_SENT); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt index 63d9f98feb..17f54aeeff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt @@ -9,7 +9,6 @@ import org.session.libsignal.utilities.JsonUtil.SaneJSONObject import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.ReactionRecord -import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.CursorUtil import javax.inject.Provider @@ -73,6 +72,9 @@ class ReactionDatabase(context: Context, helper: Provider) """ ) + @JvmField + val CREATE_MESSAGE_ID_MMS_INDEX = arrayOf("CREATE INDEX IF NOT EXISTS reaction_message_id_mms_idx ON $TABLE_NAME ($MESSAGE_ID, $IS_MMS)") + private fun readReaction(cursor: Cursor): ReactionRecord { return ReactionRecord( messageId = MessageId(CursorUtil.requireLong(cursor, MESSAGE_ID), CursorUtil.requireInt(cursor, IS_MMS) == 1), @@ -102,134 +104,105 @@ class ReactionDatabase(context: Context, helper: Provider) return reactions } - fun addReaction(reaction: ReactionRecord, notifyUnread: Boolean) { + fun addReaction(reaction: ReactionRecord) { + addReactions(mapOf(reaction.messageId to listOf(reaction)), replaceAll = false) + } + + fun addReactions(reactionsByMessageId: Map>, replaceAll: Boolean) { + if (reactionsByMessageId.isEmpty()) return + + val values = ContentValues() writableDatabase.beginTransaction() try { - val values = ContentValues().apply { - put(MESSAGE_ID, reaction.messageId.id) - put(IS_MMS, reaction.messageId.mms) - put(EMOJI, reaction.emoji) - put(AUTHOR_ID, reaction.author) - put(SERVER_ID, reaction.serverId) - put(COUNT, reaction.count) - put(SORT_ID, reaction.sortId) - put(DATE_SENT, reaction.dateSent) - put(DATE_RECEIVED, reaction.dateReceived) - } + // Delete existing reactions for the same message IDs if replaceAll is true + if (replaceAll && reactionsByMessageId.isNotEmpty()) { + // We don't need to do parameteralized queries here as messageId and isMms are always + // integers/boolean, and hence no risk of SQL injection. + val whereClause = StringBuilder("($MESSAGE_ID, $IS_MMS) IN (") + for ((i, id) in reactionsByMessageId.keys.withIndex()) { + if (i > 0) { + whereClause.append(", ") + } - writableDatabase.insert(TABLE_NAME, null, values) + whereClause + .append('(') + .append(id.id).append(',').append(id.mms) + .append(')') + } + whereClause.append(')') - if (reaction.messageId.mms) { - DatabaseComponent.get(context).mmsDatabase().updateReactionsUnread(writableDatabase, reaction.messageId.id, true, false, notifyUnread) - } else { - DatabaseComponent.get(context).smsDatabase().updateReactionsUnread(writableDatabase, reaction.messageId.id, true, false, notifyUnread) + writableDatabase.delete(TABLE_NAME, whereClause.toString(), null) } + reactionsByMessageId + .asSequence() + .flatMap { it.value.asSequence() } + .forEach { reaction -> + values.apply { + put(MESSAGE_ID, reaction.messageId.id) + put(IS_MMS, reaction.messageId.mms) + put(EMOJI, reaction.emoji) + put(AUTHOR_ID, reaction.author) + put(SERVER_ID, reaction.serverId) + put(COUNT, reaction.count) + put(SORT_ID, reaction.sortId) + put(DATE_SENT, reaction.dateSent) + put(DATE_RECEIVED, reaction.dateReceived) + } + + writableDatabase.insert(TABLE_NAME, null, values) + } + writableDatabase.setTransactionSuccessful() } finally { writableDatabase.endTransaction() } } - fun deleteReaction(emoji: String, messageId: MessageId, author: String, notifyUnread: Boolean) { + fun deleteReaction(emoji: String, messageId: MessageId, author: String) { deleteReactions( - messageId = messageId, query = "$MESSAGE_ID = ? AND $IS_MMS = ? AND $EMOJI = ? AND $AUTHOR_ID = ?", args = arrayOf("${messageId.id}", "${if (messageId.mms) 1 else 0}", emoji, author), - notifyUnread ) } fun deleteEmojiReactions(emoji: String, messageId: MessageId) { deleteReactions( - messageId = messageId, query = "$MESSAGE_ID = ? AND $IS_MMS = ? AND $EMOJI = ?", args = arrayOf("${messageId.id}", "${if (messageId.mms) 1 else 0}", emoji), - false ) } fun deleteMessageReactions(messageId: MessageId) { deleteReactions( - messageId = messageId, query = "$MESSAGE_ID = ? AND $IS_MMS = ?", args = arrayOf("${messageId.id}", "${if (messageId.mms) 1 else 0}"), - false ) } - private fun deleteReactions(messageId: MessageId, query: String, args: Array, notifyUnread: Boolean) { - writableDatabase.beginTransaction() - try { - writableDatabase.delete(TABLE_NAME, query, args) + fun deleteMessageReactions(messageIds: List) { + if (messageIds.isEmpty()) return // Early exit if the list is empty - if (messageId.mms) { - DatabaseComponent.get(context).mmsDatabase().updateReactionsUnread(writableDatabase, messageId.id, hasReactions(messageId), true, notifyUnread) - } else { - DatabaseComponent.get(context).smsDatabase().updateReactionsUnread(writableDatabase, messageId.id, hasReactions(messageId), true, notifyUnread) - } - - writableDatabase.setTransactionSuccessful() - } finally { - writableDatabase.endTransaction() - } - } - - fun deleteMessageReactions(messageIds: List) { - if (messageIds.isEmpty()) return // Early exit if the list is empty - - val conditions = mutableListOf() - val args = mutableListOf() - - for (messageId in messageIds) { - conditions.add("($MESSAGE_ID = ? AND $IS_MMS = ?)") - args.add(messageId.id.toString()) - args.add(if (messageId.mms) "1" else "0") - } - - val query = conditions.joinToString(" OR ") + val conditions = mutableListOf() + val args = mutableListOf() - deleteReactions( - messageIds = messageIds, - query = query, - args = args.toTypedArray(), - notifyUnread = false - ) - } + for (messageId in messageIds) { + conditions.add("($MESSAGE_ID = ? AND $IS_MMS = ?)") + args.add(messageId.id.toString()) + args.add(if (messageId.mms) "1" else "0") + } - private fun deleteReactions(messageIds: List, query: String, args: Array, notifyUnread: Boolean) { - writableDatabase.beginTransaction() - try { - writableDatabase.delete(TABLE_NAME, query, args) - - // Update unread status for each message - for (messageId in messageIds) { - val hasReaction = hasReactions(messageId) - if (messageId.mms) { - DatabaseComponent.get(context).mmsDatabase().updateReactionsUnread( - writableDatabase, messageId.id, hasReaction, true, notifyUnread - ) - } else { - DatabaseComponent.get(context).smsDatabase().updateReactionsUnread( - writableDatabase, messageId.id, hasReaction, true, notifyUnread - ) - } - } + val query = conditions.joinToString(" OR ") - writableDatabase.setTransactionSuccessful() - } finally { - writableDatabase.endTransaction() - } - } - - private fun hasReactions(messageId: MessageId): Boolean { - val query = "$MESSAGE_ID = ? AND $IS_MMS = ?" - val args = arrayOf("${messageId.id}", "${if (messageId.mms) 1 else 0}") + deleteReactions( + query = query, + args = args.toTypedArray() + ) + } - readableDatabase.query(TABLE_NAME, arrayOf(MESSAGE_ID), query, args, null, null, null).use { cursor -> - return cursor.moveToFirst() - } + private fun deleteReactions(query: String, args: Array) { + writableDatabase.delete(TABLE_NAME, query, args) } fun getReactions(cursor: Cursor): List { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 94202a969f..daec3fe928 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -15,7 +15,6 @@ import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; import org.session.libsession.utilities.recipients.Recipient.RegisteredState; -import org.session.libsession.utilities.recipients.Recipient.UnidentifiedAccessMode; import org.session.libsignal.utilities.Base64; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; @@ -57,9 +56,11 @@ public class RecipientDatabase extends Database { private static final String CALL_RINGTONE = "call_ringtone"; private static final String CALL_VIBRATE = "call_vibrate"; private static final String NOTIFICATION_CHANNEL = "notification_channel"; + @Deprecated(forRemoval = true) private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode"; private static final String FORCE_SMS_SELECTION = "force_sms_selection"; private static final String NOTIFY_TYPE = "notify_type"; // all, mentions only, none + @Deprecated(forRemoval = true) private static final String WRAPPER_HASH = "wrapper_hash"; private static final String BLOCKS_COMMUNITY_MESSAGE_REQUESTS = "blocks_community_message_requests"; private static final String AUTO_DOWNLOAD = "auto_download"; // 1 / 0 / -1 flag for whether to auto-download in a conversation, or if the user hasn't selected a preference @@ -217,9 +218,7 @@ Optional getRecipientSettings(@NonNull Cursor cursor) { String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SESSION_PROFILE_AVATAR)); boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1; String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL)); - int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE)); boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1; - String wrapperHash = cursor.getString(cursor.getColumnIndexOrThrow(WRAPPER_HASH)); boolean blocksCommunityMessageRequests = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKS_COMMUNITY_MESSAGE_REQUESTS)) == 1; MaterialColor color; @@ -252,8 +251,8 @@ Optional getRecipientSettings(@NonNull Cursor cursor) { profileKey, systemDisplayName, systemContactPhoto, systemPhoneLabel, systemContactUri, signalProfileName, signalProfileAvatar, profileSharing, - notificationChannel, Recipient.UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), - forceSmsSelection, wrapperHash, blocksCommunityMessageRequests)); + notificationChannel, + forceSmsSelection, blocksCommunityMessageRequests)); } public boolean isAutoDownloadFlagSet(Recipient recipient) { @@ -306,14 +305,6 @@ public boolean getApproved(@NonNull Address address) { return false; } - public void setRecipientHash(@NonNull Recipient recipient, String recipientHash) { - ContentValues values = new ContentValues(); - values.put(WRAPPER_HASH, recipientHash); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setWrapperHash(recipientHash); - notifyRecipientListeners(); - } - public void setApproved(@NonNull Recipient recipient, boolean approved) { ContentValues values = new ContentValues(); values.put(APPROVED, approved ? 1 : 0); @@ -392,14 +383,6 @@ public void setNotifyType(@NonNull Recipient recipient, int notifyType) { notifyRecipientListeners(); } - public void setUnidentifiedAccessMode(@NonNull Recipient recipient, @NonNull UnidentifiedAccessMode unidentifiedAccessMode) { - ContentValues values = new ContentValues(1); - values.put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.getMode()); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setUnidentifiedAccessMode(unidentifiedAccessMode); - notifyRecipientListeners(); - } - public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) { ContentValues values = new ContentValues(1); values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index dbc1d3ee25..244916d2cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -24,7 +24,7 @@ import android.content.Context; import android.database.Cursor; import android.text.TextUtils; -import android.util.Pair; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.annimon.stream.Stream; @@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import java.io.Closeable; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; @@ -245,6 +246,7 @@ public void markAsDeleted(long messageId, boolean isOutgoing, String displayedMe contentValues.put(BODY, displayedMessage); contentValues.put(HAS_MENTION, 0); contentValues.put(STATUS, Status.STATUS_NONE); + database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, @@ -258,12 +260,13 @@ public void markExpireStarted(long id, long startedAtTimestamp) { contentValues.put(EXPIRE_STARTED, startedAtTimestamp); SQLiteDatabase db = getWritableDatabase(); - db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)}); - - long threadId = getThreadIdForMessage(id); - - DatabaseComponent.get(context).threadDatabase().update(threadId, false); - notifyConversationListeners(threadId); + try (final Cursor cursor = db.rawQuery("UPDATE " + TABLE_NAME + " SET " + EXPIRE_STARTED + " = ? " + + "WHERE " + ID + " = ? RETURNING " + THREAD_ID, startedAtTimestamp, id)) { + if (cursor.moveToNext()) { + long threadId = cursor.getLong(0); + DatabaseComponent.get(context).threadDatabase().update(threadId, false); + } + } } public void markAsSentFailed(long id) { @@ -371,10 +374,10 @@ public void incrementReceiptCount(SyncMessageId messageId, boolean deliveryRecei } public List setMessagesRead(long threadId, long beforeTime) { - return setMessagesRead(THREAD_ID + " = ? AND (" + READ + " = 0 OR " + REACTIONS_UNREAD + " = 1) AND " + DATE_SENT + " <= ?", new String[]{threadId+"", beforeTime+""}); + return setMessagesRead(THREAD_ID + " = ? AND (" + READ + " = 0) AND " + DATE_SENT + " <= ?", new String[]{threadId+"", beforeTime+""}); } public List setMessagesRead(long threadId) { - return setMessagesRead(THREAD_ID + " = ? AND (" + READ + " = 0 OR " + REACTIONS_UNREAD + " = 1)", new String[] {String.valueOf(threadId)}); + return setMessagesRead(THREAD_ID + " = ? AND (" + READ + " = 0)", new String[] {String.valueOf(threadId)}); } public List setAllMessagesRead() { @@ -412,34 +415,16 @@ private List setMessagesRead(String where, String[] arguments return results; } - public void updateSentTimestamp(long messageId, long newTimestamp, long threadId) { + public void updateSentTimestamp(long messageId, long newTimestamp) { SQLiteDatabase db = getWritableDatabase(); - db.execSQL("UPDATE " + TABLE_NAME + " SET " + DATE_SENT + " = ? " + - "WHERE " + ID + " = ?", - new String[] {newTimestamp + "", messageId + ""}); - notifyConversationListeners(threadId); - notifyConversationListListeners(); - } - - public Pair updateBundleMessageBody(long messageId, String body) { - long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT; - return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type); - } - - private Pair updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) { - SQLiteDatabase db = getWritableDatabase(); - db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " + - TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " + - "WHERE " + ID + " = ?", - new String[] {body, messageId + ""}); - - long threadId = getThreadIdForMessage(messageId); + try(final Cursor cursor = db.rawQuery("UPDATE " + TABLE_NAME + " SET " + DATE_SENT + " = ? " + + "WHERE " + ID + " = ? RETURNING " + THREAD_ID, newTimestamp, messageId)) { + if (cursor.moveToNext()) { + notifyConversationListeners(cursor.getLong(0)); + } + } - DatabaseComponent.get(context).threadDatabase().update(threadId, true); - notifyConversationListeners(threadId); notifyConversationListListeners(); - - return new Pair<>(messageId, threadId); } protected Optional insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp, boolean runThreadUpdate) { @@ -453,8 +438,7 @@ protected Optional insertMessageInbox(IncomingTextMessage message, groupRecipient = Recipient.from(context, message.getGroupId(), true); } - boolean unread = (Util.isDefaultSmsProvider(context) || - message.isSecureMessage() || message.isGroup() || message.isUnreadCallMessage()); + boolean unread = (message.isSecureMessage() || message.isGroup() || message.isUnreadCallMessage()); long threadId; @@ -576,7 +560,7 @@ public long insertMessageOutbox(long threadId, OutgoingTextMessage message, Map earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date); Map earlyReadReceipts = earlyReadReceiptCache.remove(date); - ContentValues contentValues = new ContentValues(6); + ContentValues contentValues = new ContentValues(); contentValues.put(ADDRESS, address.toString()); contentValues.put(THREAD_ID, threadId); contentValues.put(BODY, message.getMessageBody()); @@ -622,14 +606,36 @@ private Cursor rawQuery(@NonNull String where, @Nullable String[] arguments) { " WHERE " + where + " GROUP BY " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID, arguments); } - public Cursor getExpirationStartedMessages() { - String where = EXPIRE_STARTED + " > 0"; - return rawQuery(where, null); + @Override + public List getExpiredMessageIDs(long nowMills) { + String query = "SELECT " + ID + " FROM " + TABLE_NAME + + " WHERE " + EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " > 0 AND " + EXPIRE_STARTED + " + " + EXPIRES_IN + " <= ?"; + + try (final Cursor cursor = getReadableDatabase().rawQuery(query, nowMills)) { + List result = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + result.add(cursor.getLong(0)); + } + + return result; + } } - public Cursor getExpirationNotStartedMessages() { - String where = EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " = 0"; - return rawQuery(where, null); + /** + * @return the next expiring timestamp for messages that have started expiring. 0 if no messages are expiring. + */ + @Override + public long getNextExpiringTimestamp() { + String query = "SELECT MIN(" + EXPIRE_STARTED + " + " + EXPIRES_IN + ") FROM " + TABLE_NAME + + " WHERE " + EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " > 0"; + + try (final Cursor cursor = getReadableDatabase().rawQuery(query)) { + if (cursor.moveToFirst()) { + return cursor.getLong(0); + } else { + return 0L; + } + } } @NonNull diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index f05f86612d..88e65c46e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -29,7 +29,6 @@ import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.Message -import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.GroupUpdated import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage @@ -96,7 +95,6 @@ import java.security.MessageDigest import javax.inject.Inject import javax.inject.Provider import javax.inject.Singleton -import kotlin.collections.set import network.loki.messenger.libsession_util.util.Contact as LibSessionContact import network.loki.messenger.libsession_util.util.GroupMember as LibSessionGroupMember @@ -313,7 +311,7 @@ open class Storage @Inject constructor( getRecipientForThread(threadId)?.let { recipient -> val currentLastRead = threadDb.getLastSeenAndHasSent(threadId).first() // don't set the last read in the volatile if we didn't set it in the DB - if (!threadDb.markAllAsRead(threadId, recipient.isGroupOrCommunityRecipient, lastSeenTime, force) && !force) return + if (!threadDb.markAllAsRead(threadId, lastSeenTime, force) && !force) return // don't process configs for inbox recipients if (recipient.isCommunityInboxRecipient) return @@ -401,10 +399,10 @@ open class Storage @Inject constructor( } val targetRecipient = Recipient.from(context, targetAddress, false) if (!targetRecipient.isGroupOrCommunityRecipient) { - if (isUserSender || isUserBlindedSender) { + if ((isUserSender || isUserBlindedSender) && !targetRecipient.isApproved) { setRecipientApproved(targetRecipient, true) - } else { - setRecipientApprovedMe(targetRecipient, true) + } else if(!targetRecipient.hasApprovedMe()){ + setRecipientApprovedMe(targetRecipient, true) } } if (message.threadID == null && !targetRecipient.isCommunityRecipient) { @@ -670,15 +668,12 @@ open class Storage @Inject constructor( override fun updateSentTimestamp( messageId: MessageId, - openGroupSentTimestamp: Long, - threadId: Long + newTimestamp: Long ) { if (messageId.mms) { - val mmsDb = mmsDatabase - mmsDb.updateSentTimestamp(messageId.id, openGroupSentTimestamp, threadId) + mmsDatabase.updateSentTimestamp(messageId.id, newTimestamp) } else { - val smsDb = smsDatabase - smsDb.updateSentTimestamp(messageId.id, openGroupSentTimestamp, threadId) + smsDatabase.updateSentTimestamp(messageId.id, newTimestamp) } } @@ -859,7 +854,20 @@ open class Storage @Inject constructor( val userPublicKey = getUserPublicKey()!! val recipient = Recipient.from(context, fromSerialized(groupID), false) val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: "" - val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, 0, true, null, listOf(), listOf()) + val infoMessage = OutgoingGroupMediaMessage( + recipient, + updateData, + groupID, + null, + sentTimestamp, + 0, + 0, + true, + null, + listOf(), + listOf(), + null + ) val mmsDB = mmsDatabase val mmsSmsDB = mmsSmsDatabase if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) { @@ -950,33 +958,33 @@ open class Storage @Inject constructor( } } - override fun insertGroupInfoChange(message: GroupUpdated, closedGroup: AccountId): Long? { + override fun insertGroupInfoChange(message: GroupUpdated, closedGroup: AccountId) { val sentTimestamp = message.sentTimestamp ?: clock.currentTimeMills() val senderPublicKey = message.sender val groupName = configFactory.withGroupConfigs(closedGroup) { it.groupInfo.getName() } ?: configFactory.getGroup(closedGroup)?.name - val updateData = UpdateMessageData.buildGroupUpdate(message, groupName.orEmpty()) ?: return null + val updateData = UpdateMessageData.buildGroupUpdate(message, groupName.orEmpty()) ?: return - return insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup) + insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup) } - override fun insertGroupInfoLeaving(closedGroup: AccountId): Long? { + override fun insertGroupInfoLeaving(closedGroup: AccountId) { val sentTimestamp = clock.currentTimeMills() - val senderPublicKey = getUserPublicKey() ?: return null + val senderPublicKey = getUserPublicKey() ?: return val updateData = UpdateMessageData.buildGroupLeaveUpdate(UpdateMessageData.Kind.GroupLeaving) - return insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup) + insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup) } - override fun insertGroupInfoErrorQuit(closedGroup: AccountId): Long? { + override fun insertGroupInfoErrorQuit(closedGroup: AccountId) { val sentTimestamp = clock.currentTimeMills() - val senderPublicKey = getUserPublicKey() ?: return null + val senderPublicKey = getUserPublicKey() ?: return val groupName = configFactory.withGroupConfigs(closedGroup) { it.groupInfo.getName() } ?: configFactory.getGroup(closedGroup)?.name val updateData = UpdateMessageData.buildGroupLeaveUpdate(UpdateMessageData.Kind.GroupErrorQuit(groupName.orEmpty())) - return insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup) + insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup) } override fun updateGroupInfoChange(messageId: Long, newType: UpdateMessageData.Kind) { @@ -989,17 +997,17 @@ open class Storage @Inject constructor( mmsSmsDatabase.deleteGroupInfoMessage(groupId, kind) } - override fun insertGroupInviteControlMessage(sentTimestamp: Long, senderPublicKey: String, senderName: String?, closedGroup: AccountId, groupName: String): Long? { + override fun insertGroupInviteControlMessage(sentTimestamp: Long, senderPublicKey: String, senderName: String?, closedGroup: AccountId, groupName: String) { val updateData = UpdateMessageData(UpdateMessageData.Kind.GroupInvitation( groupAccountId = closedGroup.hexString, invitingAdminId = senderPublicKey, invitingAdminName = senderName, groupName = groupName )) - return insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup) + insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup) } - private fun insertUpdateControlMessage(updateData: UpdateMessageData, sentTimestamp: Long, senderPublicKey: String?, closedGroup: AccountId): Long? { + private fun insertUpdateControlMessage(updateData: UpdateMessageData, sentTimestamp: Long, senderPublicKey: String?, closedGroup: AccountId): MessageId? { val userPublicKey = getUserPublicKey()!! val recipient = Recipient.from(context, fromSerialized(closedGroup.hexString), false) val threadDb = threadDatabase @@ -1023,7 +1031,8 @@ open class Storage @Inject constructor( true, null, listOf(), - listOf() + listOf(), + null ) val mmsDB = mmsDatabase val mmsSmsDB = mmsSmsDatabase @@ -1036,14 +1045,14 @@ open class Storage @Inject constructor( runThreadUpdate = true ) mmsDB.markAsSent(infoMessageID, true) - return infoMessageID + return MessageId(infoMessageID, mms = true) } else { val group = SignalServiceGroup(Hex.fromStringCondensed(closedGroup.hexString), SignalServiceGroup.GroupType.SIGNAL) val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), expiresInMillis, expireStartedAt, true, false) val infoMessage = IncomingGroupMessage(m, inviteJson, true) val smsDB = smsDatabase val insertResult = smsDB.insertMessageInbox(infoMessage, true) - return insertResult.orNull()?.messageId + return insertResult.orNull()?.messageId?.let { MessageId(it, mms = false) } } } @@ -1061,6 +1070,11 @@ open class Storage @Inject constructor( override fun updateOpenGroup(openGroup: OpenGroup) { openGroupManager.get().updateOpenGroup(openGroup, context) + + groupDatabase.updateTitle( + groupID = GroupUtil.getEncodedOpenGroupID(openGroup.groupId.toByteArray()), + newValue = openGroup.name + ) } override fun getAllGroups(includeInactive: Boolean): List { @@ -1163,9 +1177,7 @@ open class Storage @Inject constructor( sessionContactDatabase.setContact(contact) val address = fromSerialized(contact.accountID) if (!getRecipientApproved(address)) return - val recipientHash = profileManager.contactUpdatedInternal(contact) - val recipient = Recipient.from(context, address, false) - setRecipientHash(recipient, recipientHash) + profileManager.contactUpdatedInternal(contact) } override fun deleteContactAndSyncConfig(accountId: String) { @@ -1224,7 +1236,6 @@ open class Storage @Inject constructor( val (url, key) = contact.profilePicture if (key.data.size != ProfileKeyUtil.PROFILE_KEY_BYTES) return@forEach profileManager.setProfilePicture(context, recipient, url, key.data) - profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN) } else { profileManager.setProfilePicture(context, recipient, null, null) } @@ -1245,7 +1256,6 @@ open class Storage @Inject constructor( ) } } - setRecipientHash(recipient, contact.hashCode().toString()) } // if we have contacts locally but that are missing from the config, remove their corresponding thread @@ -1264,49 +1274,7 @@ open class Storage @Inject constructor( deleteContact(it.address.toString()) } } - - override fun addContacts(contacts: List) { - val recipientDatabase = recipientDatabase - val threadDatabase = threadDatabase - val mappingDb = blindedIdMappingDatabase - val moreContacts = contacts.filter { contact -> - IdPrefix.fromValue(contact.publicKey) != IdPrefix.BLINDED || - mappingDb.getBlindedIdMapping(contact.publicKey).none { it.accountId != null } - } - for (contact in moreContacts) { - val address = fromSerialized(contact.publicKey) - val recipient = Recipient.from(context, address, true) - if (!contact.profilePicture.isNullOrEmpty()) { - recipientDatabase.setProfileAvatar(recipient, contact.profilePicture) - } - if (contact.profileKey?.isNotEmpty() == true) { - recipientDatabase.setProfileKey(recipient, contact.profileKey) - } - if (contact.name.isNotEmpty()) { - recipientDatabase.setProfileName(recipient, contact.name) - } - recipientDatabase.setProfileSharing(recipient, true) - recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED) - // create Thread if needed - val threadId = threadDatabase.getThreadIdIfExistsFor(recipient) - if (contact.didApproveMe == true) { - recipientDatabase.setApprovedMe(recipient, true) - } - if (contact.isApproved == true && threadId != -1L) { - setRecipientApproved(recipient, true) - threadDatabase.setHasSent(threadId, true) - } - - val contactIsBlocked: Boolean? = contact.isBlocked - if (contactIsBlocked != null && recipient.isBlocked != contactIsBlocked) { - setBlocked(listOf(recipient), contactIsBlocked, fromConfigUpdate = true) - } - } - if (contacts.isNotEmpty()) { - threadDatabase.notifyConversationListListeners() - } - } - + override fun shouldAutoDownloadAttachments(recipient: Recipient): Boolean { return recipient.autoDownloadAttachments } @@ -1319,11 +1287,6 @@ open class Storage @Inject constructor( recipientDb.setAutoDownloadAttachments(recipient, shouldAutoDownloadAttachments) } - override fun setRecipientHash(recipient: Recipient, recipientHash: String?) { - val recipientDb = recipientDatabase - recipientDb.setRecipientHash(recipient, recipientHash) - } - override fun getLastUpdated(threadID: Long): Long { val threadDB = threadDatabase return threadDB.getLastUpdated(threadID) @@ -1344,6 +1307,48 @@ open class Storage @Inject constructor( return mmsSmsDb.getConversationCount(threadID) } + override fun getTotalPinned(): Int { + return configFactory.withUserConfigs { + var totalPins = 0 + + // check if the note to self is pinned + if (it.userProfile.getNtsPriority() == PRIORITY_PINNED) { + totalPins ++ + } + + // check for 1on1 + it.contacts.all().forEach { contact -> + if (contact.priority == PRIORITY_PINNED) { + totalPins ++ + } + } + + // check groups and communities + it.userGroups.all().forEach { group -> + when(group){ + is GroupInfo.ClosedGroupInfo -> { + if (group.priority == PRIORITY_PINNED) { + totalPins ++ + } + } + is GroupInfo.CommunityGroupInfo -> { + if (group.priority == PRIORITY_PINNED) { + totalPins ++ + } + } + + is GroupInfo.LegacyGroupInfo -> { + if (group.priority == PRIORITY_PINNED) { + totalPins ++ + } + } + } + } + + totalPins + } + } + override fun setPinned(threadID: Long, isPinned: Boolean) { val threadDB = threadDatabase threadDB.setPinned(threadID, isPinned) @@ -1489,7 +1494,6 @@ open class Storage @Inject constructor( } override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) { - val database = mmsDatabase val address = fromSerialized(senderPublicKey) val recipient = Recipient.from(context, address, false) @@ -1507,18 +1511,17 @@ open class Storage @Inject constructor( expireStartedAt, false, false, - false, Optional.absent(), Optional.absent(), Optional.absent(), + null, Optional.absent(), Optional.absent(), Optional.absent(), Optional.of(message) ) - database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true) - messageExpirationManager.maybeStartExpiration(sentTimestamp, senderPublicKey, expiryMode) + mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true) } /** @@ -1558,7 +1561,6 @@ open class Storage @Inject constructor( if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) { profileManager.setProfilePicture(context, sender, profile.profilePictureURL!!, newProfileKey!!) - profileManager.setUnidentifiedAccessMode(context, sender, Recipient.UnidentifiedAccessMode.UNKNOWN) } } @@ -1611,12 +1613,12 @@ open class Storage @Inject constructor( -1, 0, 0, - false, true, false, Optional.absent(), Optional.absent(), Optional.absent(), + null, Optional.absent(), Optional.absent(), Optional.absent(), @@ -1643,12 +1645,12 @@ open class Storage @Inject constructor( -1, 0, 0, - false, true, false, Optional.absent(), Optional.absent(), Optional.absent(), + null, Optional.absent(), Optional.absent(), Optional.absent(), @@ -1686,17 +1688,15 @@ open class Storage @Inject constructor( } override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) { - val database = smsDatabase val address = fromSerialized(senderPublicKey) val recipient = Recipient.from(context, address, false) val threadId = threadDatabase.getOrCreateThreadIdFor(recipient) val expirationConfig = getExpirationConfiguration(threadId) val expiryMode = expirationConfig?.expiryMode?.coerceSendToRead() ?: ExpiryMode.NONE val expiresInMillis = expiryMode.expiryMillis - val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 + val expireStartedAt = if (expiryMode != ExpiryMode.NONE) clock.currentTimeMills() else 0 val callMessage = IncomingTextMessage.fromCallInfo(callMessageType, address, Optional.absent(), sentTimestamp, expiresInMillis, expireStartedAt) - database.insertCallMessage(callMessage) - messageExpirationManager.maybeStartExpiration(sentTimestamp, senderPublicKey, expiryMode) + smsDatabase.insertCallMessage(callMessage) } override fun conversationHasOutgoing(userPublicKey: String): Boolean { @@ -1789,10 +1789,10 @@ open class Storage @Inject constructor( return } - addReaction(messageId, reaction, messageSender, notifyUnread) + addReaction(messageId, reaction, messageSender) } - override fun addReaction(messageId: MessageId, reaction: Reaction, messageSender: String, notifyUnread: Boolean) { + override fun addReaction(messageId: MessageId, reaction: Reaction, messageSender: String) { reactionDatabase.addReaction( ReactionRecord( messageId = messageId, @@ -1803,8 +1803,18 @@ open class Storage @Inject constructor( sortId = reaction.index!!, dateSent = reaction.dateSent!!, dateReceived = reaction.dateReceived!! - ), - notifyUnread + ) + ) + } + + override fun addReactions( + reactions: Map>, + replaceAll: Boolean, + notifyUnread: Boolean + ) { + reactionDatabase.addReactions( + reactionsByMessageId = reactions, + replaceAll = replaceAll ) } @@ -1816,7 +1826,11 @@ open class Storage @Inject constructor( notifyUnread: Boolean ) { val messageRecord = mmsSmsDatabase.getMessageForTimestamp(threadId, messageTimestamp) ?: return - reactionDatabase.deleteReaction(emoji, MessageId(messageRecord.id, messageRecord.isMms), author, notifyUnread) + reactionDatabase.deleteReaction( + emoji, + MessageId(messageRecord.id, messageRecord.isMms), + author + ) } override fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long) { @@ -1940,27 +1954,6 @@ open class Storage @Inject constructor( ) } - override fun getExpiringMessages(messageIds: List): List> { - val expiringMessages = mutableListOf>() - val smsDb = smsDatabase - smsDb.readerFor(smsDb.expirationNotStartedMessages).use { reader -> - while (reader.next != null) { - if (messageIds.isEmpty() || reader.current.id in messageIds) { - expiringMessages.add(reader.current.id to reader.current.expiresIn) - } - } - } - val mmsDb = mmsDatabase - mmsDb.expireNotStartedMessages.use { reader -> - while (reader.next != null) { - if (messageIds.isEmpty() || reader.current.id in messageIds) { - expiringMessages.add(reader.current.id to reader.current.expiresIn) - } - } - } - return expiringMessages - } - override fun updateDisappearingState( messageSender: String, threadID: Long, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index ee31899f99..f87e9e629f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -65,17 +66,21 @@ import java.io.Closeable; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import javax.inject.Inject; import javax.inject.Provider; +import javax.inject.Singleton; +import kotlin.collections.CollectionsKt; +import kotlinx.serialization.json.Json; import network.loki.messenger.libsession_util.util.GroupInfo; +@Singleton public class ThreadDatabase extends Database { public interface ConversationThreadUpdateListener { @@ -101,6 +106,10 @@ public interface ConversationThreadUpdateListener { private static final String ERROR = "error"; public static final String SNIPPET_TYPE = "snippet_type"; public static final String SNIPPET_URI = "snippet_uri"; + /** + * The column that hold a {@link MessageContent}. See {@link MmsDatabase#MESSAGE_CONTENT} for more information + */ + public static final String SNIPPET_CONTENT = "snippet_content"; public static final String ARCHIVED = "archived"; public static final String STATUS = "status"; public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count"; @@ -126,9 +135,11 @@ public interface ConversationThreadUpdateListener { "CREATE INDEX IF NOT EXISTS archived_count_index ON " + TABLE_NAME + " (" + ARCHIVED + ", " + MESSAGE_COUNT + ");", }; + public static final String ADD_SNIPPET_CONTENT_COLUMN = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + SNIPPET_CONTENT + " TEXT DEFAULT NULL;"; + private static final String[] THREAD_PROJECTION = { ID, THREAD_CREATION_DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, UNREAD_MENTION_COUNT, DISTRIBUTION_TYPE, ERROR, SNIPPET_TYPE, - SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED + SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED, SNIPPET_CONTENT, }; private static final List TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION) @@ -156,9 +167,39 @@ public static String getUnreadMentionCountCommand() { } private ConversationThreadUpdateListener updateListener; - - public ThreadDatabase(Context context, Provider databaseHelper) { + private final Json json; + private final TextSecurePreferences prefs; + + @Inject + public ThreadDatabase( + @dagger.hilt.android.qualifiers.ApplicationContext Context context, + Provider databaseHelper, + TextSecurePreferences prefs, + Json json) { super(context, databaseHelper); + this.json = json; + this.prefs = prefs; + } + + // This method is called when the application is created, providing an opportunity to perform + // initialization tasks that need to be done only once. + public void onAppCreated() { + if (!prefs.getMigratedDisappearingMessagesToMessageContent()) { + migrateDisappearingMessagesToMessageContent(); + prefs.setMigratedDisappearingMessagesToMessageContent(true); + } + } + + // As we migrate disappearing messages to MessageContent, we need to ensure that + // if they appear in the snippet, they have to be re-generated with the new MessageContent. + private void migrateDisappearingMessagesToMessageContent() { + String sql = "SELECT " + ID + " FROM " + TABLE_NAME + + " WHERE " + SNIPPET_TYPE + " & " + MmsSmsColumns.Types.EXPIRATION_TIMER_UPDATE_BIT + " != 0"; + try (final Cursor cursor = getReadableDatabase().rawQuery(sql)) { + while (cursor.moveToNext()) { + update(cursor.getLong(0), false); + } + } } public void setUpdateListener(ConversationThreadUpdateListener updateListener) { @@ -178,7 +219,7 @@ private long createThreadForRecipient(Address address, boolean group, int distri return db.insert(TABLE_NAME, null, contentValues); } - private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, + private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, @Nullable MessageContent messageContent, long date, int status, int deliveryReceiptCount, long type, boolean unarchive, long expiresIn, int readReceiptCount) { @@ -188,6 +229,7 @@ private void updateThread(long threadId, long count, String body, @Nullable Uri if (!body.isEmpty()) { contentValues.put(SNIPPET, body); } + contentValues.put(SNIPPET_CONTENT, messageContent == null ? null : json.encodeToString(MessageContent.Companion.serializer(), messageContent)); contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); contentValues.put(SNIPPET_TYPE, type); contentValues.put(STATUS, status); @@ -206,25 +248,7 @@ public void clearSnippet(long threadId){ ContentValues contentValues = new ContentValues(1); contentValues.put(SNIPPET, ""); - - SQLiteDatabase db = getWritableDatabase(); - db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); - notifyConversationListListeners(); - } - - public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type, boolean unarchive) { - ContentValues contentValues = new ContentValues(4); - - contentValues.put(THREAD_CREATION_DATE, date - date % 1000); - if (!snippet.isEmpty()) { - contentValues.put(SNIPPET, snippet); - } - contentValues.put(SNIPPET_TYPE, type); - contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); - - if (unarchive) { - contentValues.put(ARCHIVED, 0); - } + contentValues.put(SNIPPET_CONTENT, ""); SQLiteDatabase db = getWritableDatabase(); db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); @@ -323,10 +347,6 @@ public List setRead(long threadId, long lastReadTime) { final List smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId, lastReadTime); final List mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId, lastReadTime); - if (smsRecords.isEmpty() && mmsRecords.isEmpty()) { - return Collections.emptyList(); - } - ContentValues contentValues = new ContentValues(2); contentValues.put(READ, smsRecords.isEmpty() && mmsRecords.isEmpty()); contentValues.put(LAST_SEEN, lastReadTime); @@ -336,10 +356,7 @@ public List setRead(long threadId, long lastReadTime) { notifyConversationListListeners(); - return new LinkedList() {{ - addAll(smsRecords); - addAll(mmsRecords); - }}; + return CollectionsKt.plus(smsRecords, mmsRecords); } public List setRead(long threadId, boolean lastSeen) { @@ -544,13 +561,6 @@ public boolean setLastSeen(long threadId, long timestamp) { return true; } - /** - * @param threadId - * @return true if we have set the last seen for the thread, false if there were no messages in the thread - */ - public boolean setLastSeen(long threadId) { - return setLastSeen(threadId, -1); - } public Pair getLastSeenAndHasSent(long threadId) { SQLiteDatabase db = getReadableDatabase(); @@ -718,7 +728,7 @@ record = reader.getNext(); } } if (record != null && !record.isDeleted()) { - updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record), + updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record), record.getMessageContent(), record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(), record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount()); return false; @@ -759,11 +769,10 @@ public boolean isPinned(long threadId) { /** * @param threadId - * @param isGroupRecipient * @param lastSeenTime * @return true if we have set the last seen for the thread, false if there were no messages in the thread */ - public boolean markAllAsRead(long threadId, boolean isGroupRecipient, long lastSeenTime, boolean force) { + public boolean markAllAsRead(long threadId, long lastSeenTime, boolean force) { MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && !force) return false; List messages = setRead(threadId, lastSeenTime); @@ -895,6 +904,7 @@ public ThreadRecord getCurrent() { Uri snippetUri = getSnippetUri(cursor); boolean pinned = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.IS_PINNED)) != 0; String invitingAdmin = cursor.getString(cursor.getColumnIndexOrThrow(LokiMessageDatabase.invitingSessionId)); + String messageContentJson = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_CONTENT)); if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { readReceiptCount = 0; @@ -924,9 +934,19 @@ public ThreadRecord getCurrent() { groupThreadStatus = GroupThreadStatus.None; } - return new ThreadRecord(body, snippetUri, lastMessage, recipient, date, count, + MessageContent messageContent; + try { + messageContent = (messageContentJson == null || messageContentJson.isEmpty()) ? null : json.decodeFromString( + MessageContent.Companion.serializer(), + messageContentJson + ); + } catch (Exception e) { + Log.e(TAG, "Failed to parse message content for thread: " + threadId, e); + messageContent = null; + } + return new ThreadRecord(body, snippetUri, lastMessage, recipient, date, count, unreadCount, unreadMentionCount, threadId, deliveryReceiptCount, status, type, - distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned, invitingAdmin, groupThreadStatus); + distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned, invitingAdmin, groupThreadStatus, messageContent); } private @Nullable Uri getSnippetUri(Cursor cursor) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 853fc6775f..378b491057 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -100,9 +100,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV47 = 68; private static final int lokiV48 = 69; private static final int lokiV49 = 70; + private static final int lokiV50 = 71; + private static final int lokiV51 = 72; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV49; + private static final int DATABASE_VERSION = lokiV51; private static final int MIN_DATABASE_VERSION = lokiV7; public static final String DATABASE_NAME = "session.db"; @@ -229,6 +231,7 @@ public void onCreate(SQLiteDatabase db) { executeStatements(db, ReactionDatabase.CREATE_INDEXS); executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS); + executeStatements(db, ReactionDatabase.CREATE_MESSAGE_ID_MMS_INDEX); db.execSQL(RecipientDatabase.getAddWrapperHash()); db.execSQL(RecipientDatabase.getAddBlocksCommunityMessageRequests()); db.execSQL(LokiAPIDatabase.CREATE_LAST_LEGACY_MESSAGE_TABLE); @@ -239,6 +242,8 @@ public void onCreate(SQLiteDatabase db) { db.execSQL(LokiMessageDatabase.getCreateThreadDeleteTrigger()); db.execSQL(SmsDatabase.ADD_IS_GROUP_UPDATE_COLUMN); db.execSQL(MmsDatabase.ADD_IS_GROUP_UPDATE_COLUMN); + db.execSQL(MmsDatabase.ADD_MESSAGE_CONTENT_COLUMN); + db.execSQL(ThreadDatabase.ADD_SNIPPET_CONTENT_COLUMN); } @Override @@ -533,6 +538,16 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(LokiMessageDatabase.getUpdateErrorMessageTableCommand()); } + if (oldVersion < lokiV50) { + executeStatements(db, ReactionDatabase.CREATE_MESSAGE_ID_MMS_INDEX); + } + + if (oldVersion < lokiV51) { + db.execSQL(MmsDatabase.ADD_MESSAGE_CONTENT_COLUMN); + db.execSQL(MmsDatabase.MIGRATE_EXPIRY_CONTROL_MESSAGES); + db.execSQL(ThreadDatabase.ADD_SNIPPET_CONTENT_COLUMN); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java deleted file mode 100644 index 21ed07ac66..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.thoughtcrime.securesms.database.loaders; - - -import android.Manifest; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.MediaStore; -import androidx.loader.content.CursorLoader; - -import org.thoughtcrime.securesms.permissions.Permissions; - -public class RecentPhotosLoader extends CursorLoader { - - public static Uri BASE_URL = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - - private static final String[] PROJECTION = new String[] { - MediaStore.Images.ImageColumns._ID, - MediaStore.Images.ImageColumns.DATE_TAKEN, - MediaStore.Images.ImageColumns.DATE_MODIFIED, - MediaStore.Images.ImageColumns.ORIENTATION, - MediaStore.Images.ImageColumns.MIME_TYPE, - MediaStore.Images.ImageColumns.BUCKET_ID, - MediaStore.Images.ImageColumns.SIZE, - MediaStore.Images.ImageColumns.WIDTH, - MediaStore.Images.ImageColumns.HEIGHT - }; - - private final Context context; - - public RecentPhotosLoader(Context context) { - super(context); - this.context = context.getApplicationContext(); - } - - @Override - public Cursor loadInBackground() { - if (Permissions.hasAll(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { - return context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - PROJECTION, null, null, - MediaStore.Images.ImageColumns.DATE_MODIFIED + " DESC"); - } else { - return null; - } - } - - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java deleted file mode 100644 index 3f5c108356..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.thoughtcrime.securesms.database.loaders; - - -import android.content.Context; -import android.database.Cursor; - -import androidx.annotation.NonNull; - -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import org.thoughtcrime.securesms.util.AbstractCursorLoader; - -public class ThreadMediaLoader extends AbstractCursorLoader { - - private final Address address; - private final boolean gallery; - - public ThreadMediaLoader(@NonNull Context context, @NonNull Address address, boolean gallery) { - super(context); - this.address = address; - this.gallery = gallery; - } - - @Override - public Cursor getCursor() { - long threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(Recipient.from(getContext(), address, true)); - - if (gallery) return DatabaseComponent.get(context).mediaDatabase().getGalleryMediaForThread(threadId); - else return DatabaseComponent.get(context).mediaDatabase().getDocumentMediaForThread(threadId); - } - - public Address getAddress() { - return address; - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 7eeec84d07..8faaab63a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -19,10 +19,13 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate; +import org.thoughtcrime.securesms.database.model.content.MessageContent; /** * The base class for all message record models. Encapsulates basic data @@ -43,9 +46,12 @@ public abstract class DisplayRecord { private final int deliveryReceiptCount; private final int readReceiptCount; + @Nullable + private final MessageContent messageContent; + DisplayRecord(String body, Recipient recipient, long dateSent, - long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, - long type, int readReceiptCount) + long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, + long type, int readReceiptCount, @Nullable MessageContent messageContent) { this.threadId = threadId; this.recipient = recipient; @@ -56,6 +62,7 @@ public abstract class DisplayRecord { this.deliveryReceiptCount = deliveryReceiptCount; this.readReceiptCount = readReceiptCount; this.deliveryStatus = deliveryStatus; + this.messageContent = messageContent; } public @NonNull String getBody() { @@ -88,10 +95,14 @@ public boolean isPending() { return isPending; } + @Nullable + public MessageContent getMessageContent() { + return messageContent; + } + public boolean isCallLog() { return SmsDatabase.Types.isCallLog(type); } public boolean isDataExtractionNotification() { return isMediaSavedNotification() || isScreenshotNotification(); } public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); } - public boolean isExpirationTimerUpdate() { return SmsDatabase.Types.isExpirationTimerUpdate(type); } public boolean isFirstMissedCall() { return SmsDatabase.Types.isFirstMissedCall(type); } public boolean isGroupUpdateMessage() { return SmsDatabase.Types.isGroupUpdateMessage(type); } public boolean isIncoming() { return !MmsSmsColumns.Types.isOutgoingMessageType(type); } @@ -112,10 +123,10 @@ public boolean isPending() { public boolean isControlMessage() { return isGroupUpdateMessage() || - isExpirationTimerUpdate() || isDataExtractionNotification() || isMessageRequestResponse() || - isCallLog(); + isCallLog() || + (messageContent instanceof DisappearingMessageUpdate); } public boolean isGroupV2ExpirationTimerUpdate() { return false; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index fdba0510e3..7fbcf9669e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -27,6 +27,7 @@ import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.SmsDatabase.Status; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.mms.SlideDeck; import java.util.List; @@ -53,12 +54,13 @@ public MediaMmsMessageRecord(long id, Recipient conversationRecipient, long expiresIn, long expireStarted, int readReceiptCount, @Nullable Quote quote, @NonNull List contacts, @NonNull List linkPreviews, - @NonNull List reactions, boolean hasMention) + @NonNull List reactions, boolean hasMention, + @Nullable MessageContent messageContent) { super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures, expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts, - linkPreviews, reactions, hasMention); + linkPreviews, reactions, hasMention, messageContent); this.partCount = partCount; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index e5e375dac9..208fdec88f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -37,6 +37,8 @@ import org.session.libsession.utilities.ThemeUtil; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.AccountId; +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import network.loki.messenger.R; @@ -62,10 +64,6 @@ public abstract class MessageRecord extends DisplayRecord { @Nullable private UpdateMessageData groupUpdateMessage; - public final boolean isNotDisappearAfterRead() { - return expireStarted == getTimestamp(); - } - public abstract boolean isMms(); public abstract boolean isMmsNotification(); @@ -74,16 +72,17 @@ public final MessageId getMessageId() { } MessageRecord(long id, String body, Recipient conversationRecipient, - Recipient individualRecipient, - long dateSent, long dateReceived, long threadId, - int deliveryStatus, int deliveryReceiptCount, long type, - List mismatches, - List networkFailures, - long expiresIn, long expireStarted, - int readReceiptCount, List reactions, boolean hasMention) + Recipient individualRecipient, + long dateSent, long dateReceived, long threadId, + int deliveryStatus, int deliveryReceiptCount, long type, + List mismatches, + List networkFailures, + long expiresIn, long expireStarted, + int readReceiptCount, List reactions, boolean hasMention, + @Nullable MessageContent messageContent) { super(body, conversationRecipient, dateSent, dateReceived, - threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount); + threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount, messageContent); this.id = id; this.individualRecipient = individualRecipient; this.mismatches = mismatches; @@ -159,10 +158,10 @@ public CharSequence getDisplayBody(@NonNull Context context) { } return text; - } else if (isExpirationTimerUpdate()) { - int seconds = (int) (getExpiresIn() / 1000); + } else if (getMessageContent() instanceof DisappearingMessageUpdate) { boolean isGroup = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(getThreadId()).isGroupOrCommunityRecipient(); - return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, isGroup, getIndividualRecipient().getAddress().toString(), isOutgoing(), getTimestamp(), expireStarted)); + return UpdateMessageBuilder.INSTANCE + .buildExpirationTimerMessage(context, ((DisappearingMessageUpdate) getMessageContent()).getExpiryMode(), isGroup, getIndividualRecipient().getAddress().toString(), isOutgoing()); } else if (isDataExtractionNotification()) { if (isScreenshotNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().toString()))); else if (isMediaSavedNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().toString()))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index 320eb4e739..802d16b835 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -8,6 +8,7 @@ import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -22,15 +23,16 @@ public abstract class MmsMessageRecord extends MessageRecord { private final @NonNull List linkPreviews = new LinkedList<>(); MmsMessageRecord(long id, String body, Recipient conversationRecipient, - Recipient individualRecipient, long dateSent, - long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, - long type, List mismatches, - List networkFailures, long expiresIn, - long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, - @Nullable Quote quote, @NonNull List contacts, - @NonNull List linkPreviews, List reactions, boolean hasMention) + Recipient individualRecipient, long dateSent, + long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, + long type, List mismatches, + List networkFailures, long expiresIn, + long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, + @Nullable Quote quote, @NonNull List contacts, + @NonNull List linkPreviews, List reactions, boolean hasMention, + @Nullable MessageContent messageContent) { - super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, reactions, hasMention); + super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, reactions, hasMention, messageContent); this.slideDeck = slideDeck; this.quote = quote; this.contacts.addAll(contacts); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index 4a746d23a2..c33c1efb6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -47,7 +47,7 @@ public SmsMessageRecord(long id, super(id, body, recipient, individualRecipient, dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, mismatches, new LinkedList<>(), - expiresIn, expireStarted, readReceiptCount, reactions, hasMention); + expiresIn, expireStarted, readReceiptCount, reactions, hasMention, null); } public long getType() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 4a4b8ae462..fd58198d7e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -37,6 +37,8 @@ import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.ui.UtilKt; /** @@ -67,11 +69,11 @@ public class ThreadRecord extends DisplayRecord { public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, @Nullable MessageRecord lastMessage, @NonNull Recipient recipient, long date, long count, int unreadCount, int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, - long snippetType, int distributionType, boolean archived, long expiresIn, + long snippetType, int distributionType, boolean archived, long expiresIn, long lastSeen, int readReceiptCount, boolean pinned, String invitingAdminId, - @NonNull GroupThreadStatus groupThreadStatus) + @NonNull GroupThreadStatus groupThreadStatus, @Nullable MessageContent messageContent) { - super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount); + super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount, messageContent); this.snippetUri = snippetUri; this.lastMessage = lastMessage; this.count = count; @@ -141,7 +143,7 @@ else if (isGroupUpdateMessage()) { return Phrase.from(context, R.string.callsMissedCallFrom) .put(NAME_KEY, getName()) .format().toString(); - } else if (SmsDatabase.Types.isExpirationTimerUpdate(type)) { + } else if (getMessageContent() instanceof DisappearingMessageUpdate) { // Use the same message as we would for displaying on the conversation screen. // lastMessage shouldn't be null here, but we'll check just in case. if (lastMessage != null) { @@ -149,7 +151,8 @@ else if (isGroupUpdateMessage()) { } else { return ""; } - } else if (MmsSmsColumns.Types.isMediaSavedExtraction(type)) { + } + else if (MmsSmsColumns.Types.isMediaSavedExtraction(type)) { return Phrase.from(context, R.string.attachmentsMediaSaved) .put(NAME_KEY, getName()) .format().toString(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/content/DisappearingMessageUpdate.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/DisappearingMessageUpdate.kt new file mode 100644 index 0000000000..ab42a31ab2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/DisappearingMessageUpdate.kt @@ -0,0 +1,54 @@ +package org.thoughtcrime.securesms.database.model.content + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import network.loki.messenger.libsession_util.util.ExpiryMode +import org.session.libsignal.protos.SignalServiceProtos +import org.thoughtcrime.securesms.util.ProtobufEnumSerializer + + +@Serializable +@SerialName(DisappearingMessageUpdate.TYPE_NAME) +data class DisappearingMessageUpdate( + @SerialName(KEY_EXPIRY_TIME_SECONDS) + val expiryTimeSeconds: Long, + + @SerialName(KEY_EXPIRY_TYPE) + @Serializable(with = ExpirationTypeSerializer::class) + val expiryType: SignalServiceProtos.Content.ExpirationType, +) : MessageContent { + val expiryMode: ExpiryMode + get() = when (expiryType) { + SignalServiceProtos.Content.ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(expiryTimeSeconds) + SignalServiceProtos.Content.ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(expiryTimeSeconds) + else -> ExpiryMode.NONE + } + + constructor(mode: ExpiryMode) : this( + expiryTimeSeconds = mode.expirySeconds, + expiryType = when (mode) { + is ExpiryMode.AfterSend -> SignalServiceProtos.Content.ExpirationType.DELETE_AFTER_SEND + is ExpiryMode.AfterRead -> SignalServiceProtos.Content.ExpirationType.DELETE_AFTER_READ + ExpiryMode.NONE -> SignalServiceProtos.Content.ExpirationType.UNKNOWN + } + ) + + class ExpirationTypeSerializer : ProtobufEnumSerializer() { + override fun fromNumber(number: Int): SignalServiceProtos.Content.ExpirationType + = SignalServiceProtos.Content.ExpirationType.forNumber(number) ?: SignalServiceProtos.Content.ExpirationType.UNKNOWN + } + + companion object { + const val TYPE_NAME = "disappearing_message_update" + + const val KEY_EXPIRY_TIME_SECONDS = "expiry_time_seconds" + const val KEY_EXPIRY_TYPE = "expiry_type" + + // These constants map to SignalServiceProtos.Content.ExpirationType but given we want to use + // a constants it's impossible to use the enum directly. Luckily the values aren't supposed + // to change so we can safely use these constants. + const val EXPIRY_MODE_AFTER_SENT = 2 + const val EXPIRY_MODE_AFTER_READ = 1 + const val EXPIRY_MODE_NONE = 0 + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/content/MessageContent.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/MessageContent.kt new file mode 100644 index 0000000000..c77ddb647f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/MessageContent.kt @@ -0,0 +1,27 @@ +package org.thoughtcrime.securesms.database.model.content + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonClassDiscriminator +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass + + +@OptIn(ExperimentalSerializationApi::class) +@Serializable +@JsonClassDiscriminator(MessageContent.DISCRIMINATOR) +sealed interface MessageContent { + companion object { + const val DISCRIMINATOR = "type" + + fun serializersModule() = SerializersModule { + polymorphic(MessageContent::class) { + subclass(DisappearingMessageUpdate::class) + defaultDeserializer { + UnknownMessageContent.serializer() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/content/UnknownMessageContent.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/UnknownMessageContent.kt new file mode 100644 index 0000000000..cc8abeedcd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/UnknownMessageContent.kt @@ -0,0 +1,10 @@ +package org.thoughtcrime.securesms.database.model.content + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UnknownMessageContent( + @SerialName(MessageContent.DISCRIMINATOR) + val type: String, +) : MessageContent \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt index 38997082da..676e40507a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt @@ -58,7 +58,7 @@ import org.thoughtcrime.securesms.debugmenu.DebugMenuViewModel.Commands.ShowEnvi import org.thoughtcrime.securesms.debugmenu.DebugMenuViewModel.Commands.GenerateContacts import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.Cell -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.LoadingDialog import org.thoughtcrime.securesms.ui.components.BackAppBar @@ -127,11 +127,11 @@ fun DebugMenu( text = "This will restart the app...", showCloseButton = false, // don't display the 'x' button buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(R.string.cancel), onClick = { sendCommand(HideDeprecationChangeDialog) } ), - DialogButtonModel( + DialogButtonData( text = GetString(android.R.string.ok), onClick = { sendCommand(OverrideDeprecationState) } ) @@ -146,11 +146,11 @@ fun DebugMenu( text = "Changing this setting will result in all conversations and Snode data being cleared...", showCloseButton = false, // don't display the 'x' button buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(R.string.cancel), onClick = { sendCommand(HideEnvironmentWarningDialog) } ), - DialogButtonModel( + DialogButtonData( text = GetString(android.R.string.ok), onClick = { sendCommand(ChangeEnvironment) } ) @@ -203,6 +203,33 @@ fun DebugMenu( ) } + // Session Pro + DebugCell("Session Pro") { + DebugSwitchRow( + text = "Set current user as Pro", + checked = uiState.forceCurrentUserAsPro, + onCheckedChange = { + sendCommand(DebugMenuViewModel.Commands.ForceCurrentUserAsPro(it)) + } + ) + + DebugSwitchRow( + text = "Set all incoming messages as Pro", + checked = uiState.forceIncomingMessagesAsPro, + onCheckedChange = { + sendCommand(DebugMenuViewModel.Commands.ForceIncomingMessagesAsPro(it)) + } + ) + + DebugSwitchRow( + text = "Set app as post Pro launch", + checked = uiState.forcePostPro, + onCheckedChange = { + sendCommand(DebugMenuViewModel.Commands.ForcePostPro(it)) + } + ) + } + // Fake contacts DebugCell("Generate fake contacts") { var prefix by remember { mutableStateOf("User-") } @@ -394,14 +421,14 @@ fun DebugMenu( }, title = "Set Time", buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(R.string.cancel), onClick = { showingDeprecatedTimePicker = false showingDeprecatingStartTimePicker = false } ), - DialogButtonModel( + DialogButtonData( text = GetString(android.R.string.ok), onClick = { if (showingDeprecatedTimePicker) { @@ -530,7 +557,10 @@ fun PreviewDebugMenu() { forceDeprecationState = null, deprecatedTime = ZonedDateTime.now(), availableDeprecationState = emptyList(), - deprecatingStartTime = ZonedDateTime.now() + deprecatingStartTime = ZonedDateTime.now(), + forceCurrentUserAsPro = false, + forceIncomingMessagesAsPro = false, + forcePostPro = false, ), sendCommand = {}, onClose = {} diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt index 285bb07f8e..aaddbf527c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt @@ -69,6 +69,9 @@ class DebugMenuViewModel @Inject constructor( availableDeprecationState = listOf(null) + LegacyGroupDeprecationManager.DeprecationState.entries.toList(), deprecatedTime = deprecationManager.deprecatedTime.value, deprecatingStartTime = deprecationManager.deprecatingStartTime.value, + forceCurrentUserAsPro = textSecurePreferences.forceCurrentUserAsPro(), + forceIncomingMessagesAsPro = textSecurePreferences.forceIncomingMessagesAsPro(), + forcePostPro = textSecurePreferences.forcePostPro(), ) ) val uiState: StateFlow @@ -200,6 +203,27 @@ class DebugMenuViewModel @Inject constructor( _uiState.update { it.copy(showLoadingDialog = false) } } } + + is Commands.ForceCurrentUserAsPro -> { + textSecurePreferences.setForceCurrentUserAsPro(command.set) + _uiState.update { + it.copy(forceCurrentUserAsPro = command.set) + } + } + + is Commands.ForceIncomingMessagesAsPro -> { + textSecurePreferences.setForceIncomingMessagesAsPro(command.set) + _uiState.update { + it.copy(forceIncomingMessagesAsPro = command.set) + } + } + + is Commands.ForcePostPro -> { + textSecurePreferences.setForcePostPro(command.set) + _uiState.update { + it.copy(forcePostPro = command.set) + } + } } } @@ -291,6 +315,9 @@ class DebugMenuViewModel @Inject constructor( val showDeprecatedStateWarningDialog: Boolean, val hideMessageRequests: Boolean, val hideNoteToSelf: Boolean, + val forceCurrentUserAsPro: Boolean, + val forceIncomingMessagesAsPro: Boolean, + val forcePostPro: Boolean, val forceDeprecationState: LegacyGroupDeprecationManager.DeprecationState?, val availableDeprecationState: List, val deprecatedTime: ZonedDateTime, @@ -306,6 +333,9 @@ class DebugMenuViewModel @Inject constructor( object CopyAccountId : Commands() data class HideMessageRequest(val hide: Boolean) : Commands() data class HideNoteToSelf(val hide: Boolean) : Commands() + data class ForceCurrentUserAsPro(val set: Boolean) : Commands() + data class ForceIncomingMessagesAsPro(val set: Boolean) : Commands() + data class ForcePostPro(val set: Boolean) : Commands() data class ShowDeprecationChangeDialog(val state: LegacyGroupDeprecationManager.DeprecationState?) : Commands() object HideDeprecationChangeDialog : Commands() object OverrideDeprecationState : Commands() diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt index 5bf323d5f6..7208de9df8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt @@ -8,12 +8,16 @@ import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.plus import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences +import org.thoughtcrime.securesms.database.model.content.MessageContent import org.thoughtcrime.securesms.groups.GroupManagerV2Impl import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier import org.thoughtcrime.securesms.repository.ConversationRepository @@ -26,6 +30,14 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) class AppModule { + @Provides + @Singleton + fun provideJson(): Json { + return Json { + ignoreUnknownKeys = true + serializersModule += MessageContent.serializersModule() + } + } } @Module diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt index ead6657cff..3351cf7b71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -28,8 +28,8 @@ import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.MultiEncrypt import network.loki.messenger.libsession_util.util.UserPic +import okio.ByteString.Companion.decodeBase64 import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.snode.OwnedSwarmAuth import org.session.libsession.snode.SnodeClock import org.session.libsession.snode.SwarmAuth @@ -292,6 +292,7 @@ class ConfigFactory @Inject constructor( private fun doWithMutableGroupConfigs( groupId: AccountId, + fromMerge: Boolean, cb: (GroupConfigsImpl) -> Pair): T { val (lock, configs) = ensureGroupConfigsInitialized(groupId) val (result, changed) = lock.write { @@ -302,7 +303,7 @@ class ConfigFactory @Inject constructor( coroutineScope.launch { // Config change notifications are important so we must use suspend version of // emit (not tryEmit) - _configUpdateNotifications.emit(ConfigUpdateNotification.GroupConfigsUpdated(groupId)) + _configUpdateNotifications.emit(ConfigUpdateNotification.GroupConfigsUpdated(groupId, fromMerge = fromMerge)) } } @@ -313,7 +314,7 @@ class ConfigFactory @Inject constructor( groupId: AccountId, cb: (MutableGroupConfigs) -> T ): T { - return doWithMutableGroupConfigs(groupId = groupId) { + return doWithMutableGroupConfigs(groupId = groupId, fromMerge = false) { cb(it) to it.dumpIfNeeded(clock) } } @@ -364,7 +365,7 @@ class ConfigFactory @Inject constructor( info: List, members: List ) { - val changed = doWithMutableGroupConfigs(groupId) { configs -> + val changed = doWithMutableGroupConfigs(groupId, fromMerge = true) { configs -> // Keys must be loaded first as they are used to decrypt the other config messages val keysLoaded = keys.fold(false) { acc, msg -> configs.groupKeys.loadKey(msg.data, msg.hash, msg.timestamp, configs.groupInfo.pointer, configs.groupMembers.pointer) || acc @@ -434,7 +435,7 @@ class ConfigFactory @Inject constructor( return } - doWithMutableGroupConfigs(groupId) { configs -> + doWithMutableGroupConfigs(groupId, fromMerge = false) { configs -> members?.let { (push, result) -> configs.groupMembers.confirmPushed(push.seqNo, result.hashes.toTypedArray()) } info?.let { (push, result) -> configs.groupInfo.confirmPushed(push.seqNo, result.hashes.toTypedArray()) } keysPush?.let { (hashes, timestamp) -> @@ -664,12 +665,10 @@ private fun MutableUserProfile.initFrom(storage: StorageProtocol, ) { val ownPublicKey = storage.getUserPublicKey() ?: return val displayName = usernameUtils.getCurrentUsername() ?: return - val profilePicture = textSecurePreferences.getProfilePictureURL() - val config = ConfigurationMessage.getCurrent(displayName, profilePicture, listOf()) ?: return - setName(config.displayName) - val picUrl = config.profilePicture - val picKey = config.profileKey - if (!picUrl.isNullOrEmpty() && picKey.isNotEmpty()) { + val picUrl = textSecurePreferences.getProfilePictureURL() + val picKey = textSecurePreferences.getProfileKey()?.decodeBase64()?.toByteArray() + setName(displayName) + if (!picUrl.isNullOrEmpty() && picKey != null && picKey.isNotEmpty()) { setPic(UserPic(picUrl, picKey)) } val ownThreadId = storage.getThreadId(Address.fromSerialized(ownPublicKey)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt index ddad988b59..7718a2e159 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt @@ -65,9 +65,6 @@ object DatabaseModule { @Singleton fun provideSmsDatabase(@ApplicationContext context: Context, openHelper: Provider) = SmsDatabase(context, openHelper) - @Provides - @Singleton - fun provideMmsDatabase(@ApplicationContext context: Context, openHelper: Provider) = MmsDatabase(context, openHelper) @Provides @Singleton @@ -78,10 +75,6 @@ object DatabaseModule { @Singleton fun provideMediaDatbase(@ApplicationContext context: Context, openHelper: Provider) = MediaDatabase(context, openHelper) - @Provides - @Singleton - fun provideThread(@ApplicationContext context: Context, openHelper: Provider) = ThreadDatabase(context,openHelper) - @Provides @Singleton fun provideMmsSms(@ApplicationContext context: Context, openHelper: Provider) = MmsSmsDatabase(context, openHelper) diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java index fcf5f98dc7..67c27f5032 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.giph.ui; import android.annotation.SuppressLint; -import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; @@ -13,19 +12,15 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.viewpager.widget.ViewPager; import androidx.viewpager2.adapter.FragmentStateAdapter; -import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; import org.session.libsession.utilities.MediaTypes; import org.session.libsession.utilities.NonTranslatableStringConstants; import org.thoughtcrime.securesms.ScreenLockActionBarActivity; import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.providers.BlobUtils; import org.session.libsession.utilities.ViewUtil; import java.io.IOException; @@ -112,7 +107,7 @@ protected Uri doInBackground(Void... params) { try { byte[] data = viewHolder.getData(forMms); - return BlobProvider.getInstance() + return BlobUtils.getInstance() .forData(data) .withMimeType(MediaTypes.IMAGE_GIF) .createForSingleSessionOnDisk(GiphyActivity.this, e -> Log.w(TAG, "Failed to write to disk.", e)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt index 138a61c60e..3e1fe9d78b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -39,12 +40,12 @@ abstract class BaseGroupMembersViewModel ( ) : ViewModel() { // Output: the source-of-truth group information. Other states are derived from this. protected val groupInfo: StateFlow>?> = - configFactory.configUpdateNotifications + (configFactory.configUpdateNotifications .filter { it is ConfigUpdateNotification.GroupConfigsUpdated && it.groupId == groupId || it is ConfigUpdateNotification.UserConfigsMerged - } - .onStart { emit(ConfigUpdateNotification.GroupConfigsUpdated(groupId)) } + } as Flow<*>) + .onStart { emit(Unit) } .map { _ -> withContext(Dispatchers.Default) { val currentUserId = AccountId(checkNotNull(storage.getUserPublicKey()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupLeavingWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupLeavingWorker.kt new file mode 100644 index 0000000000..468b3d5ea9 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupLeavingWorker.kt @@ -0,0 +1,143 @@ +package org.thoughtcrime.securesms.groups + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.Data +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CancellationException +import org.session.libsession.messaging.groups.GroupScope +import org.session.libsession.messaging.messages.control.GroupUpdated +import org.session.libsession.messaging.sending_receiving.MessageSender +import org.session.libsession.messaging.utilities.UpdateMessageData +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.getGroup +import org.session.libsession.utilities.waitUntilGroupConfigsPushed +import org.session.libsignal.exceptions.NonRetryableException +import org.session.libsignal.protos.SignalServiceProtos.DataMessage +import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage +import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.LokiAPIDatabase +import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.dependencies.ConfigFactory + +@HiltWorker +class GroupLeavingWorker @AssistedInject constructor( + @Assisted context: Context, + @Assisted params: WorkerParameters, + private val storage: Storage, + private val configFactory: ConfigFactory, + private val groupScope: GroupScope, + private val lokiAPIDatabase: LokiAPIDatabase, +) : CoroutineWorker(context, params) { + override suspend fun doWork(): Result { + val groupId = requireNotNull(inputData.getString(KEY_GROUP_ID)) { + "Group ID must be provided" + }.let(::AccountId) + + Log.d(TAG, "Group leaving work started for $groupId") + + return groupScope.launchAndWait(groupId, "GroupLeavingWorker") { + val group = configFactory.getGroup(groupId) + + // Make sure we only have one group leaving control message + storage.deleteGroupInfoMessages(groupId, UpdateMessageData.Kind.GroupLeaving::class.java) + storage.insertGroupInfoLeaving(groupId) + + try { + if (group?.destroyed != true) { + // Only send the left/left notification group message when we are not kicked and we are not the only admin (only admin has a special treatment) + val weAreTheOnlyAdmin = configFactory.withGroupConfigs(groupId) { config -> + val allMembers = config.groupMembers.all() + allMembers.count { it.admin } == 1 && + allMembers.first { it.admin } + .accountId() == storage.getUserPublicKey() + } + + if (group != null && !group.kicked && !weAreTheOnlyAdmin) { + val address = Address.fromSerialized(groupId.hexString) + + // Always send a "XXX left" message to the group if we can + MessageSender.send( + GroupUpdated( + GroupUpdateMessage.newBuilder() + .setMemberLeftNotificationMessage(DataMessage.GroupUpdateMemberLeftNotificationMessage.getDefaultInstance()) + .build() + ), + address + ) + + // If we are not the only admin, send a left message for other admin to handle the member removal + MessageSender.send( + GroupUpdated( + GroupUpdateMessage.newBuilder() + .setMemberLeftMessage(DataMessage.GroupUpdateMemberLeftMessage.getDefaultInstance()) + .build() + ), + address, + ) + } + + // If we are the only admin, leaving this group will destroy the group + if (weAreTheOnlyAdmin) { + configFactory.withMutableGroupConfigs(groupId) { configs -> + configs.groupInfo.destroyGroup() + } + + // Must wait until the config is pushed, otherwise if we go through the rest + // of the code it will destroy the conversation, destroying the necessary configs + // along the way, we won't be able to push the "destroyed" state anymore. + configFactory.waitUntilGroupConfigsPushed(groupId, timeoutMills = 0L) + } + } + + // Delete conversation and group configs + storage.getThreadId(Address.fromSerialized(groupId.hexString)) + ?.let(storage::deleteConversation) + configFactory.removeGroup(groupId) + lokiAPIDatabase.clearLastMessageHashes(groupId.hexString) + lokiAPIDatabase.clearReceivedMessageHashValues(groupId.hexString) + Log.d(TAG, "Group $groupId left successfully") + Result.success() + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + storage.insertGroupInfoErrorQuit(groupId) + Log.e(TAG, "Failed to leave group $groupId", e) + if (e is NonRetryableException) { + Result.failure() + } else { + Result.retry() + } + } finally { + storage.deleteGroupInfoMessages(groupId, UpdateMessageData.Kind.GroupLeaving::class.java) + } + } + } + + companion object { + private const val TAG = "GroupLeavingWorker" + + private const val KEY_GROUP_ID = "group_id" + + fun schedule(context: Context, groupId: AccountId) { + WorkManager.getInstance(context) + .enqueue( + OneTimeWorkRequestBuilder() + .addTag(KEY_GROUP_ID) + .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED)) + .setInputData( + Data.Builder().putString(KEY_GROUP_ID, groupId.hexString).build() + ) + .build() + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index bc9c29329b..5eb6fa1602 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -103,6 +103,11 @@ class GroupManagerV2Impl @Inject constructor( ) { "Only admin is allowed to invite members" } } + // Comparator to sort group members, ensuring a consistent order. + // This is more for the benefit of testing rather than correctness. + private val groupMemberComparator: GroupMemberComparator get() = + GroupMemberComparator(AccountId(requireNotNull(storage.getUserPublicKey()) { "User not logged in"})) + override suspend fun createGroup( groupName: String, groupDescription: String, @@ -345,7 +350,7 @@ class GroupManagerV2Impl @Inject constructor( GroupUpdateMessage.newBuilder() .setMemberChangeMessage( GroupUpdateMemberChangeMessage.newBuilder() - .addAllMemberSessionIds(newMembers.map { it.hexString }) + .addAllMemberSessionIds(newMembers.sortedWith(groupMemberComparator).map { it.hexString }) .setType(GroupUpdateMemberChangeMessage.Type.ADDED) .setAdminSignature(ByteString.copyFrom(signature)) ) @@ -384,7 +389,7 @@ class GroupManagerV2Impl @Inject constructor( val updateMessage = GroupUpdateMessage.newBuilder() .setMemberChangeMessage( GroupUpdateMemberChangeMessage.newBuilder() - .addAllMemberSessionIds(removedMembers.map { it.hexString }) + .addAllMemberSessionIds(removedMembers.sortedWith(groupMemberComparator).map { it.hexString }) .setType(GroupUpdateMemberChangeMessage.Type.REMOVED) .setAdminSignature(ByteString.copyFrom(signature)) ) @@ -459,73 +464,11 @@ class GroupManagerV2Impl @Inject constructor( } override suspend fun leaveGroup(groupId: AccountId) { - scope.launchAndWait(groupId, "Leave group") { - withContext(SupervisorJob()) { - val group = configFactory.getGroup(groupId) - - storage.insertGroupInfoLeaving(groupId) - - try { - if (group?.destroyed != true) { - // Only send the left/left notification group message when we are not kicked and we are not the only admin (only admin has a special treatment) - val weAreTheOnlyAdmin = configFactory.withGroupConfigs(groupId) { config -> - val allMembers = config.groupMembers.all() - allMembers.count { it.admin } == 1 && - allMembers.first { it.admin } - .accountId() == storage.getUserPublicKey() - } - - if (group != null && !group.kicked && !weAreTheOnlyAdmin) { - val address = Address.fromSerialized(groupId.hexString) - - // Always send a "XXX left" message to the group if we can - MessageSender.send( - GroupUpdated( - GroupUpdateMessage.newBuilder() - .setMemberLeftNotificationMessage(DataMessage.GroupUpdateMemberLeftNotificationMessage.getDefaultInstance()) - .build() - ), - address - ) + // Insert the control message immediately so we can see the leaving message + storage.insertGroupInfoLeaving(groupId) - // If we are not the only admin, send a left message for other admin to handle the member removal - MessageSender.send( - GroupUpdated( - GroupUpdateMessage.newBuilder() - .setMemberLeftMessage(DataMessage.GroupUpdateMemberLeftMessage.getDefaultInstance()) - .build() - ), - address, - ) - } - - // If we are the only admin, leaving this group will destroy the group - if (weAreTheOnlyAdmin) { - configFactory.withMutableGroupConfigs(groupId) { configs -> - configs.groupInfo.destroyGroup() - } - - // Must wait until the config is pushed, otherwise if we go through the rest - // of the code it will destroy the conversation, destroying the necessary configs - // along the way, we won't be able to push the "destroyed" state anymore. - configFactory.waitUntilGroupConfigsPushed(groupId) - } - } - - // Delete conversation and group configs - storage.getThreadId(Address.fromSerialized(groupId.hexString)) - ?.let(storage::deleteConversation) - configFactory.removeGroup(groupId) - lokiAPIDatabase.clearLastMessageHashes(groupId.hexString) - lokiAPIDatabase.clearReceivedMessageHashValues(groupId.hexString) - } catch (e: Exception) { - storage.insertGroupInfoErrorQuit(groupId) - throw e - } finally { - storage.deleteGroupInfoMessages(groupId, UpdateMessageData.Kind.GroupLeaving::class.java) - } - } - } + // The group leaving work could start or wait depend on the network condition + GroupLeavingWorker.schedule(context = application, groupId) } override suspend fun promoteMember( @@ -556,7 +499,7 @@ class GroupManagerV2Impl @Inject constructor( GroupUpdateMessage.newBuilder() .setMemberChangeMessage( GroupUpdateMemberChangeMessage.newBuilder() - .addAllMemberSessionIds(members.map { it.hexString }) + .addAllMemberSessionIds(members.sortedWith(groupMemberComparator).map { it.hexString }) .setType(GroupUpdateMemberChangeMessage.Type.PROMOTED) .setAdminSignature(ByteString.copyFrom(signature)) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMemberComparator.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMemberComparator.kt new file mode 100644 index 0000000000..93bc3958d9 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMemberComparator.kt @@ -0,0 +1,21 @@ +package org.thoughtcrime.securesms.groups + +import org.session.libsignal.utilities.AccountId + +/** + * A comparator for group members that ensures the current user appears first in the list, + * then sorts the rest of the members by their pub key. + */ +class GroupMemberComparator( + private val currentUserAccountId: AccountId +) : Comparator { + override fun compare(o1: AccountId, o2: AccountId): Int { + if (o1 == currentUserAccountId) { + return -1 // Current user should come first + } else if (o2 == currentUserAccountId) { + return 1 // Current user should come first + } else { + return o1.hexString.compareTo(o2.hexString) // Compare other members normally + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index e8000318c3..f46216678f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -11,6 +11,7 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller +import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPollerManager import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.StringSubstitutionConstants.COMMUNITY_NAME_KEY @@ -31,6 +32,7 @@ class OpenGroupManager @Inject constructor( private val threadDb: ThreadDatabase, private val configFactory: ConfigFactoryProtocol, private val groupMemberDatabase: GroupMemberDatabase, + private val pollerManager: OpenGroupPollerManager, ) { // flow holding information on write access for our current communities @@ -69,6 +71,10 @@ class OpenGroupManager @Inject constructor( pollInfo = info.toPollInfo(), createGroupIfMissingWithPublicKey = publicKey ) + + // If existing poller for the same server exist, we'll request a poll once now so new room + // can be polled immediately. + pollerManager.pollers.value[server]?.poller?.requestPollOnce() } fun delete(server: String, room: String, context: Context) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroupScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroupScreen.kt index 913fc58240..396d19d834 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroupScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroupScreen.kt @@ -2,17 +2,13 @@ package org.thoughtcrime.securesms.groups.compose import android.widget.Toast import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeContent -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold @@ -29,7 +25,6 @@ import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import network.loki.messenger.R import org.session.libsignal.utilities.AccountId @@ -40,7 +35,7 @@ import org.thoughtcrime.securesms.ui.BottomFadingEdgeBox import org.thoughtcrime.securesms.ui.LoadingArcOr import org.thoughtcrime.securesms.ui.SearchBar import org.thoughtcrime.securesms.ui.components.BackAppBar -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalColors @@ -186,7 +181,7 @@ fun CreateGroup( Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing)) - PrimaryOutlineButton( + AccentOutlineButton( onClick = onCreateClicked, modifier = Modifier .padding(horizontal = LocalDimensions.current.spacing) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupScreen.kt index 0f250fb342..f838a400c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupScreen.kt @@ -1,11 +1,8 @@ package org.thoughtcrime.securesms.groups.compose import android.widget.Toast -import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer @@ -17,7 +14,6 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.windowInsetsBottomHeight @@ -25,7 +21,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -44,7 +39,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import com.squareup.phrase.Phrase import network.loki.messenger.BuildConfig import network.loki.messenger.R @@ -55,14 +49,13 @@ import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.groups.EditGroupViewModel import org.thoughtcrime.securesms.groups.GroupMemberState import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.LoadingDialog import org.thoughtcrime.securesms.ui.components.ActionSheet import org.thoughtcrime.securesms.ui.components.ActionSheetItemData import org.thoughtcrime.securesms.ui.components.BackAppBar -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton -import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.components.annotatedStringResource import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalColors @@ -165,7 +158,7 @@ fun EditGroup( ) if (showAddMembers) { - PrimaryOutlineButton( + AccentOutlineButton( stringResource(R.string.membersInvite), onClick = onAddMemberClick, modifier = Modifier.qaTag(R.string.AccessibilityId_membersInvite) @@ -266,13 +259,13 @@ private fun ConfirmRemovingMemberDialog( ) { val context = LocalContext.current val buttons = buildList { - this += DialogButtonModel( + this += DialogButtonData( text = GetString(R.string.remove), color = LocalColors.current.danger, onClick = { onConfirmed(member.accountId, false) } ) - this += DialogButtonModel( + this += DialogButtonData( text = GetString(R.string.cancel), onClick = onDismissRequest, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/InviteContactsScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/InviteContactsScreen.kt index 9156d2d98b..f68cfbdf5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/InviteContactsScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/InviteContactsScreen.kt @@ -1,16 +1,12 @@ package org.thoughtcrime.securesms.groups.compose -import androidx.annotation.StringRes -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState @@ -30,7 +26,7 @@ import org.thoughtcrime.securesms.groups.SelectContactsViewModel import org.thoughtcrime.securesms.ui.BottomFadingEdgeBox import org.thoughtcrime.securesms.ui.SearchBar import org.thoughtcrime.securesms.ui.components.BackAppBar -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -131,7 +127,7 @@ fun InviteContacts( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxWidth() ) { - PrimaryOutlineButton( + AccentOutlineButton( onClick = onDoneClicked, enabled = contacts.any { it.selected }, modifier = Modifier diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/handler/DestroyedGroupSync.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/handler/DestroyedGroupSync.kt index f750d48ef2..ec16d3422f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/handler/DestroyedGroupSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/handler/DestroyedGroupSync.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.groups.handler import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.launch import org.session.libsession.database.StorageProtocol @@ -32,6 +33,7 @@ class DestroyedGroupSync @Inject constructor( job = GlobalScope.launch { configFactory.configUpdateNotifications .filterIsInstance() + .filter { it.fromMerge } .collect { update -> val isDestroyed = configFactory.withGroupConfigs(update.groupId) { it.groupInfo.isDestroyed() diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index e0776a6244..2af1728aa0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -93,7 +93,6 @@ class ConversationView : LinearLayout { val senderDisplayName = getTitle(thread.recipient) binding.conversationViewDisplayNameTextView.text = senderDisplayName binding.timestampTextView.text = thread.date.takeIf { it != 0L }?.let { dateUtils.getDisplayFormattedTimeSpanString( - Locale.getDefault(), it ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/EmptyView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/EmptyView.kt index 2ab5cad673..54a6743cce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/EmptyView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/EmptyView.kt @@ -56,7 +56,7 @@ internal fun EmptyView(newAccount: Boolean) { .format().toString() }, style = LocalType.current.base, - color = LocalColors.current.primary, + color = LocalColors.current.accent, textAlign = TextAlign.Center ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 214d81eec8..eb1122d02e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -11,6 +11,9 @@ import android.os.Bundle import android.view.ViewGroup.MarginLayoutParams import android.widget.Toast import androidx.activity.viewModels +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.os.bundleOf import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat @@ -243,6 +246,18 @@ class HomeActivity : ScreenLockActionBarActivity(), EmptyView(isNewAccount) } + // set the compose dialog content + binding.dialogs.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setThemedContent { + val dialogsState by homeViewModel.dialogsState.collectAsState() + HomeDialogs( + dialogsState = dialogsState, + sendCommand = homeViewModel::onCommand + ) + } + } + // Set up new conversation button binding.newConversationButton.setOnClickListener { showStartConversation() } // Observe blocked contacts changed events @@ -666,10 +681,7 @@ class HomeActivity : ScreenLockActionBarActivity(), } private fun setConversationPinned(threadId: Long, pinned: Boolean) { - lifecycleScope.launch(Dispatchers.Default) { - storage.setPinned(threadId, pinned) - homeViewModel.tryReload() - } + homeViewModel.setPinned(threadId, pinned) } private fun markAllAsRead(thread: ThreadRecord) { @@ -770,7 +782,7 @@ class HomeActivity : ScreenLockActionBarActivity(), } else { // If this is a 1-on-1 conversation title = getString(R.string.conversationsDelete) - message = Phrase.from(this, R.string.conversationsDeleteDescription) + message = Phrase.from(this, R.string.deleteConversationDescription) .put(NAME_KEY, recipient.name) .format() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt new file mode 100644 index 0000000000..e84902a3ab --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt @@ -0,0 +1,28 @@ +package org.thoughtcrime.securesms.home + +import androidx.compose.runtime.Composable +import org.thoughtcrime.securesms.home.HomeViewModel.Commands.* +import org.thoughtcrime.securesms.ui.PinProCTA +import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme + +@Composable +fun HomeDialogs( + dialogsState: HomeViewModel.DialogsState, + sendCommand: (HomeViewModel.Commands) -> Unit +) { + SessionMaterialTheme { + // pin CTA + if(dialogsState.pinCTA != null){ + PinProCTA( + overTheLimit = dialogsState.pinCTA.overTheLimit, + onUpgrade = { + sendCommand(GoToProUpgradeScreen) + }, + + onCancel = { + sendCommand(HidePinCTADialog) + } + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeLoader.kt deleted file mode 100644 index 6935fb24a1..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeLoader.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.thoughtcrime.securesms.home - -import android.content.Context -import android.database.Cursor -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.util.AbstractCursorLoader - -class HomeLoader(context: Context, val onNewCursor: (Cursor?) -> Unit) : AbstractCursorLoader(context) { - - override fun getCursor(): Cursor { - return DatabaseComponent.get(context).threadDatabase().approvedConversationList - } - - override fun deliverResult(newCursor: Cursor?) { - super.deliverResult(newCursor) - onNewCursor(newCursor) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index 3ce691e367..a4ca2c9b0d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -2,12 +2,10 @@ package org.thoughtcrime.securesms.home import android.content.ContentResolver import android.content.Context -import android.widget.Toast import androidx.annotation.AttrRes import androidx.lifecycle.ViewModel import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope -import com.squareup.phrase.Phrase import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -28,27 +26,27 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onErrorResume import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigUpdateNotification -import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Log import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsViewModel.PinProCTA import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory +import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository import org.thoughtcrime.securesms.util.observeChanges import org.thoughtcrime.securesms.webrtc.CallManager @@ -67,7 +65,8 @@ class HomeViewModel @Inject constructor( private val callManager: CallManager, private val usernameUtils: UsernameUtils, private val storage: StorageProtocol, - private val groupManager: GroupManagerV2 + private val groupManager: GroupManagerV2, + private val proStatusManager: ProStatusManager ) : ViewModel() { // SharedFlow that emits whenever the user asks us to reload the conversation private val manualReloadTrigger = MutableSharedFlow( @@ -87,6 +86,9 @@ class HomeViewModel @Inject constructor( } else null // null when the call isn't in progress / incoming }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = null) + private val _dialogsState = MutableStateFlow(DialogsState()) + val dialogsState: StateFlow = _dialogsState + /** * A [StateFlow] that emits the list of threads and the typing status of each thread. * @@ -239,6 +241,54 @@ class HomeViewModel @Inject constructor( } } + fun setPinned(threadId: Long, pinned: Boolean) { + // check the pin limit before continuing + val totalPins = storage.getTotalPinned() + val maxPins = proStatusManager.getPinnedConversationLimit() + if(pinned && totalPins >= maxPins){ + // the user has reached the pin limit, show the CTA + _dialogsState.update { + it.copy( + pinCTA = PinProCTA(overTheLimit = totalPins > maxPins) + ) + } + } else { + viewModelScope.launch(Dispatchers.Default) { + storage.setPinned(threadId, pinned) + } + } + } + + fun onCommand(command: Commands) { + when (command) { + is Commands.HidePinCTADialog -> { + _dialogsState.update { it.copy(pinCTA = null) } + } + + is Commands.GoToProUpgradeScreen -> { + // hide dialog + _dialogsState.update { it.copy(pinCTA = null) } + + // to go Pro upgrade screen + //todo PRO go to screen once it exists + } + + } + } + + data class DialogsState( + val pinCTA: PinProCTA? = null, + ) + + data class PinProCTA( + val overTheLimit: Boolean + ) + + sealed interface Commands { + data object HidePinCTADialog: Commands + data object GoToProUpgradeScreen: Commands + } + companion object { private const val CHANGE_NOTIFICATION_DEBOUNCE_MILLS = 100L } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/SeedReminder.kt b/app/src/main/java/org/thoughtcrime/securesms/home/SeedReminder.kt index 983e378261..5be7239cae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/SeedReminder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/SeedReminder.kt @@ -20,7 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import network.loki.messenger.R import org.thoughtcrime.securesms.ui.SessionShieldIcon -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -37,7 +37,7 @@ internal fun SeedReminder(startRecoveryPasswordActivity: () -> Unit) { Modifier .fillMaxWidth() .height(LocalDimensions.current.indicatorHeight) - .background(LocalColors.current.primary) + .background(LocalColors.current.accent) ) Row( Modifier @@ -64,7 +64,7 @@ internal fun SeedReminder(startRecoveryPasswordActivity: () -> Unit) { ) } Spacer(Modifier.width(LocalDimensions.current.smallSpacing)) - PrimaryOutlineButton( + AccentOutlineButton( text = stringResource(R.string.theContinue), modifier = Modifier .align(Alignment.CenterVertically) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index b03e255732..2f4d0fcd91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -124,7 +124,6 @@ fun ContentView.bindModel(query: String?, model: Message, dateUtils: DateUtils) searchResultTimestamp.isVisible = true searchResultTimestamp.text = dateUtils.getDisplayFormattedTimeSpanString( - Locale.getDefault(), model.messageResult.sentTimestampMs ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 84023436fb..76de9d7fb6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.net.CompositeRequestController; import org.thoughtcrime.securesms.net.ContentProxySafetyInterceptor; import org.thoughtcrime.securesms.net.RequestController; -import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.providers.BlobUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -178,7 +178,7 @@ private static Optional bitmapToAttachment(@Nullable Bitmap bitmap, bitmap.compress(format, 80, baos); byte[] bytes = baos.toByteArray(); - Uri uri = BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory(); + Uri uri = BlobUtils.getInstance().forData(bytes).createForSingleSessionInMemory(); return Optional.of(new UriAttachment(uri, uri, diff --git a/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java b/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java index 2072e783b2..6bfee03f16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java @@ -7,9 +7,11 @@ import org.session.libsession.utilities.Conversions; import org.session.libsession.utilities.Util; +import org.thoughtcrime.securesms.util.LimitedInputStream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.Closeable; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; @@ -47,14 +49,14 @@ public static class Writer { private final GrowingBuffer ciphertextBuffer = new GrowingBuffer(); private final byte[] secret; - private final File file; + final File file; private final Cipher cipher; private final BufferedOutputStream outputStream; - Writer(@NonNull byte[] secret, @NonNull File file) throws IOException { + Writer(@NonNull byte[] secret, @NonNull File file, boolean append) throws IOException { this.secret = secret; this.file = file; - this.outputStream = new BufferedOutputStream(new FileOutputStream(file, true)); + this.outputStream = new BufferedOutputStream(new FileOutputStream(file, append)); try { this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); @@ -63,7 +65,7 @@ public static class Writer { } } - void writeEntry(@NonNull String entry) throws IOException { + void writeEntry(@NonNull String entry, boolean flush) throws IOException { SECURE_RANDOM.nextBytes(ivBuffer); byte[] plaintext = entry.getBytes(); @@ -80,12 +82,18 @@ void writeEntry(@NonNull String entry) throws IOException { outputStream.write(ciphertext, 0, cipherLength); } - outputStream.flush(); + if (flush) { + outputStream.flush(); + } } catch (ShortBufferException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) { throw new AssertionError(e); } } + void flush() throws IOException { + outputStream.flush(); + } + long getLogSize() { return file.length(); } @@ -95,7 +103,7 @@ void close() { } } - static class Reader { + static class Reader implements Closeable { private final byte[] ivBuffer = new byte[16]; private final byte[] intBuffer = new byte[4]; @@ -107,7 +115,8 @@ static class Reader { Reader(@NonNull byte[] secret, @NonNull File file) throws IOException { this.secret = secret; - this.inputStream = new BufferedInputStream(new FileInputStream(file)); + // Limit the input stream to the file size to prevent endless reading in the case of a streaming file. + this.inputStream = new BufferedInputStream(new LimitedInputStream(new FileInputStream(file), file.length())); try { this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); @@ -116,18 +125,12 @@ static class Reader { } } - String readAll() throws IOException { - StringBuilder builder = new StringBuilder(); - - String entry; - while ((entry = readEntry()) != null) { - builder.append(entry).append('\n'); - } - - return builder.toString(); + @Override + public void close() throws IOException { + Util.close(inputStream); } - String readEntry() throws IOException { + byte[] readEntryBytes() throws IOException { try { // Read the IV and length Util.readFully(inputStream, ivBuffer); @@ -151,7 +154,7 @@ String readEntry() throws IOException { synchronized (CIPHER_LOCK) { cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer)); byte[] plaintext = cipher.doFinal(ciphertext, 0, length); - return new String(plaintext); + return plaintext; } } catch (BadPaddingException e) { // Bad padding likely indicates a corrupted or incomplete entry. diff --git a/app/src/main/java/org/thoughtcrime/securesms/logging/PersistentLogger.java b/app/src/main/java/org/thoughtcrime/securesms/logging/PersistentLogger.java deleted file mode 100644 index 9fd5968f6a..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/logging/PersistentLogger.java +++ /dev/null @@ -1,250 +0,0 @@ -package org.thoughtcrime.securesms.logging; - -import android.content.Context; - -import androidx.annotation.AnyThread; -import androidx.annotation.WorkerThread; - -import org.session.libsignal.utilities.ListenableFuture; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.NoExternalStorageException; -import org.session.libsignal.utilities.SettableFuture; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -public class PersistentLogger extends Log.Logger { - - private static final String TAG = PersistentLogger.class.getSimpleName(); - - private static final String LOG_V = "V"; - private static final String LOG_D = "D"; - private static final String LOG_I = "I"; - private static final String LOG_W = "W"; - private static final String LOG_E = "E"; - private static final String LOG_WTF = "A"; - - private static final String LOG_DIRECTORY = "log"; - private static final String FILENAME_PREFIX = "log-"; - private static final int MAX_LOG_FILES = 5; - private static final int MAX_LOG_SIZE = 300 * 1024; - private static final int MAX_LOG_EXPORT = 10_000; - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS zzz"); - - private final Context context; - private final Executor executor; - private final byte[] secret; - - private LogFile.Writer writer; - - public PersistentLogger(Context context) { - this.context = context.getApplicationContext(); - this.secret = LogSecretProvider.getOrCreateAttachmentSecret(context); - this.executor = Executors.newSingleThreadExecutor(r -> { - Thread thread = new Thread(r, "PersistentLogger"); - thread.setPriority(Thread.MIN_PRIORITY); - return thread; - }); - - executor.execute(this::initializeWriter); - } - - @Override - public void v(String tag, String message, Throwable t) { - write(LOG_V, tag, message, t); - } - - @Override - public void d(String tag, String message, Throwable t) { - write(LOG_D, tag, message, t); - } - - @Override - public void i(String tag, String message, Throwable t) { - write(LOG_I, tag, message, t); - } - - @Override - public void w(String tag, String message, Throwable t) { - write(LOG_W, tag, message, t); - } - - @Override - public void e(String tag, String message, Throwable t) { - write(LOG_E, tag, message, t); - } - - @Override - public void wtf(String tag, String message, Throwable t) { - write(LOG_WTF, tag, message, t); - } - - @Override - public void blockUntilAllWritesFinished() { - CountDownLatch latch = new CountDownLatch(1); - - executor.execute(latch::countDown); - - try { - latch.await(); - } catch (InterruptedException e) { - android.util.Log.w(TAG, "Failed to wait for all writes."); - } - } - - @WorkerThread - public ListenableFuture getLogs() { - final SettableFuture future = new SettableFuture<>(); - - executor.execute(() -> { - StringBuilder builder = new StringBuilder(); - long entriesWritten = 0; - - try { - File[] logs = getSortedLogFiles(); - for (int i = logs.length - 1; i >= 0 && entriesWritten <= MAX_LOG_EXPORT; i--) { - try { - LogFile.Reader reader = new LogFile.Reader(secret, logs[i]); - String entry; - while ((entry = reader.readEntry()) != null) { - entriesWritten++; - builder.append(entry).append('\n'); - } - } catch (IOException e) { - android.util.Log.w(TAG, "Failed to read log at index " + i + ". Removing reference."); - logs[i].delete(); - } - } - - future.set(builder.toString()); - } catch (NoExternalStorageException e) { - future.setException(e); - } - }); - - return future; - } - - @WorkerThread - private void initializeWriter() { - try { - writer = new LogFile.Writer(secret, getOrCreateActiveLogFile()); - } catch (NoExternalStorageException | IOException e) { - android.util.Log.e(TAG, "Failed to initialize writer.", e); - } - } - - @AnyThread - private void write(String level, String tag, String message, Throwable t) { - executor.execute(() -> { - try { - if (writer == null) { - return; - } - - if (writer.getLogSize() >= MAX_LOG_SIZE) { - writer.close(); - writer = new LogFile.Writer(secret, createNewLogFile()); - trimLogFilesOverMax(); - } - - for (String entry : buildLogEntries(level, tag, message, t)) { - writer.writeEntry(entry); - } - - } catch (NoExternalStorageException e) { - android.util.Log.w(TAG, "Cannot persist logs.", e); - } catch (IOException e) { - android.util.Log.w(TAG, "Failed to write line. Deleting all logs and starting over."); - deleteAllLogs(); - initializeWriter(); - } - }); - } - - private void trimLogFilesOverMax() throws NoExternalStorageException { - File[] logs = getSortedLogFiles(); - if (logs.length > MAX_LOG_FILES) { - for (int i = MAX_LOG_FILES; i < logs.length; i++) { - logs[i].delete(); - } - } - } - - private void deleteAllLogs() { - try { - File[] logs = getSortedLogFiles(); - for (File log : logs) { - log.delete(); - } - } catch (NoExternalStorageException e) { - android.util.Log.w(TAG, "Was unable to delete logs.", e); - } - } - - private File getOrCreateActiveLogFile() throws NoExternalStorageException { - File[] logs = getSortedLogFiles(); - if (logs.length > 0) { - return logs[0]; - } - - return createNewLogFile(); - } - - private File createNewLogFile() throws NoExternalStorageException { - return new File(getOrCreateLogDirectory(), FILENAME_PREFIX + System.currentTimeMillis()); - } - - private File[] getSortedLogFiles() throws NoExternalStorageException { - File[] logs = getOrCreateLogDirectory().listFiles(); - if (logs != null) { - Arrays.sort(logs, (o1, o2) -> o2.getName().compareTo(o1.getName())); - return logs; - } - return new File[0]; - } - - private File getOrCreateLogDirectory() throws NoExternalStorageException { - File logDir = new File(context.getCacheDir(), LOG_DIRECTORY); - if (!logDir.exists() && !logDir.mkdir()) { - throw new NoExternalStorageException("Unable to create log directory."); - } - - return logDir; - } - - private List buildLogEntries(String level, String tag, String message, Throwable t) { - List entries = new LinkedList<>(); - Date date = new Date(); - - entries.add(buildEntry(level, tag, message, date)); - - if (t != null) { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - t.printStackTrace(new PrintStream(outputStream)); - - String trace = new String(outputStream.toByteArray()); - String[] lines = trace.split("\\n"); - - for (String line : lines) { - entries.add(buildEntry(level, tag, line, date)); - } - } - - return entries; - } - - private String buildEntry(String level, String tag, String message, Date date) { - return DATE_FORMAT.format(date) + ' ' + level + ' ' + tag + ": " + message; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/logging/PersistentLogger.kt b/app/src/main/java/org/thoughtcrime/securesms/logging/PersistentLogger.kt new file mode 100644 index 0000000000..5fd299e36e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/logging/PersistentLogger.kt @@ -0,0 +1,337 @@ +package org.thoughtcrime.securesms.logging + +import android.content.Context +import android.net.Uri +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull +import org.session.libsignal.utilities.Log.Logger +import java.io.File +import java.io.FileOutputStream +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.regex.Pattern +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.time.Duration.Companion.milliseconds + +/** + * A [Logger] that writes logs to encrypted files in the app's cache directory. + */ +@Singleton +class PersistentLogger @Inject constructor( + @param:ApplicationContext private val context: Context +) : Logger() { + private val freeLogEntryPool = LogEntryPool() + private val logEntryChannel: SendChannel + private val logChannelIdleSignal = MutableSharedFlow() + + private val logDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS zzz", Locale.ENGLISH) + + private val secret by lazy { + LogSecretProvider.getOrCreateAttachmentSecret(context) + } + + private val logFolder by lazy { + File(context.cacheDir, "logs").apply { + mkdirs() + } + } + + init { + val channel = Channel(capacity = MAX_PENDING_LOG_ENTRIES) + logEntryChannel = channel + + GlobalScope.launch { + val bulk = ArrayList() + var logWriter: LogFile.Writer? = null + val entryBuilder = StringBuilder() + + try { + while (true) { + channel.receiveBulkLogs(bulk) + + if (bulk.isNotEmpty()) { + if (logWriter == null) { + val currentFile = File(logFolder, CURRENT_LOG_FILE_NAME) + + // If current file exist, we need to make sure we can decrypt it + // as this file can come from a previous session. + val append = if (currentFile.exists() && currentFile.length() > 0) { + LogFile.Reader(secret, currentFile).use { + it.readEntryBytes() != null + } + } else { + true + } + + logWriter = LogFile.Writer(secret, currentFile, append) + } + + bulkWrite(entryBuilder, logWriter, bulk) + + // Release entries back to the pool + freeLogEntryPool.release(bulk) + bulk.clear() + + // Rotate the log file if necessary + if (logWriter.logSize > MAX_SINGLE_LOG_FILE_SIZE) { + rotateAndTrimLogFiles(logWriter.file) + logWriter.close() + logWriter = null + } + } + + // Notify that the log channel is idle + logChannelIdleSignal.tryEmit(Unit) + } + } catch (e: Exception) { + logWriter?.close() + + android.util.Log.e( + TAG, + "Error while processing log entries: ${e.message}", + e + ) + } + } + } + + fun deleteAllLogs() { + logFolder.deleteRecursively() + } + + private fun rotateAndTrimLogFiles(currentFile: File) { + val permLogFile = File(logFolder, "${System.currentTimeMillis()}$PERM_LOG_FILE_SUFFIX") + if (currentFile.renameTo(permLogFile)) { + android.util.Log.d(TAG, "Rotated log file: $currentFile to $permLogFile") + currentFile.createNewFile() + } else { + android.util.Log.e(TAG, "Failed to rotate log file: $currentFile") + } + + val logFilesNewToOld = getLogFilesSorted(includeActiveLogFile = false) + + // Keep the last N log files, delete the rest + while (logFilesNewToOld.size > MAX_LOG_FILE_COUNT) { + val last = logFilesNewToOld.removeLastOrNull()!! + if (last.delete()) { + android.util.Log.d(TAG, "Deleted old log file: $last") + } else { + android.util.Log.e(TAG, "Failed to delete log file: $last") + } + } + } + + private fun bulkWrite(sb: StringBuilder, writer: LogFile.Writer, bulk: List) { + for (entry in bulk) { + sb.clear() + sb.append(logDateFormat.format(entry.timestampMills)) + .append(' ') + .append(entry.logLevel) + .append(' ') + .append(entry.tag.orEmpty()) + .append(": ") + .append(entry.message.orEmpty()) + .append('\n') + entry.err?.let { + sb.append('\n') + sb.append(it.stackTraceToString()) + } + writer.writeEntry(sb.toString(), false) + } + + writer.flush() + } + + private suspend fun ReceiveChannel.receiveBulkLogs(out: MutableList) { + out += receive() + + withTimeoutOrNull(500.milliseconds) { + repeat(15) { + out += receiveCatching().getOrNull() ?: return@repeat + } + } + } + + private fun sendLogEntry( + level: String, + tag: String?, + message: String?, + t: Throwable? = null + ) { + val entry = freeLogEntryPool.createLogEntry(level, tag, message, t) + if (logEntryChannel.trySend(entry).isFailure) { + android.util.Log.e(TAG, "Failed to send log entry, buffer is full") + } + } + + override fun v(tag: String?, message: String?, t: Throwable?) = + sendLogEntry(LOG_V, tag, message, t) + + override fun d(tag: String?, message: String?, t: Throwable?) = + sendLogEntry(LOG_D, tag, message, t) + + override fun i(tag: String?, message: String?, t: Throwable?) = + sendLogEntry(LOG_I, tag, message, t) + + override fun w(tag: String?, message: String?, t: Throwable?) = + sendLogEntry(LOG_W, tag, message, t) + + override fun e(tag: String?, message: String?, t: Throwable?) = + sendLogEntry(LOG_E, tag, message, t) + + override fun wtf(tag: String?, message: String?, t: Throwable?) = + sendLogEntry(LOG_WTF, tag, message, t) + + override fun blockUntilAllWritesFinished() { + runBlocking { + withTimeoutOrNull(1000) { + logChannelIdleSignal.first() + } + } + } + + private fun getLogFilesSorted(includeActiveLogFile: Boolean): MutableList { + val files = (logFolder.listFiles()?.asSequence() ?: emptySequence()) + .mapNotNull { + if (!it.isFile) return@mapNotNull null + PERM_LOG_FILE_PATTERN.matcher(it.name).takeIf { it.matches() } + ?.group(1) + ?.toLongOrNull() + ?.let { timestamp -> it to timestamp } + } + .sortedByDescending { (_, timestamp) -> timestamp } + .mapTo(arrayListOf()) { it.first } + + if (includeActiveLogFile) { + val currentLogFile = File(logFolder, CURRENT_LOG_FILE_NAME) + if (currentLogFile.exists()) { + files.add(0, currentLogFile) + } + } + + return files + } + + /** + * Reads all log entries from the log files and writes them as a ZIP file at the specified URI. + * + * This method will block until all log entries are read and written. + */ + fun readAllLogsCompressed(output: Uri) { + val logs = getLogFilesSorted(includeActiveLogFile = true).apply { reverse() } + + if (logs.isEmpty()) { + android.util.Log.w(TAG, "No log files found to read.") + return + } + + requireNotNull(context.contentResolver.openOutputStream(output, "w")?.buffered()) { + "Failed to open output stream for URI: $output" + }.use { outStream -> + ZipOutputStream(outStream).use { zipOut -> + zipOut.putNextEntry(ZipEntry("log.txt")) + + for (log in logs) { + LogFile.Reader(secret, log).use { reader -> + var count = 0 + generateSequence { reader.readEntryBytes() } + .forEach { entry -> + zipOut.write(entry) + + if (entry.isEmpty() || entry.last().toInt() != '\n'.code) { + zipOut.write('\n'.code) + } + + count++ + } + + android.util.Log.d(TAG, "Read $count entries from ${log.name}") + } + } + zipOut.closeEntry() + } + } + } + + private class LogEntry( + var logLevel: String, + var tag: String?, + var message: String?, + var err: Throwable?, + var timestampMills: Long, + ) + + /** + * A pool for reusing [LogEntry] objects to reduce memory allocations. + */ + private class LogEntryPool { + private val pool = ArrayList(MAX_LOG_ENTRIES_POOL_SIZE) + + fun createLogEntry(level: String, tag: String?, message: String?, t: Throwable?): LogEntry { + val fromPool = synchronized(pool) { pool.removeLastOrNull() } + + val now = System.currentTimeMillis() + + if (fromPool != null) { + fromPool.logLevel = level + fromPool.tag = tag + fromPool.message = message + fromPool.err = t + fromPool.timestampMills = now + return fromPool + } + + return LogEntry( + logLevel = level, + tag = tag, + message = message, + err = t, + timestampMills = now + ) + } + + fun release(entry: Iterable) { + val iterator = entry.iterator() + synchronized(pool) { + while (pool.size < MAX_LOG_ENTRIES_POOL_SIZE && iterator.hasNext()) { + pool.add(iterator.next()) + } + } + } + } + + companion object { + private const val TAG = "PersistentLoggingV2" + + private const val LOG_V: String = "V" + private const val LOG_D: String = "D" + private const val LOG_I: String = "I" + private const val LOG_W: String = "W" + private const val LOG_E: String = "E" + private const val LOG_WTF: String = "A" + + private const val PERM_LOG_FILE_SUFFIX = ".permlog" + private const val CURRENT_LOG_FILE_NAME = "current.log" + private val PERM_LOG_FILE_PATTERN by lazy { Pattern.compile("^(\\d+?)\\.permlog$") } + + // Maximum size of a single log file + private const val MAX_SINGLE_LOG_FILE_SIZE = 2 * 1024 * 1024 + + // Maximum number of log files to keep + private const val MAX_LOG_FILE_COUNT = 10 + + private const val MAX_LOG_ENTRIES_POOL_SIZE = 64 + private const val MAX_PENDING_LOG_ENTRIES = 65536 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewScreen.kt index 4bafc31272..38bf670080 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewScreen.kt @@ -43,7 +43,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import network.loki.messenger.R import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.components.CircularProgressIndicator import org.thoughtcrime.securesms.ui.components.SessionTabRow @@ -237,8 +237,8 @@ private fun SaveAttachmentWarningDialog( title = context.getString(R.string.warning), text = context.resources.getString(R.string.attachmentsWarning), buttons = listOf( - DialogButtonModel(GetString(R.string.save), color = LocalColors.current.danger, onClick = onAccepted), - DialogButtonModel(GetString(android.R.string.cancel), dismissOnClick = true) + DialogButtonData(GetString(R.string.save), color = LocalColors.current.danger, onClick = onAccepted), + DialogButtonData(GetString(android.R.string.cancel), dismissOnClick = true) ) ) } @@ -255,8 +255,8 @@ private fun DeleteConfirmationDialog( title = stringResource(R.string.delete), text = stringResource(R.string.deleteMessageDeviceOnly), buttons = listOf( - DialogButtonModel(GetString(R.string.delete), color = LocalColors.current.danger, onClick = onAccepted), - DialogButtonModel(GetString(android.R.string.cancel), dismissOnClick = true) + DialogButtonData(GetString(R.string.delete), color = LocalColors.current.danger, onClick = onAccepted), + DialogButtonData(GetString(android.R.string.cancel), dismissOnClick = true) ) ) } @@ -276,7 +276,7 @@ private fun ActionProgressDialog( horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing), verticalAlignment = Alignment.CenterVertically, ) { - CircularProgressIndicator(color = LocalColors.current.primary) + CircularProgressIndicator(color = LocalColors.current.accent) Text( text, style = LocalType.current.large, diff --git a/app/src/main/java/org/thoughtcrime/securesms/media/MediaPage.kt b/app/src/main/java/org/thoughtcrime/securesms/media/MediaPage.kt index 32558f6c12..ced52541a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/media/MediaPage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/media/MediaPage.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars @@ -30,7 +29,6 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -191,7 +189,7 @@ private fun ThumbnailRow( modifier = Modifier.padding(start = LocalDimensions.current.xxxsSpacing), painter = painterResource(R.drawable.triangle_right), contentDescription = null, - colorFilter = ColorFilter.tint(LocalColors.current.primary) + colorFilter = ColorFilter.tint(LocalColors.current.accent) ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java index 1f13efd396..152e44b313 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java @@ -11,8 +11,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel; import dagger.hilt.android.qualifiers.ApplicationContext; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; + import javax.inject.Inject; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; @@ -34,6 +37,9 @@ public class MediaPreviewViewModel extends ViewModel { private @Nullable Cursor cursor; + // map of playback position of the pager's items + private final Map playbackPositions = new HashMap<>(); + public void setCursor(@NonNull Context context, @Nullable Cursor cursor, boolean leftIsRecent) { boolean firstLoad = (this.cursor == null) && (cursor != null); if (this.cursor != null && !this.cursor.equals(cursor)) { @@ -47,6 +53,15 @@ public void setCursor(@NonNull Context context, @Nullable Cursor cursor, boolean } } + public void savePlaybackPosition(Uri videoUri, long position) { + playbackPositions.put(videoUri, position); + } + + public long getSavedPlaybackPosition(Uri videoUri) { + Long position = playbackPositions.get(videoUri); + return position != null ? position : 0L; + } + public void setActiveAlbumRailItem(@NonNull Context context, int activePosition) { if (cursor == null) { previewData.postValue(new PreviewData(Collections.emptyList(), null, 0)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Controller.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Controller.java deleted file mode 100644 index 2c14ed8d5c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Controller.java +++ /dev/null @@ -1,259 +0,0 @@ -package org.thoughtcrime.securesms.mediasend; - -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import androidx.annotation.NonNull; -import android.view.Surface; - -import org.session.libsignal.utilities.Log; - -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -class Camera1Controller { - - private static final String TAG = Camera1Controller.class.getSimpleName(); - - private final int screenWidth; - private final int screenHeight; - private final OrderEnforcer enforcer; - private final EventListener eventListener; - - private Camera camera; - private int cameraId; - private SurfaceTexture previewSurface; - private int screenRotation; - - Camera1Controller(int preferredDirection, int screenWidth, int screenHeight, @NonNull EventListener eventListener) { - this.eventListener = eventListener; - this.enforcer = new OrderEnforcer<>(Stage.INITIALIZED, Stage.PREVIEW_STARTED); - this.cameraId = Camera.getNumberOfCameras() > 1 ? preferredDirection : Camera.CameraInfo.CAMERA_FACING_BACK; - this.screenWidth = screenWidth; - this.screenHeight = screenHeight; - } - - void initialize() { - Log.d(TAG, "initialize()"); - - if (Camera.getNumberOfCameras() <= 0) { - Log.w(TAG, "Device doesn't have any cameras."); - onCameraUnavailable(); - return; - } - - try { - camera = Camera.open(cameraId); - } catch (Exception e) { - Log.w(TAG, "Failed to open camera.", e); - onCameraUnavailable(); - return; - } - - if (camera == null) { - Log.w(TAG, "Null camera instance."); - onCameraUnavailable(); - return; - } - - Camera.Parameters params = camera.getParameters(); - Camera.Size previewSize = getClosestSize(camera.getParameters().getSupportedPreviewSizes(), screenWidth, screenHeight); - Camera.Size pictureSize = getClosestSize(camera.getParameters().getSupportedPictureSizes(), screenWidth, screenHeight); - final List focusModes = params.getSupportedFocusModes(); - - Log.d(TAG, "Preview size: " + previewSize.width + "x" + previewSize.height + " Picture size: " + pictureSize.width + "x" + pictureSize.height); - - params.setPreviewSize(previewSize.width, previewSize.height); - params.setPictureSize(pictureSize.width, pictureSize.height); - params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); - params.setColorEffect(Camera.Parameters.EFFECT_NONE); - params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO); - - if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { - params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); - } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { - params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); - } - - - camera.setParameters(params); - - enforcer.markCompleted(Stage.INITIALIZED); - - eventListener.onPropertiesAvailable(getProperties()); - } - - void release() { - Log.d(TAG, "release() called"); - enforcer.run(Stage.INITIALIZED, () -> { - Log.d(TAG, "release() executing"); - previewSurface = null; - camera.stopPreview(); - camera.release(); - enforcer.reset(); - }); - } - - void linkSurface(@NonNull SurfaceTexture surfaceTexture) { - Log.d(TAG, "linkSurface() called"); - enforcer.run(Stage.INITIALIZED, () -> { - try { - Log.d(TAG, "linkSurface() executing"); - previewSurface = surfaceTexture; - - camera.setPreviewTexture(surfaceTexture); - camera.startPreview(); - enforcer.markCompleted(Stage.PREVIEW_STARTED); - } catch (Exception e) { - Log.w(TAG, "Failed to start preview.", e); - eventListener.onCameraUnavailable(); - } - }); - } - - void capture(@NonNull CaptureCallback callback) { - enforcer.run(Stage.PREVIEW_STARTED, () -> { - camera.takePicture(null, null, null, (data, camera) -> { - callback.onCaptureAvailable(data, cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT); - }); - }); - } - - int flip() { - Log.d(TAG, "flip()"); - SurfaceTexture surfaceTexture = previewSurface; - cameraId = (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK; - - release(); - initialize(); - linkSurface(surfaceTexture); - setScreenRotation(screenRotation); - - return cameraId; - } - - void setScreenRotation(int screenRotation) { - Log.d(TAG, "setScreenRotation(" + screenRotation + ") called"); - enforcer.run(Stage.PREVIEW_STARTED, () -> { - Log.d(TAG, "setScreenRotation(" + screenRotation + ") executing"); - this.screenRotation = screenRotation; - - int previewRotation = getPreviewRotation(screenRotation); - int outputRotation = getOutputRotation(screenRotation); - - Log.d(TAG, "Preview rotation: " + previewRotation + " Output rotation: " + outputRotation); - - camera.setDisplayOrientation(previewRotation); - - Camera.Parameters params = camera.getParameters(); - params.setRotation(outputRotation); - camera.setParameters(params); - }); - } - - private void onCameraUnavailable() { - enforcer.reset(); - eventListener.onCameraUnavailable(); - } - - private Properties getProperties() { - Camera.Size previewSize = camera.getParameters().getPreviewSize(); - return new Properties(Camera.getNumberOfCameras(), previewSize.width, previewSize.height); - } - - private Camera.Size getClosestSize(List sizes, int width, int height) { - Collections.sort(sizes, ASC_SIZE_COMPARATOR); - - int i = 0; - while (i < sizes.size() && (sizes.get(i).width * sizes.get(i).height) < (width * height)) { - i++; - } - - return sizes.get(Math.min(i, sizes.size() - 1)); - } - - private int getOutputRotation(int displayRotationCode) { - int degrees = convertRotationToDegrees(displayRotationCode); - - Camera.CameraInfo info = new Camera.CameraInfo(); - Camera.getCameraInfo(cameraId, info); - - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - return (info.orientation + degrees) % 360; - } else { - return (info.orientation - degrees + 360) % 360; - } - } - - private int getPreviewRotation(int displayRotationCode) { - int degrees = convertRotationToDegrees(displayRotationCode); - - Camera.CameraInfo info = new Camera.CameraInfo(); - Camera.getCameraInfo(cameraId, info); - - int result; - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - result = (info.orientation + degrees) % 360; - result = (360 - result) % 360; - } else { - result = (info.orientation - degrees + 360) % 360; - } - - return result; - } - - private int convertRotationToDegrees(int screenRotation) { - switch (screenRotation) { - case Surface.ROTATION_0: return 0; - case Surface.ROTATION_90: return 90; - case Surface.ROTATION_180: return 180; - case Surface.ROTATION_270: return 270; - } - return 0; - } - - private final Comparator ASC_SIZE_COMPARATOR = (o1, o2) -> Integer.compare(o1.width * o1.height, o2.width * o2.height); - - private enum Stage { - INITIALIZED, PREVIEW_STARTED - } - - class Properties { - - private final int cameraCount; - private final int previewWidth; - private final int previewHeight; - - Properties(int cameraCount, int previewWidth, int previewHeight) { - this.cameraCount = cameraCount; - this.previewWidth = previewWidth; - this.previewHeight = previewHeight; - } - - int getCameraCount() { - return cameraCount; - } - - int getPreviewWidth() { - return previewWidth; - } - - int getPreviewHeight() { - return previewHeight; - } - - @Override - public @NonNull String toString() { - return "cameraCount: " + cameraCount + " previewWidth: " + previewWidth + " previewHeight: " + previewHeight; - } - } - - interface EventListener { - void onPropertiesAvailable(@NonNull Properties properties); - void onCameraUnavailable(); - } - - interface CaptureCallback { - void onCaptureAvailable(@NonNull byte[] jpegData, boolean frontFacing); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java deleted file mode 100644 index 8005794653..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java +++ /dev/null @@ -1,346 +0,0 @@ -package org.thoughtcrime.securesms.mediasend; - -import android.annotation.SuppressLint; - -import androidx.core.view.WindowCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.lifecycle.ViewModelProvider; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.SurfaceTexture; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import android.view.Display; -import android.view.GestureDetector; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.TextureView; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.RotateAnimation; -import android.widget.Button; -import android.widget.ImageButton; - -import com.bumptech.glide.load.MultiTransformation; -import com.bumptech.glide.load.Transformation; -import com.bumptech.glide.load.resource.bitmap.CenterCrop; -import com.bumptech.glide.request.target.SimpleTarget; -import com.bumptech.glide.request.transition.Transition; - -import dagger.hilt.android.AndroidEntryPoint; -import network.loki.messenger.R; -import org.session.libsignal.utilities.Log; -import com.bumptech.glide.Glide; -import org.session.libsession.utilities.ServiceUtil; -import org.thoughtcrime.securesms.util.Stopwatch; -import org.session.libsession.utilities.TextSecurePreferences; -import org.thoughtcrime.securesms.util.ViewUtilitiesKt; - -import java.io.ByteArrayOutputStream; - -@AndroidEntryPoint -public class Camera1Fragment extends Fragment implements TextureView.SurfaceTextureListener, - Camera1Controller.EventListener -{ - - private static final String TAG = Camera1Fragment.class.getSimpleName(); - - private TextureView cameraPreview; - private ViewGroup controlsContainer; - private View cameraCloseButton; - private ImageButton flipButton; - private Button captureButton; - private Camera1Controller camera; - private Controller controller; - private OrderEnforcer orderEnforcer; - private Camera1Controller.Properties properties; - private MediaSendViewModel viewModel; - - public static Camera1Fragment newInstance() { - return new Camera1Fragment(); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (!(getActivity() instanceof Controller)) { - throw new IllegalStateException("Parent activity must implement the Controller interface."); - } - - WindowManager windowManager = ServiceUtil.getWindowManager(getActivity()); - Display display = windowManager.getDefaultDisplay(); - Point displaySize = new Point(); - - display.getSize(displaySize); - - controller = (Controller) getActivity(); - camera = new Camera1Controller(TextSecurePreferences.getDirectCaptureCameraId(getContext()), displaySize.x, displaySize.y, this); - orderEnforcer = new OrderEnforcer<>(Stage.SURFACE_AVAILABLE, Stage.CAMERA_PROPERTIES_AVAILABLE); - viewModel = new ViewModelProvider(requireActivity()).get(MediaSendViewModel.class); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.camera_fragment, container, false); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - cameraPreview = view.findViewById(R.id.camera_preview); - controlsContainer = view.findViewById(R.id.camera_controls_container); - cameraCloseButton = view.findViewById(R.id.camera_close_button); - - ViewUtilitiesKt.applySafeInsetsPaddings(view.findViewById(R.id.camera_controls_safe_area)); - - onOrientationChanged(getResources().getConfiguration().orientation); - - cameraPreview.setSurfaceTextureListener(this); - - GestureDetector gestureDetector = new GestureDetector(flipGestureListener); - cameraPreview.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event)); - - cameraCloseButton.setOnClickListener(v -> requireActivity().onBackPressed()); - } - - @Override - public void onResume() { - super.onResume(); - viewModel.onCameraStarted(); - camera.initialize(); - - if (cameraPreview.isAvailable()) { - orderEnforcer.markCompleted(Stage.SURFACE_AVAILABLE); - } - - if (properties != null) { - orderEnforcer.markCompleted(Stage.CAMERA_PROPERTIES_AVAILABLE); - } - - orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> { - camera.linkSurface(cameraPreview.getSurfaceTexture()); - camera.setScreenRotation(controller.getDisplayRotation()); - }); - - orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); - - // Enter fullscreen mode - WindowCompat.getInsetsController(requireActivity().getWindow(), requireActivity().getWindow().getDecorView()) - .hide(WindowInsetsCompat.Type.systemBars()); - } - - @Override - public void onPause() { - super.onPause(); - camera.release(); - orderEnforcer.reset(); - - // Exit fullscreen mode - WindowCompat.getInsetsController(requireActivity().getWindow(), requireActivity().getWindow().getDecorView()) - .show(WindowInsetsCompat.Type.systemBars()); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - onOrientationChanged(newConfig.orientation); - } - - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - Log.d(TAG, "onSurfaceTextureAvailable"); - orderEnforcer.markCompleted(Stage.SURFACE_AVAILABLE); - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> camera.setScreenRotation(controller.getDisplayRotation())); - orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - return false; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - } - - @Override - public void onPropertiesAvailable(@NonNull Camera1Controller.Properties properties) { - Log.d(TAG, "Got camera properties: " + properties); - this.properties = properties; - orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); - orderEnforcer.markCompleted(Stage.CAMERA_PROPERTIES_AVAILABLE); - } - - @Override - public void onCameraUnavailable() { - controller.onCameraError(); - } - - @SuppressLint("ClickableViewAccessibility") - private void initControls() { - flipButton = getView().findViewById(R.id.camera_flip_button); - captureButton = getView().findViewById(R.id.camera_capture_button); - - captureButton.setOnTouchListener((v, event) -> { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - Animation shrinkAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.camera_capture_button_shrink); - shrinkAnimation.setFillAfter(true); - shrinkAnimation.setFillEnabled(true); - captureButton.startAnimation(shrinkAnimation); - onCaptureClicked(); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_OUTSIDE: - Animation growAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.camera_capture_button_grow); - growAnimation.setFillAfter(true); - growAnimation.setFillEnabled(true); - captureButton.startAnimation(growAnimation); - captureButton.setEnabled(false); - break; - } - return true; - }); - - orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, () -> { - if (properties.getCameraCount() > 1) { - flipButton.setVisibility(properties.getCameraCount() > 1 ? View.VISIBLE : View.GONE); - flipButton.setOnClickListener(v -> { - int newCameraId = camera.flip(); - TextSecurePreferences.setDirectCaptureCameraId(getContext(), newCameraId); - - Animation animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); - animation.setDuration(200); - animation.setInterpolator(new DecelerateInterpolator()); - flipButton.startAnimation(animation); - }); - } else { - flipButton.setVisibility(View.GONE); - } - }); - } - - private void onCaptureClicked() { - orderEnforcer.reset(); - - Stopwatch fastCaptureTimer = new Stopwatch("Capture"); - - camera.capture((jpegData, frontFacing) -> { - fastCaptureTimer.split("captured"); - - Transformation transformation = frontFacing ? new MultiTransformation<>(new CenterCrop(), new FlipTransformation()) - : new CenterCrop(); - - Glide.with(this) - .asBitmap() - .load(jpegData) - .transform(transformation) - .override(cameraPreview.getWidth(), cameraPreview.getHeight()) - .into(new SimpleTarget() { - @Override - public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition transition) { - fastCaptureTimer.split("transform"); - - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - resource.compress(Bitmap.CompressFormat.JPEG, 80, stream); - fastCaptureTimer.split("compressed"); - - byte[] data = stream.toByteArray(); - fastCaptureTimer.split("bytes"); - fastCaptureTimer.stop(TAG); - - controller.onImageCaptured(data, resource.getWidth(), resource.getHeight()); - } - - @Override - public void onLoadFailed(@Nullable Drawable errorDrawable) { - controller.onCameraError(); - } - }); - }); - } - - private PointF getScaleTransform(float viewWidth, float viewHeight, int cameraWidth, int cameraHeight) { - float camWidth = isPortrait() ? Math.min(cameraWidth, cameraHeight) : Math.max(cameraWidth, cameraHeight); - float camHeight = isPortrait() ? Math.max(cameraWidth, cameraHeight) : Math.min(cameraWidth, cameraHeight); - - float scaleX = 1; - float scaleY = 1; - - if ((camWidth / viewWidth) > (camHeight / viewHeight)) { - float targetWidth = viewHeight * (camWidth / camHeight); - scaleX = targetWidth / viewWidth; - } else { - float targetHeight = viewWidth * (camHeight / camWidth); - scaleY = targetHeight / viewHeight; - } - - return new PointF(scaleX, scaleY); - } - - private void onOrientationChanged(int orientation) { - int layout = orientation == Configuration.ORIENTATION_PORTRAIT ? R.layout.camera_controls_portrait - : R.layout.camera_controls_landscape; - - controlsContainer.removeAllViews(); - controlsContainer.addView(LayoutInflater.from(getContext()).inflate(layout, controlsContainer, false)); - initControls(); - } - - private void updatePreviewScale() { - PointF scale = getScaleTransform(cameraPreview.getWidth(), cameraPreview.getHeight(), properties.getPreviewWidth(), properties.getPreviewHeight()); - Matrix matrix = new Matrix(); - - float camWidth = isPortrait() ? Math.min(cameraPreview.getWidth(), cameraPreview.getHeight()) : Math.max(cameraPreview.getWidth(), cameraPreview.getHeight()); - float camHeight = isPortrait() ? Math.max(cameraPreview.getWidth(), cameraPreview.getHeight()) : Math.min(cameraPreview.getWidth(), cameraPreview.getHeight()); - - matrix.setScale(scale.x, scale.y); - matrix.postTranslate((camWidth - (camWidth * scale.x)) / 2, (camHeight - (camHeight * scale.y)) / 2); - cameraPreview.setTransform(matrix); - } - - private boolean isPortrait() { - return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - } - - private final GestureDetector.OnGestureListener flipGestureListener = new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onDown(MotionEvent e) { - return true; - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - flipButton.performClick(); - return true; - } - }; - - public interface Controller { - void onCameraError(); - void onImageCaptured(@NonNull byte[] data, int width, int height); - int getDisplayRotation(); - } - - private enum Stage { - SURFACE_AVAILABLE, CAMERA_PROPERTIES_AVAILABLE - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt new file mode 100644 index 0000000000..6faff31f88 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt @@ -0,0 +1,278 @@ +package org.thoughtcrime.securesms.mediasend + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.util.Size +import android.view.LayoutInflater +import android.view.OrientationEventListener +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageCaptureException +import androidx.camera.core.ImageProxy +import androidx.camera.core.resolutionselector.ResolutionSelector +import androidx.camera.core.resolutionselector.ResolutionStrategy +import androidx.camera.view.LifecycleCameraController +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import dagger.hilt.android.AndroidEntryPoint +import network.loki.messenger.databinding.CameraxFragmentBinding +import org.session.libsession.utilities.MediaTypes +import org.session.libsession.utilities.TextSecurePreferences +import org.thoughtcrime.securesms.providers.BlobUtils +import org.thoughtcrime.securesms.util.applySafeInsetsMargins +import org.thoughtcrime.securesms.util.setSafeOnClickListener +import java.io.ByteArrayOutputStream +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import javax.inject.Inject + +@AndroidEntryPoint +class CameraXFragment : Fragment() { + + interface Controller { + fun onImageCaptured(imageUri: Uri, size: Long, width: Int, height: Int) + fun onCameraError() + } + + private lateinit var binding: CameraxFragmentBinding + + private var callbacks: Controller? = null + + private lateinit var cameraController: LifecycleCameraController + private lateinit var cameraExecutor: ExecutorService + + + private lateinit var orientationListener: OrientationEventListener + private var lastRotation: Int = Surface.ROTATION_0 + + @Inject + lateinit var prefs: TextSecurePreferences + + companion object { + private const val TAG = "CameraXFragment" + private const val REQUEST_CODE_PERMISSIONS = 10 + private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + binding = CameraxFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + cameraExecutor = Executors.newSingleThreadExecutor() + + // permissions should be handled prior to landing in this fragment + // but this is added for safety + if (allPermissionsGranted()) { + startCamera() + } else { + ActivityCompat.requestPermissions( + requireActivity(), REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS + ) + } + + binding.cameraControlsSafeArea.applySafeInsetsMargins() + + binding.cameraCaptureButton.setSafeOnClickListener { takePhoto() } + binding.cameraFlipButton.setSafeOnClickListener { flipCamera() } + binding.cameraCloseButton.setSafeOnClickListener { + requireActivity().onBackPressedDispatcher.onBackPressed() + } + + // keep track of orientation changes + orientationListener = object : OrientationEventListener(requireContext()) { + override fun onOrientationChanged(degrees: Int) { + if (degrees == ORIENTATION_UNKNOWN) return + + val newRotation = when { + degrees in 45..134 -> Surface.ROTATION_270 + degrees in 135..224 -> Surface.ROTATION_180 + degrees in 225..314 -> Surface.ROTATION_90 + else -> Surface.ROTATION_0 + } + + if (newRotation != lastRotation) { + lastRotation = newRotation + updateUiForRotation(newRotation) + } + } + } + } + + override fun onResume() { + super.onResume() + orientationListener.enable() + } + + override fun onPause() { + orientationListener.disable() + super.onPause() + } + + override fun onAttach(context: Context) { + super.onAttach(context) + if (context is Controller) { + callbacks = context + } else { + throw RuntimeException("$context must implement CameraXFragment.Controller") + } + } + + private fun updateUiForRotation(rotation: Int = lastRotation) { + val angle = when (rotation) { + Surface.ROTATION_0 -> 0f + Surface.ROTATION_90 -> 90f + Surface.ROTATION_180 -> 180f + else -> 270f + } + + binding.cameraFlipButton.animate() + .rotation(angle) + .setDuration(150) + .start() + } + + private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission( + requireContext(), it + ) == PackageManager.PERMISSION_GRANTED + } + + private fun startCamera() { + // work out a resolution based on available memory + val activityManager = requireContext().getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager + val memoryClassMb = activityManager.memoryClass // e.g. 128, 256, etc. + val preferredResolution: Size = when { + memoryClassMb >= 256 -> Size(1920, 1440) + memoryClassMb >= 128 -> Size(1280, 960) + else -> Size(640, 480) + } + Log.d(TAG, "Selected resolution: $preferredResolution based on memory class: $memoryClassMb MB") + + val resolutionSelector = ResolutionSelector.Builder() + .setResolutionStrategy( + ResolutionStrategy( + preferredResolution, + ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER + ) + ) + .build() + + // set up camera + cameraController = LifecycleCameraController(requireContext()).apply { + cameraSelector = prefs.getPreferredCameraDirection() + setImageCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) + setTapToFocusEnabled(true) + setPinchToZoomEnabled(true) + + // Configure image capture resolution + setImageCaptureResolutionSelector(resolutionSelector) + } + + // attach it to the view + binding.previewView.controller = cameraController + cameraController.bindToLifecycle(viewLifecycleOwner) + + // wait for initialisation to complete + cameraController.initializationFuture.addListener({ + val hasFront = cameraController.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) + val hasBack = cameraController.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) + + binding.cameraFlipButton.visibility = + if (hasFront && hasBack) View.VISIBLE else View.GONE + }, ContextCompat.getMainExecutor(requireContext())) + } + + private fun takePhoto() { + val isFrontCamera = cameraController.cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA + cameraController.takePicture( + cameraExecutor, + object : ImageCapture.OnImageCapturedCallback() { + override fun onCaptureSuccess(img: ImageProxy) { + try { + val buffer = img.planes[0].buffer + val originalBytes = ByteArray(buffer.remaining()).also { buffer.get(it) } + val rotationDegrees = img.imageInfo.rotationDegrees + img.close() + + // Decode, rotate, mirror if needed + val bitmap = BitmapFactory.decodeByteArray(originalBytes, 0, originalBytes.size) + var correctedBitmap = rotateBitmap(bitmap, rotationDegrees.toFloat()) + if (isFrontCamera) { + correctedBitmap = mirrorBitmap(correctedBitmap) + } + + val outputStream = ByteArrayOutputStream() + correctedBitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream) + val compressedBytes = outputStream.toByteArray() + + // Recycle bitmaps + bitmap.recycle() + if (correctedBitmap !== bitmap) correctedBitmap.recycle() + + val uri = BlobUtils.getInstance() + .forData(compressedBytes) + .withMimeType(MediaTypes.IMAGE_JPEG) + .createForSingleSessionInMemory() + + callbacks?.onImageCaptured(uri, compressedBytes.size.toLong(), correctedBitmap.width, correctedBitmap.height) + } catch (t: Throwable) { + Log.e(TAG, "capture failed", t) + callbacks?.onCameraError() + } + } + override fun onError(e: ImageCaptureException) { + Log.e(TAG, "takePicture error", e) + callbacks?.onCameraError() + } + } + ) + } + + private fun mirrorBitmap(src: Bitmap): Bitmap { + val matrix = android.graphics.Matrix().apply { preScale(-1f, 1f) } + return Bitmap.createBitmap(src, 0, 0, src.width, src.height, matrix, true) + } + + private fun rotateBitmap(src: Bitmap, degrees: Float): Bitmap { + if (degrees == 0f) return src + val matrix = android.graphics.Matrix().apply { postRotate(degrees) } + return Bitmap.createBitmap(src, 0, 0, src.width, src.height, matrix, true) + } + + private fun flipCamera() { + val newSelector = + if (cameraController.cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) + CameraSelector.DEFAULT_FRONT_CAMERA + else + CameraSelector.DEFAULT_BACK_CAMERA + + cameraController.cameraSelector = newSelector + prefs.setPreferredCameraDirection(newSelector) + + // animate icon + binding.cameraFlipButton.animate() + .rotationBy(-180f) + .setDuration(200) + .start() + } + + override fun onDestroyView() { + cameraExecutor.shutdown() + super.onDestroyView() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt index 3c6be77dab..7a2b925bdf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.mediasend import android.Manifest import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.View import android.view.animation.AccelerateDecelerateInterpolator @@ -16,8 +17,10 @@ import androidx.activity.viewModels import androidx.core.view.ViewGroupCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import androidx.lifecycle.lifecycleScope import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.MediasendActivityBinding import org.session.libsession.utilities.Address.Companion.fromSerialized @@ -30,11 +33,9 @@ import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.mediasend.MediaSendViewModel.CountButtonState import org.thoughtcrime.securesms.permissions.Permissions -import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.scribbles.ImageEditorFragment import org.thoughtcrime.securesms.util.FilenameUtils.constructPhotoFilename import org.thoughtcrime.securesms.util.applySafeInsetsPaddings -import java.io.IOException /** * Encompasses the entire flow of sending media, starting from the selection process to the actual @@ -46,17 +47,22 @@ import java.io.IOException @AndroidEntryPoint class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragment.Controller, MediaPickerItemFragment.Controller, MediaSendFragment.Controller, - ImageEditorFragment.Controller, - Camera1Fragment.Controller { + ImageEditorFragment.Controller, CameraXFragment.Controller{ private var recipient: Recipient? = null private val viewModel: MediaSendViewModel by viewModels() private lateinit var binding: MediasendActivityBinding + private val threadId: Long by lazy { + intent.getLongExtra(KEY_THREADID, 0L) + } override val applyDefaultWindowInsets: Boolean get() = false // we want to handle window insets manually here for fullscreen fragments like the camera screen + override val applyAutoScrimForNavigationBar: Boolean + get() = false + override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) @@ -86,14 +92,15 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme val isCamera = intent.getBooleanExtra(KEY_IS_CAMERA, false) if (isCamera) { - val fragment: Fragment = Camera1Fragment.newInstance() + val fragment: Fragment = CameraXFragment() supportFragmentManager.beginTransaction() .replace(R.id.mediasend_fragment_container, fragment, TAG_CAMERA) .commit() } else if (!isEmpty(media)) { viewModel.onSelectedMediaChanged(this, media!!) - val fragment: Fragment = MediaSendFragment.newInstance(recipient!!) + val fragment: Fragment = MediaSendFragment.newInstance(recipient!!, threadId) + supportFragmentManager.beginTransaction() .replace(R.id.mediasend_fragment_container, fragment, TAG_SEND) .commit() @@ -230,36 +237,25 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme } override fun onCameraError() { - Toast.makeText(this, R.string.cameraErrorUnavailable, Toast.LENGTH_SHORT).show() - setResult(RESULT_CANCELED, Intent()) - finish() + lifecycleScope.launch { + Toast.makeText(applicationContext, R.string.cameraErrorUnavailable, Toast.LENGTH_SHORT).show() + setResult(RESULT_CANCELED, Intent()) + finish() + } } - override fun onImageCaptured(data: ByteArray, width: Int, height: Int) { + override fun onImageCaptured(imageUri: Uri, size: Long, width: Int, height: Int) { Log.i(TAG, "Camera image captured.") SimpleTask.run(lifecycle, { try { - val uri = BlobProvider.getInstance() - .forData(data) - .withMimeType(MediaTypes.IMAGE_JPEG) - .createForSingleSessionOnDisk( - this - ) { e: IOException? -> - Log.w( - TAG, - "Failed to write to disk.", - e - ) - }.get() - return@run Media( - uri, + imageUri, constructPhotoFilename(this), MediaTypes.IMAGE_JPEG, System.currentTimeMillis(), width, height, - data.size.toLong(), + size, Media.ALL_MEDIA_BUCKET_ID, null ) @@ -278,10 +274,6 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme }) } - override fun getDisplayRotation(): Int { - return windowManager.defaultDisplay.rotation - } - private fun initializeCountButtonObserver() { viewModel.getCountButtonState().observe( this @@ -346,7 +338,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme } private fun navigateToMediaSend(recipient: Recipient) { - val fragment = MediaSendFragment.newInstance(recipient) + val fragment = MediaSendFragment.newInstance(recipient, threadId) var backstackTag: String? = null if (supportFragmentManager.findFragmentByTag(TAG_SEND) != null) { @@ -394,6 +386,8 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme ) .addToBackStack(null) .commit() + + viewModel.onCameraStarted() } .onAnyDenied { Toast.makeText( @@ -405,12 +399,12 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme .execute() } - private val orCreateCameraFragment: Camera1Fragment + private val orCreateCameraFragment: CameraXFragment get() { val fragment = - supportFragmentManager.findFragmentByTag(TAG_CAMERA) as Camera1Fragment? + supportFragmentManager.findFragmentByTag(TAG_CAMERA) as CameraXFragment? - return fragment ?: Camera1Fragment.newInstance() + return fragment ?: CameraXFragment() } private fun animateButtonVisibility(button: View, oldVisibility: Int, newVisibility: Int) { @@ -511,6 +505,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme const val EXTRA_MESSAGE: String = "message" private const val KEY_ADDRESS = "address" + private const val KEY_THREADID = "threadid" private const val KEY_BODY = "body" private const val KEY_MEDIA = "media" private const val KEY_IS_CAMERA = "is_camera" @@ -524,10 +519,11 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme * Get an intent to launch the media send flow starting with the picker. */ @JvmStatic - fun buildGalleryIntent(context: Context, recipient: Recipient, body: String): Intent { + fun buildGalleryIntent(context: Context, recipient: Recipient, threadId: Long, body: String): Intent { val intent = Intent(context, MediaSendActivity::class.java) intent.putExtra(KEY_ADDRESS, recipient.address.toString()) intent.putExtra(KEY_BODY, body) + intent.putExtra(KEY_THREADID, threadId) return intent } @@ -535,8 +531,8 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme * Get an intent to launch the media send flow starting with the camera. */ @JvmStatic - fun buildCameraIntent(context: Context, recipient: Recipient): Intent { - val intent = buildGalleryIntent(context, recipient, "") + fun buildCameraIntent(context: Context, recipient: Recipient, threadId: Long, body: String): Intent { + val intent = buildGalleryIntent(context, recipient, threadId, body) intent.putExtra(KEY_IS_CAMERA, true) return intent } @@ -549,9 +545,10 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme context: Context, media: List, recipient: Recipient, + threadId: Long, body: String ): Intent { - val intent = buildGalleryIntent(context, recipient, body) + val intent = buildGalleryIntent(context, recipient, threadId, body) intent.putParcelableArrayListExtra(KEY_MEDIA, ArrayList(media)) return intent } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt index 762ca90992..f2b122fdf7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt @@ -2,23 +2,27 @@ package org.thoughtcrime.securesms.mediasend import android.content.Context import android.graphics.Bitmap -import android.graphics.Rect import android.net.Uri import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher -import android.view.KeyEvent import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View -import android.view.View.OnFocusChangeListener import android.view.ViewGroup -import android.view.WindowManager -import android.view.inputmethod.EditorInfo +import android.view.ViewGroup.LayoutParams import android.widget.TextView +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import androidx.viewpager.widget.ViewPager.SimpleOnPageChangeListener import com.bumptech.glide.Glide @@ -26,50 +30,56 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext -import network.loki.messenger.R import network.loki.messenger.databinding.MediasendFragmentBinding import org.session.libsession.utilities.MediaTypes -import org.session.libsession.utilities.TextSecurePreferences.Companion.isEnterSendsEnabled import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardHiddenListener -import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener +import org.thoughtcrime.securesms.InputBarDialogs +import org.thoughtcrime.securesms.conversation.v2.ConversationV2Dialogs +import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate +import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel +import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter.RailItemListener -import org.thoughtcrime.securesms.providers.BlobProvider +import org.thoughtcrime.securesms.providers.BlobUtils import org.thoughtcrime.securesms.scribbles.ImageEditorFragment -import org.thoughtcrime.securesms.util.PushCharacterCalculator +import org.thoughtcrime.securesms.ui.setThemedContent import org.thoughtcrime.securesms.util.applySafeInsetsPaddings +import org.thoughtcrime.securesms.util.hideKeyboard import java.io.File import java.io.FileInputStream import java.io.FileOutputStream -import java.util.Locale +import javax.inject.Inject /** * Allows the user to edit and caption a set of media items before choosing to send them. */ @AndroidEntryPoint -class MediaSendFragment : Fragment(), RailItemListener, - OnKeyboardShownListener, OnKeyboardHiddenListener { +class MediaSendFragment : Fragment(), RailItemListener, InputBarDelegate { private var binding: MediasendFragmentBinding? = null private var fragmentPagerAdapter: MediaSendFragmentPagerAdapter? = null private var mediaRailAdapter: MediaRailAdapter? = null - private var visibleHeight = 0 private var viewModel: MediaSendViewModel? = null - private val visibleBounds = Rect() - - private val characterCalculator = PushCharacterCalculator() - private val controller: Controller get() = (parentFragment as? Controller) ?: requireActivity() as Controller + // Mentions + private val threadId: Long + get() = arguments?.getLong(KEY_THREADID) ?: -1L + + @Inject lateinit var mentionViewModelFactory: MentionViewModel.AssistedFactory + private val mentionViewModel: MentionViewModel by viewModels { + mentionViewModelFactory.create(threadId) + } + override fun onAttach(context: Context) { super.onAttach(context) @@ -96,19 +106,30 @@ class MediaSendFragment : Fragment(), RailItemListener, this.binding = it } - binding.mediasendSafeArea.applySafeInsetsPaddings() - - binding.mediasendSendButton.setOnClickListener { v: View? -> - fragmentPagerAdapter?.let { processMedia(it.allMedia, it.savedState) } - } + binding.mediasendSafeArea.applySafeInsetsPaddings( + applyBottom = false, + alsoApply = { + binding.bottomSpacer.updateLayoutParams { + height = it.bottom + } + } + ) - val composeKeyPressedListener = ComposeKeyPressedListener() + binding.inputBar.delegate = this + binding.inputBar.setInputBarEditableFactory(mentionViewModel.editableFactory) - binding.mediasendComposeText.setOnKeyListener(composeKeyPressedListener) - binding.mediasendComposeText.addTextChangedListener(composeKeyPressedListener) - binding.mediasendComposeText.setOnFocusChangeListener(composeKeyPressedListener) + viewLifecycleOwner.lifecycleScope.launch { + val pretty = mentionViewModel.reconstructMentions(viewModel?.body?.toString().orEmpty()) + binding.inputBar.setText(pretty, TextView.BufferType.EDITABLE) + } - binding.mediasendComposeText.requestFocus() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel?.inputBarState?.collect { state -> + binding.inputBar.setState(state) + } + } + } fragmentPagerAdapter = MediaSendFragmentPagerAdapter(childFragmentManager) binding.mediasendPager.setAdapter(fragmentPagerAdapter) @@ -127,16 +148,26 @@ class MediaSendFragment : Fragment(), RailItemListener, ) binding.mediasendMediaRail.setAdapter(mediaRailAdapter) - binding.mediasendComposeText.append(viewModel?.body) - - binding.mediasendComposeText.setHint(getString(R.string.message), null) - binding.mediasendComposeText.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? -> - val isSend = actionId == EditorInfo.IME_ACTION_SEND - if (isSend) binding.mediasendSendButton.performClick() - isSend + binding.mediasendCloseButton.setOnClickListener { + binding.inputBar.clearFocus() + binding.root.hideKeyboard() + requireActivity().finish() } - binding.mediasendCloseButton.setOnClickListener { requireActivity().finish() } + // set the compose dialog content + binding.dialogs.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setThemedContent { + if(viewModel == null) return@setThemedContent + val dialogsState by viewModel!!.inputBarStateDialogsState.collectAsState() + InputBarDialogs ( + inputBarDialogsState = dialogsState, + sendCommand = { + viewModel?.onInputBarCommand(it) + } + ) + } + } } override fun onDestroyView() { @@ -157,10 +188,6 @@ class MediaSendFragment : Fragment(), RailItemListener, } } - override fun onHiddenChanged(hidden: Boolean) { - super.onHiddenChanged(hidden) - } - override fun onStop() { super.onStop() @@ -188,25 +215,6 @@ class MediaSendFragment : Fragment(), RailItemListener, ) } - override fun onKeyboardShown() { - val binding = binding ?: return - - if (binding.mediasendComposeText.hasFocus()) { - binding.mediasendMediaRail.visibility = View.VISIBLE - binding.mediasendComposeContainer.visibility = View.VISIBLE - } else { - binding.mediasendMediaRail.visibility = View.GONE - binding.mediasendComposeContainer.visibility = View.VISIBLE - } - } - - override fun onKeyboardHidden() { - binding?.apply { - mediasendComposeContainer.visibility = View.VISIBLE - mediasendMediaRail.visibility = View.VISIBLE - } - } - fun onTouchEventsNeeded(needed: Boolean) { binding?.mediasendPager?.isEnabled = !needed } @@ -235,44 +243,16 @@ class MediaSendFragment : Fragment(), RailItemListener, binding?.mediasendPager?.setCurrentItem(position, true) mediaRailAdapter?.setActivePosition(position) binding?.mediasendMediaRail?.smoothScrollToPosition(position) - - val playbackControls = fragmentPagerAdapter?.getPlaybackControls(position) - if (playbackControls != null) { - val params = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - playbackControls.layoutParams = params - binding?.mediasendPlaybackControlsContainer?.removeAllViews() - binding?.mediasendPlaybackControlsContainer?.addView(playbackControls) - } else { - binding?.mediasendPlaybackControlsContainer?.removeAllViews() - } } viewModel.getBucketId().observe(this) { bucketId: String? -> if (bucketId == null) return@observe - mediaRailAdapter!!.setAddButtonListener { controller.onAddMediaClicked(bucketId) } - } - } + mediaRailAdapter!!.setAddButtonListener { + // save existing text in VM + viewModel.onBodyChanged(mentionViewModel.deconstructMessageMentions()) - - private fun presentCharactersRemaining() { - val binding = binding ?: return - val messageBody = binding.mediasendComposeText.textTrimmed - val characterState = characterCalculator.calculateCharacters(messageBody) - - if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) { - binding.mediasendCharactersLeft.text = String.format( - Locale.getDefault(), - "%d/%d (%d)", - characterState.charactersRemaining, - characterState.maxTotalMessageSize, - characterState.messagesSpent - ) - binding.mediasendCharactersLeft.visibility = View.VISIBLE - } else { - binding.mediasendCharactersLeft.visibility = View.GONE + controller.onAddMediaClicked(bucketId) + } } } @@ -328,7 +308,7 @@ class MediaSendFragment : Fragment(), RailItemListener, // Once we have the JPEG file, save it as our blob val jpegSize = jpegOut.length() - jpegSize to BlobProvider.getInstance() + jpegSize to BlobUtils.getInstance() .forData(FileInputStream(jpegOut), jpegSize) .withMimeType(MediaTypes.IMAGE_JPEG) .withFileName(media.filename) @@ -352,7 +332,7 @@ class MediaSendFragment : Fragment(), RailItemListener, } } else { // No changes to the original media, copy and return as is - val newUri = BlobProvider.getInstance() + val newUri = BlobUtils.getInstance() .forData(requireNotNull(context.contentResolver.openInputStream(media.uri)) { "Invalid URI" }, media.size) @@ -380,9 +360,11 @@ class MediaSendFragment : Fragment(), RailItemListener, } } - controller.onSendClicked(updatedMedia, binding.mediasendComposeText.textTrimmed) + controller.onSendClicked(updatedMedia, mentionViewModel.normalizeMessageBody()) delayedShowLoader.cancel() binding.loader.isVisible = false + binding.inputBar.clearFocus() + binding.root.hideKeyboard() } } @@ -391,51 +373,36 @@ class MediaSendFragment : Fragment(), RailItemListener, if (fullScreen) View.GONE else View.VISIBLE } - private inner class FragmentPageChangeListener : SimpleOnPageChangeListener() { - override fun onPageSelected(position: Int) { - viewModel!!.onPageChanged(position) - } + override fun inputBarEditTextContentChanged(newContent: CharSequence) { + // use the normalised version of the text's body to get the characters amount with the + // mentions as their account id + viewModel?.onTextChanged(mentionViewModel.deconstructMessageMentions()) } - private inner class ComposeKeyPressedListener : View.OnKeyListener, - TextWatcher, OnFocusChangeListener { - var beforeLength: Int = 0 - - override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean { - if (event.action == KeyEvent.ACTION_DOWN) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { - if (isEnterSendsEnabled(requireContext())) { - binding?.mediasendSendButton?.dispatchKeyEvent( - KeyEvent( - KeyEvent.ACTION_DOWN, - KeyEvent.KEYCODE_ENTER - ) - ) - binding?.mediasendSendButton?.dispatchKeyEvent( - KeyEvent( - KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_ENTER - ) - ) - return true - } - } - } - return false - } + override fun sendMessage() { + // validate message length before sending + if(viewModel == null || viewModel?.validateMessageLength() == false) return - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - beforeLength = binding?.mediasendComposeText?.textTrimmed?.length ?: return - } + fragmentPagerAdapter?.let { processMedia(it.allMedia, it.savedState) } + } - override fun afterTextChanged(s: Editable) { - presentCharactersRemaining() - viewModel!!.onBodyChanged(s) - } + override fun onCharLimitTapped() { + viewModel?.onCharLimitTapped() + } - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + // Unused callbacks + override fun commitInputContent(contentUri: Uri) {} + override fun toggleAttachmentOptions() {} + override fun showVoiceMessageUI() {} + override fun startRecordingVoiceMessage() {} + override fun onMicrophoneButtonMove(event: MotionEvent) {} + override fun onMicrophoneButtonCancel(event: MotionEvent) {} + override fun onMicrophoneButtonUp(event: MotionEvent) {} - override fun onFocusChange(v: View, hasFocus: Boolean) {} + private inner class FragmentPageChangeListener : SimpleOnPageChangeListener() { + override fun onPageSelected(position: Int) { + viewModel!!.onPageChanged(position) + } } interface Controller { @@ -448,10 +415,12 @@ class MediaSendFragment : Fragment(), RailItemListener, private val TAG: String = MediaSendFragment::class.java.simpleName private const val KEY_ADDRESS = "address" + private const val KEY_THREADID = "threadid" - fun newInstance(recipient: Recipient): MediaSendFragment { + fun newInstance(recipient: Recipient, threadId: Long): MediaSendFragment { val args = Bundle() args.putParcelable(KEY_ADDRESS, recipient.address) + args.putLong(KEY_THREADID, threadId) val fragment = MediaSendFragment() fragment.arguments = args diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java index ea2ee1b403..fc56b04609 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java @@ -118,7 +118,4 @@ void restoreState(@NonNull Map state) { savedState.putAll(state); } - @Nullable View getPlaybackControls(int position) { - return fragments.containsKey(position) ? fragments.get(position).getPlaybackControls() : null; - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendGifFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendGifFragment.java index 41293c6256..d1bd4c11ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendGifFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendGifFragment.java @@ -53,11 +53,6 @@ public void setUri(@NonNull Uri uri) { return uri; } - @Override - public @Nullable View getPlaybackControls() { - return null; - } - @Override public @Nullable Object saveState() { return null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendPageFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendPageFragment.java index 6de248489f..1456e6c690 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendPageFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendPageFragment.java @@ -14,8 +14,6 @@ public interface MediaSendPageFragment { void setUri(@NonNull Uri uri); - @Nullable View getPlaybackControls(); - @Nullable Object saveState(); void restoreState(@NonNull Object state); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java index f32e3e926d..309ebeb7f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java @@ -74,12 +74,6 @@ public void onDestroyView() { @Override public @NonNull Uri getUri() { return uri; } - @Override - public @Nullable View getPlaybackControls() { - VideoPlayer player = (VideoPlayer) getView(); - return player != null ? player.getControlView() : null; - } - @Override public @Nullable Object saveState() { return null; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.kt index 1831eb97aa..944793b07c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.kt @@ -12,8 +12,10 @@ import org.session.libsession.utilities.Util.equals import org.session.libsession.utilities.Util.runOnMain import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional +import org.thoughtcrime.securesms.InputbarViewModel import org.thoughtcrime.securesms.mms.MediaConstraints -import org.thoughtcrime.securesms.providers.BlobProvider +import org.thoughtcrime.securesms.pro.ProStatusManager +import org.thoughtcrime.securesms.providers.BlobUtils import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.SingleLiveEvent import java.util.LinkedList @@ -25,8 +27,12 @@ import javax.inject.Inject @HiltViewModel internal class MediaSendViewModel @Inject constructor( - private val application: Application -) : ViewModel() { + private val application: Application, + private val proStatusManager: ProStatusManager, +) : InputbarViewModel( + application = application, + proStatusManager = proStatusManager +) { private val selectedMedia: MutableLiveData?> private val bucketMedia: MutableLiveData> private val position: MutableLiveData @@ -201,8 +207,8 @@ internal class MediaSendViewModel @Inject constructor( val updatedList = selectedMediaOrDefault.toMutableList() val removed: Media = updatedList.removeAt(position) - if (BlobProvider.isAuthority(removed.uri)) { - BlobProvider.getInstance().delete(context, removed.uri) + if (BlobUtils.isAuthority(removed.uri)) { + BlobUtils.getInstance().delete(context, removed.uri) } selectedMedia.setValue(updatedList) @@ -243,7 +249,7 @@ internal class MediaSendViewModel @Inject constructor( selected.remove(lastImageCapture.get()) selectedMedia.value = selected countButtonState.value = CountButtonState(selected.size, countButtonVisibility) - BlobProvider.getInstance().delete(context, lastImageCapture.get().uri) + BlobUtils.getInstance().delete(context, lastImageCapture.get().uri) } } @@ -326,12 +332,12 @@ internal class MediaSendViewModel @Inject constructor( Stream.of(selectedMediaOrDefault) .map({ obj: Media -> obj.uri }) .filter({ uri: Uri? -> - BlobProvider.isAuthority( + BlobUtils.isAuthority( uri!! ) }) .forEach({ uri: Uri? -> - BlobProvider.getInstance().delete( + BlobUtils.getInstance().delete( application.applicationContext, uri!! ) }) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt index 7a2397c8ce..e43aeabeca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt @@ -40,7 +40,6 @@ class MessageRequestView : LinearLayout { binding.displayNameTextView.text = senderDisplayName binding.timestampTextView.text = dateUtils.getDisplayFormattedTimeSpanString( - Locale.getDefault(), thread.date ) val snippet = highlightMentions( diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt index 5fa8a50ff7..004c8a7050 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt @@ -3,8 +3,11 @@ package org.thoughtcrime.securesms.messagerequests import android.content.Intent import android.database.Cursor import android.os.Bundle +import android.view.ViewGroup.MarginLayoutParams import androidx.activity.viewModels import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding import androidx.loader.app.LoaderManager import androidx.loader.content.Loader import com.bumptech.glide.Glide @@ -23,6 +26,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.DateUtils +import org.thoughtcrime.securesms.util.applySafeInsetsPaddings import org.thoughtcrime.securesms.util.push @AndroidEntryPoint @@ -40,6 +44,9 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick MessageRequestsAdapter(context = this, cursor = null, dateUtils = dateUtils, listener = this) } + override val applyDefaultWindowInsets: Boolean + get() = false + override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) binding = ActivityMessageRequestsBinding.inflate(layoutInflater) @@ -52,6 +59,10 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick binding.recyclerView.adapter = adapter binding.clearAllMessageRequestsButton.setOnClickListener { deleteAll() } + + binding.root.applySafeInsetsPaddings( + applyBottom = false, + ) } override fun onResume() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/migration/DatabaseMigrationScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/migration/DatabaseMigrationScreen.kt index 79a221797f..74fb9332d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migration/DatabaseMigrationScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/migration/DatabaseMigrationScreen.kt @@ -1,15 +1,6 @@ package org.thoughtcrime.securesms.migration -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.animation.togetherWith -import androidx.compose.animation.with import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -46,14 +37,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY -import org.thoughtcrime.securesms.preferences.ClearAllDataDialog import org.thoughtcrime.securesms.preferences.ShareLogsDialog import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString -import org.thoughtcrime.securesms.ui.components.CircularProgressIndicator import org.thoughtcrime.securesms.ui.components.OutlineButton -import org.thoughtcrime.securesms.ui.components.PrimaryFillButton +import org.thoughtcrime.securesms.ui.components.AccentFillButton import org.thoughtcrime.securesms.ui.components.SmallCircularProgressIndicator import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -150,7 +139,7 @@ private fun DatabaseMigration( Spacer(Modifier.size(LocalDimensions.current.spacing)) Row { - PrimaryFillButton( + AccentFillButton( text = stringResource(R.string.retry), onClick = onRetry ) @@ -213,12 +202,12 @@ private fun DatabaseMigration( onDismissRequest = { showingClearDeviceRestartWarning = false }, text = stringResource(R.string.databaseErrorClearDataWarning), buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString.FromResId(R.string.clear), color = LocalColors.current.danger, onClick = onClearData ), - DialogButtonModel( + DialogButtonData( text = GetString.FromResId(R.string.cancel), onClick = { showingClearDeviceRestartWarning = false } ) @@ -231,12 +220,12 @@ private fun DatabaseMigration( onDismissRequest = { showingClearDeviceRestoreWarning = false }, text = stringResource(R.string.databaseErrorRestoreDataWarning), buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString.FromResId(R.string.clear), color = LocalColors.current.danger, onClick = onClearDataWithoutLoggingOut ), - DialogButtonModel( + DialogButtonData( text = GetString.FromResId(R.string.cancel), onClick = { showingClearDeviceRestoreWarning = false } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/PartAuthority.java b/app/src/main/java/org/thoughtcrime/securesms/mms/PartAuthority.java index cc9fb9cdc6..37439304f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/PartAuthority.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/PartAuthority.java @@ -11,8 +11,8 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import org.thoughtcrime.securesms.providers.BlobProvider; -import org.thoughtcrime.securesms.providers.PartProvider; +import org.thoughtcrime.securesms.providers.BlobUtils; +import org.thoughtcrime.securesms.providers.PartAndBlobProvider; public class PartAuthority { @@ -36,7 +36,7 @@ public class PartAuthority { uriMatcher.addURI("network.loki.provider.securesms", "part/*/#", PART_ROW); uriMatcher.addURI("network.loki.provider.securesms", "thumb/*/#", THUMB_ROW); uriMatcher.addURI("network.loki.provider.securesms", "sticker/#", STICKER_ROW); - uriMatcher.addURI(BlobProvider.AUTHORITY, BlobProvider.PATH, BLOB_ROW); + uriMatcher.addURI(BlobUtils.AUTHORITY, BlobUtils.PATH, BLOB_ROW); } public static InputStream getAttachmentStream(@NonNull Context context, @NonNull Uri uri) @@ -47,7 +47,7 @@ public static InputStream getAttachmentStream(@NonNull Context context, @NonNull switch (match) { case PART_ROW: return DatabaseComponent.get(context).attachmentDatabase().getAttachmentStream(new PartUriParser(uri).getPartId(), 0); case THUMB_ROW: return DatabaseComponent.get(context).attachmentDatabase().getThumbnailStream(new PartUriParser(uri).getPartId()); - case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri); + case BLOB_ROW: return BlobUtils.getInstance().getStream(context, uri); default: return context.getContentResolver().openInputStream(uri); } } catch (SecurityException se) { @@ -66,7 +66,7 @@ public static InputStream getAttachmentStream(@NonNull Context context, @NonNull if (attachment != null) return attachment.getFilename(); else return null; case BLOB_ROW: - return BlobProvider.getFileName(uri); + return BlobUtils.getFileName(uri); default: return null; } @@ -83,7 +83,7 @@ public static InputStream getAttachmentStream(@NonNull Context context, @NonNull if (attachment != null) return attachment.getSize(); else return null; case BLOB_ROW: - return BlobProvider.getFileSize(uri); + return BlobUtils.getFileSize(uri); default: return null; } @@ -100,7 +100,7 @@ public static InputStream getAttachmentStream(@NonNull Context context, @NonNull if (attachment != null) return attachment.getContentType(); else return null; case BLOB_ROW: - return BlobProvider.getMimeType(uri); + return BlobUtils.getMimeType(uri); default: return null; } @@ -108,7 +108,7 @@ public static InputStream getAttachmentStream(@NonNull Context context, @NonNull public static Uri getAttachmentPublicUri(Uri uri) { PartUriParser partUri = new PartUriParser(uri); - return PartProvider.getContentUri(partUri.getPartId()); + return PartAndBlobProvider.getContentUri(partUri.getPartId()); } public static Uri getAttachmentDataUri(AttachmentId attachmentId) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt index 8c0761b57c..9708714657 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt @@ -59,7 +59,14 @@ import org.session.libsignal.utilities.Util import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.crypto.KeyPairUtilities.getUserED25519KeyPair +import org.thoughtcrime.securesms.database.MmsDatabase.Companion.MESSAGE_BOX +import org.thoughtcrime.securesms.database.MmsSmsColumns +import org.thoughtcrime.securesms.database.MmsSmsColumns.NOTIFIED +import org.thoughtcrime.securesms.database.MmsSmsColumns.READ +import org.thoughtcrime.securesms.database.MmsSmsDatabase.MMS_TRANSPORT +import org.thoughtcrime.securesms.database.MmsSmsDatabase.TRANSPORT import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.database.SmsDatabase import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord @@ -78,6 +85,7 @@ import org.thoughtcrime.securesms.util.SpanUtil * * @author Moxie Marlinspike */ +private const val CONTENT_SIGNATURE = "content_signature" class DefaultMessageNotifier( val avatarUtils: AvatarUtils ) : MessageNotifier { @@ -206,7 +214,7 @@ class DefaultMessageNotifier( var telcoCursor: Cursor? = null try { - telcoCursor = get(context).mmsSmsDatabase().unread // TODO: add a notification specific lighter query here + telcoCursor = get(context).mmsSmsDatabase().unreadOrUnseenReactions // TODO: add a notification specific lighter query here if ((telcoCursor == null || telcoCursor.isAfterLast) || getLocalNumber(context) == null) { updateBadge(context, 0) @@ -268,10 +276,25 @@ class DefaultMessageNotifier( return } - val builder = SingleRecipientNotificationBuilder(context, getNotificationPrivacy(context), avatarUtils) + // Bail early if the existing displayed notification has the same content as what we are trying to send now val notifications = notificationState.notifications - val messageOriginator = notifications[0].recipient val notificationId = (SUMMARY_NOTIFICATION_ID + (if (bundled) notifications[0].threadId else 0)).toInt() + val contentSignature = notifications.map { + getNotificationSignature(it) + }.sorted().joinToString("|") + + val existingNotifications = ServiceUtil.getNotificationManager(context).activeNotifications + val existingSignature = existingNotifications.find { it.id == notificationId }?.notification?.extras?.getString(CONTENT_SIGNATURE) + + if (existingSignature == contentSignature) { + Log.i(TAG, "Skipping duplicate single thread notification for ID $notificationId") + return + } + + val builder = SingleRecipientNotificationBuilder(context, getNotificationPrivacy(context), avatarUtils) + builder.putStringExtra(CONTENT_SIGNATURE, contentSignature) + + val messageOriginator = notifications[0].recipient val messageIdTag = notifications[0].timestamp.toString() val timestamp = notifications[0].timestamp @@ -366,6 +389,10 @@ class DefaultMessageNotifier( Log.i(TAG, "Posted notification. $notification") } + private fun getNotificationSignature(notification: NotificationItem): String { + return "${notification.id}_${notification.text}_${notification.timestamp}_${notification.threadId}" + } + // Note: The `signal` parameter means "play an audio signal for the notification". private fun sendMultipleThreadNotification( context: Context, @@ -374,8 +401,21 @@ class DefaultMessageNotifier( ) { Log.i(TAG, "sendMultiThreadNotification() signal: $signal") - val builder = MultipleRecipientNotificationBuilder(context, getNotificationPrivacy(context)) val notifications = notificationState.notifications + val contentSignature = notifications.map { + getNotificationSignature(it) + }.sorted().joinToString("|") + + val existingNotifications = ServiceUtil.getNotificationManager(context).activeNotifications + val existingSignature = existingNotifications.find { it.id == SUMMARY_NOTIFICATION_ID }?.notification?.extras?.getString(CONTENT_SIGNATURE) + + if (existingSignature == contentSignature) { + Log.i(TAG, "Skipping duplicate multi-thread notification") + return + } + + val builder = MultipleRecipientNotificationBuilder(context, getNotificationPrivacy(context)) + builder.putStringExtra(CONTENT_SIGNATURE, contentSignature) builder.setMessageCount(notificationState.notificationCount, notificationState.threadCount) builder.setMostRecentSender(notifications[0].individualRecipient, notifications[0].recipient) @@ -461,106 +501,178 @@ class DefaultMessageNotifier( val threadDatabase = get(context).threadDatabase() val cache: MutableMap = HashMap() - // CAREFUL: Do not put this loop back as `while ((reader.next.also { record = it }) != null) {` because it breaks with a Null Pointer Exception! var record: MessageRecord? = null do { record = reader.next if (record == null) break // Bail if there are no more MessageRecords - val id = record.getId() - val mms = record.isMms || record.isMmsNotification - val recipient = record.individualRecipient - val conversationRecipient = record.recipient val threadId = record.threadId - var body: CharSequence = record.getDisplayBody(context) - var threadRecipients: Recipient? = null - var slideDeck: SlideDeck? = null - val timestamp = record.timestamp - var messageRequest = false - - if (threadId != -1L) { - threadRecipients = threadDatabase.getRecipientForThreadId(threadId) - messageRequest = threadRecipients != null && !threadRecipients.isGroupOrCommunityRecipient && - !threadRecipients.isApproved && !threadDatabase.getLastSeenAndHasSent(threadId).second() - if (messageRequest && (threadDatabase.getMessageCount(threadId) > 1 || !hasHiddenMessageRequests(context))) { - continue - } - } + val threadRecipients = if (threadId != -1L) { + threadDatabase.getRecipientForThreadId(threadId) + } else null + + // Start by checking various scenario that we should skip - // If this is a message request from an unknown user.. - if (messageRequest) { - body = SpanUtil.italic(context.getString(R.string.messageRequestsNew)) - - // If we received some manner of notification but Session is locked.. - } else if (KeyCachingService.isLocked(context)) { - // Note: We provide 1 because `messageNewYouveGot` is now a plurals string and we don't have a count yet, so just - // giving it 1 will result in "You got a new message". - body = SpanUtil.italic(context.resources.getQuantityString(R.plurals.messageNewYouveGot, 1, 1)) - - // ----- Note: All further cases assume we know the contact and that Session isn't locked ----- - - // If this is a notification about a multimedia message which contains no text but DOES contain a slide deck with at least one slide.. - } else if (record.isMms && TextUtils.isEmpty(body) && !(record as MmsMessageRecord).slideDeck.slides.isEmpty()) { - slideDeck = (record as MediaMmsMessageRecord).slideDeck - body = SpanUtil.italic(slideDeck.body) - - // If this is a notification about a multimedia message, but it's not ITSELF a multimedia notification AND it contains a slide deck with at least one slide.. - } else if (record.isMms && !record.isMmsNotification && !(record as MmsMessageRecord).slideDeck.slides.isEmpty()) { - slideDeck = (record as MediaMmsMessageRecord).slideDeck - val message = slideDeck.body + ": " + record.body - val italicLength = message.length - body.length - body = SpanUtil.italic(message, italicLength) - - // If this is a notification about an invitation to a community.. - } else if (record.isOpenGroupInvitation) { - body = SpanUtil.italic(context.getString(R.string.communityInvitation)) + // Skip if muted or calls + if (threadRecipients?.isMuted == true) continue + if (record.isIncomingCall || record.isOutgoingCall) continue + + // Handle message requests early + val isMessageRequest = threadRecipients != null && + !threadRecipients.isGroupOrCommunityRecipient && + !threadRecipients.isApproved && + !threadDatabase.getLastSeenAndHasSent(threadId).second() + + if (isMessageRequest && (threadDatabase.getMessageCount(threadId) > 1 || !hasHiddenMessageRequests(context))) { + continue } + // Check notification settings + if (threadRecipients?.notifyType == RecipientDatabase.NOTIFY_TYPE_NONE) continue + val userPublicKey = getLocalNumber(context) - var blindedPublicKey = cache[threadId] - if (blindedPublicKey == null) { - blindedPublicKey = generateBlindedId(threadId, context) - cache[threadId] = blindedPublicKey - } - if (threadRecipients == null || !threadRecipients.isMuted) { - if(record.isIncomingCall || record.isOutgoingCall){ - // do nothing here as we do not want to display a notification for incoming and outgoing calls, - // they will instead be handled independently by the pre offer + + // Check mentions-only setting + if (threadRecipients?.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { + var blindedPublicKey = cache[threadId] + if (blindedPublicKey == null) { + blindedPublicKey = generateBlindedId(threadId, context) + cache[threadId] = blindedPublicKey } - else if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { - // check if mentioned here - var isQuoteMentioned = false - if (record is MmsMessageRecord) { - val quote = (record as MmsMessageRecord).quote - val quoteAddress = quote?.author - val serializedAddress = quoteAddress?.toString() - isQuoteMentioned = (serializedAddress != null && userPublicKey == serializedAddress) || - (blindedPublicKey != null && userPublicKey == blindedPublicKey) - } - if (body.toString().contains("@$userPublicKey") || body.toString().contains("@$blindedPublicKey") || isQuoteMentioned) { - notificationState.addNotification(NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck)) + + var isMentioned = false + val body = record.getDisplayBody(context).toString() + + // Check for @mentions + if (body.contains("@$userPublicKey") || + (blindedPublicKey != null && body.contains("@$blindedPublicKey"))) { + isMentioned = true + } + + // Check for quote mentions + if (record is MmsMessageRecord) { + val quote = record.quote + val quoteAuthor = quote?.author?.toString() + if ((quoteAuthor != null && userPublicKey == quoteAuthor) || + (blindedPublicKey != null && quoteAuthor == blindedPublicKey)) { + isMentioned = true } - } else if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_NONE) { - // do nothing, no notifications + } + + if (!isMentioned) continue + } + + Log.w(TAG, "Processing: ID=${record.getId()}, outgoing=${record.isOutgoing}, read=${record.isRead}, hasReactions=${record.reactions.isNotEmpty()}") + + // Determine the reason this message was returned by the query + val isNotified = cursor.getInt(cursor.getColumnIndexOrThrow(NOTIFIED)) == 1 + val isUnreadIncoming = !record.isOutgoing && !record.isRead() && !isNotified // << Case 1 + val hasUnreadReactions = record.reactions.isNotEmpty() // << Case 2 + + Log.w(TAG, " -> isUnreadIncoming=$isUnreadIncoming, hasUnreadReactions=$hasUnreadReactions, isNotified=${isNotified}") + + // CASE 1: TRULY NEW UNREAD INCOMING MESSAGE + // Only show message notification if it's incoming, unread AND not yet notified + if (isUnreadIncoming) { + // Prepare message body + var body: CharSequence = record.getDisplayBody(context) + var slideDeck: SlideDeck? = null + + if (isMessageRequest) { + body = SpanUtil.italic(context.getString(R.string.messageRequestsNew)) + } else if (KeyCachingService.isLocked(context)) { + body = SpanUtil.italic(context.resources.getQuantityString(R.plurals.messageNewYouveGot, 1, 1)) } else { - notificationState.addNotification(NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck)) + // Handle MMS content + if (record.isMms && TextUtils.isEmpty(body) && (record as MmsMessageRecord).slideDeck.slides.isNotEmpty()) { + slideDeck = (record as MediaMmsMessageRecord).slideDeck + body = SpanUtil.italic(slideDeck.body) + } else if (record.isMms && !record.isMmsNotification && (record as MmsMessageRecord).slideDeck.slides.isNotEmpty()) { + slideDeck = (record as MediaMmsMessageRecord).slideDeck + val message = slideDeck.body + ": " + record.body + val italicLength = message.length - body.length + body = SpanUtil.italic(message, italicLength) + } else if (record.isOpenGroupInvitation) { + body = SpanUtil.italic(context.getString(R.string.communityInvitation)) + } + } + + Log.w(TAG, "Adding incoming message notification: ${body}") + + // Add incoming message notification + notificationState.addNotification( + NotificationItem( + record.getId(), + record.isMms || record.isMmsNotification, + record.individualRecipient, + record.recipient, + threadRecipients, + threadId, + body, + record.timestamp, + slideDeck + ) + ) + } + // CASE 2: REACTIONS TO OUR OUTGOING MESSAGES + // Only if: it's OUR message AND it has reactions AND it's NOT an unread incoming message + else if (record.isOutgoing && + hasUnreadReactions && + threadRecipients != null && + !threadRecipients.isGroupOrCommunityRecipient) { + + var blindedPublicKey = cache[threadId] + if (blindedPublicKey == null) { + blindedPublicKey = generateBlindedId(threadId, context) + cache[threadId] = blindedPublicKey + } + + // Find reactions from others (not from us) + val reactionsFromOthers = record.reactions.filter { reaction -> + reaction.author != userPublicKey && + (blindedPublicKey == null || reaction.author != blindedPublicKey) } - val userBlindedPublicKey = blindedPublicKey - val lastReact = Stream.of(record.reactions) - .filter { r: ReactionRecord -> !(r.author == userPublicKey || r.author == userBlindedPublicKey) } - .findLast() - - if (lastReact.isPresent) { - if (threadRecipients != null && !threadRecipients.isGroupOrCommunityRecipient) { - val reaction = lastReact.get() - val reactor = Recipient.from(context, fromSerialized(reaction.author), false) - val emoji = Phrase.from(context, R.string.emojiReactsNotification).put(EMOJI_KEY, reaction.emoji).format().toString() - notificationState.addNotification(NotificationItem(id, mms, reactor, reactor, threadRecipients, threadId, emoji, reaction.dateSent, slideDeck)) + if (reactionsFromOthers.isNotEmpty()) { + // Get the most recent reaction from others + val latestReaction = reactionsFromOthers.maxByOrNull { it.dateSent } + + if (latestReaction != null) { + val reactor = Recipient.from(context, fromSerialized(latestReaction.author), false) + val emoji = Phrase.from(context, R.string.emojiReactsNotification) + .put(EMOJI_KEY, latestReaction.emoji).format().toString() + + // Use unique ID to avoid conflicts with message notifications + val reactionId = "reaction_${record.getId()}_${latestReaction.emoji}_${latestReaction.author}".hashCode().toLong() + + Log.w(TAG, "Adding reaction notification: ${emoji} to our message ID ${record.getId()}") + + notificationState.addNotification( + NotificationItem( + reactionId, + record.isMms || record.isMmsNotification, + reactor, + reactor, + threadRecipients, + threadId, + emoji, + latestReaction.dateSent, + null + ) + ) } } } - } while (record != null) // This will never hit because we break early if we get a null record at the start of the do..while loop + // CASE 3: IGNORED SCENARIOS + // This handles cases like: + // - Contact's message with reactions (hasUnreadReactions=true, but isOutgoing=false) + // - Already read messages that somehow got returned + // - etc. + else { + Log.w(TAG, "Ignoring message: not unread incoming and not our outgoing with reactions") + } + + } while (record != null) reader.close() return notificationState diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt index 76a298b380..9539b17ea7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt @@ -1,51 +1,54 @@ package org.thoughtcrime.securesms.notifications -import android.annotation.SuppressLint import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.os.AsyncTask import androidx.core.app.NotificationManagerCompat import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import org.session.libsession.database.StorageProtocol import org.session.libsession.database.userAuth import org.session.libsession.messaging.MessagingModuleConfiguration.Companion.shared import org.session.libsession.messaging.messages.control.ReadReceipt import org.session.libsession.messaging.sending_receiving.MessageSender.send import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI.nowWithOffset -import org.session.libsession.snode.utilities.await -import org.session.libsession.utilities.SSKEnvironment +import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled import org.session.libsession.utilities.associateByNotNull import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType -import org.thoughtcrime.securesms.database.ExpirationInfo import org.thoughtcrime.securesms.database.MarkedMessageInfo -import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.SessionMetaProtocol.shouldSendReadReceipt +import javax.inject.Inject @AndroidEntryPoint class MarkReadReceiver : BroadcastReceiver() { - @SuppressLint("StaticFieldLeak") + @Inject + lateinit var storage: StorageProtocol + + @Inject + lateinit var clock: SnodeClock + override fun onReceive(context: Context, intent: Intent) { if (CLEAR_ACTION != intent.action) return val threadIds = intent.getLongArrayExtra(THREAD_IDS_EXTRA) ?: return NotificationManagerCompat.from(context).cancel(intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1)) - object : AsyncTask() { - override fun doInBackground(vararg params: Void?): Void? { - val currentTime = nowWithOffset - threadIds.forEach { - Log.i(TAG, "Marking as read: $it") - shared.storage.markConversationAsRead(it, currentTime, true) - } - return null + GlobalScope.launch { + val currentTime = clock.currentTimeMills() + threadIds.forEach { + Log.i(TAG, "Marking as read: $it") + storage.markConversationAsRead( + threadId = it, + lastSeenTime = currentTime, + force = true + ) } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } } companion object { @@ -54,8 +57,6 @@ class MarkReadReceiver : BroadcastReceiver() { const val THREAD_IDS_EXTRA = "thread_ids" const val NOTIFICATION_ID_EXTRA = "notification_id" - val messageExpirationManager = SSKEnvironment.shared.messageExpirationManager - @JvmStatic fun process( context: Context, @@ -74,14 +75,22 @@ class MarkReadReceiver : BroadcastReceiver() { .asSequence() .filter { it.expiryType == ExpiryType.AFTER_READ } .filter { mmsSmsDatabase.getMessageById(it.expirationInfo.id)?.run { - isExpirationTimerUpdate && threadDb.getRecipientForThreadId(threadId)?.isGroupOrCommunityRecipient == true } == false + (messageContent is DisappearingMessageUpdate) + && threadDb.getRecipientForThreadId(threadId)?.isGroupOrCommunityRecipient == true } == false + } + .forEach { + val db = if (it.expirationInfo.id.mms) { + DatabaseComponent.get(context).mmsDatabase() + } else { + DatabaseComponent.get(context).smsDatabase() + } + + db.markExpireStarted(it.expirationInfo.id.id, nowWithOffset) } - .forEach { messageExpirationManager.startDisappearAfterRead(it.syncMessageId.timetamp, it.syncMessageId.address.toString()) } hashToDisappearAfterReadMessage(context, markedReadMessages)?.let { hashToMessages -> GlobalScope.launch { try { - fetchUpdatedExpiriesAndScheduleDeletion(context, hashToMessages) shortenExpiryOfDisappearingAfterRead(hashToMessages) } catch (e: Exception) { Log.e(TAG, "Failed to fetch updated expiries and schedule deletion", e) @@ -135,38 +144,5 @@ class MarkReadReceiver : BroadcastReceiver() { .let { send(it, address) } } } - - private suspend fun fetchUpdatedExpiriesAndScheduleDeletion( - context: Context, - hashToMessage: Map - ) { - @Suppress("UNCHECKED_CAST") - val expiries = SnodeAPI.getExpiries(hashToMessage.keys.toList(), shared.storage.userAuth!!).await()["expiries"] as Map - hashToMessage.forEach { (hash, info) -> expiries[hash]?.let { scheduleDeletion(context, info.expirationInfo, it - info.expirationInfo.expireStarted) } } - } - - private fun scheduleDeletion( - context: Context, - expirationInfo: ExpirationInfo, - expiresIn: Long = expirationInfo.expiresIn - ) { - if (expiresIn == 0L) return - - val now = nowWithOffset - - val expireStarted = expirationInfo.expireStarted - - if (expirationInfo.isDisappearAfterRead() && expireStarted == 0L || now < expireStarted) { - val db = DatabaseComponent.get(context).run { if (expirationInfo.id.mms) mmsDatabase() else smsDatabase() } - db.markExpireStarted(expirationInfo.id.id, now) - } - - ApplicationContext.getInstance(context).expiringMessageManager.get().scheduleDeletion( - expirationInfo.id.id, - expirationInfo.id.mms, - now, - expiresIn - ) - } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java index bc85ff0874..aa704daa97 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -33,15 +33,20 @@ public NotificationState(@NonNull List items) { } } - public void addNotification(NotificationItem item) { - // Add this new notification at the beginning of the list - notifications.addFirst(item); + public void addNotification(@NonNull NotificationItem item) { + // find the index to insert the message based on their timestamp + int i = 0; + while (i < notifications.size() && + notifications.get(i).getTimestamp() > item.getTimestamp()) { + i++; + } + notifications.add(i, item); // Put a notification at the front by removing it then re-adding it? threads.remove(item.getThreadId()); threads.add(item.getThreadId()); - notificationCount++; + notificationCount = notifications.size(); } public @Nullable Uri getRingtone(@NonNull Context context) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt index 4c262a4dbe..e1ad385bcb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt @@ -43,11 +43,11 @@ import javax.inject.Inject private const val TAG = "PushHandler" class PushReceiver @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, private val configFactory: ConfigFactory, private val groupRevokedMessageHandler: GroupRevokedMessageHandler, + private val json: Json, ) { - private val json = Json { ignoreUnknownKeys = true } /** * Both push services should hit this method once they receive notification data diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistrationHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistrationHandler.kt index c54f069a49..7a53a758f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistrationHandler.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistrationHandler.kt @@ -1,14 +1,15 @@ package org.thoughtcrime.securesms.notifications +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce @@ -16,15 +17,14 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.scan import kotlinx.coroutines.launch -import network.loki.messenger.libsession_util.Namespace +import kotlinx.coroutines.supervisorScope import org.session.libsession.database.userAuth import org.session.libsession.messaging.notifications.TokenFetcher -import org.session.libsession.snode.OwnedSwarmAuth import org.session.libsession.snode.SwarmAuth import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.dependencies.ConfigFactory import javax.inject.Inject @@ -40,11 +40,12 @@ private const val TAG = "PushRegistrationHandler" class PushRegistrationHandler @Inject constructor( - private val pushRegistry: PushRegistryV2, private val configFactory: ConfigFactory, private val preferences: TextSecurePreferences, - private val storage: Storage, private val tokenFetcher: TokenFetcher, + @ApplicationContext private val context: Context, + private val registry: PushRegistryV2, + private val storage: Storage, ) { @OptIn(DelicateCoroutinesApi::class) private val scope: CoroutineScope = GlobalScope @@ -60,124 +61,85 @@ constructor( (configFactory.configUpdateNotifications as Flow) .debounce(500L) .onStart { emit(Unit) }, - IdentityKeyUtil.CHANGES.onStart { emit(Unit) }, + preferences.watchLocalNumber(), preferences.pushEnabled, tokenFetcher.token, - ) { _, _, enabled, token -> - if (!enabled || token.isNullOrEmpty()) { - return@combine emptyMap() + ) { _, myAccountId, enabled, token -> + if (!enabled || myAccountId == null || token.isNullOrEmpty()) { + return@combine emptySet() } - val userAuth = - storage.userAuth ?: return@combine emptyMap() - getGroupSubscriptions( - token = token - ) + mapOf( - SubscriptionKey(userAuth.accountId, token) to Subscription(userAuth, listOf( - Namespace.DEFAULT())) - ) + setOf(SubscriptionKey(AccountId(myAccountId), token)) + getGroupSubscriptions(token) } - .scan, Pair, Map>?>( - null - ) { acc, current -> - val prev = acc?.second.orEmpty() - prev to current + .scan(emptySet() to emptySet()) { acc, current -> + acc.second to current } - .filterNotNull() .collect { (prev, current) -> - val addedAccountIds = current.keys - prev.keys - val removedAccountIDs = prev.keys - current.keys - if (addedAccountIds.isNotEmpty()) { - Log.d(TAG, "Adding ${addedAccountIds.size} new subscriptions") + val added = current - prev + val removed = prev - current + if (added.isNotEmpty()) { + Log.d(TAG, "Adding ${added.size} new subscriptions") } - if (removedAccountIDs.isNotEmpty()) { - Log.d(TAG, "Removing ${removedAccountIDs.size} subscriptions") + if (removed.isNotEmpty()) { + Log.d(TAG, "Removing ${removed.size} subscriptions") } - val deferred = mutableListOf>() - - addedAccountIds.mapTo(deferred) { key -> - val subscription = current.getValue(key) - async { - try { - pushRegistry.register( - token = key.token, - swarmAuth = subscription.auth, - namespaces = subscription.namespaces.toList() - ) - } catch (e: Exception) { - Log.e(TAG, "Failed to register for push notification", e) - } - } + for (key in added) { + PushRegistrationWorker.schedule( + context = context, + token = key.token, + accountId = key.accountId, + ) } - removedAccountIDs.mapTo(deferred) { key -> - val subscription = prev.getValue(key) - async { - try { - pushRegistry.unregister( - token = key.token, - swarmAuth = subscription.auth, - ) - } catch (e: Exception) { - Log.e(TAG, "Failed to unregister for push notification", e) + supervisorScope { + for (key in removed) { + PushRegistrationWorker.cancelRegistration( + context = context, + accountId = key.accountId, + ) + + launch { + Log.d(TAG, "Unregistering push token for account: ${key.accountId}") + try { + val swarmAuth = swarmAuthForAccount(key.accountId) + ?: throw IllegalStateException("No SwarmAuth found for account: ${key.accountId}") + + registry.unregister( + token = key.token, + swarmAuth = swarmAuth, + ) + + Log.d(TAG, "Successfully unregistered push token for account: ${key.accountId}") + } catch (e: Exception) { + if (e !is CancellationException) { + Log.e(TAG, "Failed to unregister push token for account: ${key.accountId}", e) + } + } } } } - - deferred.awaitAll() } } } + private fun swarmAuthForAccount(accountId: AccountId): SwarmAuth? { + return when (accountId.prefix) { + IdPrefix.STANDARD -> storage.userAuth?.takeIf { it.accountId == accountId } + IdPrefix.GROUP -> configFactory.getGroupAuth(accountId) + else -> null // Unsupported account ID prefix + } + } + private fun getGroupSubscriptions( token: String - ): Map { - return buildMap { - val groups = configFactory.withUserConfigs { it.userGroups.allClosedGroupInfo() } - .filter { it.shouldPoll } - - val namespaces = listOf( - Namespace.GROUP_MESSAGES(), - Namespace.GROUP_INFO(), - Namespace.GROUP_MEMBERS(), - Namespace.GROUP_KEYS(), - Namespace.REVOKED_GROUP_MESSAGES(), - ) - - for (group in groups) { - val adminKey = group.adminKey?.data - val groupId = AccountId(group.groupAccountId) - if (adminKey != null && adminKey.isNotEmpty()) { - put( - SubscriptionKey(groupId, token), - Subscription( - auth = OwnedSwarmAuth.ofClosedGroup(groupId, adminKey), - namespaces = namespaces - ) - ) - continue - } - - val authData = group.authData?.data - if (authData != null && authData.isNotEmpty()) { - val subscription = configFactory.getGroupAuth(groupId) - ?.let { - Subscription( - auth = it, - namespaces = namespaces - ) - } - - if (subscription != null) { - put(SubscriptionKey(groupId, token), subscription) - } - } - } - } + ): Set { + return configFactory.withUserConfigs { it.userGroups.allClosedGroupInfo() } + .asSequence() + .filter { it.shouldPoll } + .mapTo(hashSetOf()) { SubscriptionKey(accountId = AccountId(it.groupAccountId), token = token) } } private data class SubscriptionKey(val accountId: AccountId, val token: String) - private data class Subscription(val auth: SwarmAuth, val namespaces: List) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistrationWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistrationWorker.kt new file mode 100644 index 0000000000..f9c3810bbd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistrationWorker.kt @@ -0,0 +1,131 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.Operation +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import androidx.work.await +import androidx.work.impl.background.systemjob.setRequiredNetworkRequest +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CancellationException +import network.loki.messenger.libsession_util.Namespace +import org.session.libsession.database.userAuth +import org.session.libsignal.exceptions.NonRetryableException +import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.IdPrefix +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.dependencies.ConfigFactory +import java.time.Duration + +@HiltWorker +class PushRegistrationWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted val params: WorkerParameters, + val registry: PushRegistryV2, + val storage: Storage, + val configFactory: ConfigFactory, +) : CoroutineWorker(context, params) { + override suspend fun doWork(): Result { + val accountId = checkNotNull(inputData.getString(ARG_ACCOUNT_ID) + ?.let(AccountId::fromStringOrNull)) { + "PushRegistrationWorker requires a valid account ID" + } + + val token = checkNotNull(inputData.getString(ARG_TOKEN)) { + "PushRegistrationWorker requires a valid FCM token" + } + + Log.d(TAG, "Registering push token for account: $accountId with token: ${token.substring(0..10)}") + + val (swarmAuth, namespaces) = when (accountId.prefix) { + IdPrefix.STANDARD -> { + val auth = requireNotNull(storage.userAuth) { + "PushRegistrationWorker requires user authentication to register push notifications" + } + + // A standard account ID means ourselves, so we use the local auth. + require(accountId == auth.accountId) { + "PushRegistrationWorker can only register the local account ID" + } + + auth to REGULAR_PUSH_NAMESPACES + } + IdPrefix.GROUP -> { + requireNotNull(configFactory.getGroupAuth(accountId)) to GROUP_PUSH_NAMESPACES + } + else -> { + throw IllegalArgumentException("Unsupported account ID prefix: ${accountId.prefix}") + } + } + + try { + registry.register(token = token, swarmAuth = swarmAuth, namespaces = namespaces) + Log.d(TAG, "Successfully registered push token for account: $accountId") + return Result.success() + } catch (e: CancellationException) { + Log.d(TAG, "Push registration cancelled for account: $accountId") + throw e + } catch (e: Exception) { + Log.e(TAG, "Unexpected error while registering push token for account: $accountId", e) + return if (e is NonRetryableException) Result.failure() else Result.retry() + } + } + + companion object { + private const val ARG_TOKEN = "token" + private const val ARG_ACCOUNT_ID = "account_id" + + private const val TAG = "PushRegistrationWorker" + + private val GROUP_PUSH_NAMESPACES = listOf( + Namespace.GROUP_MESSAGES(), + Namespace.GROUP_INFO(), + Namespace.GROUP_MEMBERS(), + Namespace.GROUP_KEYS(), + Namespace.REVOKED_GROUP_MESSAGES(), + ) + + private val REGULAR_PUSH_NAMESPACES = listOf(Namespace.DEFAULT()) + + private fun uniqueWorkName(accountId: AccountId): String { + return "push-registration-${accountId.hexString}" + } + + fun schedule( + context: Context, + token: String, + accountId: AccountId, + ) { + val request = OneTimeWorkRequestBuilder() + .setInputData( + Data.Builder().putString(ARG_TOKEN, token) + .putString(ARG_ACCOUNT_ID, accountId.hexString).build() + ) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, Duration.ofSeconds(10)) + .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED)) + .build() + + WorkManager.getInstance(context).enqueueUniqueWork( + uniqueWorkName = uniqueWorkName(accountId), + existingWorkPolicy = ExistingWorkPolicy.REPLACE, + request = request + ) + } + + suspend fun cancelRegistration(context: Context, accountId: AccountId) { + WorkManager.getInstance(context) + .cancelUniqueWork(uniqueWorkName(accountId)) + .await() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt index b28f1211c3..17db072713 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt @@ -62,7 +62,7 @@ class PushRegistryV2 @Inject constructor( enc_key = pnKey.toHexString(), ).let(Json::encodeToJsonElement).jsonObject + signed - val response = retryResponseBody( + val response = getResponseBody( "subscribe", Json.encodeToString(requestParameters) ) @@ -91,7 +91,7 @@ class PushRegistryV2 @Inject constructor( service_info = mapOf("token" to token), ).let(Json::encodeToJsonElement).jsonObject + signature - val response: UnsubscribeResponse = retryResponseBody("unsubscribe", Json.encodeToString(requestParameters)) + val response: UnsubscribeResponse = getResponseBody("unsubscribe", Json.encodeToString(requestParameters)) check(response.isSuccess()) { "Error unsubscribing to push notifications: ${response.message}" @@ -107,9 +107,6 @@ class PushRegistryV2 @Inject constructor( }) } - private suspend inline fun retryResponseBody(path: String, requestParameters: String): T = - retryWithUniformInterval(maxRetryCount = maxRetryCount) { getResponseBody(path, requestParameters) } - @OptIn(ExperimentalSerializationApi::class) private suspend inline fun getResponseBody(path: String, requestParameters: String): T { val server = Server.LATEST diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/OnboardingBackPressAlertDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/OnboardingBackPressAlertDialog.kt index 64d91c58af..b985490675 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/OnboardingBackPressAlertDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/OnboardingBackPressAlertDialog.kt @@ -6,12 +6,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import com.squareup.phrase.Phrase import network.loki.messenger.R -import org.session.libsession.utilities.NonTranslatableStringConstants.APP_NAME import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString -import org.thoughtcrime.securesms.ui.getSubbedString import org.thoughtcrime.securesms.ui.theme.LocalColors @Composable @@ -29,12 +27,12 @@ fun OnboardingBackPressAlertDialog( Phrase.from(txt).put(APP_NAME_KEY, c.getString(R.string.app_name)).format().toString() }, buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(id = R.string.quitButton)), color = LocalColors.current.danger, onClick = quit ), - DialogButtonModel( + DialogButtonData( GetString(stringResource(R.string.cancel)) ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt index 875bd042b7..d620e7eebd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt @@ -40,11 +40,11 @@ import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.components.BorderlessHtmlButton -import org.thoughtcrime.securesms.ui.components.PrimaryFillButton -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton +import org.thoughtcrime.securesms.ui.components.AccentFillButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -83,11 +83,11 @@ internal fun LandingScreen( text = stringResource(R.string.urlOpenBrowser), showCloseButton = true, // display the 'x' button buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(R.string.onboardingTos), onClick = openTerms ), - DialogButtonModel( + DialogButtonData( text = GetString(R.string.onboardingPrivacy), onClick = openPrivacyPolicy ) @@ -164,7 +164,7 @@ internal fun LandingScreen( } Column(modifier = Modifier.padding(horizontal = LocalDimensions.current.xlargeSpacing)) { - PrimaryFillButton( + AccentFillButton( text = stringResource(R.string.onboardingAccountCreate), modifier = Modifier .fillMaxWidth() @@ -173,7 +173,7 @@ internal fun LandingScreen( onClick = createAccount ) Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing)) - PrimaryOutlineButton( + AccentOutlineButton( stringResource(R.string.onboardingAccountExists), modifier = Modifier .fillMaxWidth() @@ -218,7 +218,7 @@ private fun MessageText(text: String, isOutgoing: Boolean, modifier: Modifier) { Box(modifier = modifier then Modifier.fillMaxWidth()) { MessageText( text, - color = if (isOutgoing) LocalColors.current.primary else LocalColors.current.backgroundBubbleReceived, + color = if (isOutgoing) LocalColors.current.accent else LocalColors.current.backgroundBubbleReceived, textColor = if (isOutgoing) LocalColors.current.textBubbleSent else LocalColors.current.textBubbleReceived, modifier = Modifier.align(if (isOutgoing) Alignment.TopEnd else Alignment.TopStart) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccount.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccount.kt index 7295142cb5..ba2154d0c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccount.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccount.kt @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.onboarding.loadaccount -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -21,17 +19,15 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import kotlinx.coroutines.flow.Flow import network.loki.messenger.R -import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton +import org.thoughtcrime.securesms.onboarding.ui.ContinueAccentOutlineButton import org.thoughtcrime.securesms.ui.components.QRScannerScreen import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField import org.thoughtcrime.securesms.ui.components.SessionTabRow -import org.thoughtcrime.securesms.ui.contentDescription import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType @@ -132,6 +128,6 @@ private fun RecoveryPassword( Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing)) Spacer(Modifier.weight(2f)) - ContinuePrimaryOutlineButton(modifier = Modifier.align(Alignment.CenterHorizontally), onContinue) + ContinueAccentOutlineButton(modifier = Modifier.align(Alignment.CenterHorizontally), onContinue) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt index 5f42159fcb..3c5bb0eb75 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -25,7 +24,7 @@ import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.thoughtcrime.securesms.onboarding.OnboardingBackPressAlertDialog import org.thoughtcrime.securesms.onboarding.messagenotifications.MessageNotificationsViewModel.UiState -import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton +import org.thoughtcrime.securesms.onboarding.ui.ContinueAccentOutlineButton import org.thoughtcrime.securesms.ui.components.CircularProgressIndicator import org.thoughtcrime.securesms.ui.components.RadioButton import org.thoughtcrime.securesms.ui.qaTag @@ -49,7 +48,7 @@ internal fun MessageNotificationsScreen( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { - CircularProgressIndicator(color = LocalColors.current.primary) + CircularProgressIndicator(color = LocalColors.current.accent) } return @@ -102,7 +101,7 @@ internal fun MessageNotificationsScreen( Spacer(Modifier.weight(1f)) - ContinuePrimaryOutlineButton(Modifier.align(Alignment.CenterHorizontally), onContinue) + ContinueAccentOutlineButton(Modifier.align(Alignment.CenterHorizontally), onContinue) } } @@ -167,7 +166,7 @@ private fun NotificationRadioButton( Text( stringResource(it), modifier = Modifier.padding(top = LocalDimensions.current.xxsSpacing), - color = LocalColors.current.primary, + color = LocalColors.current.accent, style = LocalType.current.h9 ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayName.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayName.kt index 2b0f4e89df..7fbb4ef68a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayName.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayName.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import network.loki.messenger.R import org.thoughtcrime.securesms.onboarding.OnboardingBackPressAlertDialog -import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton +import org.thoughtcrime.securesms.onboarding.ui.ContinueAccentOutlineButton import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -79,6 +79,6 @@ internal fun PickDisplayName( Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing)) Spacer(Modifier.weight(2f)) - ContinuePrimaryOutlineButton(modifier = Modifier.align(Alignment.CenterHorizontally), onContinue) + ContinueAccentOutlineButton(modifier = Modifier.align(Alignment.CenterHorizontally), onContinue) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/ui/ContinueButton.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/ui/ContinueButton.kt index aaf67f75b4..349bc1093f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/ui/ContinueButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/ui/ContinueButton.kt @@ -6,13 +6,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import network.loki.messenger.R -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalDimensions @Composable -fun ContinuePrimaryOutlineButton(modifier: Modifier, onContinue: () -> Unit) { - PrimaryOutlineButton( +fun ContinueAccentOutlineButton(modifier: Modifier, onContinue: () -> Unit) { + AccentOutlineButton( stringResource(R.string.theContinue), modifier = modifier .qaTag(R.string.AccessibilityId_theContinue) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt index aa815aba71..87d0b46fd6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt @@ -10,6 +10,7 @@ import android.widget.TextView import android.widget.Toast import androidx.core.view.isInvisible import androidx.preference.Preference +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsignal.utilities.Log @@ -18,6 +19,7 @@ import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.ui.getSubbedCharSequence import org.thoughtcrime.securesms.ui.getSubbedString +@AndroidEntryPoint class HelpSettingsActivity: ScreenLockActionBarActivity() { override val applyDefaultWindowInsets: Boolean @@ -32,6 +34,7 @@ class HelpSettingsActivity: ScreenLockActionBarActivity() { } } +@AndroidEntryPoint class HelpSettingsFragment: CorrectedPreferenceFragment() { companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index 2595fdeb1e..610bca0c98 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -92,7 +92,7 @@ import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity import org.thoughtcrime.securesms.tokenpage.TokenPageActivity import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.Cell -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.Divider import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.LargeItemButton @@ -100,8 +100,8 @@ import org.thoughtcrime.securesms.ui.LargeItemButtonWithDrawable import org.thoughtcrime.securesms.ui.OpenURLAlertDialog import org.thoughtcrime.securesms.ui.components.Avatar import org.thoughtcrime.securesms.ui.components.BaseBottomSheet -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineCopyButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton +import org.thoughtcrime.securesms.ui.components.AcccentOutlineCopyButton import org.thoughtcrime.securesms.ui.getCellBottomShape import org.thoughtcrime.securesms.ui.getCellTopShape import org.thoughtcrime.securesms.ui.qaTag @@ -113,7 +113,7 @@ import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors import org.thoughtcrime.securesms.ui.theme.dangerButtonColors -import org.thoughtcrime.securesms.ui.theme.primaryTextButtonColors +import org.thoughtcrime.securesms.ui.theme.accentTextButtonColors import org.thoughtcrime.securesms.util.FileProviderUtil import org.thoughtcrime.securesms.util.applyCommonWindowInsetsOnViews import org.thoughtcrime.securesms.util.push @@ -483,13 +483,13 @@ class SettingsActivity : ScreenLockActionBarActivity() { .padding(top = LocalDimensions.current.xxsSpacing), horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing), ) { - PrimaryOutlineButton( + AccentOutlineButton( stringResource(R.string.share), modifier = Modifier.weight(1f), onClick = ::sendInvitationToUseSession ) - PrimaryOutlineCopyButton( + AcccentOutlineCopyButton( modifier = Modifier.weight(1f), onClick = ::copyPublicKey, ) @@ -519,7 +519,7 @@ class SettingsActivity : ScreenLockActionBarActivity() { textId = R.string.donate, icon = R.drawable.ic_heart, modifier = Modifier.qaTag(R.string.qa_settings_item_donate), - colors = primaryTextButtonColors() + colors = accentTextButtonColors() ) { urlToOPen = "https://session.foundation/donate#app" } @@ -815,7 +815,7 @@ class SettingsActivity : ScreenLockActionBarActivity() { .size(LocalDimensions.current.spacing) .background( shape = CircleShape, - color = LocalColors.current.primary + color = LocalColors.current.accent ) .padding(LocalDimensions.current.xxxsSpacing) .align(Alignment.BottomEnd) @@ -828,12 +828,12 @@ class SettingsActivity : ScreenLockActionBarActivity() { }, showCloseButton = true, // display the 'x' button buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(R.string.save), enabled = state is TempAvatar, onClick = saveAvatar ), - DialogButtonModel( + DialogButtonData( text = GetString(R.string.remove), color = LocalColors.current.danger, enabled = state is UserAvatar || // can remove is the user has an avatar set diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ShareLogsDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/ShareLogsDialog.kt index b35871f847..639ba9b47e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ShareLogsDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ShareLogsDialog.kt @@ -4,24 +4,20 @@ import android.app.Dialog import android.content.ContentResolver import android.content.ContentValues import android.content.Intent -import android.media.MediaScannerConnection import android.net.Uri import android.os.Build import android.os.Bundle -import android.os.Environment import android.provider.MediaStore -import android.webkit.MimeTypeMap import android.widget.Toast import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import com.squareup.phrase.Phrase +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CancellationException import java.io.File -import java.io.FileOutputStream import java.io.IOException -import java.util.Objects import java.util.concurrent.TimeUnit import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -30,16 +26,20 @@ import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsignal.utilities.ExternalStorageUtil import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.createSessionDialog +import org.thoughtcrime.securesms.logging.PersistentLogger import org.thoughtcrime.securesms.util.FileProviderUtil -import org.thoughtcrime.securesms.util.StreamUtil +import javax.inject.Inject +@AndroidEntryPoint class ShareLogsDialog(private val updateCallback: (Boolean)->Unit): DialogFragment() { private val TAG = "ShareLogsDialog" private var shareJob: Job? = null + @Inject + lateinit var persistentLogger: PersistentLogger + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog { title(R.string.helpReportABugExportLogs) val appName = context.getString(R.string.app_name) @@ -63,59 +63,26 @@ class ShareLogsDialog(private val updateCallback: (Boolean)->Unit): DialogFragme updateCallback(true) - shareJob = lifecycleScope.launch(Dispatchers.IO) { - val persistentLogger = ApplicationContext.getInstance(requireContext()).persistentLogger + shareJob = lifecycleScope.launch { try { Log.d(TAG, "Starting share logs job...") - - val context = requireContext() - val outputUri: Uri = ExternalStorageUtil.getDownloadUri() - val mediaUri = getExternalFile() ?: return@launch - - val inputStream = persistentLogger.logs.get().byteInputStream() - val updateValues = ContentValues() - - // Add details into the output or media files as appropriate - if (outputUri.scheme == ContentResolver.SCHEME_FILE) { - FileOutputStream(mediaUri.path).use { outputStream -> - StreamUtil.copy(inputStream, outputStream) - MediaScannerConnection.scanFile(context, arrayOf(mediaUri.path), arrayOf("text/plain"), null) - } - } else { - context.contentResolver.openOutputStream(mediaUri, "w").use { outputStream -> - val total: Long = StreamUtil.copy(inputStream, outputStream) - if (total > 0) { - updateValues.put(MediaStore.MediaColumns.SIZE, total) - } - } + val mediaUri = withContext(Dispatchers.IO) { + withExternalFile(persistentLogger::readAllLogsCompressed) + } ?: return@launch + + val shareIntent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_STREAM, mediaUri) + type = "application/zip" + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } - if (Build.VERSION.SDK_INT > 28) { - updateValues.put(MediaStore.MediaColumns.IS_PENDING, 0) - } - if (updateValues.size() > 0) { - requireContext().contentResolver.update(mediaUri, updateValues, null, null) - } - - val shareUri = if (mediaUri.scheme == ContentResolver.SCHEME_FILE) { - FileProviderUtil.getUriFor(context, File(mediaUri.path!!)) - } else { - mediaUri - } - - withContext(Main) { - val shareIntent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, shareUri) - type = "text/plain" - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - startActivity(Intent.createChooser(shareIntent, getString(R.string.share))) - } + startActivity(Intent.createChooser(shareIntent, getString(R.string.share))) } catch (e: Exception) { - withContext(Main) { + if (e !is CancellationException) { Log.e("Loki", "Error saving logs", e) - Toast.makeText(context,getString(R.string.errorUnknown), Toast.LENGTH_LONG).show() + Toast.makeText(context, getString(R.string.errorUnknown), Toast.LENGTH_LONG) + .show() } } }.also { shareJob -> @@ -146,33 +113,13 @@ class ShareLogsDialog(private val updateCallback: (Boolean)->Unit): DialogFragme } } - @Throws(IOException::class) - private fun pathTaken(outputUri: Uri, dataPath: String): Boolean { - requireContext().contentResolver.query(outputUri, arrayOf(MediaStore.MediaColumns.DATA), - MediaStore.MediaColumns.DATA + " = ?", arrayOf(dataPath), - null).use { cursor -> - if (cursor == null) { - throw IOException("Something is wrong with the filename to save") - } - return cursor.moveToFirst() - } - } - - private fun getExternalFile(): Uri? { + private fun withExternalFile(action: (Uri) -> Unit): Uri? { val context = requireContext() val base = "${Build.MANUFACTURER}-${Build.DEVICE}-API${Build.VERSION.SDK_INT}-v${BuildConfig.VERSION_NAME}-${System.currentTimeMillis()}" - val extension = "txt" + val extension = "zip" val fileName = "$base.$extension" - val mimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType("text/plain") val outputUri: Uri = ExternalStorageUtil.getDownloadUri() - val contentValues = ContentValues() - contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) - contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType) - contentValues.put(MediaStore.MediaColumns.DATE_ADDED, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())) - contentValues.put(MediaStore.MediaColumns.DATE_MODIFIED, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())) - if (Build.VERSION.SDK_INT > 28) { - contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1) - } else if (Objects.equals(outputUri.scheme, ContentResolver.SCHEME_FILE)) { + if (outputUri.scheme == ContentResolver.SCHEME_FILE) { val outputDirectory = File(outputUri.path) var outputFile = File(outputDirectory, "$base.$extension") var i = 0 @@ -182,19 +129,34 @@ class ShareLogsDialog(private val updateCallback: (Boolean)->Unit): DialogFragme if (outputFile.isHidden) { throw IOException("Specified name would not be visible") } - return Uri.fromFile(outputFile) + try { + return FileProviderUtil.getUriFor(requireContext(), outputFile).also(action) + } catch (e: Exception) { + outputFile.delete() + throw e + } } else { - var outputFileName = fileName - val externalPath = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)!! - var dataPath = String.format("%s/%s", externalPath, outputFileName) - var i = 0 - while (pathTaken(outputUri, dataPath)) { - outputFileName = base + "-" + ++i + "." + extension - dataPath = String.format("%s/%s", externalPath, outputFileName) + val contentValues = ContentValues() + contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) + contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "application/zip") + contentValues.put(MediaStore.MediaColumns.DATE_ADDED, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())) + contentValues.put(MediaStore.MediaColumns.DATE_MODIFIED, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())) + contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1) + val uri = context.contentResolver.insert(outputUri, contentValues) ?: return null + try { + action(uri) + + // Remove the pending flag to make the file available + contentValues.clear() + contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0) + context.contentResolver.update(uri, contentValues, null, null) + } catch (e: Exception) { + context.contentResolver.delete(uri, null, null) + throw e } - contentValues.put(MediaStore.MediaColumns.DATA, dataPath) + + return uri } - return context.contentResolver.insert(outputUri, contentValues) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppDisguiseSettings.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppDisguiseSettings.kt index 898cb60614..ec25fccaf6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppDisguiseSettings.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppDisguiseSettings.kt @@ -47,7 +47,7 @@ import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.Cell -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.components.BackAppBar import org.thoughtcrime.securesms.ui.qaTag @@ -175,11 +175,11 @@ private fun AppDisguiseSettings( .toString(), title = stringResource(R.string.appIconAndNameChange), buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(R.string.closeApp), color = LocalColors.current.danger, ) { onCommand(AppDisguiseSettingsViewModel.Command.IconSelectConfirmed(dialogState.id)) }, - DialogButtonModel(text = GetString(R.string.cancel), dismissOnClick = true) + DialogButtonData(text = GetString(R.string.cancel), dismissOnClick = true) ) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt new file mode 100644 index 0000000000..6410e693d1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt @@ -0,0 +1,50 @@ +package org.thoughtcrime.securesms.pro + +import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.utilities.TextSecurePreferences +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ProStatusManager @Inject constructor( + private val prefs: TextSecurePreferences +){ + val MAX_CHARACTER_PRO = 10000 // max characters in a message for pro users + private val MAX_CHARACTER_REGULAR = 2000 // max characters in a message for non pro users + private val MAX_PIN_REGULAR = 5 // max pinned conversation for non pro users + + fun isCurrentUserPro(): Boolean { + // if the debug is set, return that + if (prefs.forceCurrentUserAsPro()) return true + + // otherwise return the true value + return false //todo PRO implement real logic once it's in + } + + /** + * Returns the max length that a visible message can have based on its Pro status + */ + fun getIncomingMessageMaxLength(message: VisibleMessage): Int { + // if the debug is set, return that + if (prefs.forceIncomingMessagesAsPro()) return MAX_CHARACTER_PRO + + // otherwise return the true value + return if(isPostPro()) MAX_CHARACTER_REGULAR else MAX_CHARACTER_PRO //todo PRO implement real logic once it's in + } + + // Temporary method and concept that we should remove once Pro is out + fun isPostPro(): Boolean { + return prefs.forcePostPro() + } + + fun getCharacterLimit(): Int { + return if (isCurrentUserPro()) MAX_CHARACTER_PRO else MAX_CHARACTER_REGULAR + } + + fun getPinnedConversationLimit(): Int { + if(!isPostPro()) return Int.MAX_VALUE // allow infinite pins while not in post Pro + + return if (isCurrentUserPro()) Int.MAX_VALUE else MAX_PIN_REGULAR + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/providers/BlobProvider.java b/app/src/main/java/org/thoughtcrime/securesms/providers/BlobUtils.java similarity index 96% rename from app/src/main/java/org/thoughtcrime/securesms/providers/BlobProvider.java rename to app/src/main/java/org/thoughtcrime/securesms/providers/BlobUtils.java index 26a25bb3dc..e40d4d30cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/providers/BlobProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/providers/BlobUtils.java @@ -31,9 +31,9 @@ /** * Allows for the creation and retrieval of blobs. */ -public class BlobProvider { +public class BlobUtils { - private static final String TAG = BlobProvider.class.getSimpleName(); + private static final String TAG = BlobUtils.class.getSimpleName(); private static final String MULTI_SESSION_DIRECTORY = "multi_session_blobs"; private static final String SINGLE_SESSION_DIRECTORY = "single_session_blobs"; @@ -53,12 +53,12 @@ public class BlobProvider { addURI(AUTHORITY, PATH, MATCH); }}; - private static final BlobProvider INSTANCE = new BlobProvider(); + private static final BlobUtils INSTANCE = new BlobUtils(); private final Map memoryBlobs = new HashMap<>(); - public static BlobProvider getInstance() { + public static BlobUtils getInstance() { return INSTANCE; } @@ -277,7 +277,7 @@ public CompletableFuture createForSingleSessionOnDisk(@NonNull Context cont /** * Create a blob that will exist for multiple app sessions. It is the caller's responsibility to - * eventually call {@link BlobProvider#delete(Context, Uri)} when the blob is no longer in use. + * eventually call {@link BlobUtils#delete(Context, Uri)} when the blob is no longer in use. */ @WorkerThread public CompletableFuture createForMultipleSessionsOnDisk(@NonNull Context context, @Nullable ErrorListener errorListener) throws IOException { @@ -316,7 +316,7 @@ public Uri createForSingleUseInMemory() { /** * Create a blob that is stored in memory. Will persist for a single app session. You should - * always try to call {@link BlobProvider#delete(Context, Uri)} after you're done with the blob + * always try to call {@link BlobUtils#delete(Context, Uri)} after you're done with the blob * to free up memory. */ public Uri createForSingleSessionInMemory() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java b/app/src/main/java/org/thoughtcrime/securesms/providers/PartAndBlobProvider.java similarity index 57% rename from app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java rename to app/src/main/java/org/thoughtcrime/securesms/providers/PartAndBlobProvider.java index 89809d9ac1..70506573ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/providers/PartAndBlobProvider.java @@ -23,7 +23,6 @@ import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; -import android.os.MemoryFile; import android.os.ParcelFileDescriptor; import android.provider.OpenableColumns; @@ -36,26 +35,27 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.mms.PartUriParser; import org.thoughtcrime.securesms.service.KeyCachingService; -import org.thoughtcrime.securesms.util.MemoryFileUtil; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -public class PartProvider extends ContentProvider { +public class PartAndBlobProvider extends ContentProvider { - private static final String TAG = PartProvider.class.getSimpleName(); + private static final String TAG = PartAndBlobProvider.class.getSimpleName(); private static final String CONTENT_URI_STRING = "content://network.loki.provider.securesms/part"; private static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING); private static final int SINGLE_ROW = 1; + private static final int BLOB_ROW = 2; // New constant for blob URIs private static final UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI("network.loki.provider.securesms", "part/*/#", SINGLE_ROW); + uriMatcher.addURI("network.loki.provider.securesms", "blob/*/*/*/*/*", BLOB_ROW); // Add blob pattern } @Override @@ -71,7 +71,7 @@ public static Uri getContentUri(AttachmentId attachmentId) { @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { - Log.i(TAG, "openFile() called!"); + Log.i(TAG, "openFile() called for URI: " + uri); if (KeyCachingService.isLocked(getContext())) { Log.w(TAG, "masterSecret was null, abandoning."); @@ -79,15 +79,24 @@ public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) thr } switch (uriMatcher.match(uri)) { - case SINGLE_ROW: - Log.i(TAG, "Parting out a single row..."); - try { - final PartUriParser partUri = new PartUriParser(uri); - return getParcelStreamForAttachment(partUri.getPartId()); - } catch (IOException ioe) { - Log.w(TAG, ioe); - throw new FileNotFoundException("Error opening file"); - } + case SINGLE_ROW: + Log.i(TAG, "Parting out a single row..."); + try { + final PartUriParser partUri = new PartUriParser(uri); + return getParcelStreamForAttachment(partUri.getPartId()); + } catch (IOException ioe) { + Log.w(TAG, ioe); + throw new FileNotFoundException("Error opening file"); + } + + case BLOB_ROW: + Log.i(TAG, "Handling blob URI..."); + try { + return getParcelStreamForBlob(uri); + } catch (IOException ioe) { + Log.w(TAG, "Error opening blob file", ioe); + throw new FileNotFoundException("Error opening blob file"); + } } throw new FileNotFoundException("Request for bad part."); @@ -107,11 +116,16 @@ public String getType(@NonNull Uri uri) { case SINGLE_ROW: PartUriParser partUriParser = new PartUriParser(uri); DatabaseAttachment attachment = DatabaseComponent.get(getContext()).attachmentDatabase() - .getAttachment(partUriParser.getPartId()); + .getAttachment(partUriParser.getPartId()); if (attachment != null) { return attachment.getContentType(); } + break; + + case BLOB_ROW: + // For blob URIs, get the mime type from the BlobProvider + return BlobUtils.getMimeType(uri); } return null; @@ -147,6 +161,22 @@ public Cursor query(@NonNull Uri url, String[] projection, String selection, Str matrixCursor.addRow(resultRow); return matrixCursor; + + case BLOB_ROW: + // For blob URIs, create a cursor with blob information + MatrixCursor blobCursor = new MatrixCursor(projection, 1); + Object[] blobRow = new Object[projection.length]; + + for (int i = 0; i < projection.length; i++) { + if (OpenableColumns.DISPLAY_NAME.equals(projection[i])) { + blobRow[i] = BlobUtils.getFileName(url); + } else if (OpenableColumns.SIZE.equals(projection[i])) { + blobRow[i] = BlobUtils.getFileSize(url); + } + } + + blobCursor.addRow(blobRow); + return blobCursor; } return null; @@ -159,16 +189,49 @@ public int update(@NonNull Uri arg0, ContentValues arg1, String arg2, String[] a } private ParcelFileDescriptor getParcelStreamForAttachment(AttachmentId attachmentId) throws IOException { - long plaintextLength = Util.getStreamLength(DatabaseComponent.get(getContext()).attachmentDatabase().getAttachmentStream(attachmentId, 0)); - MemoryFile memoryFile = new MemoryFile(attachmentId.toString(), Util.toIntExact(plaintextLength)); + return getStreamingParcelFileDescriptor(() -> + DatabaseComponent.get(getContext()).attachmentDatabase().getAttachmentStream(attachmentId, 0) + ); + } + + private ParcelFileDescriptor getStreamingParcelFileDescriptor(InputStreamProvider provider) throws IOException { + try { + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + ParcelFileDescriptor readSide = pipe[0]; + ParcelFileDescriptor writeSide = pipe[1]; + + new Thread(() -> { + try (InputStream inputStream = provider.getInputStream(); + OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeSide)) { - InputStream in = DatabaseComponent.get(getContext()).attachmentDatabase().getAttachmentStream(attachmentId, 0); - OutputStream out = memoryFile.getOutputStream(); + Util.copy(inputStream, outputStream); + + } catch (IOException e) { + Log.w(TAG, "Error streaming data", e); + try { + writeSide.closeWithError("Error streaming data: " + e.getMessage()); + } catch (IOException closeException) { + Log.w(TAG, "Error closing write side of pipe", closeException); + } + } + }).start(); - Util.copy(in, out); - Util.close(out); - Util.close(in); + return readSide; + + } catch (IOException e) { + Log.w(TAG, "Error creating streaming pipe", e); + throw new FileNotFoundException("Error creating streaming pipe: " + e.getMessage()); + } + } + + private interface InputStreamProvider { + InputStream getInputStream() throws IOException; + } - return MemoryFileUtil.getParcelFileDescriptor(memoryFile); + private ParcelFileDescriptor getParcelStreamForBlob(Uri uri) throws IOException { + // Always use streaming for blobs since they're often shared media files that can be large + return getStreamingParcelFileDescriptor(() -> + BlobUtils.getInstance().getStream(getContext(), uri) + ); } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt index 387c101b48..82dc311543 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt @@ -27,14 +27,13 @@ import androidx.compose.ui.unit.dp import network.loki.messenger.R import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.Cell -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.SessionShieldIcon +import org.thoughtcrime.securesms.ui.border import org.thoughtcrime.securesms.ui.components.QrImage import org.thoughtcrime.securesms.ui.components.SlimOutlineButton import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton -import org.thoughtcrime.securesms.ui.components.border -import org.thoughtcrime.securesms.ui.contentDescription import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -151,7 +150,7 @@ private fun RecoveryPassword(mnemonic: String) { .padding(LocalDimensions.current.spacing), textAlign = TextAlign.Center, style = LocalType.current.extraSmall.monospace(), - color = LocalColors.current.run { if (isLight) text else primary }, + color = LocalColors.current.run { if (isLight) text else accent }, ) } @@ -198,12 +197,12 @@ private fun HideRecoveryPasswordCell( title = stringResource(R.string.recoveryPasswordHidePermanently), text = stringResource(R.string.recoveryPasswordHidePermanentlyDescription1), buttons = listOf( - DialogButtonModel( + DialogButtonData( GetString(R.string.theContinue), color = LocalColors.current.danger, onClick = { showHideRecoveryConfirmationDialog = true } ), - DialogButtonModel(GetString(android.R.string.cancel)) + DialogButtonData(GetString(android.R.string.cancel)) ) ) } @@ -215,12 +214,12 @@ private fun HideRecoveryPasswordCell( title = stringResource(R.string.recoveryPasswordHidePermanently), text = stringResource(R.string.recoveryPasswordHidePermanentlyDescription2), buttons = listOf( - DialogButtonModel( + DialogButtonData( GetString(R.string.yes), color = LocalColors.current.danger, onClick = confirmHideRecovery ), - DialogButtonModel(GetString(android.R.string.cancel)) + DialogButtonData(GetString(android.R.string.cancel)) ) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java index fba50cc8cd..7cbff2d8f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java @@ -166,12 +166,6 @@ public Uri getUri() { return imageUri; } - @Nullable - @Override - public View getPlaybackControls() { - return null; - } - @Override public Object saveState() { Data data = new Data(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java index 2556e4b990..aa10ce2e4f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java @@ -8,11 +8,17 @@ import org.thoughtcrime.securesms.ApplicationContext; +/** + * This is a [BroadcastReceiver] that receives the alarm event from the OS. The + * alarm is most likely set off by the disappearing message handling, which only requires the apps + * to be alive. So the whole purpose of this receiver is just to bring the app up (or keep it alive) + * when the alarm goes off. The disappearing message handling will pick themselves up from the + * previous states. + */ public class ExpirationListener extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - ApplicationContext.getInstance(context).expiringMessageManager.get().checkSchedule(); } public static void setAlarm(Context context, long waitTimeMillis) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt index 969de03e3e..e6cd59ad08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt @@ -3,14 +3,21 @@ package org.thoughtcrime.securesms.service import android.content.Context import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull import network.loki.messenger.libsession_util.util.ExpiryMode -import network.loki.messenger.libsession_util.util.ExpiryMode.AfterSend +import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.signal.IncomingMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage +import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage +import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized +import org.session.libsession.utilities.DistributionTypes import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol @@ -21,58 +28,54 @@ import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional +import org.thoughtcrime.securesms.database.DatabaseContentProviders +import org.thoughtcrime.securesms.database.MessagingDatabase import org.thoughtcrime.securesms.database.MmsDatabase -import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.SmsDatabase import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate import org.thoughtcrime.securesms.mms.MmsException +import org.thoughtcrime.securesms.util.observeChanges import java.io.IOException -import java.util.TreeSet -import java.util.concurrent.Executor -import java.util.concurrent.Executors import javax.inject.Inject import javax.inject.Singleton private val TAG = ExpiringMessageManager::class.java.simpleName +/** + * A manager that reactively looking into the [MmsDatabase] and [SmsDatabase] for expired messages, + * and deleting them. This is done by observing the expiration timestamps of messages and scheduling + * the deletion of them when they are expired. + * + * There is no need (and no way) to ask this manager to schedule a deletion of a message, instead, all you + * need to do is set the expiryMills and expiryStarted fields of the message and save to db, + * this manager will take care of the rest. + */ @Singleton class ExpiringMessageManager @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, private val smsDatabase: SmsDatabase, private val mmsDatabase: MmsDatabase, - private val mmsSmsDatabase: MmsSmsDatabase, private val clock: SnodeClock, private val storage: Lazy, private val preferences: TextSecurePreferences, ) : MessageExpirationManagerProtocol { - private val expiringMessageReferences = TreeSet() - private val executor: Executor = Executors.newSingleThreadExecutor() init { - executor.execute(LoadTask()) - executor.execute(ProcessTask()) - } - - private fun getDatabase(mms: Boolean) = if (mms) mmsDatabase else smsDatabase - - fun scheduleDeletion(id: Long, mms: Boolean, startedAtTimestamp: Long, expiresInMillis: Long) { - if (startedAtTimestamp <= 0) return - - val expiresAtMillis = startedAtTimestamp + expiresInMillis - synchronized(expiringMessageReferences) { - expiringMessageReferences += ExpiringMessageReference(id, mms, expiresAtMillis) - (expiringMessageReferences as Object).notifyAll() + GlobalScope.launch { + listOf( + launch { processDatabase(smsDatabase) }, + launch { processDatabase(mmsDatabase) } + ).joinAll() } } - fun checkSchedule() { - synchronized(expiringMessageReferences) { (expiringMessageReferences as Object).notifyAll() } - } + private fun getDatabase(mms: Boolean) = if (mms) mmsDatabase else smsDatabase private fun insertIncomingExpirationTimerMessage( message: ExpirationTimerUpdate, - expireStartedAt: Long - ) { + ): MessageId? { val senderPublicKey = message.sender val sentTimestamp = message.sentTimestamp val groupId = message.groupPublicKey @@ -82,8 +85,8 @@ class ExpiringMessageManager @Inject constructor( var recipient = Recipient.from(context, address, false) // if the sender is blocked, we don't display the update, except if it's in a closed group - if (recipient.isBlocked && groupId == null) return - try { + if (recipient.isBlocked && groupId == null) return null + return try { if (groupId != null) { val groupAddress: Address groupInfo = when { @@ -99,15 +102,18 @@ class ExpiringMessageManager @Inject constructor( } recipient = Recipient.from(context, groupAddress, false) } - val threadId = storage.get().getThreadId(recipient) ?: return + val threadId = storage.get().getThreadId(recipient) ?: return null val mediaMessage = IncomingMediaMessage( address, sentTimestamp!!, -1, - expiresInMillis, expireStartedAt, true, + expiresInMillis, + 0, // Marking expiryStartedAt as 0 as expiration logic will be universally applied on received messages + // We no longer set this to true anymore as it won't be used in the future, false, false, Optional.absent(), groupInfo, Optional.absent(), + DisappearingMessageUpdate(message.expiryMode), Optional.absent(), Optional.absent(), Optional.absent(), @@ -115,17 +121,20 @@ class ExpiringMessageManager @Inject constructor( ) //insert the timer update message mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true) + .orNull() + ?.let { MessageId(it.messageId, mms = true) } } catch (ioe: IOException) { Log.e("Loki", "Failed to insert expiration update message.") + null } catch (ioe: MmsException) { Log.e("Loki", "Failed to insert expiration update message.") + null } } private fun insertOutgoingExpirationTimerMessage( message: ExpirationTimerUpdate, - expireStartedAt: Long - ) { + ): MessageId? { val sentTimestamp = message.sentTimestamp val groupId = message.groupPublicKey val duration = message.expiryMode.expiryMillis @@ -139,100 +148,120 @@ class ExpiringMessageManager @Inject constructor( val recipient = Recipient.from(context, address, false) message.threadID = storage.get().getOrCreateThreadIdFor(address) - val timerUpdateMessage = OutgoingExpirationUpdateMessage( + val content = DisappearingMessageUpdate(message.expiryMode) + val timerUpdateMessage = if (groupId != null) OutgoingGroupMediaMessage( recipient, + "", + groupId, + null, sentTimestamp!!, duration, - expireStartedAt, - groupId + 0, // Marking as 0 as expiration shouldn't start until we send the message + false, + null, + emptyList(), + emptyList(), + content + ) else OutgoingSecureMediaMessage( + recipient, + "", + emptyList(), + sentTimestamp!!, + DistributionTypes.CONVERSATION, + duration, + 0, // Marking as 0 as expiration shouldn't start until we send the message + null, + emptyList(), + emptyList(), + content ) - mmsDatabase.insertSecureDecryptedMessageOutbox( + + return mmsDatabase.insertSecureDecryptedMessageOutbox( timerUpdateMessage, message.threadID!!, sentTimestamp, true - ) + ).orNull()?.messageId?.let { MessageId(it, mms = true) } } catch (ioe: MmsException) { Log.e("Loki", "Failed to insert expiration update message.", ioe) + return null } catch (ioe: IOException) { Log.e("Loki", "Failed to insert expiration update message.", ioe) + return null } } override fun insertExpirationTimerMessage(message: ExpirationTimerUpdate) { - val expiryMode: ExpiryMode = message.expiryMode - val userPublicKey = preferences.getLocalNumber() val senderPublicKey = message.sender - val sentTimestamp = message.sentTimestamp ?: 0 - val expireStartedAt = if ((expiryMode is AfterSend || message.isSenderSelf) && !message.isGroup) sentTimestamp else 0 - // Notify the user - if (senderPublicKey == null || userPublicKey == senderPublicKey) { + message.id = if (senderPublicKey == null || userPublicKey == senderPublicKey) { // sender is self or a linked device - insertOutgoingExpirationTimerMessage(message, expireStartedAt) + insertOutgoingExpirationTimerMessage(message) } else { - insertIncomingExpirationTimerMessage(message, expireStartedAt) + insertIncomingExpirationTimerMessage(message) } - - maybeStartExpiration(message) } - override fun startAnyExpiration(timestamp: Long, author: String, expireStartedAt: Long) { - mmsSmsDatabase.getMessageFor(timestamp, author)?.run { - getDatabase(isMms()).markExpireStarted(getId(), expireStartedAt) - scheduleDeletion(getId(), isMms(), expireStartedAt, expiresIn) - } ?: Log.e(TAG, "no message record!") + override fun onMessageSent(message: Message) { + // When a message is sent, we'll schedule deletion immediately if we have an expiry mode, + // even if the expiry mode is set to AfterRead, as we don't have a reliable way to know + // that the recipient has read the message at at all. From our perspective it's better + // to disappear the message regardlessly for the safety of ourselves. + // As for the receiver, they will be able to disappear the message correctly after + // they've done reading it. + val messageId = message.id + if (message.expiryMode != ExpiryMode.NONE && messageId != null) { + getDatabase(messageId.mms) + .markExpireStarted(messageId.id, clock.currentTimeMills()) + } } - private inner class LoadTask : Runnable { - override fun run() { - val smsReader = smsDatabase.readerFor(smsDatabase.getExpirationStartedMessages()) - val mmsReader = mmsDatabase.expireStartedMessages - - val smsMessages = smsReader.use { generateSequence { it.next }.toList() } - val mmsMessages = mmsReader.use { generateSequence { it.next }.toList() } - - (smsMessages + mmsMessages).forEach { messageRecord -> - expiringMessageReferences += ExpiringMessageReference( - messageRecord.getId(), - messageRecord.isMms, - messageRecord.expireStarted + messageRecord.expiresIn - ) - } + override fun onMessageReceived(message: Message) { + // When we receive a message, we'll schedule deletion if it has an expiry mode set to + // AfterSend, as the message would be considered sent from the sender's perspective. + val messageId = message.id + if (message.expiryMode is ExpiryMode.AfterSend && messageId != null) { + getDatabase(messageId.mms) + .markExpireStarted(messageId.id, clock.currentTimeMills()) } } - private inner class ProcessTask : Runnable { - override fun run() { - while (true) { - synchronized(expiringMessageReferences) { + private suspend fun processDatabase(db: MessagingDatabase) { + while (true) { + val expiredMessages = db.getExpiredMessageIDs(clock.currentTimeMills()) + + if (expiredMessages.isNotEmpty()) { + Log.d(TAG, "Deleting ${expiredMessages.size} expired messages from ${db.javaClass.simpleName}") + for (messageId in expiredMessages) { try { - while (expiringMessageReferences.isEmpty()) (expiringMessageReferences as Object).wait() - val nextReference = expiringMessageReferences.first() - val waitTime = nextReference.expiresAtMillis - clock.currentTimeMills() - if (waitTime > 0) { - ExpirationListener.setAlarm(context, waitTime) - (expiringMessageReferences as Object).wait(waitTime) - null - } else { - expiringMessageReferences -= nextReference - nextReference - } - } catch (e: InterruptedException) { - Log.w(TAG, e) - null + db.deleteMessage(messageId) + } catch (e: Exception) { + Log.e(TAG, "Failed to delete expired message with ID $messageId", e) } - }?.run { getDatabase(mms).deleteMessage(id) } + } } - } - } - private data class ExpiringMessageReference( - val id: Long, - val mms: Boolean, - val expiresAtMillis: Long - ): Comparable { - override fun compareTo(other: ExpiringMessageReference) = compareValuesBy(this, other, { it.expiresAtMillis }, { it.id }, { it.mms }) + val nextExpiration = db.nextExpiringTimestamp + val now = clock.currentTimeMills() + + if (nextExpiration > 0 && nextExpiration <= now) { + continue // Proceed to the next iteration if the next expiration is already or about go to in the past + } + + val dbChanges = context.contentResolver.observeChanges(DatabaseContentProviders.Conversation.CONTENT_URI, true) + + if (nextExpiration > 0) { + val delayMills = nextExpiration - now + Log.d(TAG, "Wait for up to $delayMills ms for next expiration in ${db.javaClass.simpleName}") + withTimeoutOrNull(delayMills) { + dbChanges.first() + } + } else { + Log.d(TAG, "No next expiration found, waiting for any change in ${db.javaClass.simpleName}") + // If there are no next expiration, just wait for any change in the database + dbChanges.first() + } + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 0af4dffe47..da9258d710 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -89,9 +89,6 @@ class ProfileManager @Inject constructor( } } - override fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) { - recipientDatabase.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode) - } override fun contactUpdatedInternal(contact: Contact): String? { if (contact.accountID == preferences.getLocalNumber()) return null diff --git a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPage.kt b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPage.kt index e2978368f9..1486e8a3d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPage.kt @@ -70,7 +70,7 @@ import org.thoughtcrime.securesms.ui.SimplePopup import org.thoughtcrime.securesms.ui.components.BackAppBar import org.thoughtcrime.securesms.ui.components.BlurredImage import org.thoughtcrime.securesms.ui.components.CircularProgressIndicator -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButtonRect +import org.thoughtcrime.securesms.ui.components.AccentOutlineButtonRect import org.thoughtcrime.securesms.ui.components.SmallCircularProgressIndicator import org.thoughtcrime.securesms.ui.components.annotatedStringResource import org.thoughtcrime.securesms.ui.components.iconExternalLink @@ -125,7 +125,7 @@ fun TokenPage( state = pullToRefreshState, isRefreshing = uiState.isRefreshing, containerColor = LocalColors.current.backgroundSecondary, - color = LocalColors.current.primary, + color = LocalColors.current.accent, modifier = Modifier.align(TopCenter) ) } @@ -233,7 +233,7 @@ fun SessionNetworkInfoSection(modifier: Modifier = Modifier) { // 2.) Session network description val sessionNetworkDetailsAnnotatedString = annotatedStringResource( - highlightColor = LocalColors.current.primaryText, + highlightColor = LocalColors.current.accentText, text = Phrase.from(context.getText(R.string.sessionNetworkDescription)) .put(NETWORK_NAME_KEY, NETWORK_NAME) .put(TOKEN_NAME_LONG_KEY, TOKEN_NAME_LONG) @@ -276,7 +276,7 @@ fun StatsImageBox( .aspectRatio(1.15f) .border( width = 1.dp, - color = LocalColors.current.primary, + color = LocalColors.current.accent, shape = MaterialTheme.shapes.extraSmall ) ) { @@ -320,7 +320,7 @@ fun StatsImageBox( contentDescription = null, modifier = Modifier.matchParentSize(), contentScale = ContentScale.Fit, - colorFilter = ColorFilter.tint(LocalColors.current.primary) + colorFilter = ColorFilter.tint(LocalColors.current.accent) ) } } @@ -338,14 +338,14 @@ fun NodeDetailsBox( val appName = context.getString(R.string.app_name) val nodesInSwarmAS = annotatedStringResource( - highlightColor = LocalColors.current.primaryText, + highlightColor = LocalColors.current.accentText, text = Phrase.from(context, R.string.sessionNetworkNodesSwarm) .put(APP_NAME_KEY, appName) .format() ) val nodesSecuringMessagesAS = annotatedStringResource( - highlightColor = LocalColors.current.primaryText, + highlightColor = LocalColors.current.accentText, text = Phrase.from(context, R.string.sessionNetworkNodesSecuring) .put(APP_NAME_KEY, appName) .format() @@ -415,7 +415,7 @@ fun NodeDetailRow( Text( text = display, style = LocalType.current.h3, - color = LocalColors.current.primaryText, + color = LocalColors.current.accentText, maxLines = maxLines, onTextLayout = { result -> if (result.hasVisualOverflow && !useShort) { @@ -696,7 +696,7 @@ fun SessionTokenSection( // Finally, add a button that links us to the staging page to learn more var showTheOpenUrlModal by remember { mutableStateOf(false) } - PrimaryOutlineButtonRect( + AccentOutlineButtonRect( text = LocalContext.current.getString(R.string.sessionNetworkLearnAboutStaking), modifier = Modifier .fillMaxWidth() @@ -779,7 +779,7 @@ fun ThreeLineTextCell( Text( text = secondLine, style = LocalType.current.h5, - color = LocalColors.current.primaryText, + color = LocalColors.current.accentText, textAlign = TextAlign.Start, modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenRepository.kt index 2d306f2826..041e64a1cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenRepository.kt @@ -25,8 +25,9 @@ interface TokenRepository { @Singleton class TokenRepositoryImpl @Inject constructor( - @ApplicationContext val context: Context, - private val storage: StorageProtocol + @param:ApplicationContext val context: Context, + private val storage: StorageProtocol, + private val json: Json, ): TokenRepository { private val TAG = "TokenRepository" @@ -56,8 +57,6 @@ class TokenRepositoryImpl @Inject constructor( ) } - private val json = Json { ignoreUnknownKeys = true } - private suspend inline fun sendOnionRequest( path: String, url: String, body: ByteArray? = null, customCatch: (Exception) -> T? = { e -> defaultErrorHandling(e) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt index d6ddd31af1..8c8e088d54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.takeOrElse @@ -49,7 +50,7 @@ import org.thoughtcrime.securesms.ui.theme.LocalType import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.bold -class DialogButtonModel( +data class DialogButtonData( val text: GetString, val qaTag: String? = null, val color: Color = Color.Unspecified, @@ -58,6 +59,22 @@ class DialogButtonModel( val onClick: () -> Unit = {}, ) +/** + * Data to display a simple dialog + */ +data class SimpleDialogData( + val title: String, + val message: CharSequence, + val positiveText: String? = null, + val positiveStyleDanger: Boolean = true, + val showXIcon: Boolean = false, + val negativeText: String? = null, + val positiveQaTag: String? = null, + val negativeQaTag: String? = null, + val onPositive: () -> Unit = {}, + val onNegative: () -> Unit = {} +) + @Composable fun AlertDialog( onDismissRequest: () -> Unit, @@ -65,7 +82,7 @@ fun AlertDialog( title: String? = null, text: String? = null, maxLines: Int? = null, - buttons: List? = null, + buttons: List? = null, showCloseButton: Boolean = false, content: @Composable () -> Unit = {} ) { @@ -89,7 +106,7 @@ fun AlertDialog( title: AnnotatedString? = null, text: AnnotatedString? = null, maxLines: Int? = null, - buttons: List? = null, + buttons: List? = null, showCloseButton: Boolean = false, content: @Composable () -> Unit = {} ) { @@ -155,9 +172,9 @@ fun AlertDialog( } content() } - buttons?.takeIf { it.isNotEmpty() }?.let { + if(buttons?.isNotEmpty() == true) { Row(Modifier.height(IntrinsicSize.Min)) { - it.forEach { + buttons.forEach { DialogButton( text = it.text(), modifier = Modifier @@ -172,6 +189,8 @@ fun AlertDialog( } } } + } else { + Spacer(Modifier.height(LocalDimensions.current.smallSpacing)) } } } @@ -198,12 +217,12 @@ fun OpenURLAlertDialog( maxLines = 5, showCloseButton = true, // display the 'x' button buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(R.string.open), color = LocalColors.current.danger, onClick = { context.openUrl(url) } ), - DialogButtonModel( + DialogButtonData( text = GetString(android.R.string.copyUrl), onClick = { context.copyURLToClipboard(url) @@ -263,6 +282,7 @@ fun DialogBg( color = LocalColors.current.borders, shape = MaterialTheme.shapes.small ) + .clip(MaterialTheme.shapes.small) ) { content() @@ -325,12 +345,12 @@ fun PreviewSimpleDialog() { title = stringResource(R.string.warning), text = stringResource(R.string.onboardingBackAccountCreation), buttons = listOf( - DialogButtonModel( + DialogButtonData( GetString(stringResource(R.string.cancel)), color = LocalColors.current.danger, onClick = { } ), - DialogButtonModel( + DialogButtonData( GetString(stringResource(android.R.string.ok)) ) ) @@ -347,11 +367,11 @@ fun PreviewXCloseDialog() { text = stringResource(R.string.urlOpenBrowser), showCloseButton = true, // display the 'x' button buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(R.string.onboardingTos), onClick = {} ), - DialogButtonModel( + DialogButtonData( text = GetString(R.string.onboardingPrivacy), onClick = {} ) @@ -361,6 +381,19 @@ fun PreviewXCloseDialog() { } } +@Preview +@Composable +fun PreviewXCloseNoButtonsDialog() { + PreviewTheme { + AlertDialog( + title = stringResource(R.string.urlOpen), + text = stringResource(R.string.urlOpenBrowser), + showCloseButton = true, // display the 'x' button + onDismissRequest = {} + ) + } +} + @Preview @Composable fun PreviewOpenURLDialog() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt index 77075510f7..9e224c9eb8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -1,32 +1,33 @@ package org.thoughtcrime.securesms.ui -import android.content.Context import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.appcompat.content.res.AppCompatResources import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image -import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background - import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn @@ -35,9 +36,11 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ButtonColors import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor @@ -56,12 +59,13 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape @@ -69,19 +73,19 @@ import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.IntOffset @@ -91,18 +95,36 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupPositionProvider +import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi +import com.bumptech.glide.integration.compose.GlideSubcomposition +import com.bumptech.glide.integration.compose.RequestState import com.google.accompanist.drawablepainter.rememberDrawablePainter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import network.loki.messenger.R -import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton +import org.session.libsession.utilities.NonTranslatableStringConstants +import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel +import org.thoughtcrime.securesms.openUrl +import org.thoughtcrime.securesms.pro.ProStatusManager +import org.thoughtcrime.securesms.ui.components.AccentFillButtonRect +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.components.SmallCircularProgressIndicator +import org.thoughtcrime.securesms.ui.components.TertiaryFillButtonRect import org.thoughtcrime.securesms.ui.components.TitledRadioButton import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType import org.thoughtcrime.securesms.ui.theme.PreviewTheme +import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider +import org.thoughtcrime.securesms.ui.theme.ThemeColors +import org.thoughtcrime.securesms.ui.theme.primaryBlue +import org.thoughtcrime.securesms.ui.theme.primaryGreen +import org.thoughtcrime.securesms.ui.theme.primaryOrange +import org.thoughtcrime.securesms.ui.theme.primaryPink +import org.thoughtcrime.securesms.ui.theme.primaryPurple +import org.thoughtcrime.securesms.ui.theme.primaryRed +import org.thoughtcrime.securesms.ui.theme.primaryYellow import org.thoughtcrime.securesms.ui.theme.transparentButtonColors import kotlin.math.roundToInt @@ -422,7 +444,8 @@ fun ItemButton( ) Column( - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() .align(Alignment.CenterVertically) ) { Text( @@ -435,7 +458,8 @@ fun ItemButton( subtitle?.let { Text( text = it, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() .qaTag(subtitleQaTag), style = LocalType.current.small, ) @@ -502,25 +526,6 @@ fun getCellBottomShape() = RoundedCornerShape( bottomStart = LocalDimensions.current.shapeSmall ) -@Composable -fun Modifier.contentDescription(text: GetString?): Modifier { - return text?.let { - val context = LocalContext.current - semantics { contentDescription = it(context) } - } ?: this -} - -@Composable -fun Modifier.contentDescription(@StringRes id: Int?): Modifier { - val context = LocalContext.current - return id?.let { semantics { contentDescription = context.getString(it) } } ?: this -} - -@Composable -fun Modifier.contentDescription(text: String?): Modifier { - return text?.let { semantics { contentDescription = it } } ?: this -} - @Composable fun BottomFadingEdgeBox( modifier: Modifier = Modifier, @@ -539,7 +544,7 @@ fun BottomFadingEdgeBox( .background( Brush.verticalGradient( 0f to Color.Transparent, - 1f to fadingColor, + 0.9f to fadingColor, tileMode = TileMode.Repeated ) ) @@ -566,7 +571,7 @@ private fun BottomFadingEdgeBoxPreview() { }, ) - PrimaryOutlineButton( + AccentOutlineButton( modifier = Modifier .align(Alignment.CenterHorizontally), text = "Do stuff", onClick = {} @@ -574,6 +579,274 @@ private fun BottomFadingEdgeBoxPreview() { } } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SessionProCTA( + content: @Composable () -> Unit, + text: String, + features: List, + modifier: Modifier = Modifier, + onUpgrade: () -> Unit, + onCancel: () -> Unit, +){ + BasicAlertDialog( + modifier = modifier, + onDismissRequest = onCancel, + content = { + DialogBg { + Column(modifier = Modifier.fillMaxWidth()) { + // hero image + BottomFadingEdgeBox( + fadingEdgeHeight = 70.dp, + fadingColor = LocalColors.current.backgroundSecondary, + content = { _ -> + content() + }, + ) + + // content + Column( + modifier = Modifier + .fillMaxWidth() + .padding(LocalDimensions.current.smallSpacing) + ) { + // title + Row( + modifier = Modifier.align(Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.xxxsSpacing) + + ) { + Text( + text = stringResource(R.string.upgradeTo), + style = LocalType.current.h5 + ) + + Image( + painter = painterResource(id = R.drawable.ic_pro_badge), + contentScale = ContentScale.FillHeight, + contentDescription = NonTranslatableStringConstants.APP_PRO, + ) + } + + Spacer(Modifier.height(LocalDimensions.current.contentSpacing)) + + // main message + Text( + modifier = Modifier.align(Alignment.CenterHorizontally), + text = text, + textAlign = TextAlign.Center, + style = LocalType.current.base.copy( + color = LocalColors.current.textSecondary + ) + ) + + Spacer(Modifier.height(LocalDimensions.current.contentSpacing)) + + // features + features.forEachIndexed { index, feature -> + ProCTAFeature(data = feature) + if(index < features.size - 1){ + Spacer(Modifier.height(LocalDimensions.current.xsSpacing)) + } + } + + Spacer(Modifier.height(LocalDimensions.current.contentSpacing)) + + // buttons + Row( + Modifier.height(IntrinsicSize.Min), + horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.xsSpacing), + ) { + AccentFillButtonRect( + modifier = Modifier.weight(1f).shimmerOverlay(), + text = stringResource(R.string.theContinue), + onClick = onUpgrade + ) + + TertiaryFillButtonRect( + modifier = Modifier.weight(1f), + text = stringResource(R.string.cancel), + onClick = onCancel + ) + } + } + } + } + } + ) +} + +sealed interface CTAFeature { + val text: String + + data class Icon( + override val text: String, + @DrawableRes val icon: Int = R.drawable.ic_circle_check, + ): CTAFeature + + data class RainbowIcon( + override val text: String, + @DrawableRes val icon: Int = R.drawable.ic_pro_sparkle_custom, + ): CTAFeature +} + +@Composable +fun SimpleSessionProCTA( + @DrawableRes heroImage: Int, + text: String, + features: List, + modifier: Modifier = Modifier, + onUpgrade: () -> Unit, + onCancel: () -> Unit, +){ + SessionProCTA( + modifier = modifier, + text = text, + features = features, + onUpgrade = onUpgrade, + onCancel = onCancel, + content = { + Image( + modifier = Modifier + .fillMaxWidth() + .background(LocalColors.current.accent), + contentScale = ContentScale.FillWidth, + painter = painterResource(id = heroImage), + contentDescription = null, + ) + }) +} + +@OptIn(ExperimentalGlideComposeApi::class) +@Composable +fun AnimatedSessionProCTA( + @DrawableRes heroImageBg: Int, + @DrawableRes heroImageAnimatedFg: Int, + text: String, + features: List, + modifier: Modifier = Modifier, + onUpgrade: () -> Unit, + onCancel: () -> Unit, +){ + SessionProCTA( + modifier = modifier, + text = text, + features = features, + onUpgrade = onUpgrade, + onCancel = onCancel, + content = { + Image( + modifier = Modifier + .fillMaxWidth() + .background(LocalColors.current.accent), + contentScale = ContentScale.FillWidth, + painter = painterResource(id = heroImageBg), + contentDescription = null, + ) + + GlideSubcomposition( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min), + model = heroImageAnimatedFg, + ){ + when (state) { + is RequestState.Success -> { + Image( + modifier = Modifier.fillMaxWidth(), + contentScale = ContentScale.FillWidth, + painter = painter, + contentDescription = null, + ) + } + + else -> {} + } + } + }) +} + +/** + * Added here for reusability since multiple screens need this dialog + */ +@Composable +fun PinProCTA( + overTheLimit: Boolean, + onUpgrade: () -> Unit, + onCancel: () -> Unit, + modifier: Modifier = Modifier, +){ + SimpleSessionProCTA( + modifier = modifier, + heroImage = R.drawable.cta_hero_pins, + text = if(overTheLimit) stringResource(R.string.proCallToActionPinnedConversations) + else stringResource(R.string.proCallToActionPinnedConversationsMoreThan), + features = listOf( + CTAFeature.Icon(stringResource(R.string.proFeatureListPinnedConversations)), + CTAFeature.Icon(stringResource(R.string.proFeatureListLargerGroups)), + CTAFeature.RainbowIcon(stringResource(R.string.proFeatureListLoadsMore)), + ), + onUpgrade = onUpgrade, + onCancel = onCancel + ) +} + +@Preview +@Composable +private fun PreviewProCTA( + @PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors +) { + PreviewTheme(colors) { + SimpleSessionProCTA( + heroImage = R.drawable.cta_hero_char_limit, + text = "This is a description of this Pro feature", + features = listOf( + CTAFeature.Icon("Feature one"), + CTAFeature.Icon("Feature two", R.drawable.ic_eye), + CTAFeature.RainbowIcon("Feature three"), + ), + onUpgrade = {}, + onCancel = {} + ) + } +} + +@Composable +fun ProCTAFeature( + data: CTAFeature, + modifier: Modifier = Modifier +){ + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = LocalDimensions.current.xxxsSpacing), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.xxsSpacing) + ) { + when(data){ + is CTAFeature.Icon -> { + Image( + painter = painterResource(id = data.icon), + colorFilter = ColorFilter.tint(LocalColors.current.accentText ), + contentDescription = null, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + is CTAFeature.RainbowIcon -> { + AnimatedGradientDrawable( + vectorRes = data.icon + ) + } + } + + Text( + text = data.text, + style = LocalType.current.base + ) + } +} + @Composable fun Divider(modifier: Modifier = Modifier, startIndent: Dp = 0.dp) { HorizontalDivider( @@ -603,7 +876,7 @@ fun ProgressArc(progress: Float, modifier: Modifier = Modifier) { fun Arc( modifier: Modifier = Modifier, percentage: Float = 0.25f, - fillColor: Color = LocalColors.current.primary, + fillColor: Color = LocalColors.current.accent, backgroundColor: Color = LocalColors.current.borders, strokeWidth: Dp = 18.dp, sweepAngle: Float = 310f, @@ -664,74 +937,6 @@ fun LoadingArcOr(loading: Boolean, content: @Composable () -> Unit) { } } -// Permanently visible vertical scrollbar. -// Note: This scrollbar modifier was adapted from Mardann's fantastic solution at: https://stackoverflow.com/a/78453760/24337669 -@Composable -fun Modifier.verticalScrollbar( - state: ScrollState, - scrollbarWidth: Dp = 6.dp, - barColour: Color = LocalColors.current.textSecondary, - backgroundColour: Color = LocalColors.current.borders, - edgePadding: Dp = LocalDimensions.current.xxsSpacing -): Modifier { - // Calculate the viewport and content heights - val viewHeight = state.viewportSize.toFloat() - val contentHeight = state.maxValue + viewHeight - - // Determine if the scrollbar is needed - val isScrollbarNeeded = contentHeight > viewHeight - - // Set the target alpha based on whether scrolling is possible - val alphaTarget = when { - !isScrollbarNeeded -> 0f // No scrollbar needed, set alpha to 0f - state.isScrollInProgress -> 1f - else -> 0.2f - } - - // Animate the alpha value smoothly - val alpha by animateFloatAsState( - targetValue = alphaTarget, - animationSpec = tween(400, delayMillis = if (state.isScrollInProgress) 0 else 700), - label = "VerticalScrollbarAnimation" - ) - - return this.then(Modifier.drawWithContent { - drawContent() - - // Only proceed if the scrollbar is needed - if (isScrollbarNeeded) { - val minScrollBarHeight = 10.dp.toPx() - val maxScrollBarHeight = viewHeight - val scrollbarHeight = (viewHeight * (viewHeight / contentHeight)).coerceIn( - minOf(minScrollBarHeight, maxScrollBarHeight)..maxOf(minScrollBarHeight, maxScrollBarHeight) - ) - val variableZone = viewHeight - scrollbarHeight - val scrollbarYoffset = (state.value.toFloat() / state.maxValue) * variableZone - - // Calculate the horizontal offset with padding - val scrollbarXOffset = size.width - scrollbarWidth.toPx() - edgePadding.toPx() - - // Draw the missing section of the scrollbar track - drawRoundRect( - color = backgroundColour, - topLeft = Offset(scrollbarXOffset, 0f), - size = Size(scrollbarWidth.toPx(), viewHeight), - cornerRadius = CornerRadius(scrollbarWidth.toPx() / 2), - alpha = alpha - ) - - // Draw the scrollbar thumb - drawRoundRect( - color = barColour, - topLeft = Offset(scrollbarXOffset, scrollbarYoffset), - size = Size(scrollbarWidth.toPx(), scrollbarHeight), - cornerRadius = CornerRadius(scrollbarWidth.toPx() / 2), - alpha = alpha - ) - } - }) -} - @Composable fun SimplePopup( arrowSize: DpSize = DpSize( @@ -857,7 +1062,8 @@ fun SearchBar( colorFilter = ColorFilter.tint( LocalColors.current.textSecondary ), - modifier = Modifier.qaTag(R.string.qa_conversation_search_clear) + modifier = Modifier + .qaTag(R.string.qa_conversation_search_clear) .padding( horizontal = LocalDimensions.current.smallSpacing, vertical = LocalDimensions.current.xxsSpacing @@ -1010,7 +1216,7 @@ fun BaseExpandableText( edgePadding = scrollEdge ) .verticalScroll(scrollState) - .padding(end = scrollWidth + scrollEdge*2) + .padding(end = scrollWidth + scrollEdge * 2) } Column( @@ -1104,23 +1310,45 @@ private fun PreviewBaseExpandedTextLongExpandedMaxLines() { } /** - * Applies an opinionated safety width on content based our design decisions: - * - Max width of maxContentWidth - * - Extra horizontal padding - * - Smaller extra padding for small devices (arbitrarily decided as devices below 380 width + * Animated gradient drawable that cycle through the gradient colors in a linear animation */ @Composable -fun Modifier.safeContentWidth( - regularExtraPadding: Dp = LocalDimensions.current.mediumSpacing, - smallExtraPadding: Dp = LocalDimensions.current.xsSpacing, -): Modifier { - val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp - - return this.widthIn(max = LocalDimensions.current.maxContentWidth) - .padding( - horizontal = when { - screenWidthDp < 380.dp -> smallExtraPadding - else -> regularExtraPadding - } +fun AnimatedGradientDrawable( + @DrawableRes vectorRes: Int, + modifier: Modifier = Modifier, + gradientColors: List = listOf( + primaryGreen, primaryBlue, primaryPurple, + primaryPink, primaryRed, primaryOrange, primaryYellow + ) +) { + val infiniteTransition = rememberInfiniteTransition(label = "vector_vertical") + val animatedOffset by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 200f, + animationSpec = infiniteRepeatable( + animation = tween(3000, easing = LinearEasing), + repeatMode = RepeatMode.Restart ) + ) + + Icon( + painter = painterResource(id = vectorRes), + contentDescription = null, + modifier = modifier + .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } + .drawWithContent { + val gradientBrush = Brush.linearGradient( + colors = gradientColors, + start = Offset(0f, animatedOffset), + end = Offset(0f, animatedOffset + 100f), + tileMode = TileMode.Mirror + ) + + drawContent() + drawRect( + brush = gradientBrush, + blendMode = BlendMode.SrcAtop + ) + } + ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Modifiers.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Modifiers.kt new file mode 100644 index 0000000000..4c76695858 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Modifiers.kt @@ -0,0 +1,320 @@ +package org.thoughtcrime.securesms.ui + +import androidx.annotation.StringRes +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.StartOffset +import androidx.compose.animation.core.StartOffsetType +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.keyframes +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay +import org.thoughtcrime.securesms.ui.theme.LocalColors +import org.thoughtcrime.securesms.ui.theme.LocalDimensions + + +/** + * This is used to set the test tag that the QA team can use to retrieve an element in appium + * In order to do so we need to set the testTagsAsResourceId to true, which ideally should be done only once + * in the root composable, but our app is currently made up of multiple isolated composables + * set up in the old activity/fragment view system + * As such we need to repeat it for every component that wants to use testTag, until such + * a time as we have one root composable + */ +@Composable +fun Modifier.qaTag(tag: String?): Modifier { + if (tag == null) return this + return this.semantics { testTagsAsResourceId = true }.testTag(tag) +} + +@Composable +fun Modifier.qaTag(@StringRes tagResId: Int?): Modifier { + if (tagResId == null) return this + return this.semantics { testTagsAsResourceId = true }.testTag(stringResource(tagResId)) +} + +@Composable +fun Modifier.border( + shape: Shape = MaterialTheme.shapes.small +) = this.border( + width = LocalDimensions.current.borderStroke, + brush = SolidColor(LocalColors.current.borders), + shape = shape +) + + +@Composable +fun Modifier.contentDescription(text: GetString?): Modifier { + return text?.let { + val context = LocalContext.current + semantics { contentDescription = it(context) } + } ?: this +} + +@Composable +fun Modifier.contentDescription(@StringRes id: Int?): Modifier { + val context = LocalContext.current + return id?.let { semantics { contentDescription = context.getString(it) } } ?: this +} + +@Composable +fun Modifier.contentDescription(text: String?): Modifier { + return text?.let { semantics { contentDescription = it } } ?: this +} + +/** + * Applies an opinionated safety width on content based our design decisions: + * - Max width of maxContentWidth + * - Extra horizontal padding + * - Smaller extra padding for small devices (arbitrarily decided as devices below 380 width + */ +@Composable +fun Modifier.safeContentWidth( + regularExtraPadding: Dp = LocalDimensions.current.mediumSpacing, + smallExtraPadding: Dp = LocalDimensions.current.xsSpacing, +): Modifier { + val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp + + return this + .widthIn(max = LocalDimensions.current.maxContentWidth) + .padding( + horizontal = when { + screenWidthDp < 380.dp -> smallExtraPadding + else -> regularExtraPadding + } + ) +} + +// Permanently visible vertical scrollbar. +// Note: This scrollbar modifier was adapted from Mardann's fantastic solution at: https://stackoverflow.com/a/78453760/24337669 +@Composable +fun Modifier.verticalScrollbar( + state: ScrollState, + scrollbarWidth: Dp = 6.dp, + barColour: Color = LocalColors.current.textSecondary, + backgroundColour: Color = LocalColors.current.borders, + edgePadding: Dp = LocalDimensions.current.xxsSpacing +): Modifier { + // Calculate the viewport and content heights + val viewHeight = state.viewportSize.toFloat() + val contentHeight = state.maxValue + viewHeight + + // Determine if the scrollbar is needed + val isScrollbarNeeded = contentHeight > viewHeight + + // Set the target alpha based on whether scrolling is possible + val alphaTarget = when { + !isScrollbarNeeded -> 0f // No scrollbar needed, set alpha to 0f + state.isScrollInProgress -> 1f + else -> 0.2f + } + + // Animate the alpha value smoothly + val alpha by animateFloatAsState( + targetValue = alphaTarget, + animationSpec = tween(400, delayMillis = if (state.isScrollInProgress) 0 else 700), + label = "VerticalScrollbarAnimation" + ) + + return this.then(Modifier.drawWithContent { + drawContent() + + // Only proceed if the scrollbar is needed + if (isScrollbarNeeded) { + val minScrollBarHeight = 10.dp.toPx() + val maxScrollBarHeight = viewHeight + val scrollbarHeight = (viewHeight * (viewHeight / contentHeight)).coerceIn( + minOf(minScrollBarHeight, maxScrollBarHeight)..maxOf(minScrollBarHeight, maxScrollBarHeight) + ) + val variableZone = viewHeight - scrollbarHeight + val scrollbarYoffset = (state.value.toFloat() / state.maxValue) * variableZone + + // Calculate the horizontal offset with padding + val scrollbarXOffset = size.width - scrollbarWidth.toPx() - edgePadding.toPx() + + // Draw the missing section of the scrollbar track + drawRoundRect( + color = backgroundColour, + topLeft = Offset(scrollbarXOffset, 0f), + size = Size(scrollbarWidth.toPx(), viewHeight), + cornerRadius = CornerRadius(scrollbarWidth.toPx() / 2), + alpha = alpha + ) + + // Draw the scrollbar thumb + drawRoundRect( + color = barColour, + topLeft = Offset(scrollbarXOffset, scrollbarYoffset), + size = Size(scrollbarWidth.toPx(), scrollbarHeight), + cornerRadius = CornerRadius(scrollbarWidth.toPx() / 2), + alpha = alpha + ) + } + }) +} + + +/** + * Creates a shimmer overlay effect that renders on top of existing content + * @param color The base shimmer color (usually semi-transparent) + * @param highlightColor The highlight color that moves across + * @param animationDuration Duration of one shimmer cycle in milliseconds + * @param delayBetweenCycles Delay between animation cycles in milliseconds (0 = continuous) + * @param initialDelay Delay before the very first animation starts in milliseconds (0 = start immediately) + */ +fun Modifier.shimmerOverlay( + color: Color = Color.White.copy(alpha = 0.0f), + highlightColor: Color = Color.White.copy(alpha = 0.6f), + animationDuration: Int = 1200, + delayBetweenCycles: Int = 3000, + initialDelay: Int = 0 +): Modifier = composed { + // Single transition with a one-off start delay + val progress by rememberInfiniteTransition(label = "shimmer") + .animateFloat( + initialValue = 0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = keyframes { + durationMillis = animationDuration + delayBetweenCycles + 0f at 0 + 1f at animationDuration + 1f at animationDuration + delayBetweenCycles + }, + initialStartOffset = StartOffset( + initialDelay, + StartOffsetType.Delay + ), + repeatMode = RepeatMode.Restart + ), + label = "shimmerProgress" + ) + + graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen + } + .drawWithCache { + // Work this out once per size change, not every frame + val diagonal = kotlin.math.hypot(size.width, size.height) + val bandWidth = diagonal * 0.30f + val travelDist = size.width + bandWidth * 2 + + onDrawWithContent { + drawContent() + + // Map 0-1 progress → current band centre + val centre = -bandWidth + progress * travelDist + + val brush = Brush.linearGradient( + colors = listOf( + Color.Transparent, + color, + highlightColor, + color, + Color.Transparent + ), + start = Offset(centre - bandWidth, centre - bandWidth), + end = Offset(centre + bandWidth, centre + bandWidth) + ) + + drawRect( + brush = brush, + size = size, + blendMode = BlendMode.SrcAtop // only where there’s content + ) + } + } +} + +private fun createShimmerBrush( + progress: Float, + color: Color, + highlightColor: Color, + width: Float, + height: Float +): Brush { + // Calculate the diagonal distance to ensure shimmer covers the entire component + val diagonal = kotlin.math.sqrt(width * width + height * height) + val shimmerWidth = diagonal * 0.3f // Width of the shimmer band + + // Start completely off-screen (left side) and end completely off-screen (right side) + val totalDistance = width + shimmerWidth * 2 + val currentPosition = -shimmerWidth + (progress * totalDistance) + + return Brush.linearGradient( + colors = listOf( + Color.Transparent, + color, + highlightColor, + color, + Color.Transparent + ), + start = Offset( + x = currentPosition - shimmerWidth, + y = currentPosition - shimmerWidth + ), + end = Offset( + x = currentPosition + shimmerWidth, + y = currentPosition + shimmerWidth + ) + ) +} + +private fun DrawScope.drawShimmerOverlay( + progress: Float, + color: Color, + highlightColor: Color +) { + val shimmerBrush = createShimmerBrush( + progress = progress, + color = color, + highlightColor = highlightColor, + width = size.width, + height = size.height + ) + + // Use SrcAtop blend mode to only draw shimmer where content already exists + drawRect( + brush = shimmerBrush, + size = size, + blendMode = BlendMode.SrcAtop + ) +} + diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Util.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Util.kt index f428728d14..b987c831a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Util.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Util.kt @@ -5,7 +5,6 @@ import android.content.Context import android.content.ContextWrapper import android.view.View import android.view.ViewTreeObserver -import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.core.FiniteAnimationSpec @@ -17,10 +16,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner @@ -83,26 +78,6 @@ inline fun T.afterMeasured(crossinline block: T.() -> Unit) { }) } -/** - * This is used to set the test tag that the QA team can use to retrieve an element in appium - * In order to do so we need to set the testTagsAsResourceId to true, which ideally should be done only once - * in the root composable, but our app is currently made up of multiple isolated composables - * set up in the old activity/fragment view system - * As such we need to repeat it for every component that wants to use testTag, until such - * a time as we have one root composable - */ -@Composable -fun Modifier.qaTag(tag: String?): Modifier { - if (tag == null) return this - return this.semantics { testTagsAsResourceId = true }.testTag(tag) -} - -@Composable -fun Modifier.qaTag(@StringRes tagResId: Int?): Modifier { - if (tagResId == null) return this - return this.semantics { testTagsAsResourceId = true }.testTag(stringResource(tagResId)) -} - /** * helper function to observe flows as events properly * Including not losing events when the lifecycle gets destroyed by using Dispatchers.Main.immediate diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/AnnotatedString.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/AnnotatedString.kt index aabbcae6e0..7aedf5027e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/AnnotatedString.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/AnnotatedString.kt @@ -61,7 +61,7 @@ private fun resources(): Resources { @Composable fun annotatedStringResource( @StringRes id: Int, - highlightColor: Color = LocalColors.current.primary + highlightColor: Color = LocalColors.current.accent ): AnnotatedString { val resources = resources() val density = LocalDensity.current @@ -74,7 +74,7 @@ fun annotatedStringResource( @Composable fun annotatedStringResource( text: CharSequence, - highlightColor: Color = LocalColors.current.primary + highlightColor: Color = LocalColors.current.accent ): AnnotatedString { val density = LocalDensity.current return remember(text.hashCode()) { @@ -260,7 +260,7 @@ fun inlineContentMap(textSize: TextUnit = 15.sp) = mapOf( ) { Image( painter = painterResource(id = R.drawable.ic_square_arrow_up_right), - colorFilter = ColorFilter.tint(LocalColors.current.primaryText), + colorFilter = ColorFilter.tint(LocalColors.current.accentText), contentDescription = null, modifier = Modifier.fillMaxSize() ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Border.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Border.kt deleted file mode 100644 index 87e1aae271..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Border.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.thoughtcrime.securesms.ui.components - -import androidx.compose.foundation.border -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.SolidColor -import org.thoughtcrime.securesms.ui.theme.LocalDimensions -import org.thoughtcrime.securesms.ui.theme.LocalColors - -@Composable -fun Modifier.border( - shape: Shape = MaterialTheme.shapes.small -) = this.border( - width = LocalDimensions.current.borderStroke, - brush = SolidColor(LocalColors.current.borders), - shape = shape -) \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Button.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Button.kt index 3524cef9fd..344c696bfd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Button.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Button.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.min import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter @@ -116,24 +115,33 @@ fun Button( Button(text, onClick, ButtonType.Fill, modifier, enabled) } -@Composable fun PrimaryFillButton(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { - Button(text, onClick, ButtonType.PrimaryFill, modifier, enabled) +@Composable fun AccentFillButton(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { + Button(text, onClick, ButtonType.AccentFill, modifier, enabled) } -@Composable fun PrimaryFillButtonRect(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { - Button(text, onClick, ButtonType.PrimaryFill, modifier, enabled, shape = sessionShapes().extraSmall) +@Composable fun FillButtonRect(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { + Button(text, onClick, ButtonType.Fill, modifier, enabled, shape = sessionShapes().extraSmall) } -@Composable fun PrimaryFillButtonRect(modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit) { - Button(onClick = onClick, ButtonType.PrimaryFill, modifier = modifier, enabled = enabled, shape = sessionShapes().extraSmall, content = content) +@Composable fun TertiaryFillButtonRect(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { + Button(text, onClick, ButtonType.TertiaryFill, modifier, enabled, shape = sessionShapes().extraSmall) +} + +@Composable fun AccentFillButtonRect(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { + Button(text, onClick, ButtonType.AccentFill, modifier, enabled, shape = sessionShapes().extraSmall) + +} + +@Composable fun AccentFillButtonRect(modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit) { + Button(onClick = onClick, ButtonType.AccentFill, modifier = modifier, enabled = enabled, shape = sessionShapes().extraSmall, content = content) } @Composable fun OutlineButton(text: String, modifier: Modifier = Modifier, color: Color = LocalColors.current.text, enabled: Boolean = true, onClick: () -> Unit) { Button(text, onClick, ButtonType.Outline(color), modifier, enabled) } -@Composable fun PrimaryOutlineButtonRect(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { - Button(text, onClick, ButtonType.Outline(LocalColors.current.primaryText), modifier, enabled, shape = sessionShapes().extraSmall) +@Composable fun AccentOutlineButtonRect(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { + Button(text, onClick, ButtonType.Outline(LocalColors.current.accentText), modifier, enabled, shape = sessionShapes().extraSmall) } @Composable fun OutlineButton(modifier: Modifier = Modifier, color: Color = LocalColors.current.text, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit) { @@ -146,13 +154,13 @@ fun Button( ) } -@Composable fun PrimaryOutlineButton(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, - minWidth: Dp = LocalDimensions.current.minButtonWidth, onClick: () -> Unit) { - Button(text, onClick, ButtonType.Outline(LocalColors.current.primaryText), modifier, enabled, minWidth = minWidth) +@Composable fun AccentOutlineButton(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, + minWidth: Dp = LocalDimensions.current.minButtonWidth, onClick: () -> Unit) { + Button(text, onClick, ButtonType.Outline(LocalColors.current.accentText), modifier, enabled, minWidth = minWidth) } -@Composable fun PrimaryOutlineButton(modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit) { - Button(onClick, ButtonType.Outline(LocalColors.current.primaryText), modifier, enabled, content = content) +@Composable fun AccentOutlineButton(modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit) { + Button(onClick, ButtonType.Outline(LocalColors.current.accentText), modifier, enabled, content = content) } @Composable fun SlimOutlineButton(modifier: Modifier = Modifier, color: Color = LocalColors.current.text, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit) { @@ -166,17 +174,17 @@ fun Button( Button(text, onClick, ButtonType.Outline(color), modifier, enabled, ButtonStyle.Slim) } -@Composable fun SlimPrimaryOutlineButton(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { - Button(text, onClick, ButtonType.Outline(LocalColors.current.primaryText), modifier, enabled, ButtonStyle.Slim) +@Composable fun SlimAccentOutlineButton(text: String, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) { + Button(text, onClick, ButtonType.Outline(LocalColors.current.accentText), modifier, enabled, ButtonStyle.Slim) } @Composable -fun PrimaryOutlineCopyButton( +fun AcccentOutlineCopyButton( modifier: Modifier = Modifier, style: ButtonStyle = ButtonStyle.Large, onClick: () -> Unit ) { - OutlineCopyButton(modifier, style, LocalColors.current.primaryText, onClick) + OutlineCopyButton(modifier, style, LocalColors.current.accentText, onClick) } @Composable @@ -328,24 +336,27 @@ private fun VariousButtons( verticalArrangement = Arrangement.spacedBy(8.dp), maxItemsInEachRow = 2 ) { - PrimaryFillButton("Primary Fill") {} - PrimaryFillButton("Primary Fill Disabled", enabled = false) {} + AccentFillButton("Accent Fill") {} + AccentFillButton("Accent Fill Disabled", enabled = false) {} FillButton("Fill Button") {} FillButton("Fill Button Disabled", enabled = false) {} - PrimaryOutlineButton("Primary Outline Button") {} - PrimaryOutlineButton("Primary Outline Disabled", enabled = false) {} + AccentOutlineButton("Accent Outline Button") {} + AccentOutlineButton("Accent Outline Disabled", enabled = false) {} OutlineButton("Outline Button") {} OutlineButton("Outline Button Disabled", enabled = false) {} SlimOutlineButton("Slim Outline") {} SlimOutlineButton("Slim Outline Disabled", enabled = false) {} - SlimPrimaryOutlineButton("Slim Primary") {} + SlimAccentOutlineButton("Slim Accent") {} SlimOutlineButton("Slim Danger", color = LocalColors.current.danger) {} BorderlessButton("Borderless Button") {} BorderlessButton("Borderless Secondary", color = LocalColors.current.textSecondary) {} - PrimaryFillButtonRect("Primary Fill Rect") {} - PrimaryFillButtonRect("Primary Fill Rect Disabled", enabled = false) {} - PrimaryOutlineButtonRect("Outline Button Rect") {} - PrimaryOutlineButtonRect("Outline ButtonDisabled", enabled = false) {} + FillButtonRect("Fill Rect") {} + FillButtonRect("Fill Rect Disabled", enabled = false) {} + TertiaryFillButtonRect("Tertiary Fill Rect") {} + AccentFillButtonRect("Accent Fill Rect") {} + AccentFillButtonRect("Accent Fill Rect Disabled", enabled = false) {} + AccentOutlineButtonRect("Outline Button Rect") {} + AccentOutlineButtonRect("Outline ButtonDisabled", enabled = false) {} } } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/ButtonType.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/ButtonType.kt index f267aa9466..2453c4663c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/ButtonType.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/ButtonType.kt @@ -53,13 +53,25 @@ interface ButtonType { ) } - object PrimaryFill: ButtonType { + object AccentFill: ButtonType { @Composable override fun border(enabled: Boolean) = if (enabled) null else disabledBorder @Composable override fun buttonColors() = ButtonDefaults.buttonColors( - contentColor = LocalColors.current.primaryButtonFillText, - containerColor = LocalColors.current.primary, + contentColor = LocalColors.current.accentButtonFillText, + containerColor = LocalColors.current.accent, + disabledContentColor = LocalColors.current.disabled, + disabledContainerColor = Color.Transparent + ) + } + + object TertiaryFill: ButtonType { + @Composable + override fun border(enabled: Boolean) = if (enabled) null else disabledBorder + @Composable + override fun buttonColors() = ButtonDefaults.buttonColors( + contentColor = LocalColors.current.text, + containerColor = LocalColors.current.backgroundTertiary, disabledContentColor = LocalColors.current.disabled, disabledContainerColor = Color.Transparent ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/DropDown.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/DropDown.kt index de7f437356..f2608d7cb5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/DropDown.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/DropDown.kt @@ -60,10 +60,10 @@ fun DropDown( errorIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, - disabledTrailingIconColor = LocalColors.current.primary, - errorTrailingIconColor = LocalColors.current.primary, - focusedTrailingIconColor = LocalColors.current.primary, - unfocusedTrailingIconColor = LocalColors.current.primary, + disabledTrailingIconColor = LocalColors.current.accent, + errorTrailingIconColor = LocalColors.current.accent, + focusedTrailingIconColor = LocalColors.current.accent, + unfocusedTrailingIconColor = LocalColors.current.accent, disabledTextColor = LocalColors.current.text, errorTextColor = LocalColors.current.text, focusedTextColor = LocalColors.current.text, diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/QR.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/QR.kt index 4cd5ef363f..12921f5f72 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/QR.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/QR.kt @@ -2,9 +2,7 @@ package org.thoughtcrime.securesms.ui.components import android.Manifest import android.annotation.SuppressLint -import android.app.Activity import android.content.Intent -import android.content.pm.PackageManager import android.net.Uri import android.provider.Settings import androidx.camera.core.CameraSelector @@ -47,8 +45,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState @@ -68,7 +64,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.findActivity import org.thoughtcrime.securesms.ui.getSubbedString @@ -120,7 +116,7 @@ fun QRScannerScreen( textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(LocalDimensions.current.spacing)) - PrimaryOutlineButton( + AccentOutlineButton( stringResource(R.string.cameraGrantAccess), modifier = Modifier.fillMaxWidth(), onClick = { @@ -150,11 +146,11 @@ fun QRScannerScreen( text = context.getSubbedString(R.string.permissionsCameraDenied, APP_NAME_KEY to context.getString(R.string.app_name)), buttons = listOf( - DialogButtonModel( + DialogButtonData( text = GetString(stringResource(id = R.string.sessionSettings)), onClick = onClickSettings ), - DialogButtonModel( + DialogButtonData( GetString(stringResource(R.string.cancel)) ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/RadioButton.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/RadioButton.kt index 99b8b98aa5..5aed11093a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/RadioButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/RadioButton.kt @@ -14,14 +14,12 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -30,7 +28,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview @@ -41,7 +38,6 @@ import network.loki.messenger.libsession_util.util.ExpiryMode import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.RadioOption -import org.thoughtcrime.securesms.ui.contentDescription import org.thoughtcrime.securesms.ui.qaTag import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -119,7 +115,7 @@ fun RadioButtonIndicator( modifier = Modifier .fillMaxSize() .background( - color = if (enabled) LocalColors.current.primary else LocalColors.current.disabled, + color = if (enabled) LocalColors.current.accent else LocalColors.current.disabled, shape = CircleShape ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/SessionSwitch.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/SessionSwitch.kt index ce40fc2a43..baccc515a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/SessionSwitch.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/SessionSwitch.kt @@ -21,8 +21,8 @@ fun SessionSwitch( onCheckedChange = onCheckedChange, enabled = enabled, colors = SwitchDefaults.colors( - checkedThumbColor = LocalColors.current.primary, - checkedTrackColor = LocalColors.current.primary.copy(alpha = 0.3f), + checkedThumbColor = LocalColors.current.accent, + checkedTrackColor = LocalColors.current.accent.copy(alpha = 0.3f), uncheckedThumbColor = LocalColors.current.disabled, uncheckedTrackColor = LocalColors.current.disabled.copy(alpha = 0.3f), uncheckedBorderColor = Color.Transparent, diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/SessionTabRow.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/SessionTabRow.kt index 5dae714380..632cb49b0c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/SessionTabRow.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/SessionTabRow.kt @@ -38,7 +38,7 @@ fun SessionTabRow(pagerState: PagerState, titles: List) { indicator = { tabPositions -> TabRowDefaults.SecondaryIndicator( Modifier.tabIndicatorOffset(tabPositions[pagerState.currentPage]), - color = LocalColors.current.primary, + color = LocalColors.current.accent, height = LocalDimensions.current.indicatorHeight ) }, diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/Dimensions.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/Dimensions.kt index 1af7cfb22c..4580cc1fc3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/Dimensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/Dimensions.kt @@ -11,6 +11,7 @@ data class Dimensions( val xxsSpacing: Dp = 8.dp, val xsSpacing: Dp = 12.dp, val smallSpacing: Dp = 16.dp, + val contentSpacing: Dp = 20.dp, val spacing: Dp = 24.dp, val mediumSpacing: Dp = 36.dp, val xlargeSpacing: Dp = 64.dp, diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeColors.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeColors.kt index 8d19c6c16c..e2fd221faa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeColors.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeColors.kt @@ -17,14 +17,15 @@ import androidx.compose.ui.tooling.preview.PreviewParameter interface ThemeColors { // properties to override for each theme val isLight: Boolean - val primary: Color - val onInvertedBackgroundPrimary: Color + val accent: Color + val onInvertedBackgroundAccent: Color val textAlert: Color val danger: Color val warning: Color val disabled: Color val background: Color val backgroundSecondary: Color + val backgroundTertiary: Color val text: Color val textSecondary: Color val borders: Color @@ -33,15 +34,15 @@ interface ThemeColors { val textBubbleReceived: Color val qrCodeContent: Color val qrCodeBackground: Color - val primaryButtonFillText: Color - val primaryText: Color + val accentButtonFillText: Color + val accentText: Color } // extra functions and properties that work for all themes val ThemeColors.textSelectionColors get() = TextSelectionColors( - handleColor = primary, - backgroundColor = primary.copy(alpha = 0.5f) + handleColor = accent, + backgroundColor = accent.copy(alpha = 0.5f) ) fun ThemeColors.text(isError: Boolean): Color = if (isError) danger else text @@ -101,22 +102,23 @@ fun dangerButtonColors() = ButtonDefaults.buttonColors( ) @Composable -fun primaryTextButtonColors() = ButtonDefaults.buttonColors( +fun accentTextButtonColors() = ButtonDefaults.buttonColors( containerColor = Color.Transparent, - contentColor = LocalColors.current.primaryText, + contentColor = LocalColors.current.accentText, disabledContainerColor = Color.Transparent, disabledContentColor = LocalColors.current.disabled ) // Our themes -data class ClassicDark(override val primary: Color = primaryGreen) : ThemeColors { +data class ClassicDark(override val accent: Color = primaryGreen) : ThemeColors { override val isLight = false override val danger = dangerDark override val warning = primaryOrange override val disabled = disabledDark override val background = classicDark0 override val backgroundSecondary = classicDark1 - override val onInvertedBackgroundPrimary = background + override val backgroundTertiary = classicDark2 + override val onInvertedBackgroundAccent = background override val text = classicDark6 override val textSecondary = classicDark5 override val borders = classicDark3 @@ -125,19 +127,20 @@ data class ClassicDark(override val primary: Color = primaryGreen) : ThemeColors override val textBubbleReceived = Color.White override val qrCodeContent = background override val qrCodeBackground = text - override val primaryButtonFillText = Color.Black - override val primaryText = primary + override val accentButtonFillText = Color.Black + override val accentText = accent override val textAlert: Color = classicDark0 } -data class ClassicLight(override val primary: Color = primaryGreen) : ThemeColors { +data class ClassicLight(override val accent: Color = primaryGreen) : ThemeColors { override val isLight = true override val danger = dangerLight override val warning = rust override val disabled = disabledLight override val background = classicLight6 override val backgroundSecondary = classicLight5 - override val onInvertedBackgroundPrimary = primary + override val backgroundTertiary = classicLight4 + override val onInvertedBackgroundAccent = accent override val text = classicLight0 override val textSecondary = classicLight1 override val borders = classicLight3 @@ -146,19 +149,20 @@ data class ClassicLight(override val primary: Color = primaryGreen) : ThemeColor override val textBubbleReceived = classicLight4 override val qrCodeContent = text override val qrCodeBackground = backgroundSecondary - override val primaryButtonFillText = Color.Black - override val primaryText = text + override val accentButtonFillText = Color.Black + override val accentText = text override val textAlert: Color = classicLight0 } -data class OceanDark(override val primary: Color = primaryBlue) : ThemeColors { +data class OceanDark(override val accent: Color = primaryBlue) : ThemeColors { override val isLight = false override val danger = dangerDark override val warning = primaryOrange override val disabled = disabledDark override val background = oceanDark2 override val backgroundSecondary = oceanDark1 - override val onInvertedBackgroundPrimary = background + override val backgroundTertiary = oceanDark0 + override val onInvertedBackgroundAccent = background override val text = oceanDark7 override val textSecondary = oceanDark5 override val borders = oceanDark4 @@ -167,19 +171,20 @@ data class OceanDark(override val primary: Color = primaryBlue) : ThemeColors { override val textBubbleReceived = oceanDark4 override val qrCodeContent = background override val qrCodeBackground = text - override val primaryButtonFillText = Color.Black - override val primaryText = primary + override val accentButtonFillText = Color.Black + override val accentText = accent override val textAlert: Color = oceanDark0 } -data class OceanLight(override val primary: Color = primaryBlue) : ThemeColors { +data class OceanLight(override val accent: Color = primaryBlue) : ThemeColors { override val isLight = true override val danger = dangerLight override val warning = rust override val disabled = disabledLight override val background = oceanLight7 override val backgroundSecondary = oceanLight6 - override val onInvertedBackgroundPrimary = background + override val backgroundTertiary = oceanLight5 + override val onInvertedBackgroundAccent = background override val text = oceanLight1 override val textSecondary = oceanLight2 override val borders = oceanLight3 @@ -188,8 +193,8 @@ data class OceanLight(override val primary: Color = primaryBlue) : ThemeColors { override val textBubbleReceived = oceanLight1 override val qrCodeContent = text override val qrCodeBackground = backgroundSecondary - override val primaryButtonFillText = Color.Black - override val primaryText = text + override val accentButtonFillText = Color.Black + override val accentText = text override val textAlert: Color = oceanLight0 } @@ -204,11 +209,11 @@ fun PreviewThemeColors( @Composable private fun ThemeColors() { Column { - Box(Modifier.background(LocalColors.current.primary)) { - Text("primary", style = LocalType.current.base) + Box(Modifier.background(LocalColors.current.accent)) { + Text("accent", style = LocalType.current.base) } - Box(Modifier.background(LocalColors.current.primaryText)) { - Text("primaryText", style = LocalType.current.base) + Box(Modifier.background(LocalColors.current.accentText)) { + Text("accentText", style = LocalType.current.base) } Box(Modifier.background(LocalColors.current.background)) { Text("background", style = LocalType.current.base) @@ -216,6 +221,9 @@ private fun ThemeColors() { Box(Modifier.background(LocalColors.current.backgroundSecondary)) { Text("backgroundSecondary", style = LocalType.current.base) } + Box(Modifier.background(LocalColors.current.backgroundTertiary)) { + Text("backgroundTertiary", style = LocalType.current.base) + } Box(Modifier.background(LocalColors.current.text)) { Text("text", style = LocalType.current.base) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeFromPreferences.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeFromPreferences.kt index 403235ef79..326c2d8d49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeFromPreferences.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeFromPreferences.kt @@ -18,8 +18,8 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.YELLOW_A fun TextSecurePreferences.getColorsProvider(): ThemeColorsProvider { val selectedTheme = getThemeStyle() - // get the chosen primary color from the preferences - val selectedPrimary = primaryColor() + // get the chosen accent color from the preferences + val selectedAccent = accentColor() val isOcean = "ocean" in selectedTheme @@ -28,15 +28,15 @@ fun TextSecurePreferences.getColorsProvider(): ThemeColorsProvider { return when { getFollowSystemSettings() -> FollowSystemThemeColorsProvider( - light = createLight(selectedPrimary), - dark = createDark(selectedPrimary) + light = createLight(selectedAccent), + dark = createDark(selectedAccent) ) - "light" in selectedTheme -> ThemeColorsProvider(createLight(selectedPrimary)) - else -> ThemeColorsProvider(createDark(selectedPrimary)) + "light" in selectedTheme -> ThemeColorsProvider(createLight(selectedAccent)) + else -> ThemeColorsProvider(createDark(selectedAccent)) } } -fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) { +fun TextSecurePreferences.accentColor(): Color = when(getSelectedAccentColor()) { BLUE_ACCENT -> primaryBlue PURPLE_ACCENT -> primaryPurple PINK_ACCENT -> primaryPink diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CharacterCalculator.java b/app/src/main/java/org/thoughtcrime/securesms/util/CharacterCalculator.java deleted file mode 100644 index 08080ffc3f..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CharacterCalculator.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (C) 2015 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.util; - -import android.os.Parcel; -import androidx.annotation.NonNull; - -public abstract class CharacterCalculator { - - public abstract CharacterState calculateCharacters(String messageBody); - - public static void writeToParcel(@NonNull Parcel dest, @NonNull CharacterCalculator calculator) { - if (calculator instanceof SmsCharacterCalculator) { - dest.writeInt(1); - } else if (calculator instanceof MmsCharacterCalculator) { - dest.writeInt(2); - } else if (calculator instanceof PushCharacterCalculator) { - dest.writeInt(3); - } else { - throw new IllegalArgumentException("Tried to write an unsupported calculator to a parcel."); - } - } - - public static class CharacterState { - public final int charactersRemaining; - public final int messagesSpent; - public final int maxTotalMessageSize; - public final int maxPrimaryMessageSize; - - public CharacterState(int messagesSpent, int charactersRemaining, int maxTotalMessageSize, int maxPrimaryMessageSize) { - this.messagesSpent = messagesSpent; - this.charactersRemaining = charactersRemaining; - this.maxTotalMessageSize = maxTotalMessageSize; - this.maxPrimaryMessageSize = maxPrimaryMessageSize; - } - } -} - diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ClearDataUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ClearDataUtils.kt index 024d73d825..47a0a166fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ClearDataUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ClearDataUtils.kt @@ -21,6 +21,7 @@ import org.session.libsignal.utilities.hexEncodedPublicKey import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.KeyPairUtilities import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.logging.PersistentLogger import org.thoughtcrime.securesms.migration.DatabaseMigrationManager class ClearDataUtils @Inject constructor( @@ -29,6 +30,7 @@ class ClearDataUtils @Inject constructor( private val tokenFetcher: TokenFetcher, private val storage: Storage, private val prefs: TextSecurePreferences, + private val persistentLogger: PersistentLogger, ) { // Method to clear the local data - returns true on success otherwise false @SuppressLint("ApplySharedPref") @@ -50,6 +52,8 @@ class ClearDataUtils @Inject constructor( application.getSharedPreferences(ApplicationContext.PREFERENCES_NAME, 0).edit(commit = true) { clear() } configFactory.clearAll() + persistentLogger.deleteAllLogs() + // The token deletion is nice but not critical, so don't let it block the rest of the process runCatching { tokenFetcher.resetToken() diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt index a693998ad3..709af67dc9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt @@ -38,6 +38,8 @@ class DateUtils @Inject constructor( private val twelveHourFormat = "h:mm a" private val defaultDateTimeFormat = "d MMM YYYY hh:mm a" + private val messageDateTimeFormat = "h:mm a EEE, MM/dd/yyyy" + // System defaults and patterns private val systemDefaultPattern by lazy { DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMdd") @@ -60,8 +62,13 @@ class DateUtils @Inject constructor( textSecurePreferences.setStringPreference(DATE_FORMAT_PREF, value) } + // The user time format is the one chosen by the user,if they chose one from the ui (not yet available but coming later) + // Or we check for the system preference setting for 12 vs 24h format private var userTimeFormat: String - get() = textSecurePreferences.getStringPreference(TIME_FORMAT_PREF, defaultTimeFormat)!! + get() = textSecurePreferences.getStringPreference( + TIME_FORMAT_PREF, + if (DateFormat.is24HourFormat(context)) defaultTimeFormat else twelveHourFormat + )!! private set(value) { textSecurePreferences.setStringPreference(TIME_FORMAT_PREF, value) } @@ -149,7 +156,7 @@ class DateUtils @Inject constructor( // Note: Date patterns are in TR-35 format. // See: https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns - fun getDisplayFormattedTimeSpanString(locale: Locale, timestamp: Long): String = + fun getDisplayFormattedTimeSpanString(timestamp: Long, locale: Locale = Locale.getDefault()): String = when { // If it's within the last 24 hours we just give the time in 24-hour format, such as "13:27" for 1:27pm isToday(timestamp) -> formatTime(timestamp, userTimeFormat, locale) @@ -169,6 +176,8 @@ class DateUtils @Inject constructor( fun getMediumDateTimeFormatter(): DateTimeFormatter = DateTimeFormatter.ofPattern(defaultDateTimeFormat) + fun getMessageDateTimeFormattedString(timestamp: Long): String = getLocaleFormattedDate(timestamp, messageDateTimeFormat) + // Method to get the String for a relative day in a locale-aware fashion, including using the // auto-localised words for "today" and "yesterday" as appropriate. fun getRelativeDate(locale: Locale, timestamp: Long): String = diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FileProviderUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/FileProviderUtil.java index 0cf28b6f81..4fa1caabcc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FileProviderUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FileProviderUtil.java @@ -11,6 +11,7 @@ public class FileProviderUtil { public static final String AUTHORITY = "network.loki.securesms.fileprovider"; + @NonNull public static Uri getUriFor(@NonNull Context context, @NonNull File file) { return FileProvider.getUriForFile(context, AUTHORITY, file); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FilenameUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/FilenameUtils.kt index 3f68f3a3ec..9476844413 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FilenameUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FilenameUtils.kt @@ -23,7 +23,7 @@ object FilenameUtils { // Filename for when we create a new voice message @JvmStatic - fun constructNewVoiceMessageFilename(context: Context): String = context.getString(R.string.app_name) + "-" + context.getString(R.string.messageVoice).replace(" ", "") + "_${getFormattedDate()}" + ".aac" + fun constructNewVoiceMessageFilename(context: Context): String = context.getString(R.string.app_name) + "-" + context.getString(R.string.messageVoice).replace(" ", "") + "_${getFormattedDate()}" + ".mp4" // Method to synthesize a suitable filename for a voice message that we have been sent. // Note: If we have a file as an attachment then it has a `isVoiceNote` property which @@ -123,7 +123,7 @@ object FilenameUtils { if (extractedFilename.isNullOrEmpty()) { if (attachment == null) { - val timestamp = if (uri?.path.isNullOrEmpty()) null else getTimestampFromUri(uri.path!!) + val timestamp = if (uri?.path.isNullOrEmpty()) null else getTimestampFromUri(uri!!.path!!) extractedFilename = constructFallbackMediaFilenameFromMimeType(context, mimeType, timestamp) } else { // If the mimetype is audio then we generate a filename which contain "VoiceMessage" or "Audio" diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt index 9fbac1ab90..0ec305fab6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt @@ -1,8 +1,6 @@ package org.thoughtcrime.securesms.util import android.content.res.Resources -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat import androidx.recyclerview.widget.RecyclerView import kotlin.math.roundToInt @@ -24,36 +22,17 @@ fun toDp(px: Float, resources: Resources): Float { return (px / scale) } -val RecyclerView.isScrolledToBottom: Boolean +/** + * Returns true if the recyclerview is scrolled within 50dp of the bottom + */ +val RecyclerView.isNearBottom: Boolean get() = computeVerticalScrollOffset().coerceAtLeast(0) + computeVerticalScrollExtent() + toPx(50, resources) >= computeVerticalScrollRange() -val RecyclerView.isScrolledToWithin30dpOfBottom: Boolean - get() { - // Retrieve the bottom inset from the window insets, if available. - val bottomInset = ViewCompat.getRootWindowInsets(this) - ?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom ?: 0 - - return computeVerticalScrollOffset().coerceAtLeast(0) + - computeVerticalScrollExtent() + - toPx(30, resources) + - bottomInset >= computeVerticalScrollRange() - } - - val RecyclerView.isFullyScrolled: Boolean get() { - val scrollOffset = computeVerticalScrollOffset().coerceAtLeast(0) - val scrollExtent = computeVerticalScrollExtent() - val scrollRange = computeVerticalScrollRange() - - /// Retrieve the bottom inset from the window insets, if available. - val bottomInset = ViewCompat.getRootWindowInsets(this) - ?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom ?: 0 - - // We're at the bottom if the offset + extent equals the range (accounting for insets) - return scrollOffset + scrollExtent >= scrollRange - bottomInset + return scrollAmount == 0 } val RecyclerView.scrollAmount: Int diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MmsCharacterCalculator.java b/app/src/main/java/org/thoughtcrime/securesms/util/MmsCharacterCalculator.java deleted file mode 100644 index c25d47a845..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MmsCharacterCalculator.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.thoughtcrime.securesms.util; - -public class MmsCharacterCalculator extends CharacterCalculator { - - private static final int MAX_SIZE = 5000; - - @Override - public CharacterState calculateCharacters(String messageBody) { - return new CharacterState(1, MAX_SIZE - messageBody.length(), MAX_SIZE, MAX_SIZE); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ProtobufEnumSerializer.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ProtobufEnumSerializer.kt new file mode 100644 index 0000000000..57e11842fe --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ProtobufEnumSerializer.kt @@ -0,0 +1,32 @@ +package org.thoughtcrime.securesms.util + +import com.google.protobuf.ProtocolMessageEnum +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * A base serializer for Protobuf enums that implements the [KSerializer] interface. + * It provides a way to serialize and deserialize Protobuf enum values using their numeric representation. + * + * @param T The type of the Protobuf enum that extends [ProtocolMessageEnum]. + */ +abstract class ProtobufEnumSerializer : KSerializer { + abstract fun fromNumber(number: Int): T + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( + serialName = javaClass.simpleName, + kind = PrimitiveKind.INT + ) + + override fun serialize(encoder: Encoder, value: T) { + encoder.encodeInt(value.number) + } + + override fun deserialize(decoder: Decoder): T { + return fromNumber(decoder.decodeInt()) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/PushCharacterCalculator.java b/app/src/main/java/org/thoughtcrime/securesms/util/PushCharacterCalculator.java deleted file mode 100644 index 93561487c6..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/PushCharacterCalculator.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (C) 2015 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.util; - -public class PushCharacterCalculator extends CharacterCalculator { - private static final int MAX_TOTAL_SIZE = 64 * 1024; - private static final int MAX_PRIMARY_SIZE = 2000; - @Override - public CharacterState calculateCharacters(String messageBody) { - return new CharacterState(1, MAX_TOTAL_SIZE - messageBody.length(), MAX_TOTAL_SIZE, MAX_PRIMARY_SIZE); - } -} - diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SmsCharacterCalculator.java b/app/src/main/java/org/thoughtcrime/securesms/util/SmsCharacterCalculator.java deleted file mode 100644 index 5bac6ec95c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SmsCharacterCalculator.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.util; - -import android.telephony.SmsMessage; -import org.session.libsignal.utilities.Log; - -public class SmsCharacterCalculator extends CharacterCalculator { - - private static final String TAG = SmsCharacterCalculator.class.getSimpleName(); - - @Override - public CharacterState calculateCharacters(String messageBody) { - int[] length; - int messagesSpent; - int charactersSpent; - int charactersRemaining; - - try { - length = SmsMessage.calculateLength(messageBody, false); - messagesSpent = length[0]; - charactersSpent = length[1]; - charactersRemaining = length[2]; - } catch (NullPointerException e) { - Log.w(TAG, e); - messagesSpent = 1; - charactersSpent = messageBody.length(); - charactersRemaining = 1000; - } - - int maxMessageSize; - - if (messagesSpent > 0) { - maxMessageSize = (charactersSpent + charactersRemaining) / messagesSpent; - } else { - maxMessageSize = (charactersSpent + charactersRemaining); - } - - return new CharacterState(messagesSpent, charactersRemaining, maxMessageSize, maxMessageSize); - } -} - diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Trimmer.java b/app/src/main/java/org/thoughtcrime/securesms/util/Trimmer.java deleted file mode 100644 index 6707a078c8..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Trimmer.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.thoughtcrime.securesms.util; - -import android.app.ProgressDialog; -import android.content.Context; -import android.os.AsyncTask; -import android.widget.Toast; - -import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; - -import network.loki.messenger.R; - -public class Trimmer { - - public static void trimAllThreads(Context context, int threadLengthLimit) { - new TrimmingProgressTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadLengthLimit); - } - - private static class TrimmingProgressTask extends AsyncTask implements ThreadDatabase.ProgressListener { - private ProgressDialog progressDialog; - private Context context; - - public TrimmingProgressTask(Context context) { - this.context = context; - } - - @Override - protected void onPreExecute() { - progressDialog = new ProgressDialog(context); - progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - progressDialog.setCancelable(false); - progressDialog.setIndeterminate(false); - progressDialog.setTitle(R.string.deleting); - progressDialog.setMessage(context.getString(R.string.deleting)); - progressDialog.setMax(100); - progressDialog.show(); - } - - @Override - protected Void doInBackground(Integer... params) { - DatabaseComponent.get(context).threadDatabase().trimAllThreads(params[0], this); - return null; - } - - @Override - protected void onProgressUpdate(Integer... progress) { - double count = progress[1]; - double index = progress[0]; - - progressDialog.setProgress((int)Math.round((index / count) * 100.0)); - } - - @Override - protected void onPostExecute(Void result) { - progressDialog.dismiss(); - } - - @Override - public void onProgress(int complete, int total) { - this.publishProgress(complete, total); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java index 67a7e0335d..bb8bdf3b1e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java @@ -17,50 +17,47 @@ package org.thoughtcrime.securesms.video; import android.content.Context; -import android.os.Build; import android.util.AttributeSet; +import android.util.TypedValue; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; -import android.widget.Toast; -import android.widget.VideoView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import androidx.annotation.OptIn; +import androidx.core.graphics.ColorUtils; +import androidx.media3.common.AudioAttributes; import androidx.media3.common.MediaItem; import androidx.media3.common.Player; -import androidx.media3.common.AudioAttributes; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; -import androidx.media3.ui.LegacyPlayerControlView; import androidx.media3.ui.PlayerView; - import org.session.libsession.utilities.ViewUtil; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.attachments.AttachmentServer; -import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.VideoSlide; import java.io.IOException; import network.loki.messenger.R; -@UnstableApi public class VideoPlayer extends FrameLayout { private static final String TAG = VideoPlayer.class.getSimpleName(); - @Nullable private final VideoView videoView; @Nullable private final PlayerView exoView; @Nullable private ExoPlayer exoPlayer; - @Nullable private LegacyPlayerControlView exoControls; - @Nullable private AttachmentServer attachmentServer; @Nullable private Window window; + public interface VideoPlayerInteractions { + void onControllerVisibilityChanged(boolean visible); + } + + @Nullable + private VideoPlayerInteractions interactor = null; + public VideoPlayer(Context context) { this(context, null); } @@ -69,15 +66,33 @@ public VideoPlayer(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public VideoPlayer(Context context, AttributeSet attrs, int defStyleAttr) { + @OptIn(markerClass = UnstableApi.class) + public VideoPlayer(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); inflate(context, R.layout.video_player, this); this.exoView = ViewUtil.findById(this, R.id.video_view); - this.videoView = null; - this.exoControls = new LegacyPlayerControlView(getContext()); - this.exoControls.setShowTimeoutMs(-1); + exoView.setControllerShowTimeoutMs(3000); + + TypedValue tv = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.colorPrimary, tv, true); + int bgColor = tv.data; + findViewById(R.id.custom_controls).setBackgroundColor( + ColorUtils.setAlphaComponent(bgColor, (int) (0.7f * 255)) + ); + + // listen to changes in the controller visibility + exoView.setControllerVisibilityListener(new PlayerView.ControllerVisibilityListener() { + @Override + public void onVisibilityChanged(int visibility) { + if (interactor != null) interactor.onControllerVisibilityChanged(visibility == View.VISIBLE); + } + }); + } + + public void setInteractor(@Nullable VideoPlayerInteractions interactor) { + this.interactor = interactor; } public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay) @@ -86,32 +101,25 @@ public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay) setExoViewSource(videoSource, autoplay); } - public void pause() { - if (this.attachmentServer != null && this.videoView != null) { - this.videoView.stopPlayback(); - } else if (this.exoPlayer != null) { - this.exoPlayer.setPlayWhenReady(false); - } + public void setControlsYPosition(int yPosition){ + org.thoughtcrime.securesms.conversation.v2.ViewUtil.setBottomMargin(findViewById(R.id.custom_controls), yPosition); } - public void hideControls() { - if (this.exoView != null) { - this.exoView.hideController(); + public Long pause() { + if (this.exoPlayer != null) { + this.exoPlayer.setPlayWhenReady(false); + // return last playback position + return exoPlayer.getCurrentPosition(); } + + return 0L; } - public @Nullable View getControlView() { - if (this.exoControls != null) { - return this.exoControls; - } - return null; + public void seek(Long position){ + if (exoPlayer != null) exoPlayer.seekTo(position); } public void cleanup() { - if (this.attachmentServer != null) { - this.attachmentServer.stop(); - } - if (this.exoPlayer != null) { this.exoPlayer.release(); } @@ -121,16 +129,16 @@ public void setWindow(@Nullable Window window) { this.window = window; } - private void setExoViewSource(@NonNull VideoSlide videoSource, boolean autoplay) + @OptIn(markerClass = UnstableApi.class) + private void setExoViewSource(@NonNull VideoSlide videoSource, boolean autoplay) throws IOException { exoPlayer = new ExoPlayer.Builder(getContext()).build(); exoPlayer.addListener(new ExoPlayerListener(window)); exoPlayer.setAudioAttributes(AudioAttributes.DEFAULT, true); //noinspection ConstantConditions - exoView.setPlayer(exoPlayer); + exoView.setPlayer(exoPlayer); //todo this should be optimised as it creates a small lag in the viewpager //noinspection ConstantConditions - exoControls.setPlayer(exoPlayer); if(videoSource.getUri() != null){ MediaItem mediaItem = MediaItem.fromUri(videoSource.getUri()); @@ -141,32 +149,7 @@ private void setExoViewSource(@NonNull VideoSlide videoSource, boolean autoplay) exoPlayer.setPlayWhenReady(autoplay); } - private void setVideoViewSource(@NonNull VideoSlide videoSource, boolean autoplay) - throws IOException - { - if (this.attachmentServer != null) { - this.attachmentServer.stop(); - } - - if (videoSource.getUri() != null && PartAuthority.isLocalUri(videoSource.getUri())) { - Log.i(TAG, "Starting video attachment server for part provider Uri..."); - this.attachmentServer = new AttachmentServer(getContext(), videoSource.asAttachment()); - this.attachmentServer.start(); - - //noinspection ConstantConditions - this.videoView.setVideoURI(this.attachmentServer.getUri()); - } else if (videoSource.getUri() != null) { - Log.i(TAG, "Playing video directly from non-local Uri..."); - //noinspection ConstantConditions - this.videoView.setVideoURI(videoSource.getUri()); - } else { - Toast.makeText(getContext(), getContext().getString(R.string.videoErrorPlay), Toast.LENGTH_LONG).show(); - return; - } - - if (autoplay) this.videoView.start(); - } - + @UnstableApi private static class ExoPlayerListener implements Player.Listener { private final Window window; diff --git a/app/src/main/res/animator/appbar_elevation.xml b/app/src/main/res/animator/appbar_elevation.xml deleted file mode 100644 index 7a7f123d25..0000000000 --- a/app/src/main/res/animator/appbar_elevation.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_round_transparent.xml b/app/src/main/res/drawable/bg_round_transparent.xml new file mode 100644 index 0000000000..5032a69355 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_transparent.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/compose_background_dark.xml b/app/src/main/res/drawable/compose_background_dark.xml deleted file mode 100644 index 56859db153..0000000000 --- a/app/src/main/res/drawable/compose_background_dark.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_item_sent_indicator_text_shape_dark.xml b/app/src/main/res/drawable/conversation_item_sent_indicator_text_shape_dark.xml deleted file mode 100644 index d02a391561..0000000000 --- a/app/src/main/res/drawable/conversation_item_sent_indicator_text_shape_dark.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/conversation_list_divider_shape_dark.xml b/app/src/main/res/drawable/conversation_list_divider_shape_dark.xml deleted file mode 100644 index 425c926052..0000000000 --- a/app/src/main/res/drawable/conversation_list_divider_shape_dark.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/cta_hero_char_limit.webp b/app/src/main/res/drawable/cta_hero_char_limit.webp new file mode 100644 index 0000000000..87e7e3e56f Binary files /dev/null and b/app/src/main/res/drawable/cta_hero_char_limit.webp differ diff --git a/app/src/main/res/drawable/cta_hero_pins.webp b/app/src/main/res/drawable/cta_hero_pins.webp new file mode 100644 index 0000000000..8a5070fb59 Binary files /dev/null and b/app/src/main/res/drawable/cta_hero_pins.webp differ diff --git a/app/src/main/res/drawable/ic_pro_badge.xml b/app/src/main/res/drawable/ic_pro_badge.xml new file mode 100644 index 0000000000..1690bff8c3 --- /dev/null +++ b/app/src/main/res/drawable/ic_pro_badge.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_pro_sparkle_custom.xml b/app/src/main/res/drawable/ic_pro_sparkle_custom.xml new file mode 100644 index 0000000000..7c3f2f8b5b --- /dev/null +++ b/app/src/main/res/drawable/ic_pro_sparkle_custom.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index c73895b001..f3fc07c128 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -18,10 +18,17 @@ app:layout_constraintBottom_toTopOf="@+id/conversation_header" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> - + + + diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 6aac354a60..9c34b14961 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -1,202 +1,213 @@ - - - + + android:layout_height="match_parent" + android:orientation="vertical" + tools:context="org.thoughtcrime.securesms.home.HomeActivity"> - + android:background="?colorPrimary" + app:contentInsetStart="0dp"> - - - - - - - + android:layout_height="wrap_content" + android:orientation="vertical"> - + + + + + + + + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent"> - + - + - + - - + android:layout_height="?actionBarSize" + android:layout_marginHorizontal="@dimen/medium_spacing" + android:visibility="gone"> - + + - + + + + + + + + - - - - - - - + android:visibility="gone" + android:text="@string/callsInProgress" + tools:visibility="visible" /> - + - + + android:layout_height="match_parent"> + + + + + + + + + + - - - - + tools:listitem="@layout/view_global_search_result" /> - - + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/camera_controls_landscape.xml b/app/src/main/res/layout/camera_controls_landscape.xml deleted file mode 100644 index 0db3e34d77..0000000000 --- a/app/src/main/res/layout/camera_controls_landscape.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - -