Fix large message size calculation to use bytes.

This commit is contained in:
Greyson Parrelli
2024-12-18 16:07:30 -05:00
parent e434cda40a
commit 16bb4d10d1
12 changed files with 225 additions and 119 deletions

View File

@@ -1052,8 +1052,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (hasExtraText(messageRecord)) {
bodyText.setOverflowText(getLongMessageSpan(messageRecord));
bodyText.setMaxLength(messageRecord.getBody().length() - 2);
} else {
bodyText.setOverflowText(null);
bodyText.setMaxLength(messageRecord.getBody().length());
}
if (messageRecord.isOutgoing()) {

View File

@@ -2,14 +2,10 @@ package org.thoughtcrime.securesms.conversation
import android.content.Context
import android.os.Parcelable
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import kotlinx.parcelize.Parcelize
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.CharacterCalculator
import org.thoughtcrime.securesms.util.PushCharacterCalculator
import java.lang.IllegalArgumentException
/**
* The kinds of messages you can send, e.g. a plain Signal message, an SMS message, etc.
@@ -22,22 +18,14 @@ sealed class MessageSendType(
val composeHintRes: Int,
@DrawableRes
val buttonDrawableRes: Int,
@DrawableRes
val menuDrawableRes: Int,
@ColorRes
val backgroundColorRes: Int,
val transportType: TransportType,
val characterCalculator: CharacterCalculator
val maxBodyByteSize: Int
) : Parcelable {
@get:JvmName("usesSignalTransport")
val usesSignalTransport
get() = transportType == TransportType.SIGNAL
fun calculateCharacters(body: String): CharacterCalculator.CharacterState {
return characterCalculator.calculateCharacters(body)
}
open fun getTitle(context: Context): String {
return context.getString(titleRes)
}
@@ -50,26 +38,12 @@ sealed class MessageSendType(
titleRes = R.string.ConversationActivity_send_message_content_description,
composeHintRes = R.string.conversation_activity__type_message_push,
buttonDrawableRes = R.drawable.ic_send_lock_24,
menuDrawableRes = R.drawable.ic_secure_24,
backgroundColorRes = R.color.core_ultramarine,
transportType = TransportType.SIGNAL,
characterCalculator = PushCharacterCalculator()
maxBodyByteSize = 2048
)
enum class TransportType {
SIGNAL,
SMS
}
companion object {
@JvmStatic
fun getAllAvailable(): List<MessageSendType> {
return listOf(SignalMessageSendType)
}
@JvmStatic
fun getFirstForTransport(transportType: TransportType): MessageSendType {
return getAllAvailable().firstOrNull { it.transportType == transportType } ?: throw IllegalArgumentException("No options available for desired type $transportType!")
}
}
}

View File

@@ -1281,25 +1281,6 @@ class ConversationFragment :
}
}
private fun calculateCharactersRemaining() {
val messageBody: String = binding.conversationInputPanel.embeddedTextEditor.textTrimmed.toString()
val charactersLeftView: TextView = binding.conversationInputSpaceLeft
val characterState = MessageSendType.SignalMessageSendType.calculateCharacters(messageBody)
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
charactersLeftView.text = String.format(
Locale.getDefault(),
"%d/%d (%d)",
characterState.charactersRemaining,
characterState.maxTotalMessageSize,
characterState.messagesSpent
)
charactersLeftView.visibility = View.VISIBLE
} else {
charactersLeftView.visibility = View.GONE
}
}
private fun registerForResults() {
addToContactsLauncher = registerForActivityResult(AddToContactsContract()) {}
conversationActivityResultContracts = ConversationActivityResultContracts(this, ActivityResultCallbacks())
@@ -4020,7 +4001,6 @@ class ConversationFragment :
}
override fun afterTextChanged(s: Editable) {
calculateCharactersRemaining()
if (composeText.textTrimmed.isEmpty() || beforeLength == 0) {
composeText.postDelayed({
if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {

View File

@@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.components.emoji.EmojiStrings
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.contactshare.ContactUtil
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.v2.RequestReviewState.GroupReviewState
import org.thoughtcrime.securesms.conversation.v2.RequestReviewState.IndividualReviewState
@@ -186,8 +185,7 @@ class ConversationRepository(
val sendCompletable = Completable.create { emitter ->
val splitMessage: MessageUtil.SplitResult = MessageUtil.getSplitMessage(
applicationContext,
body,
MessageSendType.SignalMessageSendType.calculateCharacters(body).maxPrimaryMessageSize
body
)
val outgoingMessageSlideDeck: SlideDeck? = splitMessage.textSlide.map {

View File

@@ -169,7 +169,7 @@ class MediaSelectionRepository(context: Context) {
)
)
} else {
val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody, sendType.calculateCharacters(trimmedBody).maxPrimaryMessageSize)
val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody)
val splitBody = splitMessage.body
if (splitMessage.textSlide.isPresent) {
@@ -325,7 +325,7 @@ class MediaSelectionRepository(context: Context) {
Log.w(TAG, "Asked to send an unexpected mimeType: '" + mediaItem.contentType + "'. Skipping.")
}
}
val splitMessage = MessageUtil.getSplitMessage(context, body, sendType.calculateCharacters(body).maxPrimaryMessageSize)
val splitMessage = MessageUtil.getSplitMessage(context, body)
val splitBody = splitMessage.body
if (splitMessage.textSlide.isPresent) {
slideDeck.addSlide(splitMessage.textSlide.get())

View File

@@ -51,6 +51,7 @@ import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.MessageUtil;
import org.thoughtcrime.securesms.util.Util;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -62,6 +63,8 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import okio.Utf8;
/**
* MultiShareSender encapsulates send logic (stolen from {@link org.thoughtcrime.securesms.conversation.ConversationActivity}
* and provides a means to:
@@ -113,8 +116,7 @@ public final class MultiShareSender {
List<Contact> contacts = multiShareArgs.getSharedContacts();
SlideDeck slideDeck = new SlideDeck(primarySlideDeck);
boolean needsSplit = message != null &&
message.length() > sendType.calculateCharacters(message).maxPrimaryMessageSize;
boolean needsSplit = message != null && Utf8.size(message) > MessageUtil.MAX_MESSAGE_SIZE_BYTES;
boolean hasMmsMedia = !multiShareArgs.getMedia().isEmpty() ||
(multiShareArgs.getDataUri() != null && multiShareArgs.getDataUri() != Uri.EMPTY) ||
multiShareArgs.getStickerLocator() != null ||
@@ -196,7 +198,7 @@ public final class MultiShareSender {
{
String body = multiShareArgs.getDraftText();
if (sendType.usesSignalTransport() && body != null) {
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body, sendType.calculateCharacters(body).maxPrimaryMessageSize);
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body);
body = splitMessage.getBody();
if (splitMessage.getTextSlide().isPresent()) {

View File

@@ -1,63 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.mms.TextSlide;
import org.thoughtcrime.securesms.providers.BlobProvider;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Optional;
public final class MessageUtil {
private MessageUtil() {}
/**
* @return If the message is longer than the allowed text size, this will return trimmed text with
* an accompanying TextSlide. Otherwise it'll just return the original text.
*/
public static SplitResult getSplitMessage(@NonNull Context context, @NonNull String rawText, int maxPrimaryMessageSize) {
String bodyText = rawText;
Optional<TextSlide> textSlide = Optional.empty();
if (bodyText.length() > maxPrimaryMessageSize) {
bodyText = rawText.substring(0, maxPrimaryMessageSize);
byte[] textData = rawText.getBytes();
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(new Date());
String filename = String.format("signal-%s.txt", timestamp);
Uri textUri = BlobProvider.getInstance()
.forData(textData)
.withMimeType(MediaUtil.LONG_TEXT)
.withFileName(filename)
.createForSingleSessionInMemory();
textSlide = Optional.of(new TextSlide(context, textUri, filename, textData.length));
}
return new SplitResult(bodyText, textSlide);
}
public static class SplitResult {
private final String body;
private final Optional<TextSlide> textSlide;
private SplitResult(@NonNull String body, @NonNull Optional<TextSlide> textSlide) {
this.body = body;
this.textSlide = textSlide;
}
public @NonNull String getBody() {
return body;
}
public @NonNull Optional<TextSlide> getTextSlide() {
return textSlide;
}
}
}

View File

@@ -0,0 +1,45 @@
package org.thoughtcrime.securesms.util
import android.content.Context
import org.signal.core.util.splitByByteLength
import org.thoughtcrime.securesms.mms.TextSlide
import org.thoughtcrime.securesms.providers.BlobProvider
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.Optional
object MessageUtil {
const val MAX_MESSAGE_SIZE_BYTES: Int = 2000 // Technically 2048, but we'll play it a little safe
/**
* @return If the message is longer than the allowed text size, this will return trimmed text with
* an accompanying TextSlide. Otherwise it'll just return the original text.
*/
@JvmStatic
fun getSplitMessage(context: Context, rawText: String): SplitResult {
val (trimmed, remainder) = rawText.splitByByteLength(MAX_MESSAGE_SIZE_BYTES)
return if (remainder != null) {
val textData = rawText.toByteArray()
val timestamp = SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(Date())
val filename = String.format("signal-%s.txt", timestamp)
val textUri = BlobProvider.getInstance()
.forData(textData)
.withMimeType(MediaUtil.LONG_TEXT)
.withFileName(filename)
.createForSingleSessionInMemory()
val textSlide = Optional.of(TextSlide(context, textUri, filename, textData.size.toLong()))
SplitResult(trimmed, textSlide)
} else {
SplitResult(trimmed, Optional.empty())
}
}
data class SplitResult(
val body: String,
val textSlide: Optional<TextSlide>
)
}