Merge branch 'mlin/PR/EarlyRingForNSEWake' into release/5.21.2
This commit is contained in:
commit
bfa4148cba
@ -175,7 +175,7 @@ protocol CallAudioServiceDelegate: AnyObject {
|
||||
return
|
||||
}
|
||||
|
||||
if call.state == .localRinging {
|
||||
if [.localRinging_Anticipatory, .localRinging_ReadyToAnswer, .accepting].contains(call.state) {
|
||||
// The AudioSession for playing a ring tone.
|
||||
setAudioSession(category: .playback, mode: .default)
|
||||
} else if call.hasLocalVideo {
|
||||
@ -210,7 +210,8 @@ protocol CallAudioServiceDelegate: AnyObject {
|
||||
case .dialing: handleDialing(call: call)
|
||||
case .answering: handleAnswering(call: call)
|
||||
case .remoteRinging: handleRemoteRinging(call: call)
|
||||
case .localRinging: handleLocalRinging(call: call)
|
||||
case .localRinging_Anticipatory, .localRinging_ReadyToAnswer, .accepting:
|
||||
handleLocalRinging(call: call)
|
||||
case .connected: handleConnected(call: call)
|
||||
case .reconnecting: handleReconnecting(call: call)
|
||||
case .localFailure: handleLocalFailure(call: call)
|
||||
@ -370,6 +371,9 @@ protocol CallAudioServiceDelegate: AnyObject {
|
||||
return
|
||||
}
|
||||
|
||||
// Only CallKit calls should be in the transitory ringing states
|
||||
owsAssertDebug(call.state == .localRinging_ReadyToAnswer)
|
||||
|
||||
vibrateTimer?.invalidate()
|
||||
vibrateTimer = .scheduledTimer(withTimeInterval: vibrateRepeatDuration, repeats: true) { [weak self] _ in
|
||||
self?.ringVibration()
|
||||
|
||||
@ -28,6 +28,8 @@ public final class CallService: NSObject {
|
||||
|
||||
lazy private(set) var audioService = CallAudioService()
|
||||
|
||||
public var earlyRingNextIncomingCall = false
|
||||
|
||||
private var _currentCall: SignalCall?
|
||||
@objc
|
||||
public private(set) var currentCall: SignalCall? {
|
||||
@ -58,6 +60,9 @@ public final class CallService: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
// To be safe, we reset the early ring on any call change so it's not left set from an unexpected state change
|
||||
earlyRingNextIncomingCall = false
|
||||
|
||||
Logger.debug("\(oldValue as Optional) -> \(newValue as Optional)")
|
||||
|
||||
let observers = self.observers
|
||||
@ -1062,6 +1067,10 @@ extension CallService: CallManagerDelegate {
|
||||
|
||||
call.individualCall.callId = callId
|
||||
|
||||
// We grab this before updating the currentCall since it will unset it by default as a precaution.
|
||||
let shouldEarlyRing = earlyRingNextIncomingCall && !isOutgoing
|
||||
earlyRingNextIncomingCall = false
|
||||
|
||||
// The call to be started is provided by the event.
|
||||
currentCall = call
|
||||
|
||||
@ -1070,7 +1079,8 @@ extension CallService: CallManagerDelegate {
|
||||
shouldStartCall: call,
|
||||
callId: callId,
|
||||
isOutgoing: isOutgoing,
|
||||
callMediaType: callMediaType
|
||||
callMediaType: callMediaType,
|
||||
shouldEarlyRing: shouldEarlyRing
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,14 @@ public enum CallState: String {
|
||||
case dialing
|
||||
case answering
|
||||
case remoteRinging
|
||||
case localRinging
|
||||
|
||||
// The local ringing state is a bit more complex since we sometimes kick off
|
||||
// a CallKit ring before RingRTC is ready to answer. We can only answer the call
|
||||
// once both the user has answered and RingRTC is ready.
|
||||
case localRinging_Anticipatory // RingRTC not ready. User has not answered
|
||||
case localRinging_ReadyToAnswer // RingRTC ready. User has not answered
|
||||
case accepting // RingRTC not ready. User has answered
|
||||
|
||||
case connected
|
||||
case reconnecting
|
||||
case localFailure // terminal
|
||||
@ -109,7 +116,7 @@ public class IndividualCall: NSObject, IndividualCallNotificationInfo {
|
||||
switch state {
|
||||
case .localFailure, .localHangup, .remoteHangup, .remoteHangupNeedPermission, .remoteBusy, .answeredElsewhere, .declinedElsewhere, .busyElsewhere:
|
||||
return true
|
||||
case .idle, .dialing, .answering, .remoteRinging, .localRinging, .connected, .reconnecting:
|
||||
case .idle, .dialing, .answering, .remoteRinging, .localRinging_Anticipatory, .localRinging_ReadyToAnswer, .accepting, .connected, .reconnecting:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -141,6 +148,12 @@ public class IndividualCall: NSObject, IndividualCallNotificationInfo {
|
||||
}
|
||||
}
|
||||
|
||||
var deferredAnswerCompletion: (() -> Void)? {
|
||||
didSet {
|
||||
owsAssertDebug(deferredAnswerCompletion == nil || state == .accepting)
|
||||
}
|
||||
}
|
||||
|
||||
public var state: CallState {
|
||||
didSet {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
@ -92,6 +92,13 @@ import SignalMessaging
|
||||
AssertIsOnMainThread()
|
||||
Logger.info("\(call)")
|
||||
|
||||
defer {
|
||||
// This should only be non-nil if we had to defer accepting the call while waiting for RingRTC
|
||||
// If it's set, we need to make sure we call it before returning.
|
||||
call.individualCall.deferredAnswerCompletion?()
|
||||
call.individualCall.deferredAnswerCompletion = nil
|
||||
}
|
||||
|
||||
guard callService.currentCall === call else {
|
||||
let error = OWSAssertionError("accepting call: \(call) which is different from currentCall: \(callService.currentCall as Optional)")
|
||||
handleFailedCall(failedCall: call, error: error)
|
||||
@ -148,7 +155,7 @@ import SignalMessaging
|
||||
if callRecord.callType == .outgoingIncomplete {
|
||||
callRecord.updateCallType(.outgoingMissed)
|
||||
}
|
||||
} else if call.individualCall.state == .localRinging {
|
||||
} else if [.localRinging_Anticipatory, .localRinging_ReadyToAnswer].contains(call.individualCall.state) {
|
||||
let callRecord = TSCall(
|
||||
callType: .incomingDeclined,
|
||||
offerType: call.individualCall.offerMediaType,
|
||||
@ -503,11 +510,23 @@ import SignalMessaging
|
||||
|
||||
// MARK: - Call Manager Events
|
||||
|
||||
public func callManager(_ callManager: CallService.CallManagerType, shouldStartCall call: SignalCall, callId: UInt64, isOutgoing: Bool, callMediaType: CallMediaType) {
|
||||
public func callManager(_ callManager: CallService.CallManagerType, shouldStartCall call: SignalCall, callId: UInt64, isOutgoing: Bool, callMediaType: CallMediaType, shouldEarlyRing: Bool) {
|
||||
AssertIsOnMainThread()
|
||||
owsAssertDebug(call.isIndividualCall)
|
||||
Logger.info("call: \(call)")
|
||||
|
||||
if shouldEarlyRing {
|
||||
if isOutgoing {
|
||||
// If we are using the NSE, we need to kick off a ring ASAP in case this incoming call
|
||||
// has resulted in the NSE waking up the main app.
|
||||
owsAssertDebug(callUIAdapter.adaptee(for: call) === callUIAdapter.callKitAdaptee)
|
||||
Logger.info("Performing early ring")
|
||||
handleRinging(call: call, isAnticipatory: true)
|
||||
} else {
|
||||
owsFailDebug("Cannot early ring an outgoing call")
|
||||
}
|
||||
}
|
||||
|
||||
// Start the call, asynchronously.
|
||||
getIceServers().done(on: .main) { iceServers in
|
||||
guard self.callService.currentCall === call else {
|
||||
@ -579,7 +598,7 @@ import SignalMessaging
|
||||
audioSession.isRTCAudioEnabled = false
|
||||
|
||||
switch call.individualCall.state {
|
||||
case .idle, .dialing, .answering, .localRinging, .localFailure, .remoteBusy, .remoteRinging:
|
||||
case .idle, .dialing, .answering, .localRinging_Anticipatory, .localRinging_ReadyToAnswer, .accepting, .localFailure, .remoteBusy, .remoteRinging:
|
||||
handleMissedCall(call)
|
||||
case .connected, .reconnecting, .localHangup, .remoteHangup, .remoteHangupNeedPermission, .answeredElsewhere, .declinedElsewhere, .busyElsewhere:
|
||||
Logger.info("call is finished")
|
||||
@ -601,7 +620,7 @@ import SignalMessaging
|
||||
audioSession.isRTCAudioEnabled = false
|
||||
|
||||
switch call.individualCall.state {
|
||||
case .idle, .dialing, .answering, .localRinging, .localFailure, .remoteBusy, .remoteRinging:
|
||||
case .idle, .dialing, .answering, .localRinging_Anticipatory, .localRinging_ReadyToAnswer, .accepting, .localFailure, .remoteBusy, .remoteRinging:
|
||||
handleMissedCall(call)
|
||||
case .connected, .reconnecting, .localHangup, .remoteHangup, .remoteHangupNeedPermission, .answeredElsewhere, .declinedElsewhere, .busyElsewhere:
|
||||
Logger.info("call is finished")
|
||||
@ -626,10 +645,10 @@ import SignalMessaging
|
||||
case .idle, .dialing, .remoteBusy, .remoteRinging, .answeredElsewhere, .declinedElsewhere, .busyElsewhere, .remoteHangup, .remoteHangupNeedPermission:
|
||||
handleFailedCall(failedCall: call, error: OWSAssertionError("unexpected state for endedRemoteHangupAccepted: \(call.individualCall.state)"))
|
||||
return
|
||||
case .answering, .connected:
|
||||
case .answering, .accepting, .connected:
|
||||
Logger.info("tried answering locally, but answered somewhere else first. state: \(call.individualCall.state)")
|
||||
handleAnsweredElsewhere(call: call)
|
||||
case .localRinging, .reconnecting:
|
||||
case .localRinging_Anticipatory, .localRinging_ReadyToAnswer, .reconnecting:
|
||||
handleAnsweredElsewhere(call: call)
|
||||
case .localFailure, .localHangup:
|
||||
Logger.info("ignoring 'endedRemoteHangupAccepted' since call is already finished")
|
||||
@ -647,10 +666,10 @@ import SignalMessaging
|
||||
case .idle, .dialing, .remoteBusy, .remoteRinging, .answeredElsewhere, .declinedElsewhere, .busyElsewhere, .remoteHangup, .remoteHangupNeedPermission:
|
||||
handleFailedCall(failedCall: call, error: OWSAssertionError("unexpected state for endedRemoteHangupDeclined: \(call.individualCall.state)"))
|
||||
return
|
||||
case .answering, .connected:
|
||||
case .answering, .accepting, .connected:
|
||||
Logger.info("tried answering locally, but declined somewhere else first. state: \(call.individualCall.state)")
|
||||
handleDeclinedElsewhere(call: call)
|
||||
case .localRinging, .reconnecting:
|
||||
case .localRinging_Anticipatory, .localRinging_ReadyToAnswer, .reconnecting:
|
||||
handleDeclinedElsewhere(call: call)
|
||||
case .localFailure, .localHangup:
|
||||
Logger.info("ignoring 'endedRemoteHangupDeclined' since call is already finished")
|
||||
@ -668,10 +687,10 @@ import SignalMessaging
|
||||
case .idle, .dialing, .remoteBusy, .remoteRinging, .answeredElsewhere, .declinedElsewhere, .busyElsewhere, .remoteHangup, .remoteHangupNeedPermission:
|
||||
handleFailedCall(failedCall: call, error: OWSAssertionError("unexpected state for endedRemoteHangupBusy: \(call.individualCall.state)"))
|
||||
return
|
||||
case .answering, .connected:
|
||||
case .answering, .accepting, .connected:
|
||||
Logger.info("tried answering locally, but already in a call somewhere else first. state: \(call.individualCall.state)")
|
||||
handleBusyElsewhere(call: call)
|
||||
case .localRinging, .reconnecting:
|
||||
case .localRinging_Anticipatory, .localRinging_ReadyToAnswer, .reconnecting:
|
||||
handleBusyElsewhere(call: call)
|
||||
case .localFailure, .localHangup:
|
||||
Logger.info("ignoring 'endedRemoteHangupBusy' since call is already finished")
|
||||
@ -1145,13 +1164,22 @@ import SignalMessaging
|
||||
}
|
||||
|
||||
/**
|
||||
* The clients can now communicate via WebRTC, so we can let the UI know.
|
||||
* Present UI to begin ringing.
|
||||
*
|
||||
* Called by both caller and callee. Compatible ICE messages have been exchanged between the local and remote
|
||||
* client.
|
||||
* This can be performed in response to:
|
||||
* - Established communication via WebRTC
|
||||
* - Anticipation of an expected future ring.
|
||||
*
|
||||
* In the former case, compatible ICE messages have been exchanged between the local and remote
|
||||
* client and we can ring with confidence that the call will connect.
|
||||
*
|
||||
* In the latter case, the ring is performed before any messages have been exchanged. This is to satisfy
|
||||
* callservicesd which requires that we post a CallKit ring shortly after the NSE wakes the main app.
|
||||
*/
|
||||
private func handleRinging(call: SignalCall) {
|
||||
private func handleRinging(call: SignalCall, isAnticipatory: Bool = false) {
|
||||
AssertIsOnMainThread()
|
||||
// Only incoming calls can use the early ring states
|
||||
owsAssertDebug(!(call.individualCall.direction == .outgoing && isAnticipatory))
|
||||
Logger.info("call: \(call)")
|
||||
|
||||
guard call === callService.currentCall else {
|
||||
@ -1161,19 +1189,23 @@ import SignalMessaging
|
||||
|
||||
switch call.individualCall.state {
|
||||
case .dialing:
|
||||
if call.individualCall.state != .remoteRinging {
|
||||
BenchEventComplete(eventId: "call-\(call.individualCall.localId)")
|
||||
}
|
||||
BenchEventComplete(eventId: "call-\(call.individualCall.localId)")
|
||||
call.individualCall.state = .remoteRinging
|
||||
case .answering:
|
||||
if call.individualCall.state != .localRinging {
|
||||
BenchEventComplete(eventId: "call-\(call.individualCall.localId)")
|
||||
}
|
||||
call.individualCall.state = .localRinging
|
||||
BenchEventComplete(eventId: "call-\(call.individualCall.localId)")
|
||||
call.individualCall.state = isAnticipatory ? .localRinging_Anticipatory : .localRinging_ReadyToAnswer
|
||||
self.callUIAdapter.reportIncomingCall(call, thread: call.individualCall.thread)
|
||||
case .localRinging_Anticipatory:
|
||||
// RingRTC became ready during our anticipatory ring. User hasn't tried to answer yet.
|
||||
owsAssertDebug(isAnticipatory == false)
|
||||
call.individualCall.state = .localRinging_ReadyToAnswer
|
||||
case .accepting:
|
||||
// The user answered during our early ring, but we've been waiting for RingRTC to tell us to start
|
||||
// actually ringing before trying to accept. We can do that now.
|
||||
handleAcceptCall(call)
|
||||
case .remoteRinging:
|
||||
Logger.info("call already ringing. Ignoring \(#function): \(call).")
|
||||
case .idle, .localRinging, .connected, .reconnecting, .localFailure, .localHangup, .remoteHangup, .remoteHangupNeedPermission, .remoteBusy, .answeredElsewhere, .declinedElsewhere, .busyElsewhere:
|
||||
case .idle, .connected, .reconnecting, .localFailure, .localHangup, .remoteHangup, .remoteHangupNeedPermission, .remoteBusy, .answeredElsewhere, .declinedElsewhere, .busyElsewhere, .localRinging_ReadyToAnswer:
|
||||
owsFailDebug("unexpected call state: \(call.individualCall.state): \(call).")
|
||||
}
|
||||
}
|
||||
@ -1188,7 +1220,7 @@ import SignalMessaging
|
||||
}
|
||||
|
||||
switch call.individualCall.state {
|
||||
case .remoteRinging, .localRinging:
|
||||
case .remoteRinging, .localRinging_Anticipatory, .localRinging_ReadyToAnswer, .accepting:
|
||||
Logger.debug("disconnect while ringing... we'll keep ringing")
|
||||
case .connected:
|
||||
call.individualCall.state = .reconnecting
|
||||
@ -1326,7 +1358,7 @@ import SignalMessaging
|
||||
}()
|
||||
|
||||
switch failedCall.individualCall.state {
|
||||
case .answering, .localRinging:
|
||||
case .answering, .localRinging_Anticipatory, .localRinging_ReadyToAnswer, .accepting:
|
||||
assert(failedCall.individualCall.callRecord == nil)
|
||||
// call failed before any call record could be created, make one now.
|
||||
handleMissedCall(failedCall)
|
||||
|
||||
@ -350,15 +350,24 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
||||
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.info("Received \(#function) CXAnswerCallAction")
|
||||
Logger.info("Received \(#function) CXAnswerCallAction \(action.timeoutDate)")
|
||||
guard let call = callManager.callWithLocalId(action.callUUID) else {
|
||||
owsFailDebug("call as unexpectedly nil")
|
||||
action.fail()
|
||||
return
|
||||
}
|
||||
|
||||
self.callService.individualCallService.handleAcceptCall(call)
|
||||
action.fulfill()
|
||||
if call.individualCall.state == .localRinging_Anticipatory {
|
||||
// We can't answer the call until RingRTC is ready
|
||||
call.individualCall.state = .accepting
|
||||
call.individualCall.deferredAnswerCompletion = {
|
||||
action.fulfill()
|
||||
}
|
||||
} else {
|
||||
owsAssertDebug(call.individualCall.state == .localRinging_ReadyToAnswer)
|
||||
self.callService.individualCallService.handleAcceptCall(call)
|
||||
action.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
||||
|
||||
@ -8,7 +8,7 @@ import SignalServiceKit
|
||||
import SignalMessaging
|
||||
import WebRTC
|
||||
|
||||
protocol CallUIAdaptee {
|
||||
protocol CallUIAdaptee: AnyObject {
|
||||
var notificationPresenter: NotificationPresenter { get }
|
||||
var callService: CallService { get }
|
||||
var hasManualRinger: Bool { get }
|
||||
|
||||
@ -641,7 +641,7 @@ class IndividualCallViewController: OWSViewController, CallObserver, CallAudioSe
|
||||
}
|
||||
|
||||
private var isRenderingLocalVanityVideo: Bool {
|
||||
return [.idle, .dialing, .remoteRinging, .localRinging].contains(call.individualCall.state) && !localVideoView.isHidden
|
||||
return [.idle, .dialing, .remoteRinging, .localRinging_Anticipatory, .localRinging_ReadyToAnswer].contains(call.individualCall.state) && !localVideoView.isHidden
|
||||
}
|
||||
|
||||
private var previousOrigin: CGPoint!
|
||||
@ -726,14 +726,14 @@ class IndividualCallViewController: OWSViewController, CallObserver, CallAudioSe
|
||||
return NSLocalizedString("IN_CALL_CONNECTING", comment: "Call setup status label")
|
||||
case .remoteRinging:
|
||||
return NSLocalizedString("IN_CALL_RINGING", comment: "Call setup status label")
|
||||
case .localRinging:
|
||||
case .localRinging_Anticipatory, .localRinging_ReadyToAnswer:
|
||||
switch call.individualCall.offerMediaType {
|
||||
case .audio:
|
||||
return NSLocalizedString("IN_CALL_RINGING_AUDIO", comment: "Call setup status label")
|
||||
case .video:
|
||||
return NSLocalizedString("IN_CALL_RINGING_VIDEO", comment: "Call setup status label")
|
||||
}
|
||||
case .answering:
|
||||
case .answering, .accepting:
|
||||
return NSLocalizedString("IN_CALL_SECURING", comment: "Call setup status label")
|
||||
case .connected:
|
||||
let callDuration = call.connectionDuration()
|
||||
@ -827,7 +827,7 @@ class IndividualCallViewController: OWSViewController, CallObserver, CallAudioSe
|
||||
)
|
||||
|
||||
// Show Incoming vs. Ongoing call controls
|
||||
if call.individualCall.state == .localRinging {
|
||||
if [.localRinging_Anticipatory, .localRinging_ReadyToAnswer].contains(call.individualCall.state) {
|
||||
let isVideoOffer = call.individualCall.offerMediaType == .video
|
||||
incomingVideoCallControls.isHidden = !isVideoOffer
|
||||
incomingAudioCallControls.isHidden = isVideoOffer
|
||||
|
||||
@ -53,7 +53,7 @@ class NonCallKitCallUIAdaptee: NSObject, CallUIAdaptee {
|
||||
// we re-present the notifiation every 3 seconds to make sure
|
||||
// the user sees that their phone is ringing
|
||||
incomingCallNotificationTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [weak self] _ in
|
||||
guard call.individualCall.state == .localRinging else {
|
||||
guard call.individualCall.state == .localRinging_ReadyToAnswer else {
|
||||
self?.incomingCallNotificationTimer?.invalidate()
|
||||
self?.incomingCallNotificationTimer = nil
|
||||
return
|
||||
|
||||
@ -132,6 +132,7 @@ public enum PushRegistrationError: Error {
|
||||
if let callRelayPayload = callRelayPayload {
|
||||
Logger.info("Received VoIP push from the NSE: \(callRelayPayload)")
|
||||
owsAssertDebug(isWaitingForSignal.tryToSetFlag())
|
||||
callService.earlyRingNextIncomingCall = true
|
||||
}
|
||||
|
||||
let isUnexpectedPush = AtomicBool(false)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user