diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt index a27a42c0b7..9c40cccf01 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt @@ -3028,16 +3028,25 @@ class CallActivity : CallBaseActivity() { @SuppressLint("ClickableViewAccessibility") override fun onTouch(v: View, event: MotionEvent): Boolean { v.onTouchEvent(event) - if (event.action == MotionEvent.ACTION_UP && isPushToTalkActive) { - isPushToTalkActive = false - binding!!.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px) - pulseAnimation!!.stop() - toggleMedia(false, false) + if (isPushToTalkRelease(event.action)) { + stopPushToTalk() } return true } } + // ACTION_CANCEL must end push to talk just like ACTION_UP, otherwise the microphone stays enabled although + // the gesture was aborted (e.g. by a system gesture or the notification shade) and the button shows "muted". + private fun stopPushToTalk() { + if (!isPushToTalkActive) { + return + } + isPushToTalkActive = false + binding!!.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px) + pulseAnimation!!.stop() + toggleMedia(false, false) + } + @Subscribe(threadMode = ThreadMode.BACKGROUND) fun onMessageEvent(networkEvent: NetworkEvent) { if (networkEvent.networkConnectionEvent == NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED) { @@ -3225,6 +3234,9 @@ class CallActivity : CallBaseActivity() { private const val CALLING_TIMEOUT: Long = 45000 private const val PULSE_ANIMATION_DURATION: Int = 310 + internal fun isPushToTalkRelease(action: Int): Boolean = + action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL + private const val DELAY_ON_ERROR_STOP_THRESHOLD: Int = 16 private const val SESSION_ID_PREFFIX_END: Int = 4 diff --git a/app/src/test/java/com/nextcloud/talk/activities/CallActivityPushToTalkTest.kt b/app/src/test/java/com/nextcloud/talk/activities/CallActivityPushToTalkTest.kt new file mode 100644 index 0000000000..905367b903 --- /dev/null +++ b/app/src/test/java/com/nextcloud/talk/activities/CallActivityPushToTalkTest.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.activities + +import android.view.MotionEvent +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * Push-to-talk release detection ([CallActivity.isPushToTalkRelease]). + * + * A long-press that ends with ACTION_CANCEL (system gesture, notification shade) must mute the microphone again + * just like ACTION_UP, otherwise the mic stays hot while the button shows "muted". + */ +class CallActivityPushToTalkTest { + + @Test + fun `ACTION_UP ends push to talk`() { + assertTrue(CallActivity.isPushToTalkRelease(MotionEvent.ACTION_UP)) + } + + @Test + fun `ACTION_CANCEL ends push to talk`() { + assertTrue(CallActivity.isPushToTalkRelease(MotionEvent.ACTION_CANCEL)) + } + + @Test + fun `other actions do not end push to talk`() { + assertFalse(CallActivity.isPushToTalkRelease(MotionEvent.ACTION_DOWN)) + assertFalse(CallActivity.isPushToTalkRelease(MotionEvent.ACTION_MOVE)) + assertFalse(CallActivity.isPushToTalkRelease(MotionEvent.ACTION_OUTSIDE)) + } +}