From 6eeb54ae40dbd995a55822659c40365d288e0727 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 24 Oct 2022 23:59:29 +0200 Subject: [PATCH 01/10] Stop ongoing voice broadcast on app restart --- .../features/home/HomeActivityViewModel.kt | 30 +++++++++++++ .../voicebroadcast/VoiceBroadcastHelper.kt | 4 ++ .../usecase/GetLastVoiceBroadcastUseCase.kt | 45 +++++++++++++++++++ .../usecase/StartVoiceBroadcastUseCase.kt | 10 +---- 4 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 61a8e5b79ec..1e79dc58448 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -42,6 +42,8 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -60,12 +62,14 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.room.model.Membership @@ -92,6 +96,7 @@ class HomeActivityViewModel @AssistedInject constructor( private val analyticsConfig: AnalyticsConfig, private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore, private val vectorFeatures: VectorFeatures, + private val voiceBroadcastHelper: VoiceBroadcastHelper, ) : VectorViewModel(initialState) { @AssistedFactory @@ -123,6 +128,7 @@ class HomeActivityViewModel @AssistedInject constructor( observeReleaseNotes() observeLocalNotificationsSilenced() initThreadsMigration() + stopOngoingVoiceBroadcast() } private fun observeReleaseNotes() = withState { state -> @@ -490,6 +496,30 @@ class HomeActivityViewModel @AssistedInject constructor( } } + /** + * Stop ongoing voice broadcast if any. + */ + private fun stopOngoingVoiceBroadcast() { + val session = activeSessionHolder.getSafeActiveSession() ?: return + + // FIXME Iterate only on recent rooms for the moment, improve this + val recentRooms = session.roomService().getBreadcrumbs(roomSummaryQueryParams { + displayName = QueryStringValue.NoCondition + memberships = listOf(Membership.JOIN) + }).mapNotNull { session.getRoom(it.roomId) } + + recentRooms + .forEach { room -> + val ongoingVoiceBroadcasts = voiceBroadcastHelper.getOngoingVoiceBroadcasts(room.roomId) + val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId + val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } + if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { + viewModelScope.launch { voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) } + return // No need to iterate more as we should not have more than one recording VB + } + } + } + override fun handle(action: HomeActivityViewActions) { when (action) { HomeActivityViewActions.PushPromptHasBeenReviewed -> { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index 58e7de7f323..ee9034661cd 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast +import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase import im.vector.app.features.voicebroadcast.usecase.PauseVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.ResumeVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.StartVoiceBroadcastUseCase @@ -30,6 +31,7 @@ class VoiceBroadcastHelper @Inject constructor( private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase, private val resumeVoiceBroadcastUseCase: ResumeVoiceBroadcastUseCase, private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase, + private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, private val voiceBroadcastPlayer: VoiceBroadcastPlayer, ) { suspend fun startVoiceBroadcast(roomId: String) = startVoiceBroadcastUseCase.execute(roomId) @@ -45,4 +47,6 @@ class VoiceBroadcastHelper @Inject constructor( fun pausePlayback() = voiceBroadcastPlayer.pause() fun stopPlayback() = voiceBroadcastPlayer.stop() + + fun getOngoingVoiceBroadcasts(roomId: String) = getOngoingVoiceBroadcastsUseCase.execute(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt new file mode 100644 index 00000000000..db2c6251613 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * 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 im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import timber.log.Timber +import javax.inject.Inject + +class GetOngoingVoiceBroadcastsUseCase @Inject constructor( + private val session: Session, +) { + + fun execute(roomId: String): List { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + + Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId") + + return room.stateService().getStateEvents( + setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), + QueryStringValue.IsNotEmpty + ) + .mapNotNull { it.asVoiceBroadcastEvent() } + .filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 7934d18e360..2b7ca7b9f16 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -25,9 +25,7 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState -import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.lib.multipicker.utils.toMultiPickerAudioType -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent @@ -43,6 +41,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, private val context: Context, private val buildMeta: BuildMeta, + private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -50,12 +49,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") - val onGoingVoiceBroadcastEvents = room.stateService().getStateEvents( - setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), - QueryStringValue.IsNotEmpty - ) - .mapNotNull { it.asVoiceBroadcastEvent() } - .filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + val onGoingVoiceBroadcastEvents = getOngoingVoiceBroadcastsUseCase.execute(roomId) if (onGoingVoiceBroadcastEvents.isEmpty()) { startVoiceBroadcast(room) From 53db04c8cf28d3b3ce2993367f1df986eccad962 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 25 Oct 2022 17:58:09 +0200 Subject: [PATCH 02/10] Add changelog --- changelog.d/7450.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7450.wip diff --git a/changelog.d/7450.wip b/changelog.d/7450.wip new file mode 100644 index 00000000000..de4d3dc5e1c --- /dev/null +++ b/changelog.d/7450.wip @@ -0,0 +1 @@ +[Voice Broadcast] Stop recording when opening the room after an app restart From 85bc78bd72f15b2741b02b3a6f8389e0d4474761 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 09:50:58 +0200 Subject: [PATCH 03/10] Do not pause already paused voice broadcast --- .../home/room/detail/composer/MessageComposerFragment.kt | 2 +- .../home/room/detail/composer/MessageComposerViewState.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 55ec922a578..e01dd31516f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -234,7 +234,7 @@ class MessageComposerFragment : VectorBaseFragment(), A } // TODO remove this when there will be a recording indicator outside of the timeline // Pause voice broadcast if the timeline is not shown anymore - it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Pause) + it.isRecordingVoiceBroadcast && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Pause) else -> { timelineViewModel.handle(VoiceBroadcastAction.Listening.Pause) messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString())) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index 0df1dbebd86..a4021f87b21 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -79,9 +79,8 @@ data class MessageComposerViewState( is VoiceMessageRecorderView.RecordingUiState.Recording -> true } - val isVoiceBroadcasting = when (voiceBroadcastState) { + val isRecordingVoiceBroadcast = when (voiceBroadcastState) { VoiceBroadcastState.STARTED, - VoiceBroadcastState.PAUSED, VoiceBroadcastState.RESUMED -> true else -> false } From 47047b20349c423b6f070e78797ffbbd599d764c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:00:56 +0200 Subject: [PATCH 04/10] move map operator in a new line --- .../vector/app/features/home/HomeActivityViewModel.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 1e79dc58448..2c457092912 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -503,10 +503,12 @@ class HomeActivityViewModel @AssistedInject constructor( val session = activeSessionHolder.getSafeActiveSession() ?: return // FIXME Iterate only on recent rooms for the moment, improve this - val recentRooms = session.roomService().getBreadcrumbs(roomSummaryQueryParams { - displayName = QueryStringValue.NoCondition - memberships = listOf(Membership.JOIN) - }).mapNotNull { session.getRoom(it.roomId) } + val recentRooms = session.roomService() + .getBreadcrumbs(roomSummaryQueryParams { + displayName = QueryStringValue.NoCondition + memberships = listOf(Membership.JOIN) + }) + .mapNotNull { session.getRoom(it.roomId) } recentRooms .forEach { room -> From ec80adc8aa416c07ebd61faff5cab5c26e7ccff4 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:10:56 +0200 Subject: [PATCH 05/10] Rename usecase file --- ...iceBroadcastUseCase.kt => GetOngoingVoiceBroadcastsUseCase.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/{GetLastVoiceBroadcastUseCase.kt => GetOngoingVoiceBroadcastsUseCase.kt} (100%) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt similarity index 100% rename from vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt rename to vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt From 2f14d191302b581a37fc6cd3cc5846d1009530b9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 11:26:12 +0200 Subject: [PATCH 06/10] Fix failing test --- .../usecase/StartVoiceBroadcastUseCaseTest.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index 9fa6b7a4503..f95ab2053ba 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -49,10 +49,11 @@ class StartVoiceBroadcastUseCaseTest { private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase( - fakeSession, - fakeVoiceBroadcastRecorder, - FakeContext().instance, - mockk() + session = fakeSession, + voiceBroadcastRecorder = fakeVoiceBroadcastRecorder, + context = FakeContext().instance, + buildMeta = mockk(), + getOngoingVoiceBroadcastsUseCase = GetOngoingVoiceBroadcastsUseCase(fakeSession), ) @Test From 5855fe1242d7e99317b79435a541bf2bd68980ef Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 12:44:18 +0200 Subject: [PATCH 07/10] Add StopOngoingVoiceBroadcastUseCase --- .../features/home/HomeActivityViewModel.kt | 35 +---------- .../StopOngoingVoiceBroadcastUseCase.kt | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+), 32 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 2c457092912..c3abdde022f 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -42,8 +42,7 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences -import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper -import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.usecase.StopOngoingVoiceBroadcastUseCase import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -62,14 +61,12 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.room.model.Membership @@ -96,7 +93,7 @@ class HomeActivityViewModel @AssistedInject constructor( private val analyticsConfig: AnalyticsConfig, private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore, private val vectorFeatures: VectorFeatures, - private val voiceBroadcastHelper: VoiceBroadcastHelper, + private val stopOngoingVoiceBroadcastUseCase: StopOngoingVoiceBroadcastUseCase, ) : VectorViewModel(initialState) { @AssistedFactory @@ -128,7 +125,7 @@ class HomeActivityViewModel @AssistedInject constructor( observeReleaseNotes() observeLocalNotificationsSilenced() initThreadsMigration() - stopOngoingVoiceBroadcast() + viewModelScope.launch { stopOngoingVoiceBroadcastUseCase.execute() } } private fun observeReleaseNotes() = withState { state -> @@ -496,32 +493,6 @@ class HomeActivityViewModel @AssistedInject constructor( } } - /** - * Stop ongoing voice broadcast if any. - */ - private fun stopOngoingVoiceBroadcast() { - val session = activeSessionHolder.getSafeActiveSession() ?: return - - // FIXME Iterate only on recent rooms for the moment, improve this - val recentRooms = session.roomService() - .getBreadcrumbs(roomSummaryQueryParams { - displayName = QueryStringValue.NoCondition - memberships = listOf(Membership.JOIN) - }) - .mapNotNull { session.getRoom(it.roomId) } - - recentRooms - .forEach { room -> - val ongoingVoiceBroadcasts = voiceBroadcastHelper.getOngoingVoiceBroadcasts(room.roomId) - val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId - val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } - if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { - viewModelScope.launch { voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) } - return // No need to iterate more as we should not have more than one recording VB - } - } - } - override fun handle(action: HomeActivityViewActions) { when (action) { HomeActivityViewActions.PushPromptHasBeenReviewed -> { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt new file mode 100644 index 00000000000..82baa5e6a83 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * 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 im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import timber.log.Timber +import javax.inject.Inject + +/** + * Stop ongoing voice broadcast if any. + */ +class StopOngoingVoiceBroadcastUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, + private val voiceBroadcastHelper: VoiceBroadcastHelper, +) { + + suspend fun execute() { + Timber.d("## StopOngoingVoiceBroadcastUseCase: Stop ongoing voice broadcast requested") + + val session = activeSessionHolder.getSafeActiveSession() ?: run { + Timber.w("## StopOngoingVoiceBroadcastUseCase: no active session") + return + } + // FIXME Iterate only on recent rooms for the moment, improve this + val recentRooms = session.roomService() + .getBreadcrumbs(roomSummaryQueryParams { + displayName = QueryStringValue.NoCondition + memberships = listOf(Membership.JOIN) + }) + .mapNotNull { session.getRoom(it.roomId) } + + recentRooms + .forEach { room -> + val ongoingVoiceBroadcasts = voiceBroadcastHelper.getOngoingVoiceBroadcasts(room.roomId) + val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId + val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } + if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { + voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) + return // No need to iterate more as we should not have more than one recording VB + } + } + } +} From 443d573205bec14e5a7f12d0220da0a659194f4c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 12:48:32 +0200 Subject: [PATCH 08/10] Remove getOngoingVoiceBroadcasts from VoiceBroadcastHelper --- .../app/features/voicebroadcast/VoiceBroadcastHelper.kt | 4 ---- .../usecase/StopOngoingVoiceBroadcastUseCase.kt | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index ee9034661cd..58e7de7f323 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -16,7 +16,6 @@ package im.vector.app.features.voicebroadcast -import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase import im.vector.app.features.voicebroadcast.usecase.PauseVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.ResumeVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.StartVoiceBroadcastUseCase @@ -31,7 +30,6 @@ class VoiceBroadcastHelper @Inject constructor( private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase, private val resumeVoiceBroadcastUseCase: ResumeVoiceBroadcastUseCase, private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase, - private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, private val voiceBroadcastPlayer: VoiceBroadcastPlayer, ) { suspend fun startVoiceBroadcast(roomId: String) = startVoiceBroadcastUseCase.execute(roomId) @@ -47,6 +45,4 @@ class VoiceBroadcastHelper @Inject constructor( fun pausePlayback() = voiceBroadcastPlayer.pause() fun stopPlayback() = voiceBroadcastPlayer.stop() - - fun getOngoingVoiceBroadcasts(roomId: String) = getOngoingVoiceBroadcastsUseCase.execute(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt index 82baa5e6a83..ab4d16ab600 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt @@ -31,6 +31,7 @@ import javax.inject.Inject */ class StopOngoingVoiceBroadcastUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, + private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, private val voiceBroadcastHelper: VoiceBroadcastHelper, ) { @@ -51,7 +52,7 @@ class StopOngoingVoiceBroadcastUseCase @Inject constructor( recentRooms .forEach { room -> - val ongoingVoiceBroadcasts = voiceBroadcastHelper.getOngoingVoiceBroadcasts(room.roomId) + val ongoingVoiceBroadcasts = getOngoingVoiceBroadcastsUseCase.execute(room.roomId) val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { From 23b4f6d42f467d9257f8aae068941ab566af6afb Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 12:49:51 +0200 Subject: [PATCH 09/10] Inject ActiveSessionHolder in GetOngoingVoiceBroadcastsUseCase --- .../usecase/GetOngoingVoiceBroadcastsUseCase.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt index db2c6251613..47a9ed7b4a6 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt @@ -16,21 +16,22 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom import timber.log.Timber import javax.inject.Inject class GetOngoingVoiceBroadcastsUseCase @Inject constructor( - private val session: Session, + private val activeSessionHolder: ActiveSessionHolder, ) { fun execute(roomId: String): List { + val session = activeSessionHolder.getSafeActiveSession() ?: return emptyList() val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId") From 0cc2a477b473aeaec0183add3dd3243fc2c49a18 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 14:54:55 +0200 Subject: [PATCH 10/10] Mockk GetOngoingVoiceBroadcastsUseCase and adapt tests --- .../usecase/GetOngoingVoiceBroadcastsUseCase.kt | 8 ++++++-- .../usecase/StartVoiceBroadcastUseCaseTest.kt | 16 ++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt index 47a9ed7b4a6..cb228ad8aaa 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt @@ -31,8 +31,12 @@ class GetOngoingVoiceBroadcastsUseCase @Inject constructor( ) { fun execute(roomId: String): List { - val session = activeSessionHolder.getSafeActiveSession() ?: return emptyList() - val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + println("## GetOngoingVoiceBroadcastsUseCase") + println("## GetOngoingVoiceBroadcastsUseCase activeSessionHolder $activeSessionHolder") + val session = activeSessionHolder.getSafeActiveSession() + println("## GetOngoingVoiceBroadcastsUseCase session $session") + val room = session?.getRoom(roomId) ?: error("Unknown roomId: $roomId") + println("## GetOngoingVoiceBroadcastsUseCase room $room") Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId") diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index f95ab2053ba..217a395076b 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -20,6 +20,7 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.test.fakes.FakeContext import im.vector.app.test.fakes.FakeRoom import im.vector.app.test.fakes.FakeRoomService @@ -27,13 +28,13 @@ import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBeNull import org.junit.Test -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toContent @@ -48,12 +49,13 @@ class StartVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val fakeGetOngoingVoiceBroadcastsUseCase = mockk() private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase( session = fakeSession, voiceBroadcastRecorder = fakeVoiceBroadcastRecorder, context = FakeContext().instance, buildMeta = mockk(), - getOngoingVoiceBroadcastsUseCase = GetOngoingVoiceBroadcastsUseCase(fakeSession), + getOngoingVoiceBroadcastsUseCase = fakeGetOngoingVoiceBroadcastsUseCase, ) @Test @@ -81,7 +83,7 @@ class StartVoiceBroadcastUseCaseTest { private suspend fun testVoiceBroadcastStarted(voiceBroadcasts: List) { // Given clearAllMocks() - givenAVoiceBroadcasts(voiceBroadcasts) + givenVoiceBroadcasts(voiceBroadcasts) val voiceBroadcastInfoContentInterceptor = slot() coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID } @@ -104,7 +106,7 @@ class StartVoiceBroadcastUseCaseTest { private suspend fun testVoiceBroadcastNotStarted(voiceBroadcasts: List) { // Given clearAllMocks() - givenAVoiceBroadcasts(voiceBroadcasts) + givenVoiceBroadcasts(voiceBroadcasts) // When startVoiceBroadcastUseCase.execute(A_ROOM_ID) @@ -113,7 +115,7 @@ class StartVoiceBroadcastUseCaseTest { coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) } } - private fun givenAVoiceBroadcasts(voiceBroadcasts: List) { + private fun givenVoiceBroadcasts(voiceBroadcasts: List) { val events = voiceBroadcasts.map { Event( type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, @@ -123,7 +125,9 @@ class StartVoiceBroadcastUseCaseTest { ).toContent() ) } - fakeRoom.stateService().givenGetStateEvents(QueryStringValue.IsNotEmpty, events) + .mapNotNull { it.asVoiceBroadcastEvent() } + .filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + every { fakeGetOngoingVoiceBroadcastsUseCase.execute(any()) } returns events } private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState)