From eaaee8ad0b54811e5f54828dccdb0513f7764523 Mon Sep 17 00:00:00 2001 From: Krainov Gleb Date: Sun, 18 Oct 2026 15:37:37 +0000 Subject: [PATCH] fix(chat): let the voice message notification go right after a pause Media3 keeps a media service in the foreground for 10 minutes after the player pauses or the message ends (DEFAULT_FOREGROUND_SERVICE_TIMEOUT_MS), and the notification of a foreground service cannot be swiped away. Set the timeout to 0, so that the service leaves the foreground as soon as playback stops. Assisted-by: Claude-Code:claude-sonnet-5-5 Signed-off-by: Krainov Gleb --- .../chat/data/io/VoiceMessageMediaService.kt | 8 ++++ .../VoiceMessageMediaServiceForegroundTest.kt | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 app/src/test/java/com/nextcloud/talk/chat/data/io/VoiceMessageMediaServiceForegroundTest.kt diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/io/VoiceMessageMediaService.kt b/app/src/main/java/com/nextcloud/talk/chat/data/io/VoiceMessageMediaService.kt index 3deed37744..9aa7507d85 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/io/VoiceMessageMediaService.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/io/VoiceMessageMediaService.kt @@ -21,6 +21,9 @@ class VoiceMessageMediaService : MediaSessionService() { super.onCreate() val player = ExoPlayer.Builder(this).build() mediaSession = MediaSession.Builder(this, player).build() + // By default Media3 keeps the service in the foreground for 10 minutes after a pause or the end of the + // message, and the notification of a foreground service cannot be swiped away. + setForegroundServiceTimeoutMs(FOREGROUND_TIMEOUT_AFTER_PAUSE_MS) } override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession @@ -31,4 +34,9 @@ class VoiceMessageMediaService : MediaSessionService() { mediaSession = null super.onDestroy() } + + companion object { + /** The service leaves the foreground, and its notification can be swiped away, as soon as playback stops. */ + const val FOREGROUND_TIMEOUT_AFTER_PAUSE_MS = 0L + } } diff --git a/app/src/test/java/com/nextcloud/talk/chat/data/io/VoiceMessageMediaServiceForegroundTest.kt b/app/src/test/java/com/nextcloud/talk/chat/data/io/VoiceMessageMediaServiceForegroundTest.kt new file mode 100644 index 0000000000..1479af911b --- /dev/null +++ b/app/src/test/java/com/nextcloud/talk/chat/data/io/VoiceMessageMediaServiceForegroundTest.kt @@ -0,0 +1,42 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.chat.data.io + +import androidx.media3.session.MediaSessionService +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** + * Media3 keeps a media service in the foreground, with a notification that cannot be swiped away, for + * [MediaSessionService.DEFAULT_FOREGROUND_SERVICE_TIMEOUT_MS] (10 minutes) after the player is paused or ends. The + * voice message service must leave the foreground at once. + */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [33]) +class VoiceMessageMediaServiceForegroundTest { + /** The timeout Media3 applies to the notification manager of [service], read by reflection (no getter). */ + private fun foregroundTimeoutMs(service: VoiceMessageMediaService): Long { + val getManager = MediaSessionService::class.java.getDeclaredMethod("getMediaNotificationManager") + getManager.isAccessible = true + val manager = getManager.invoke(service) + val field = manager.javaClass.getDeclaredField("userEngagedTimeoutMs") + field.isAccessible = true + return field.getLong(manager) + } + + @Test + fun `service does not stay in the foreground after the voice message is paused or ends`() { + val service = Robolectric.buildService(VoiceMessageMediaService::class.java).create().get() + + assertEquals(0L, foregroundTimeoutMs(service)) + service.onDestroy() + } +}