mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-21 02:08:40 +00:00
Allow 1:1 polls and raise character limit.
This commit is contained in:
committed by
jeffrey-signal
parent
fd635542c0
commit
f7d87f3436
@@ -4,8 +4,6 @@ import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -23,7 +21,6 @@ import org.thoughtcrime.securesms.testing.GroupTestingUtils
|
||||
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
|
||||
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
|
||||
@@ -42,10 +39,6 @@ class DataMessageProcessorTest_polls {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockkStatic(RemoteConfig::class)
|
||||
|
||||
every { RemoteConfig.receivePolls } returns true
|
||||
|
||||
alice = Recipient.resolved(harness.others[0])
|
||||
bob = Recipient.resolved(harness.others[1])
|
||||
charlie = Recipient.resolved(harness.others[2])
|
||||
|
||||
@@ -135,10 +135,6 @@ object ExportSkips {
|
||||
return log(sentTimestamp, "Poll option was invalid.")
|
||||
}
|
||||
|
||||
fun pollNotInGroupChat(sentTimestamp: Long): String {
|
||||
return log(sentTimestamp, "Poll was not in a group chat.")
|
||||
}
|
||||
|
||||
fun pinMessageIsInvalid(sentTimestamp: Long): String {
|
||||
return log(sentTimestamp, "Pin message update was invalid.")
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ private val TAG = Log.tag(ChatItemArchiveExporter::class.java)
|
||||
private val MAX_INLINED_BODY_SIZE = 128.kibiBytes.bytes.toInt()
|
||||
private val MAX_INLINED_BODY_SIZE_WITH_LONG_ATTACHMENT_POINTER = 2.kibiBytes.bytes.toInt()
|
||||
private val MAX_INLINED_QUOTE_BODY_SIZE = 2.kibiBytes.bytes.toInt()
|
||||
private const val MAX_POLL_QUESTION_CHARACTER_LENGTH = 200
|
||||
private const val MAX_POLL_CHARACTER_LENGTH = 100
|
||||
private const val MAX_POLL_OPTIONS = 10
|
||||
|
||||
@@ -398,13 +399,8 @@ class ChatItemArchiveExporter(
|
||||
}
|
||||
|
||||
extraData.pollsById[record.id] != null -> {
|
||||
if (exportState.threadIdToRecipientId[builder.chatId] !in exportState.groupRecipientIds) {
|
||||
Log.w(TAG, ExportSkips.pollNotInGroupChat(record.dateSent))
|
||||
continue
|
||||
}
|
||||
|
||||
val poll = extraData.pollsById[record.id]!!
|
||||
if (poll.question.isEmpty() || poll.question.length > MAX_POLL_CHARACTER_LENGTH) {
|
||||
if (poll.question.isEmpty() || poll.question.length > MAX_POLL_QUESTION_CHARACTER_LENGTH) {
|
||||
Log.w(TAG, ExportSkips.invalidPollQuestion(record.dateSent))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -218,12 +218,11 @@ class ConversationRepository(
|
||||
val threadRecipient = SignalDatabase.threads.getRecipientForThreadId(messageRecord.threadId)!!
|
||||
val pollSentTimestamp = messageRecord.dateSent
|
||||
|
||||
if (threadRecipient.groupId.getOrNull()?.isV2 != true) {
|
||||
if (threadRecipient.isPushV2Group && threadRecipient.groupId.getOrNull()?.isV2 != true) {
|
||||
Log.w(TAG, "Missing group id")
|
||||
emitter.tryOnError(Exception("Poll terminate failed"))
|
||||
}
|
||||
|
||||
val groupId = threadRecipient.requireGroupId().requireV2()
|
||||
val message = OutgoingMessage.pollTerminateMessage(
|
||||
threadRecipient = threadRecipient,
|
||||
sentTimeMillis = System.currentTimeMillis(),
|
||||
@@ -233,12 +232,17 @@ class ConversationRepository(
|
||||
|
||||
Log.i(TAG, "Sending poll terminate to " + message.threadRecipient.id + ", thread: " + messageRecord.threadId)
|
||||
|
||||
val possibleTargets: List<Recipient> = SignalDatabase.groups.getGroupMembers(groupId, GroupTable.MemberSet.FULL_MEMBERS_EXCLUDING_SELF)
|
||||
.map { it.resolve() }
|
||||
.distinctBy { it.id }
|
||||
val possibleTargets: List<Recipient> = if (threadRecipient.isPushV2Group) {
|
||||
SignalDatabase.groups.getGroupMembers(threadRecipient.requireGroupId().requireV2(), GroupTable.MemberSet.FULL_MEMBERS_EXCLUDING_SELF)
|
||||
.map { it.resolve() }
|
||||
.distinctBy { it.id }
|
||||
} else {
|
||||
listOf(threadRecipient)
|
||||
}
|
||||
val isSelf = threadRecipient.isSelf
|
||||
|
||||
val eligibleTargets: List<Recipient> = RecipientUtil.getEligibleForSending(possibleTargets)
|
||||
val results = sendEndPoll(threadRecipient, message, eligibleTargets, poll.messageId)
|
||||
val results = sendEndPoll(threadRecipient, message, eligibleTargets, isSelf, poll.messageId)
|
||||
val sendResults = GroupSendJobHelper.getCompletedSends(eligibleTargets, results)
|
||||
|
||||
if (sendResults.completed.isNotEmpty() || possibleTargets.isEmpty()) {
|
||||
@@ -271,9 +275,9 @@ class ConversationRepository(
|
||||
}
|
||||
|
||||
@Throws(IOException::class, GroupNotAMemberException::class, UndeliverableMessageException::class)
|
||||
fun sendEndPoll(group: Recipient, message: OutgoingMessage, destinations: List<Recipient>, messageId: Long): List<SendMessageResult?> {
|
||||
val groupId = group.requireGroupId().requireV2()
|
||||
val groupRecord: GroupRecord? = SignalDatabase.groups.getGroup(group.requireGroupId()).getOrNull()
|
||||
fun sendEndPoll(threadRecipient: Recipient, message: OutgoingMessage, destinations: List<Recipient>, isSelf: Boolean, messageId: Long): List<SendMessageResult?> {
|
||||
val groupId = if (threadRecipient.isPushV2Group) threadRecipient.requireGroupId().requireV2() else null
|
||||
val groupRecord: GroupRecord? = if (threadRecipient.isPushV2Group) SignalDatabase.groups.getGroup(threadRecipient.requireGroupId()).getOrNull() else null
|
||||
|
||||
if (groupRecord != null && groupRecord.isAnnouncementGroup && !groupRecord.isAdmin(Recipient.self())) {
|
||||
throw UndeliverableMessageException("Non-admins cannot send messages in announcement groups!")
|
||||
@@ -281,29 +285,35 @@ class ConversationRepository(
|
||||
|
||||
val builder = newBuilder()
|
||||
|
||||
GroupUtil.setDataMessageGroupContext(AppDependencies.application, builder, groupId)
|
||||
if (groupId != null) {
|
||||
GroupUtil.setDataMessageGroupContext(AppDependencies.application, builder, groupId)
|
||||
}
|
||||
|
||||
val sentTime = System.currentTimeMillis()
|
||||
val groupMessage = builder
|
||||
val message = builder
|
||||
.withTimestamp(sentTime)
|
||||
.withExpiration((message.expiresIn / 1000).toInt())
|
||||
.withProfileKey(ProfileKeyUtil.getSelfProfileKey().serialize())
|
||||
.withPollTerminate(SignalServiceDataMessage.PollTerminate(message.messageExtras!!.pollTerminate!!.targetTimestamp))
|
||||
.build()
|
||||
|
||||
return GroupSendUtil.sendResendableDataMessage(
|
||||
applicationContext,
|
||||
groupId,
|
||||
null,
|
||||
destinations,
|
||||
false,
|
||||
ContentHint.RESENDABLE,
|
||||
MessageId(messageId),
|
||||
groupMessage,
|
||||
true,
|
||||
false,
|
||||
null
|
||||
) { System.currentTimeMillis() - sentTime > POLL_TERMINATE_TIMEOUT.inWholeMilliseconds }
|
||||
return if (isSelf) {
|
||||
listOf(AppDependencies.signalServiceMessageSender.sendSyncMessage(message))
|
||||
} else {
|
||||
GroupSendUtil.sendResendableDataMessage(
|
||||
applicationContext,
|
||||
groupId,
|
||||
null,
|
||||
destinations,
|
||||
false,
|
||||
ContentHint.RESENDABLE,
|
||||
MessageId(messageId),
|
||||
message,
|
||||
true,
|
||||
false,
|
||||
null
|
||||
) { System.currentTimeMillis() - sentTime > POLL_TERMINATE_TIMEOUT.inWholeMilliseconds }
|
||||
}
|
||||
}
|
||||
|
||||
fun getPinnedMessages(threadId: Long): List<MmsMessageRecord> {
|
||||
|
||||
@@ -67,6 +67,7 @@ import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeDialogFragment
|
||||
import org.thoughtcrime.securesms.polls.Poll
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@@ -78,6 +79,7 @@ class CreatePollFragment : ComposeDialogFragment() {
|
||||
companion object {
|
||||
private val TAG = Log.tag(CreatePollFragment::class)
|
||||
|
||||
val MAX_QUESTION_CHARACTER_LENGTH = if (RemoteConfig.pollsV2) 200 else 100
|
||||
const val MAX_CHARACTER_LENGTH = 100
|
||||
const val MAX_OPTIONS = 10
|
||||
const val MIN_OPTIONS = 2
|
||||
@@ -222,7 +224,7 @@ private fun CreatePollScreen(
|
||||
TextFieldWithCountdown(
|
||||
value = question,
|
||||
label = { Text(text = stringResource(R.string.CreatePollFragment__ask_a_question)) },
|
||||
onValueChange = { question = it.substring(0, minOf(it.length, CreatePollFragment.MAX_CHARACTER_LENGTH)) },
|
||||
onValueChange = { question = it.substring(0, minOf(it.length, CreatePollFragment.MAX_QUESTION_CHARACTER_LENGTH)) },
|
||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
||||
colors = TextFieldDefaults.colors(
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
@@ -232,6 +234,7 @@ private fun CreatePollScreen(
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState -> if (focusState.isFocused) focusedOption = -1 }
|
||||
.focusRequester(focusRequester),
|
||||
maxCharacterLength = CreatePollFragment.MAX_QUESTION_CHARACTER_LENGTH,
|
||||
countdownThreshold = CreatePollFragment.CHARACTER_COUNTDOWN_THRESHOLD
|
||||
)
|
||||
|
||||
@@ -266,6 +269,7 @@ private fun CreatePollScreen(
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
},
|
||||
maxCharacterLength = CreatePollFragment.MAX_CHARACTER_LENGTH,
|
||||
countdownThreshold = CreatePollFragment.CHARACTER_COUNTDOWN_THRESHOLD
|
||||
)
|
||||
}
|
||||
@@ -316,9 +320,10 @@ private fun TextFieldWithCountdown(
|
||||
colors: TextFieldColors,
|
||||
modifier: Modifier,
|
||||
trailingIcon: @Composable () -> Unit = {},
|
||||
maxCharacterLength: Int,
|
||||
countdownThreshold: Int
|
||||
) {
|
||||
val charactersRemaining = CreatePollFragment.MAX_CHARACTER_LENGTH - value.length
|
||||
val charactersRemaining = maxCharacterLength - value.length
|
||||
val displayCountdown = charactersRemaining <= countdownThreshold
|
||||
|
||||
Box(modifier = Modifier.padding(start = 24.dp, end = 24.dp, bottom = 16.dp)) {
|
||||
|
||||
@@ -131,7 +131,7 @@ class AttachmentKeyboardFragment : LoggingFragment(R.layout.attachment_keyboard_
|
||||
private fun updateButtonsAvailable(recipient: Recipient) {
|
||||
val paymentsValues = SignalStore.payments
|
||||
val isPaymentsAvailable = paymentsValues.paymentsAvailability.isSendAllowed && !recipient.isSelf && !recipient.isGroup && recipient.isRegistered
|
||||
val isPollsAvailable = recipient.isPushV2Group && RemoteConfig.polls
|
||||
val isPollsAvailable = recipient.isPushV2Group || RemoteConfig.pollsV2
|
||||
|
||||
if (!isPaymentsAvailable && !isPollsAvailable) {
|
||||
attachmentKeyboardView.filterAttachmentKeyboardButtons(removePaymentFilter.and(removePollFilter))
|
||||
|
||||
@@ -286,6 +286,8 @@ public class IndividualSendJob extends PushSendJob {
|
||||
SignalServiceDataMessage.GiftBadge giftBadge = getGiftBadgeFor(message);
|
||||
SignalServiceDataMessage.Payment payment = getPayment(message);
|
||||
List<BodyRange> bodyRanges = getBodyRanges(message);
|
||||
SignalServiceDataMessage.PollCreate pollCreate = getPollCreate(message);
|
||||
SignalServiceDataMessage.PollTerminate pollTerminate = getPollTerminate(message);
|
||||
SignalServiceDataMessage.PinnedMessage pinnedMessage = getPinnedMessage(message);
|
||||
SignalServiceDataMessage.Builder mediaMessageBuilder = SignalServiceDataMessage.newBuilder()
|
||||
.withBody(message.getBody())
|
||||
@@ -303,6 +305,8 @@ public class IndividualSendJob extends PushSendJob {
|
||||
.asEndSessionMessage(message.isEndSession())
|
||||
.withPayment(payment)
|
||||
.withBodyRanges(bodyRanges)
|
||||
.withPollCreate(pollCreate)
|
||||
.withPollTerminate(pollTerminate)
|
||||
.withPinnedMessage(pinnedMessage);
|
||||
|
||||
if (message.getParentStoryId() != null) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
@@ -49,7 +50,11 @@ class PollVoteJob(
|
||||
return null
|
||||
}
|
||||
|
||||
val recipients = conversationRecipient.participantIds.filter { it != Recipient.self().id }.map { it.toLong() }
|
||||
val recipients = if (conversationRecipient.isGroup) {
|
||||
conversationRecipient.participantIds.filter { it != Recipient.self().id }.map { it.toLong() }
|
||||
} else {
|
||||
listOf(conversationRecipient.id.toLong())
|
||||
}
|
||||
|
||||
return PollVoteJob(
|
||||
messageId = messageId,
|
||||
@@ -108,7 +113,7 @@ class PollVoteJob(
|
||||
|
||||
val targetSentTimestamp = message.dateSent
|
||||
|
||||
val recipients = Recipient.resolvedList(recipientIds.filter { it != Recipient.self().id.toLong() }.map { RecipientId.from(it) })
|
||||
val recipients = Recipient.resolvedList(recipientIds.map { RecipientId.from(it) })
|
||||
val registered = RecipientUtil.getEligibleForSending(recipients)
|
||||
val unregistered = recipients - registered.toSet()
|
||||
val completions: List<Recipient> = deliver(conversationRecipient, registered, targetAuthor, targetSentTimestamp, poll)
|
||||
@@ -140,15 +145,18 @@ class PollVoteJob(
|
||||
)
|
||||
)
|
||||
|
||||
GroupUtil.setDataMessageGroupContext(context, dataMessageBuilder, conversationRecipient.requireGroupId().requirePush())
|
||||
if (conversationRecipient.isPushV2Group) {
|
||||
GroupUtil.setDataMessageGroupContext(context, dataMessageBuilder, conversationRecipient.requireGroupId().requirePush())
|
||||
}
|
||||
|
||||
val dataMessage = dataMessageBuilder.build()
|
||||
val nonSelfDestinations = destinations.filter { !it.isSelf }
|
||||
|
||||
val results = GroupSendUtil.sendResendableDataMessage(
|
||||
context,
|
||||
conversationRecipient.groupId.map { obj: GroupId -> obj.requireV2() }.orElse(null),
|
||||
null,
|
||||
destinations,
|
||||
nonSelfDestinations,
|
||||
false,
|
||||
ContentHint.RESENDABLE,
|
||||
MessageId(messageId),
|
||||
@@ -159,6 +167,10 @@ class PollVoteJob(
|
||||
null
|
||||
)
|
||||
|
||||
if (conversationRecipient.isSelf) {
|
||||
results.add(AppDependencies.signalServiceMessageSender.sendSyncMessage(dataMessage))
|
||||
}
|
||||
|
||||
val groupResult = GroupSendJobHelper.getCompletedSends(destinations, results)
|
||||
|
||||
for (unregistered in groupResult.unregistered) {
|
||||
|
||||
@@ -37,7 +37,6 @@ import org.thoughtcrime.securesms.messages.StorySendUtil;
|
||||
import org.thoughtcrime.securesms.mms.MessageGroupContext;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage;
|
||||
import org.thoughtcrime.securesms.polls.Poll;
|
||||
import org.thoughtcrime.securesms.ratelimit.ProofRequiredExceptionHandler;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -285,8 +284,8 @@ public final class PushGroupSendJob extends PushSendJob {
|
||||
List<SignalServicePreview> previews = getPreviewsFor(message);
|
||||
List<SignalServiceDataMessage.Mention> mentions = getMentionsFor(message.getMentions());
|
||||
List<BodyRange> bodyRanges = getBodyRanges(message);
|
||||
Optional<SignalServiceDataMessage.PollCreate> pollCreate = getPollCreate(message);
|
||||
Optional<SignalServiceDataMessage.PollTerminate> pollTerminate = getPollTerminate(message);
|
||||
SignalServiceDataMessage.PollCreate pollCreate = getPollCreate(message);
|
||||
SignalServiceDataMessage.PollTerminate pollTerminate = getPollTerminate(message);
|
||||
SignalServiceDataMessage.PinnedMessage pinnedMessage = getPinnedMessage(message);
|
||||
List<Attachment> attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList();
|
||||
List<SignalServiceAttachment> attachmentPointers = getAttachmentPointersFor(attachments);
|
||||
@@ -371,8 +370,8 @@ public final class PushGroupSendJob extends PushSendJob {
|
||||
.withPreviews(previews)
|
||||
.withMentions(mentions)
|
||||
.withBodyRanges(bodyRanges)
|
||||
.withPollCreate(pollCreate.orElse(null))
|
||||
.withPollTerminate(pollTerminate.orElse(null))
|
||||
.withPollCreate(pollCreate)
|
||||
.withPollTerminate(pollTerminate)
|
||||
.withPinnedMessage(pinnedMessage);
|
||||
|
||||
if (message.getParentStoryId() != null) {
|
||||
@@ -419,23 +418,6 @@ public final class PushGroupSendJob extends PushSendJob {
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<SignalServiceDataMessage.PollCreate> getPollCreate(OutgoingMessage message) {
|
||||
Poll poll = message.getPoll();
|
||||
if (poll == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(new SignalServiceDataMessage.PollCreate(poll.getQuestion(), poll.getAllowMultipleVotes(), poll.getPollOptions()));
|
||||
}
|
||||
|
||||
private Optional<SignalServiceDataMessage.PollTerminate> getPollTerminate(OutgoingMessage message) {
|
||||
if (message.getMessageExtras() == null || message.getMessageExtras().pollTerminate == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(new SignalServiceDataMessage.PollTerminate(message.getMessageExtras().pollTerminate.targetTimestamp));
|
||||
}
|
||||
|
||||
public static long getMessageId(@Nullable byte[] serializedData) {
|
||||
JsonJobData data = JsonJobData.deserialize(serializedData);
|
||||
return data.getLong(KEY_MESSAGE_ID);
|
||||
|
||||
@@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
||||
import org.thoughtcrime.securesms.polls.Poll;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
@@ -486,6 +487,23 @@ public abstract class PushSendJob extends SendJob {
|
||||
return getBodyRanges(message.getBodyRanges());
|
||||
}
|
||||
|
||||
protected @Nullable SignalServiceDataMessage.PollCreate getPollCreate(OutgoingMessage message) {
|
||||
Poll poll = message.getPoll();
|
||||
if (poll == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SignalServiceDataMessage.PollCreate(poll.getQuestion(), poll.getAllowMultipleVotes(), poll.getPollOptions());
|
||||
}
|
||||
|
||||
protected @Nullable SignalServiceDataMessage.PollTerminate getPollTerminate(OutgoingMessage message) {
|
||||
if (message.getMessageExtras() == null || message.getMessageExtras().pollTerminate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SignalServiceDataMessage.PollTerminate(message.getMessageExtras().pollTerminate.targetTimestamp);
|
||||
}
|
||||
|
||||
protected @Nullable List<BodyRange> getBodyRanges(@Nullable BodyRangeList bodyRanges) {
|
||||
if (bodyRanges == null || bodyRanges.ranges.size() == 0) {
|
||||
return null;
|
||||
|
||||
@@ -124,6 +124,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
object DataMessageProcessor {
|
||||
|
||||
private const val BODY_RANGE_PROCESSING_LIMIT = 250
|
||||
private const val POLL_QUESTION_CHARACTER_LIMIT = 200
|
||||
private const val POLL_CHARACTER_LIMIT = 100
|
||||
private const val POLL_OPTIONS_LIMIT = 10
|
||||
|
||||
@@ -1066,28 +1067,23 @@ object DataMessageProcessor {
|
||||
groupId: GroupId.V2?,
|
||||
receivedTime: Long
|
||||
): InsertResult? {
|
||||
if (!RemoteConfig.receivePolls) {
|
||||
log(envelope.timestamp!!, "Poll creation not allowed due to remote config.")
|
||||
return null
|
||||
}
|
||||
|
||||
log(envelope.timestamp!!, "Handle poll creation")
|
||||
val poll: DataMessage.PollCreate = message.pollCreate!!
|
||||
|
||||
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient, threadRecipient, groupId, message.expireTimerDuration, message.expireTimerVersion, receivedTime)
|
||||
|
||||
if (groupId == null) {
|
||||
warn(envelope.timestamp!!, "[handlePollCreate] Polls can only be sent to groups. author: $senderRecipient")
|
||||
if (groupId != null) {
|
||||
val groupRecord = SignalDatabase.groups.getGroup(groupId).orNull()
|
||||
if (groupRecord != null && !groupRecord.members.contains(senderRecipient.id)) {
|
||||
warn(envelope.timestamp!!, "[handlePollCreate] Poll author is not in the group. author $senderRecipient")
|
||||
return null
|
||||
}
|
||||
} else if (senderRecipient.id != threadRecipient.id && senderRecipient.id != Recipient.self().id) {
|
||||
warn(envelope.timestamp!!, "[handlePollCreate] Sender is not a part of the 1:1 thread!")
|
||||
return null
|
||||
}
|
||||
|
||||
val groupRecord = SignalDatabase.groups.getGroup(groupId).orNull()
|
||||
if (groupRecord == null || !groupRecord.members.contains(senderRecipient.id)) {
|
||||
warn(envelope.timestamp!!, "[handlePollCreate] Poll author is not in the group. author $senderRecipient")
|
||||
return null
|
||||
}
|
||||
|
||||
if (poll.question == null || poll.question!!.isEmpty() || poll.question!!.length > POLL_CHARACTER_LIMIT) {
|
||||
if (poll.question == null || poll.question!!.isEmpty() || poll.question!!.length > POLL_QUESTION_CHARACTER_LIMIT) {
|
||||
warn(envelope.timestamp!!, "[handlePollCreate] Poll question is invalid.")
|
||||
return null
|
||||
}
|
||||
@@ -1136,11 +1132,6 @@ object DataMessageProcessor {
|
||||
groupId: GroupId.V2?,
|
||||
receivedTime: Long
|
||||
): InsertResult? {
|
||||
if (!RemoteConfig.receivePolls) {
|
||||
log(envelope.timestamp!!, "Poll terminate not allowed due to remote config.")
|
||||
return null
|
||||
}
|
||||
|
||||
val pollTerminate: DataMessage.PollTerminate = message.pollTerminate!!
|
||||
val targetSentTimestamp = pollTerminate.targetSentTimestamp!!
|
||||
|
||||
@@ -1189,11 +1180,6 @@ object DataMessageProcessor {
|
||||
senderRecipient: Recipient,
|
||||
earlyMessageCacheEntry: EarlyMessageCacheEntry?
|
||||
): MessageId? {
|
||||
if (!RemoteConfig.receivePolls) {
|
||||
log(envelope.timestamp!!, "Poll vote not allowed due to remote config.")
|
||||
return null
|
||||
}
|
||||
|
||||
val pollVote: DataMessage.PollVote = message.pollVote!!
|
||||
val targetSentTimestamp = pollVote.targetSentTimestamp!!
|
||||
|
||||
@@ -1578,14 +1564,12 @@ object DataMessageProcessor {
|
||||
}
|
||||
|
||||
val groupRecord = SignalDatabase.groups.getGroup(targetThread.recipient.id).orNull()
|
||||
if (groupRecord == null) {
|
||||
warn(envelope.timestamp!!, "[handlePollValidation] Target thread needs to be a group. timestamp: $targetSentTimestamp author: ${targetAuthor.id}")
|
||||
return null
|
||||
}
|
||||
|
||||
if (!groupRecord.members.contains(senderRecipient.id)) {
|
||||
if (groupRecord != null && !groupRecord.members.contains(senderRecipient.id)) {
|
||||
warn(envelope.timestamp!!, "[handlePollValidation] Sender is not in the group. timestamp: $targetSentTimestamp author: ${targetAuthor.id}")
|
||||
return null
|
||||
} else if (groupRecord == null && senderRecipient.id != targetThread.recipient.id && senderRecipient.id != Recipient.self().id) {
|
||||
warn(envelope.timestamp!!, "[handlePollValidation] Sender is not a part of the 1:1 thread!")
|
||||
return null
|
||||
}
|
||||
|
||||
return MessageId(targetMessage.id)
|
||||
|
||||
@@ -1744,18 +1744,9 @@ object SyncMessageProcessor {
|
||||
sent: Sent,
|
||||
senderRecipient: Recipient
|
||||
): Long {
|
||||
if (!RemoteConfig.receivePolls) {
|
||||
log(envelope.timestamp!!, "Sync poll create not allowed due to remote config.")
|
||||
}
|
||||
|
||||
log(envelope.timestamp!!, "Synchronize sent poll creation message.")
|
||||
|
||||
val recipient = getSyncMessageDestination(sent)
|
||||
if (!recipient.isGroup) {
|
||||
warn(envelope.timestamp!!, "Poll creation messages should only be synced in groups. Dropping.")
|
||||
return -1
|
||||
}
|
||||
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
|
||||
val expiresInMillis = message.expireTimerDuration.inWholeMilliseconds
|
||||
@@ -1777,8 +1768,12 @@ object SyncMessageProcessor {
|
||||
question = poll.question!!
|
||||
)
|
||||
|
||||
val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, GroupReceiptTable.STATUS_UNKNOWN, null).messageId
|
||||
updateGroupReceiptStatus(sent, messageId, recipient.requireGroupId())
|
||||
val receiptStatus = if (recipient.isGroup) GroupReceiptTable.STATUS_UNKNOWN else GroupReceiptTable.STATUS_UNDELIVERED
|
||||
val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, receiptStatus, null).messageId
|
||||
|
||||
if (recipient.isGroup) {
|
||||
updateGroupReceiptStatus(sent, messageId, recipient.requireGroupId())
|
||||
}
|
||||
|
||||
log(envelope.timestamp!!, "Inserted sync poll create message as messageId $messageId")
|
||||
|
||||
@@ -1799,18 +1794,9 @@ object SyncMessageProcessor {
|
||||
senderRecipient: Recipient,
|
||||
earlyMessageCacheEntry: EarlyMessageCacheEntry?
|
||||
): Long {
|
||||
if (!RemoteConfig.receivePolls) {
|
||||
log(envelope.timestamp!!, "Sync poll end not allowed due to remote config.")
|
||||
}
|
||||
|
||||
log(envelope.timestamp!!, "Synchronize sent poll terminate message")
|
||||
|
||||
val recipient = getSyncMessageDestination(sent)
|
||||
if (!recipient.isGroup) {
|
||||
warn(envelope.timestamp!!, "Poll termination messages should only be synced in groups. Dropping.")
|
||||
return -1
|
||||
}
|
||||
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
|
||||
val expiresInMillis = message.expireTimerDuration.inWholeMilliseconds
|
||||
@@ -1847,7 +1833,8 @@ object SyncMessageProcessor {
|
||||
)
|
||||
)
|
||||
|
||||
val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, GroupReceiptTable.STATUS_UNKNOWN, null).messageId
|
||||
val receiptStatus = if (recipient.isGroup) GroupReceiptTable.STATUS_UNKNOWN else GroupReceiptTable.STATUS_UNDELIVERED
|
||||
val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, receiptStatus, null).messageId
|
||||
SignalDatabase.messages.markAsSent(messageId, true)
|
||||
|
||||
log(envelope.timestamp!!, "Inserted sync poll end message as messageId $messageId")
|
||||
|
||||
@@ -265,12 +265,11 @@ public class MessageSender {
|
||||
long messageId = insertResult.getMessageId();
|
||||
|
||||
if (!recipient.isPushV2Group()) {
|
||||
Log.w(TAG, "Can only send polls to groups.");
|
||||
return threadId;
|
||||
SignalLocalMetrics.IndividualMessageSend.onInsertedIntoDatabase(messageId, metricId);
|
||||
} else {
|
||||
SignalLocalMetrics.GroupMessageSend.onInsertedIntoDatabase(messageId, metricId);
|
||||
}
|
||||
|
||||
SignalLocalMetrics.GroupMessageSend.onInsertedIntoDatabase(messageId, metricId);
|
||||
|
||||
sendMessageInternal(context, recipient, sendType, messageId, insertResult.getQuoteAttachmentId(), Collections.emptyList());
|
||||
onMessageSent();
|
||||
SignalDatabase.threads().update(allocatedThreadId, true, true);
|
||||
|
||||
@@ -1166,14 +1166,6 @@ object RemoteConfig {
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@get:JvmName("polls")
|
||||
val polls: Boolean by remoteBoolean(
|
||||
key = "android.polls.2",
|
||||
defaultValue = false,
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
/** Whether or not to send over binary service ids (alongside string service ids). */
|
||||
@JvmStatic
|
||||
@get:JvmName("useBinaryId")
|
||||
@@ -1183,14 +1175,6 @@ object RemoteConfig {
|
||||
hotSwappable = false
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@get:JvmName("receivePolls")
|
||||
val receivePolls: Boolean by remoteBoolean(
|
||||
key = "android.receivePolls",
|
||||
defaultValue = false,
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@get:JvmName("backupsBetaMegaphone")
|
||||
val backupsBetaMegaphone: Boolean by remoteBoolean(
|
||||
@@ -1238,5 +1222,16 @@ object RemoteConfig {
|
||||
defaultValue = "*:10000",
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
/**
|
||||
* Whether or not to allow 1:1 polls and a higher character limit for questions
|
||||
*/
|
||||
@JvmStatic
|
||||
@get:JvmName("pollsV2")
|
||||
val pollsV2: Boolean by remoteBoolean(
|
||||
key = "android.pollsV2",
|
||||
defaultValue = false,
|
||||
hotSwappable = true
|
||||
)
|
||||
// endregion
|
||||
}
|
||||
|
||||
@@ -8941,7 +8941,7 @@
|
||||
<!-- Dialog title when you go to end a poll -->
|
||||
<string name="Poll__end_poll_title">End poll?</string>
|
||||
<!-- Dialog body when you go to end a poll -->
|
||||
<string name="Poll__end_poll_body">Group members will no longer be able to vote in this poll.</string>
|
||||
<string name="Poll__end_poll_body">Members of this chat will no longer be able to vote in this poll.</string>
|
||||
<!-- Body used in dialog when we are waiting for network -->
|
||||
<string name="Poll__waiting_for_network">Waiting for network…</string>
|
||||
<!-- Header when creating a new poll -->
|
||||
|
||||
@@ -26,7 +26,7 @@ androidx-navigation = "2.8.5"
|
||||
androidx-navigation3-core = "1.0.0"
|
||||
androidx-window = "1.3.0"
|
||||
glide = "4.15.1"
|
||||
libsignal-client = "0.86.8"
|
||||
libsignal-client = "0.86.9"
|
||||
mp4parser = "1.9.39"
|
||||
accompanist = "0.28.0"
|
||||
nanohttpd = "2.3.1"
|
||||
|
||||
@@ -15400,20 +15400,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
||||
<sha256 value="57b3cf8f247f1990211110734a7d1af413db145c8f17eb1b2cdc9b9321188c2b" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.signal" name="libsignal-android" version="0.86.8">
|
||||
<artifact name="libsignal-android-0.86.8.aar">
|
||||
<sha256 value="a9fc5fd6bf12fea69318f6c992f8f52804950bc7888ae3df3d7bad6b1c18e8a0" origin="Generated by Gradle"/>
|
||||
<component group="org.signal" name="libsignal-android" version="0.86.9">
|
||||
<artifact name="libsignal-android-0.86.9.aar">
|
||||
<sha256 value="1780958d9a7ef098c49ee233c2e9ae3b8b5aa3d14ca4c293678d80611d008866" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="libsignal-android-0.86.8.module">
|
||||
<sha256 value="4ce19af1fc9442c8eb231eb1f2523557291fc9e9dce2520b45d4ab7c6e9642d7" origin="Generated by Gradle"/>
|
||||
<artifact name="libsignal-android-0.86.9.module">
|
||||
<sha256 value="aa75573a7ff8f67118725242674000622eed7c903256c74ed393225878c3c7c3" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.signal" name="libsignal-client" version="0.86.8">
|
||||
<artifact name="libsignal-client-0.86.8.jar">
|
||||
<sha256 value="bac0edb5e25a73686aa418a6e43e0e81df1744e2751957a5dcb960e03d59a1c3" origin="Generated by Gradle"/>
|
||||
<component group="org.signal" name="libsignal-client" version="0.86.9">
|
||||
<artifact name="libsignal-client-0.86.9.jar">
|
||||
<sha256 value="64836ce8aab3eda74ed3e2627b9c1a2c69a463cf0fe918d793ac745a4d24e30a" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="libsignal-client-0.86.8.module">
|
||||
<sha256 value="b9e8afcc22d50a4818f9d56cb40beda66c6532710c432a27be6b05c4ba5301af" origin="Generated by Gradle"/>
|
||||
<artifact name="libsignal-client-0.86.9.module">
|
||||
<sha256 value="81a4e9765936d88dc6b7fc66517c76d1d6b879f92ba967126a7539dc1dd38fd9" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.signal" name="ringrtc-android" version="2.61.0">
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.whispersystems.signalservice.internal.util.Util
|
||||
*/
|
||||
object EnvelopeContentValidator {
|
||||
|
||||
private const val MAX_POLL_QUESTION_CHARACTER_LENGTH = 200
|
||||
private const val MAX_POLL_CHARACTER_LENGTH = 100
|
||||
private const val MIN_POLL_OPTIONS = 2
|
||||
|
||||
@@ -167,7 +168,7 @@ object EnvelopeContentValidator {
|
||||
}
|
||||
|
||||
private fun DataMessage.PollCreate.hasInvalidPollQuestion(): Boolean {
|
||||
return this.question.isNullOrBlank() || this.question.length > MAX_POLL_CHARACTER_LENGTH
|
||||
return this.question.isNullOrBlank() || this.question.length > MAX_POLL_QUESTION_CHARACTER_LENGTH
|
||||
}
|
||||
|
||||
private fun DataMessage.PollCreate.hasInvalidPollOptions(): Boolean {
|
||||
|
||||
@@ -50,11 +50,11 @@ class EnvelopeContentValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validate - ensure polls with a question exceeding 100 characters are marked invalid`() {
|
||||
fun `validate - ensure polls with a question exceeding 200 characters are marked invalid`() {
|
||||
val content = Content(
|
||||
dataMessage = DataMessage(
|
||||
pollCreate = DataMessage.PollCreate(
|
||||
question = "abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyz",
|
||||
question = "abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyz",
|
||||
options = listOf("option1", "option2"),
|
||||
allowMultiple = true
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user