A second device's ring no longer hangs up a call answered on the first #150
@@ -140,10 +140,14 @@ class CallManager(
|
||||
"fnmsg-call",
|
||||
"ring expired with no answer, releasing so the next call is not refused",
|
||||
)
|
||||
// "timeout", not a new word: the far end already renders
|
||||
// that as "No answer", which is what this is. An unknown
|
||||
// reason falls through to a flat "Call ended".
|
||||
hangUp(notifyPeer = true, reason = "timeout")
|
||||
// Released here and nothing sent. The caller gave up on its
|
||||
// own timer long before this fires, so telling it was
|
||||
// redundant, and on an account signed in on more than one
|
||||
// device it was destructive: this phone keeps ringing when
|
||||
// the call was answered on another one, because nothing
|
||||
// tells it, and a timeout end sent from here reached the
|
||||
// caller mid-call with that other device and hung it up.
|
||||
hangUp(notifyPeer = false, reason = "timeout")
|
||||
}
|
||||
}
|
||||
ringGuard = guard
|
||||
@@ -293,7 +297,9 @@ class CallManager(
|
||||
}
|
||||
|
||||
// CLOSED is what our OWN close() produces.
|
||||
PeerConnection.IceConnectionState.CLOSED -> Unit
|
||||
PeerConnection.IceConnectionState.CLOSED -> {
|
||||
Unit
|
||||
}
|
||||
|
||||
// The far end went away. Transient by definition,
|
||||
// WebRTC recovers from it often, so this is a grace
|
||||
@@ -301,11 +307,17 @@ class CallManager(
|
||||
// is cancelled below. Without it a lost `call-end`
|
||||
// left the screen up until the dial guard gave up
|
||||
// three quarters of a minute later.
|
||||
PeerConnection.IceConnectionState.DISCONNECTED -> armGoneGuard()
|
||||
PeerConnection.IceConnectionState.DISCONNECTED -> {
|
||||
armGoneGuard()
|
||||
}
|
||||
|
||||
PeerConnection.IceConnectionState.FAILED -> hangUp(notifyPeer = true)
|
||||
PeerConnection.IceConnectionState.FAILED -> {
|
||||
hangUp(notifyPeer = true)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
else -> {
|
||||
Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,6 +522,31 @@ class CallManager(
|
||||
}
|
||||
|
||||
Content.CallSignal.END -> {
|
||||
// An end for some other call must not hang up this one. This
|
||||
// branch used to hang up on any end at all, for any call id.
|
||||
if (incoming.callId != current?.callId) {
|
||||
android.util.Log.i(
|
||||
"fnmsg-call",
|
||||
"end for ${incoming.callId} ignored: not the current call",
|
||||
)
|
||||
return
|
||||
}
|
||||
// A call answered on one of the callee's devices is still
|
||||
// ringing on the others, and they all share this call id. Once
|
||||
// this call has connected, an end whose reason means the ring
|
||||
// was never picked up came from one of those other devices and
|
||||
// is not about the call in progress: somebody silencing their
|
||||
// tablet, or a ring guard from a build that still announces its
|
||||
// timeout. Honouring it hung up a live call. A plain hang-up
|
||||
// carries no such reason and still ends it.
|
||||
val neverPickedUp = incoming.reason.orEmpty() in setOf("declined", "busy", "timeout")
|
||||
if (neverPickedUp && (state == State.CONNECTING || state == State.ACTIVE)) {
|
||||
android.util.Log.i(
|
||||
"fnmsg-call",
|
||||
"'${incoming.reason}' end ignored: this call was already picked up",
|
||||
)
|
||||
return
|
||||
}
|
||||
hangUp(notifyPeer = false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.flamenet.messenger.app
|
||||
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* One account on two devices: a ring on the second must not end a call that
|
||||
* was answered on the first.
|
||||
*
|
||||
* `CallManager` needs WebRTC, which the JVM test runtime does not have, so this
|
||||
* reads the source the way the iPhone's `RingTimesOutTests` does. That iPhone
|
||||
* test reads this file as well, but only when the two checkouts sit side by
|
||||
* side under the directory names it expects, which a CI build directory never
|
||||
* has. This copy is the one that actually runs on every Android pipeline.
|
||||
*/
|
||||
class SecondDeviceCallTest {
|
||||
private val source: String by lazy {
|
||||
listOf(
|
||||
"src/main/kotlin/io/flamenet/messenger/app/CallManager.kt",
|
||||
"app/src/main/kotlin/io/flamenet/messenger/app/CallManager.kt",
|
||||
).map(::File).first { it.exists() }.readText()
|
||||
}
|
||||
|
||||
/** The function starting at [marker], up to the next one. */
|
||||
private fun body(marker: String): String {
|
||||
val start = source.indexOf(marker)
|
||||
assertTrue(start >= 0, "no $marker in CallManager.kt")
|
||||
val rest = source.substring(start + marker.length)
|
||||
val end = rest.indexOf("private fun ").let { if (it < 0) rest.length else it }
|
||||
return rest.substring(0, end)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the ring guard releases the ring without telling the caller`() {
|
||||
val guard = body("private fun armRingGuard()")
|
||||
assertTrue(guard.contains("notifyPeer = false"), "the guard must not message the caller")
|
||||
// Bounded to the guard: the connect guard below it still, correctly,
|
||||
// hangs up with notifyPeer = true.
|
||||
assertFalse(
|
||||
guard.contains("notifyPeer = true"),
|
||||
"a timeout sent from a second device hangs up a call answered on the first",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an end hangs up only its own call, and not once that call has connected`() {
|
||||
val start = source.indexOf("Content.CallSignal.END ->")
|
||||
assertTrue(start >= 0, "no end branch in onSignal")
|
||||
val branch = source.substring(start, minOf(source.length, start + 2000))
|
||||
assertTrue(
|
||||
branch.contains("incoming.callId != current?.callId"),
|
||||
"an end for another call must not hang this one up",
|
||||
)
|
||||
assertTrue(branch.contains("setOf(\"declined\", \"busy\", \"timeout\")"))
|
||||
assertTrue(
|
||||
branch.contains("state == State.CONNECTING || state == State.ACTIVE"),
|
||||
"only a call that has connected may ignore them; a ringing one must still stop",
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user