Improve thread safety in call ui adapter and adatapees.
// FREEBIE
This commit is contained in:
parent
50addfa4e8
commit
2ef80e569d
|
@ -309,7 +309,9 @@ protocol CallServiceObserver: class {
|
|||
in: thread)
|
||||
callRecord.save()
|
||||
|
||||
self.callUIAdapter.reportMissedCall(call)
|
||||
DispatchQueue.main.async {
|
||||
self.callUIAdapter.reportMissedCall(call)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -514,7 +516,9 @@ protocol CallServiceObserver: class {
|
|||
call.state = .remoteRinging
|
||||
case .answering:
|
||||
call.state = .localRinging
|
||||
self.callUIAdapter.reportIncomingCall(call, thread: thread)
|
||||
DispatchQueue.main.async {
|
||||
self.callUIAdapter.reportIncomingCall(call, thread: thread)
|
||||
}
|
||||
// cancel connection timeout
|
||||
self.fulfillCallConnectedPromise?()
|
||||
case .remoteRinging:
|
||||
|
@ -552,7 +556,9 @@ protocol CallServiceObserver: class {
|
|||
|
||||
call.state = .remoteHangup
|
||||
// Notify UI
|
||||
callUIAdapter.remoteDidHangupCall(call)
|
||||
DispatchQueue.main.async {
|
||||
self.callUIAdapter.remoteDidHangupCall(call)
|
||||
}
|
||||
|
||||
// self.call is nil'd in `terminateCall`, so it's important we update it's state *before* calling `terminateCall`
|
||||
terminateCall()
|
||||
|
@ -821,7 +827,9 @@ protocol CallServiceObserver: class {
|
|||
return
|
||||
}
|
||||
|
||||
callUIAdapter.recipientAcceptedCall(call)
|
||||
DispatchQueue.main.async {
|
||||
self.callUIAdapter.recipientAcceptedCall(call)
|
||||
}
|
||||
handleConnectedCall(call)
|
||||
|
||||
} else if message.hasHangup() {
|
||||
|
@ -958,7 +966,9 @@ protocol CallServiceObserver: class {
|
|||
// 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)
|
||||
DispatchQueue.main.async {
|
||||
self.callUIAdapter.failCall(call, error: error)
|
||||
}
|
||||
} else {
|
||||
// This can happen when we receive an out of band signaling message (e.g. IceUpdate)
|
||||
// after the call has ended
|
||||
|
@ -1114,7 +1124,7 @@ protocol CallServiceObserver: class {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Prevent screen from dimming during video call.
|
||||
let hasLocalOrRemoteVideo = localVideoTrack != nil || remoteVideoTrack != nil
|
||||
UIApplication.shared.isIdleTimerDisabled = hasLocalOrRemoteVideo
|
||||
|
|
|
@ -23,6 +23,8 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
|
||||
func startOutgoingCall(handle: String) -> SignalCall {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let call = SignalCall.outgoingCall(localId: UUID(), remotePhoneNumber: handle)
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
|
@ -37,6 +39,8 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
|
||||
func reportIncomingCall(_ call: SignalCall, callerName: String) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) \(#function)")
|
||||
|
||||
// present Call View controller
|
||||
|
@ -52,10 +56,14 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
|
||||
func reportMissedCall(_ call: SignalCall, callerName: String) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
notificationsAdapter.presentMissedCall(call, callerName: callerName)
|
||||
}
|
||||
|
||||
func answerCall(localId: UUID) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
guard let call = self.callService.call else {
|
||||
assertionFailure("\(self.TAG) in \(#function) No current call.")
|
||||
|
@ -72,6 +80,8 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
|
||||
func answerCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
guard call.localId == self.callService.call?.localId else {
|
||||
assertionFailure("\(self.TAG) in \(#function) localId does not match current call")
|
||||
|
@ -84,6 +94,8 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
|
||||
func declineCall(localId: UUID) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
guard let call = self.callService.call else {
|
||||
assertionFailure("\(self.TAG) in \(#function) No current call.")
|
||||
|
@ -100,6 +112,8 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
|
||||
func declineCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
guard call.localId == self.callService.call?.localId else {
|
||||
assertionFailure("\(self.TAG) in \(#function) localId does not match current call")
|
||||
|
@ -111,10 +125,14 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
|
||||
func recipientAcceptedCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
PeerConnectionClient.startAudioSession()
|
||||
}
|
||||
|
||||
func localHangupCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
// If both parties hang up at the same moment,
|
||||
// call might already be nil.
|
||||
|
@ -128,14 +146,20 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
|
||||
internal func remoteDidHangupCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) in \(#function) is no-op")
|
||||
}
|
||||
|
||||
internal func failCall(_ call: SignalCall, error: CallError) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) in \(#function) is no-op")
|
||||
}
|
||||
|
||||
func setIsMuted(call: SignalCall, isMuted: Bool) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
guard call.localId == self.callService.call?.localId else {
|
||||
assertionFailure("\(self.TAG) in \(#function) localId does not match current call")
|
||||
|
@ -147,6 +171,8 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
|
||||
func setHasLocalVideo(call: SignalCall, hasLocalVideo: Bool) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
guard call.localId == self.callService.call?.localId else {
|
||||
assertionFailure("\(self.TAG) in \(#function) localId does not match current call")
|
||||
|
|
|
@ -49,6 +49,8 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
init(callService: CallService, notificationsAdapter: CallNotificationsAdapter) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
self.callManager = CallKitCallManager()
|
||||
self.callService = callService
|
||||
self.notificationsAdapter = notificationsAdapter
|
||||
|
@ -62,6 +64,8 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
// MARK: CallUIAdaptee
|
||||
|
||||
func startOutgoingCall(handle: String) -> SignalCall {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let call = SignalCall.outgoingCall(localId: UUID(), remotePhoneNumber: handle)
|
||||
|
||||
// Add the new outgoing call to the app's list of calls.
|
||||
|
@ -74,11 +78,15 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
|
||||
// Called from CallService after call has ended to clean up any remaining CallKit call state.
|
||||
func failCall(_ call: SignalCall, error: CallError) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
provider.reportCall(with: call.localId, endedAt: Date(), reason: CXCallEndedReason.failed)
|
||||
self.callManager.removeCall(call)
|
||||
}
|
||||
|
||||
func reportIncomingCall(_ call: SignalCall, callerName: String) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
// Construct a CXCallUpdate describing the incoming call, including the caller.
|
||||
let update = CXCallUpdate()
|
||||
update.remoteHandle = CXHandle(type: .phoneNumber, value: call.remotePhoneNumber)
|
||||
|
@ -104,39 +112,57 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
func answerCall(localId: UUID) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
assertionFailure("CallKit should answer calls via system call screen, not via notifications.")
|
||||
}
|
||||
|
||||
func answerCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
callManager.answer(call: call)
|
||||
}
|
||||
|
||||
func declineCall(localId: UUID) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
assertionFailure("CallKit should decline calls via system call screen, not via notifications.")
|
||||
}
|
||||
|
||||
func declineCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
callManager.localHangup(call: call)
|
||||
}
|
||||
|
||||
func recipientAcceptedCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
// no - op
|
||||
// TODO provider update call connected?
|
||||
}
|
||||
|
||||
func localHangupCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
callManager.localHangup(call: call)
|
||||
}
|
||||
|
||||
func remoteDidHangupCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
provider.reportCall(with: call.localId, endedAt: nil, reason: CXCallEndedReason.remoteEnded)
|
||||
}
|
||||
|
||||
func setIsMuted(call: SignalCall, isMuted: Bool) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
callManager.setIsMuted(call: call, isMuted: isMuted)
|
||||
}
|
||||
|
||||
func setHasLocalVideo(call: SignalCall, hasLocalVideo: Bool) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let update = CXCallUpdate()
|
||||
update.remoteHandle = CXHandle(type: .phoneNumber, value: call.remotePhoneNumber)
|
||||
update.hasVideo = hasLocalVideo
|
||||
|
@ -152,6 +178,8 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
// MARK: CXProviderDelegate
|
||||
|
||||
func providerDidReset(_ provider: CXProvider) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) in \(#function)")
|
||||
|
||||
// TODO
|
||||
|
@ -174,6 +202,8 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) in \(#function) CXStartCallAction")
|
||||
|
||||
// TODO does this work when `action.handle.value` is not in e164 format, e.g. if called via intent?
|
||||
|
@ -207,6 +237,8 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) Received \(#function) CXAnswerCallAction")
|
||||
// Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
|
||||
guard let call = callManager.callWithLocalId(action.callUUID) else {
|
||||
|
@ -232,6 +264,8 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) Received \(#function) CXEndCallAction")
|
||||
guard let call = callManager.callWithLocalId(action.callUUID) else {
|
||||
action.fail()
|
||||
|
@ -257,6 +291,8 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) Received \(#function) CXSetHeldCallAction")
|
||||
guard let call = callManager.callWithLocalId(action.callUUID) else {
|
||||
action.fail()
|
||||
|
@ -282,6 +318,8 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) Received \(#function) CXSetMutedCallAction")
|
||||
guard callManager.callWithLocalId(action.callUUID) != nil else {
|
||||
Logger.error("\(TAG) Failing CXSetMutedCallAction for unknown call: \(action.callUUID)")
|
||||
|
@ -296,20 +334,28 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.warn("\(TAG) unimplemented \(#function) for CXSetGroupCallAction")
|
||||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.warn("\(TAG) unimplemented \(#function) for CXPlayDTMFCallAction")
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) Timed out \(#function)")
|
||||
|
||||
// React to the action timeout if necessary, such as showing an error UI.
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) Received \(#function)")
|
||||
|
||||
// Start recording
|
||||
|
@ -317,6 +363,8 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.debug("\(TAG) Received \(#function)")
|
||||
|
||||
/*
|
||||
|
|
|
@ -63,6 +63,8 @@ extension CallUIAdaptee {
|
|||
private let audioService: CallAudioService
|
||||
|
||||
required init(callService: CallService, contactsManager: OWSContactsManager, notificationsAdapter: CallNotificationsAdapter) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
self.contactsManager = contactsManager
|
||||
if Platform.isSimulator {
|
||||
// CallKit doesn't seem entirely supported in simulator.
|
||||
|
@ -82,6 +84,8 @@ extension CallUIAdaptee {
|
|||
}
|
||||
|
||||
internal func reportIncomingCall(_ call: SignalCall, thread: TSContactThread) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
call.addObserverAndSyncState(observer: audioService)
|
||||
|
||||
let callerName = self.contactsManager.displayName(forPhoneIdentifier: call.remotePhoneNumber)
|
||||
|
@ -89,11 +93,15 @@ extension CallUIAdaptee {
|
|||
}
|
||||
|
||||
internal func reportMissedCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let callerName = self.contactsManager.displayName(forPhoneIdentifier: call.remotePhoneNumber)
|
||||
adaptee.reportMissedCall(call, callerName: callerName)
|
||||
}
|
||||
|
||||
internal func startOutgoingCall(handle: String) -> SignalCall {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let call = adaptee.startOutgoingCall(handle: handle)
|
||||
call.addObserverAndSyncState(observer: audioService)
|
||||
|
||||
|
@ -101,56 +109,82 @@ extension CallUIAdaptee {
|
|||
}
|
||||
|
||||
internal func answerCall(localId: UUID) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.answerCall(localId: localId)
|
||||
}
|
||||
|
||||
internal func answerCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.answerCall(call)
|
||||
}
|
||||
|
||||
internal func declineCall(localId: UUID) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.declineCall(localId: localId)
|
||||
}
|
||||
|
||||
internal func declineCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.declineCall(call)
|
||||
}
|
||||
|
||||
internal func callBack(recipientId: String) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.callBack(recipientId: recipientId)
|
||||
}
|
||||
|
||||
internal func recipientAcceptedCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.recipientAcceptedCall(call)
|
||||
}
|
||||
|
||||
internal func remoteDidHangupCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.remoteDidHangupCall(call)
|
||||
}
|
||||
|
||||
internal func localHangupCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.localHangupCall(call)
|
||||
}
|
||||
|
||||
internal func failCall(_ call: SignalCall, error: CallError) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.failCall(call, error: error)
|
||||
}
|
||||
|
||||
internal func showCall(_ call: SignalCall) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.showCall(call)
|
||||
}
|
||||
|
||||
internal func setIsMuted(call: SignalCall, isMuted: Bool) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
// With CallKit, muting is handled by a CXAction, so it must go through the adaptee
|
||||
adaptee.setIsMuted(call: call, isMuted: isMuted)
|
||||
}
|
||||
|
||||
internal func setHasLocalVideo(call: SignalCall, hasLocalVideo: Bool) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
adaptee.setHasLocalVideo(call: call, hasLocalVideo: hasLocalVideo)
|
||||
}
|
||||
|
||||
internal func setIsSpeakerphoneEnabled(call: SignalCall, isEnabled: Bool) {
|
||||
// Speakerphone is not handled by CallKit (e.g. there is no CXAction), so we handle it w/o going through the
|
||||
AssertIsOnMainThread()
|
||||
|
||||
// Speakerphone is not handled by CallKit (e.g. there is no CXAction), so we handle it w/o going through the
|
||||
// adaptee, relying on the AudioService CallObserver to put the system in a state consistent with the call's
|
||||
// assigned property.
|
||||
CallService.signalingQueue.async {
|
||||
|
@ -160,6 +194,8 @@ extension CallUIAdaptee {
|
|||
|
||||
// CallKit handles ringing state on it's own. But for non-call kit we trigger ringing start/stop manually.
|
||||
internal var hasManualRinger: Bool {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
return adaptee.hasManualRinger
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue