diff --git a/client/src/main/kotlin/io/flamenet/client/Conversations.kt b/client/src/main/kotlin/io/flamenet/client/Conversations.kt index f5a7147..deca3f2 100644 --- a/client/src/main/kotlin/io/flamenet/client/Conversations.kt +++ b/client/src/main/kotlin/io/flamenet/client/Conversations.kt @@ -1636,19 +1636,33 @@ class Conversations( companion object { /** - * Whether this envelope has to reach a locked, suspended phone now + * Whether this envelope has to RING a locked, suspended phone now * (spec ยง6.14). Held out here so it can be tested for what it decides * rather than for how it is written. * - * The end of a call is as time-critical as its start. Only the offer - * was marked, so hanging up before the other phone was picked up sent - * the end as ordinary traffic: a backgrounded phone never woke for it - * and went on ringing for a caller who had already gone. + * Only an offer. An end travels the ordinary wake path, and that is + * not a downgrade: it is the difference between waking a phone and + * ringing one. + * + * This matters on the other platform rather than this one. iOS gives + * no choice about it: every VoIP push must report a call to CallKit or + * the app is killed. So an urgent end from here arrived on an iPhone as + * an incoming call -- every hangup, every busy, every end for a call + * long over. The owner, on a real handset: "I place a call, it calls me + * back." Taking the ghost back afterwards is not a fix, because + * somebody has already seen it. + * + * Ends were marked for a real reason: hanging up before the other + * phone was picked up left it ringing for a caller who had gone. But a + * ringing phone is not asleep. It has a call up, the system keeps it + * running for one, it is already polling, and the relay answers a held + * poll through Redis in under half a second. The end arrives on its + * own, and a phone that never rang has nothing to stop. */ fun ringsAPhone(body: String): Boolean { if (!body.startsWith("""{"fnc":""")) return false val action = Content.parse(body).call?.action - return action == Content.CallSignal.OFFER || action == Content.CallSignal.END + return action == Content.CallSignal.OFFER } } } diff --git a/client/src/test/kotlin/io/flamenet/client/UrgentCallOfferTest.kt b/client/src/test/kotlin/io/flamenet/client/UrgentCallOfferTest.kt index 6e7d1fe..c9b8cef 100644 --- a/client/src/test/kotlin/io/flamenet/client/UrgentCallOfferTest.kt +++ b/client/src/test/kotlin/io/flamenet/client/UrgentCallOfferTest.kt @@ -56,13 +56,27 @@ class UrgentCallOfferTest { } /** - * And so does the end of one. A call that is over has to stop the ring on - * a phone that is asleep, which takes the same push the offer took: this - * is why hanging up first left the other phone ringing to nobody. + * But NOT the end of one, and this is the whole of the ghost ringing on + * the other platform. + * + * iOS gives no choice: every VoIP push must report a call to CallKit or + * the app is killed. So an urgent end from here arrived on an iPhone as an + * incoming call -- every hangup, every busy, every end for a call long + * over. Taking the ghost back afterwards is not a fix; somebody has + * already seen it. + * + * Ends were marked for a real reason: hanging up before the other phone + * was picked up left it ringing for a caller who had gone. But a ringing + * phone is not asleep. It has a call up, the system keeps it running for + * one, it is already polling, and the relay answers a held poll through + * Redis in under half a second. */ @Test - fun hangingUpIsUrgentToo() { - assertTrue(Conversations.ringsAPhone(signal(Content.CallSignal.END))) + fun hangingUpWakesAPhoneRatherThanRingingIt() { + assertFalse( + Conversations.ringsAPhone(signal(Content.CallSignal.END)), + "an urgent end becomes a reported call on an iPhone", + ) } /** Nothing else does. */