diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentSaver.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentSaver.kt index 89eac645e8..70e526239c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentSaver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentSaver.kt @@ -48,8 +48,17 @@ class AttachmentSaver(private val host: Host) { } suspend fun saveAttachments(record: MmsMessageRecord) { - val attachments = record.slideDeck.slides - .filter { it.uri != null && (it.hasImage() || it.hasVideo() || it.hasAudio() || it.hasDocument()) } + val saveableSlides = record.slideDeck.slides.filter { it.uri != null && (it.hasImage() || it.hasVideo() || it.hasAudio() || it.hasDocument()) } + + // If a message contains a voice note, that's the only attachment we render, so it's the only one we should save. + val voiceNote = saveableSlides.firstOrNull { it.asAttachment().voiceNote } + val slidesToSave = if (voiceNote != null) { + listOf(voiceNote) + } else { + saveableSlides + } + + val attachments = slidesToSave .map { SaveAttachment(it.uri!!, it.contentType, record.dateSent, it.fileName.orNull()) } .toSet() saveAttachments(attachments) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/SignalServiceProtoUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/SignalServiceProtoUtil.kt index 3016bf742a..65b7dbb04b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/SignalServiceProtoUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/SignalServiceProtoUtil.kt @@ -178,7 +178,14 @@ object SignalServiceProtoUtil { } fun List.toPointersWithinLimit(): List { - return mapNotNull { it.toPointer() }.take(RemoteConfig.maxAttachmentCount) + val pointers = mapNotNull { it.toPointer() }.take(RemoteConfig.maxAttachmentCount) + + val voiceNote = pointers.firstOrNull { it.voiceNote } + return if (voiceNote != null) { + listOf(voiceNote) + } else { + pointers + } } fun AttachmentPointer.toPointer(stickerLocator: StickerLocator? = null): Attachment? { diff --git a/app/src/test/java/org/thoughtcrime/securesms/attachments/AttachmentSaverTest.kt b/app/src/test/java/org/thoughtcrime/securesms/attachments/AttachmentSaverTest.kt index 002f5c57c8..9d71afe153 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/attachments/AttachmentSaverTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/attachments/AttachmentSaverTest.kt @@ -29,10 +29,15 @@ import org.signal.core.ui.util.StorageUtil import org.thoughtcrime.securesms.attachments.AttachmentSaver.Host import org.thoughtcrime.securesms.attachments.AttachmentSaver.RequestPermissionResult import org.thoughtcrime.securesms.attachments.AttachmentSaver.SaveToStorageWarningResult +import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.UiHintValues +import org.thoughtcrime.securesms.mms.AudioSlide +import org.thoughtcrime.securesms.mms.DocumentSlide +import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.testing.CoroutineDispatcherRule import org.thoughtcrime.securesms.testutil.MockAppDependenciesRule +import org.thoughtcrime.securesms.testutil.UriAttachmentBuilder import org.thoughtcrime.securesms.util.SaveAttachmentUtil import org.thoughtcrime.securesms.util.SaveAttachmentUtil.SaveAttachmentsResult @@ -350,6 +355,66 @@ class AttachmentSaverTest { verify { testEnv.host.showSaveResult(SaveAttachmentsResult.WriteStoragePermissionDenied) } } + + @Test + fun `saveAttachments saves only the voice note when the message record contains a voice note`() = runTest(testDispatcher) { + val testEnv = setUpTestEnvironment( + hasDismissedSaveStorageWarning = true, + canWriteToMediaStore = true + ) + + val voiceNoteUri = Uri.parse("content://org.thoughtcrime.securesms/part/111") + val deck = SlideDeck().apply { + addSlide(AudioSlide(UriAttachmentBuilder.build(id = 111L, uri = voiceNoteUri, contentType = "audio/aac", fileName = "voice-note.aac", voiceNote = true))) + addSlide(DocumentSlide(UriAttachmentBuilder.build(id = 222L, uri = Uri.parse("content://org.thoughtcrime.securesms/part/222"), contentType = "application/pdf", fileName = "test.pdf"))) + } + + val record = mockk { + every { slideDeck } returns deck + every { dateSent } returns 1742234803832L + } + + AttachmentSaver(host = testEnv.host).saveAttachments(record) + + coVerify { + SaveAttachmentUtil.saveAttachments( + setOf( + SaveAttachmentUtil.SaveAttachment(uri = voiceNoteUri, contentType = "audio/aac", date = 1742234803832L, fileName = "voice-note.aac") + ) + ) + } + } + + @Test + fun `saveAttachments saves all attachments when the message record does not contain a voice note`() = runTest(testDispatcher) { + val testEnv = setUpTestEnvironment( + hasDismissedSaveStorageWarning = true, + canWriteToMediaStore = true + ) + + val audioUri = Uri.parse("content://org.thoughtcrime.securesms/part/111") + val documentUri = Uri.parse("content://org.thoughtcrime.securesms/part/222") + val deck = SlideDeck().apply { + addSlide(AudioSlide(UriAttachmentBuilder.build(id = 111L, uri = audioUri, contentType = "audio/aac", fileName = "audio.aac", voiceNote = false))) + addSlide(DocumentSlide(UriAttachmentBuilder.build(id = 222L, uri = documentUri, contentType = "application/pdf", fileName = "test.pdf"))) + } + + val record = mockk { + every { slideDeck } returns deck + every { dateSent } returns 1742234803832L + } + + AttachmentSaver(host = testEnv.host).saveAttachments(record) + + coVerify { + SaveAttachmentUtil.saveAttachments( + setOf( + SaveAttachmentUtil.SaveAttachment(uri = audioUri, contentType = "audio/aac", date = 1742234803832L, fileName = "audio.aac"), + SaveAttachmentUtil.SaveAttachment(uri = documentUri, contentType = "application/pdf", date = 1742234803832L, fileName = "test.pdf") + ) + ) + } + } } private data class TestEnvironment(