From 6a0ea11e7a16c49e8053e86f128419fef15d512e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 17:58:14 +0200 Subject: [PATCH] Follow the spec regarding waveform content --- .../room/model/message/AudioWaveformInfo.kt | 7 + .../room/send/LocalEchoEventFactory.kt | 3 +- .../session/room/send/WaveFormSanitizer.kt | 80 ++++++++++++ .../room/send/WaveFormSanitizerTest.kt | 123 ++++++++++++++++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt index 0e2f0f880c..d576f1057a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt @@ -19,11 +19,18 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +/** + * See https://github.com/matrix-org/matrix-doc/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md + */ @JsonClass(generateAdapter = true) data class AudioWaveformInfo( @Json(name = "duration") val duration: Int? = null, + /** + * The array should have no less than 30 elements and no more than 120. + * List of integers between zero and 1024, inclusive. + */ @Json(name = "waveform") val waveform: List? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index fb56587ac1..c610326a94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -75,6 +75,7 @@ internal class LocalEchoEventFactory @Inject constructor( private val markdownParser: MarkdownParser, private val textPillsUtils: TextPillsUtils, private val thumbnailExtractor: ThumbnailExtractor, + private val waveformSanitizer: WaveFormSanitizer, private val localEchoRepository: LocalEchoRepository, private val permalinkFactory: PermalinkFactory ) { @@ -302,7 +303,7 @@ internal class LocalEchoEventFactory @Inject constructor( url = attachment.queryUri.toString(), audioWaveformInfo = if (!isVoiceMessage) null else AudioWaveformInfo( duration = attachment.duration?.toInt(), - waveform = attachment.waveform + waveform = waveformSanitizer.sanitize(attachment.waveform) ), voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt new file mode 100644 index 0000000000..2bc5cb2475 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * 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.matrix.android.sdk.internal.session.room.send + +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.ceil + +internal class WaveFormSanitizer @Inject constructor() { + private companion object { + const val MIN_NUMBER_OF_VALUES = 30 + const val MAX_NUMBER_OF_VALUES = 120 + + const val MAX_VALUE = 1024 + } + + /** + * The array should have no less than 30 elements and no more than 120. + * List of integers between zero and 1024, inclusive. + */ + fun sanitize(waveForm: List?): List? { + if (waveForm.isNullOrEmpty()) { + return null + } + + // Limit the number of items + val result = mutableListOf() + if (waveForm.size < MIN_NUMBER_OF_VALUES) { + // Repeat the same value to have at least 30 items + val repeatTimes = ceil(MIN_NUMBER_OF_VALUES / waveForm.size.toDouble()).toInt() + waveForm.map { value -> + repeat(repeatTimes) { + result.add(value) + } + } + } else if (waveForm.size > MAX_NUMBER_OF_VALUES) { + val keepOneOf = ceil(waveForm.size.toDouble() / MAX_NUMBER_OF_VALUES).toInt() + waveForm.mapIndexed { idx, value -> + if (idx % keepOneOf == 0) { + result.add(value) + } + } + } else { + result.addAll(waveForm) + } + + // OK, ensure all items are positive + val limited = result.map { + abs(it) + } + + // Ensure max is not above MAX_VALUE + val max = limited.maxOrNull() ?: MAX_VALUE + + val final = if (max > MAX_VALUE) { + // Reduce the range + limited.map { + it * MAX_VALUE / max + } + } else { + limited + } + + return final + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt new file mode 100644 index 0000000000..23c8aeb76b --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * 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.matrix.android.sdk.internal.session.room.send + +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeInRange +import org.junit.Test + +class WaveFormSanitizerTest { + + private val waveFormSanitizer = WaveFormSanitizer() + + @Test + fun sanitizeNull() { + waveFormSanitizer.sanitize(null) shouldBe null + } + + @Test + fun sanitizeEmpty() { + waveFormSanitizer.sanitize(emptyList()) shouldBe null + } + + @Test + fun sanitizeSingleton() { + val result = waveFormSanitizer.sanitize(listOf(1))!! + result.size shouldBe 30 + checkResult(result) + } + + @Test + fun sanitize29() { + val list = generateSequence { 1 }.take(29).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize30() { + val list = generateSequence { 1 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + result.size shouldBe 30 + checkResult(result) + } + + @Test + fun sanitize31() { + val list = generateSequence { 1 }.take(31).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize119() { + val list = generateSequence { 1 }.take(119).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize120() { + val list = generateSequence { 1 }.take(120).toList() + val result = waveFormSanitizer.sanitize(list)!! + result.size shouldBe 120 + checkResult(result) + } + + @Test + fun sanitize121() { + val list = generateSequence { 1 }.take(121).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize1024() { + val list = generateSequence { 1 }.take(1024).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeNegative() { + val list = generateSequence { -1 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeMaxValue() { + val list = generateSequence { 1025 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeNegativeMaxValue() { + val list = generateSequence { -1025 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + private fun checkResult(result: List) { + result.forEach { + it shouldBeInRange 0..1024 + } + + result.size shouldBeInRange 30..120 + } +}