Recover CallKit state when remote client fails to hangup

Distinguish between localHangup, remoteHangup, and call failure.

This allows us to put CallKit in the proper state, ready to receive new
calls without having a backlog of phantom calls which haven't been
properly removed.

Note the "call error" occurs at the point ICE fails, which takes a
while. Anecdotally, like 10 seconds, which feels like a long to be
talking into the ether.

I briefly considered failing at 'disconnected', which happens much
sooner, but that's actually a recoverable state. E.g. if you toggle
airplane mode you can see that you bounce into `disconnected` and then
back to `connected`, so I don't think we'd want to fail the call as long
as WebRTC considers it "recoverable".

// FREEBIE
This commit is contained in:
Michael Kirk 2017-01-26 13:18:06 -05:00 committed by GitHub
parent 6c14f2f500
commit 814aec6cdd
8 changed files with 51 additions and 15 deletions

View file

@ -105,8 +105,8 @@
45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */; };
45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; };
45F170BC1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; };
45F170D61E315310003FC1F2 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170D51E315310003FC1F2 /* Weak.swift */; };
45F170CC1E310E22003FC1F2 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */; };
45F170D61E315310003FC1F2 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170D51E315310003FC1F2 /* Weak.swift */; };
45F2B1941D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 45F2B1931D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m */; };
45F2B1971D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45F2B1951D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib */; };
45F2B1981D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45F2B1961D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib */; };
@ -704,8 +704,8 @@
45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallAudioSessionTest.swift; path = test/call/CallAudioSessionTest.swift; sourceTree = "<group>"; };
45F170B31E2F0A6A003FC1F2 /* RTCAudioSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCAudioSession.h; sourceTree = "<group>"; };
45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioService.swift; sourceTree = "<group>"; };
45F170D51E315310003FC1F2 /* Weak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = "<group>"; };
45F170CB1E310E22003FC1F2 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = "<group>"; };
45F170D51E315310003FC1F2 /* Weak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = "<group>"; };
45F2B1921D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOutgoingMessageCollectionViewCell.h; sourceTree = "<group>"; };
45F2B1931D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOutgoingMessageCollectionViewCell.m; sourceTree = "<group>"; };
45F2B1951D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OWSIncomingMessageCollectionViewCell.xib; sourceTree = "<group>"; };

View file

@ -499,7 +499,7 @@ fileprivate let timeoutSeconds = 60
call.state = .remoteHangup
// Notify UI
callUIAdapter.endCall(call)
callUIAdapter.remoteDidHangupCall(call)
// self.call is nil'd in `terminateCall`, so it's important we update it's state *before* calling `terminateCall`
terminateCall()
@ -885,9 +885,14 @@ fileprivate let timeoutSeconds = 60
assertOnSignalingQueue()
Logger.error("\(TAG) call failed with error: \(error)")
// It's essential to set call.state before terminateCall, because terminateCall nils self.call
call?.error = error
call?.state = .localFailure
if let call = self.call {
// It's essential to set call.state before terminateCall, because terminateCall nils self.call
call.error = error
call.state = .localFailure
callUIAdapter.failCall(call, error: error)
} else {
assertionFailure("\(TAG) in \(#function) but there was no call to fail.")
}
terminateCall()
}

View file

@ -114,7 +114,7 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
PeerConnectionClient.startAudioSession()
}
func endCall(_ call: SignalCall) {
func localHangupCall(_ call: SignalCall) {
CallService.signalingQueue.async {
guard call.localId == self.callService.call?.localId else {
assertionFailure("\(self.TAG) in \(#function) localId does not match current call")
@ -125,6 +125,14 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
}
}
internal func remoteDidHangupCall(_ call: SignalCall) {
Logger.debug("\(TAG) in \(#function) is no-op")
}
internal func failCall(_ call: SignalCall, error: CallError) {
Logger.debug("\(TAG) in \(#function) is no-op")
}
func setIsMuted(call: SignalCall, isMuted: Bool) {
CallService.signalingQueue.async {
guard call.localId == self.callService.call?.localId else {

View file

@ -371,6 +371,8 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
case .failed:
Logger.warn("\(self.TAG) RTCIceConnection failed.")
self.delegate.peerConnectionClientIceFailed(self)
case .disconnected:
Logger.warn("\(self.TAG) RTCIceConnection disconnected.")
default:
Logger.debug("\(self.TAG) ignoring change IceConnectionState:\(newState.debugDescription)")
}

View file

@ -33,7 +33,7 @@ final class CallKitCallManager: NSObject {
requestTransaction(transaction)
}
func end(call: SignalCall) {
func localHangup(call: SignalCall) {
let endCallAction = CXEndCallAction(call: call.localId)
let transaction = CXTransaction()
transaction.addAction(endCallAction)

View file

@ -72,6 +72,12 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
return call
}
// Called from CallService after call has ended to clean up any remaining CallKit call state.
func failCall(_ call: SignalCall, error: CallError) {
provider.reportCall(with: call.localId, endedAt: Date(), reason: CXCallEndedReason.failed)
self.callManager.removeCall(call)
}
func reportIncomingCall(_ call: SignalCall, callerName: String) {
// Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate()
@ -110,15 +116,20 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
}
func declineCall(_ call: SignalCall) {
callManager.end(call: call)
callManager.localHangup(call: call)
}
func recipientAcceptedCall(_ call: SignalCall) {
// no - op
// TODO provider update call connected?
}
func endCall(_ call: SignalCall) {
callManager.end(call: call)
func localHangupCall(_ call: SignalCall) {
callManager.localHangup(call: call)
}
func remoteDidHangupCall(_ call: SignalCall) {
provider.reportCall(with: call.localId, endedAt: nil, reason: CXCallEndedReason.remoteEnded)
}
func setIsMuted(call: SignalCall, isMuted: Bool) {

View file

@ -19,7 +19,9 @@ protocol CallUIAdaptee {
func declineCall(localId: UUID)
func declineCall(_ call: SignalCall)
func recipientAcceptedCall(_ call: SignalCall)
func endCall(_ call: SignalCall)
func localHangupCall(_ call: SignalCall)
func remoteDidHangupCall(_ call: SignalCall)
func failCall(_ call: SignalCall, error: CallError)
func setIsMuted(call: SignalCall, isMuted: Bool)
func setHasVideo(call: SignalCall, hasVideo: Bool)
func callBack(recipientId: String)
@ -122,8 +124,16 @@ extension CallUIAdaptee {
adaptee.recipientAcceptedCall(call)
}
internal func endCall(_ call: SignalCall) {
adaptee.endCall(call)
internal func remoteDidHangupCall(_ call: SignalCall) {
adaptee.remoteDidHangupCall(call)
}
internal func localHangupCall(_ call: SignalCall) {
adaptee.localHangupCall(call)
}
internal func failCall(_ call: SignalCall, error: CallError) {
adaptee.failCall(call, error: error)
}
internal func showCall(_ call: SignalCall) {

View file

@ -464,7 +464,7 @@ class CallViewController: UIViewController, CallObserver {
func didPressHangup(sender: UIButton) {
Logger.info("\(TAG) called \(#function)")
if let call = self.call {
callUIAdapter.endCall(call)
callUIAdapter.localHangupCall(call)
} else {
Logger.warn("\(TAG) hung up, but call was unexpectedly nil")
}