From 717efe46c5a0d463122d3f53114155c02103174d Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Sun, 9 Aug 2026 04:08:56 +0300 Subject: [PATCH 1/2] Do not offer to start a call in a DM with no other member A DM whose other member has left still offered the call button. Starting that call put the user alone in a room nobody could join, with no way out of the call screen. The presenter now clears canStartCall for a DM with no other active member. activeMembersCount counts joined and invited members, so a fresh DM whose peer has been invited but has not joined yet can still start a call. Joining an already ongoing call is untouched, so a call started before the other member left stays reachable. Relates to https://github.com/element-hq/element-x-android/issues/6189 --- .../roomcall/impl/RoomCallStatePresenter.kt | 16 ++++++--- .../impl/RoomCallStatePresenterTest.kt | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt index 15e320c8f8..f57a94bf1e 100644 --- a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt +++ b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt @@ -60,10 +60,18 @@ class RoomCallStatePresenter( isUserLocallyInTheCall = isUserLocallyInTheCall, isAudioCall = roomInfo.activeCallIntentConsensus.isAudio(), ) - else -> RoomCallState.StandBy( - canStartCall = canJoinCall, - isDM = roomInfo.isDm - ) + else -> { + // Nobody would ever join a call started in a DM whose other member has left, + // and the call screen has no way out of that state. + // activeMembersCount counts joined and invited members, so a DM whose peer + // has been invited but has not joined yet can still start a call. + // See https://github.com/element-hq/element-x-android/issues/6189 + val isEmptyDm = roomInfo.isDm && roomInfo.activeMembersCount <= 1 + RoomCallState.StandBy( + canStartCall = canJoinCall && !isEmptyDm, + isDM = roomInfo.isDm + ) + } } } } diff --git a/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt b/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt index 0612adbea1..9a4c3b0af0 100644 --- a/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt +++ b/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt @@ -49,6 +49,42 @@ class RoomCallStatePresenterTest { } } + @Test + fun `present - a DM with no other member cannot start a call`() = runTest { + val room = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + initialRoomInfo = aRoomInfo(isDm = true, activeMembersCount = 1, joinedMembersCount = 1), + roomPermissions = roomPermissions(true), + ) + ) + val presenter = createRoomCallStatePresenter(joinedRoom = room) + presenter.test { + skipItems(1) + assertThat(awaitItem()).isEqualTo( + RoomCallState.StandBy( + canStartCall = false, + isDM = true + ) + ) + } + } + + @Test + fun `present - a DM becomes callable once the other member is active`() = runTest { + val baseRoom = FakeBaseRoom( + initialRoomInfo = aRoomInfo(isDm = true, activeMembersCount = 1, joinedMembersCount = 1), + roomPermissions = roomPermissions(true), + ) + val room = FakeJoinedRoom(baseRoom = baseRoom) + val presenter = createRoomCallStatePresenter(joinedRoom = room) + presenter.test { + skipItems(1) + assertThat(awaitItem()).isEqualTo(RoomCallState.StandBy(canStartCall = false, isDM = true)) + baseRoom.givenRoomInfo(aRoomInfo(isDm = true, activeMembersCount = 2, joinedMembersCount = 2)) + assertThat(awaitItem()).isEqualTo(RoomCallState.StandBy(canStartCall = true, isDM = true)) + } + } + @Test fun `present - element call not available`() = runTest { val room = FakeJoinedRoom( From 7f56c15fb811ca4a50b449fbe57122b79385e262 Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:33:51 +0300 Subject: [PATCH 2/2] Drop the comments from the empty DM call guard Review feedback: the member-count condition reads on its own. --- .../android/features/roomcall/impl/RoomCallStatePresenter.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt index f57a94bf1e..7763933a29 100644 --- a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt +++ b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt @@ -61,11 +61,6 @@ class RoomCallStatePresenter( isAudioCall = roomInfo.activeCallIntentConsensus.isAudio(), ) else -> { - // Nobody would ever join a call started in a DM whose other member has left, - // and the call screen has no way out of that state. - // activeMembersCount counts joined and invited members, so a DM whose peer - // has been invited but has not joined yet can still start a call. - // See https://github.com/element-hq/element-x-android/issues/6189 val isEmptyDm = roomInfo.isDm && roomInfo.activeMembersCount <= 1 RoomCallState.StandBy( canStartCall = canJoinCall && !isEmptyDm,