From 0f46834e8d2d7f0d4f2f36049fdcf3d83062de14 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 19 Apr 2018 09:56:09 -0400 Subject: [PATCH] Show "Reconnecting..." on call screen // FREEBIE --- .../ViewControllers/CallViewController.swift | 25 +++++++++++- Signal/src/call/CallAudioService.swift | 6 +++ Signal/src/call/CallService.swift | 40 +++++++++++++++++-- Signal/src/call/PeerConnectionClient.swift | 14 +++++++ Signal/src/call/SignalCall.swift | 8 ++-- .../translations/en.lproj/Localizable.strings | 3 ++ 6 files changed, 88 insertions(+), 8 deletions(-) diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index d9ed3dd01..7f7a9f02b 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -676,6 +676,8 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, } } return formattedDate + case .reconnecting: + return NSLocalizedString("IN_CALL_RECONNECTING", comment: "Call setup status label") case .remoteBusy: return NSLocalizedString("END_CALL_RESPONDER_IS_BUSY", comment: "Call setup status label") case .localFailure: @@ -694,18 +696,39 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, } } + var isBlinkingReconnectLabel = false func updateCallStatusLabel(callState: CallState) { assert(Thread.isMainThread) let text = String(format: CallStrings.callStatusFormat, localizedTextForCallState(callState)) self.callStatusLabel.text = text + + // Handle reconnecting blinking + if case .reconnecting = callState { + if !isBlinkingReconnectLabel { + isBlinkingReconnectLabel = true + UIView.animate(withDuration: 0.7, delay: 0, options: [.autoreverse, .repeat], + animations: { + self.callStatusLabel.alpha = 0.2 + }, completion: nil) + } else { + // already blinking + } + } else { + // We're no longer in a reconnecting state, either the call failed or we reconnected. + // Stop the blinking animation + if isBlinkingReconnectLabel { + self.callStatusLabel.layer.removeAllAnimations() + self.callStatusLabel.alpha = 1 + isBlinkingReconnectLabel = false + } + } } func updateCallUI(callState: CallState) { assert(Thread.isMainThread) updateCallStatusLabel(callState: callState) - if isShowingSettingsNag { settingsNagView.isHidden = false contactAvatarView.isHidden = true diff --git a/Signal/src/call/CallAudioService.swift b/Signal/src/call/CallAudioService.swift index 43b48ff3c..db5121747 100644 --- a/Signal/src/call/CallAudioService.swift +++ b/Signal/src/call/CallAudioService.swift @@ -289,6 +289,7 @@ protocol CallAudioServiceDelegate: class { case .remoteRinging: handleRemoteRinging(call: call) case .localRinging: handleLocalRinging(call: call) case .connected: handleConnected(call: call) + case .reconnecting: handleReconnecting(call: call) case .localFailure: handleLocalFailure(call: call) case .localHangup: handleLocalHangup(call: call) case .remoteHangup: handleRemoteHangup(call: call) @@ -335,6 +336,11 @@ protocol CallAudioServiceDelegate: class { SwiftAssertIsOnMainThread(#function) } + private func handleReconnecting(call: SignalCall) { + Logger.debug("\(self.logTag) \(#function)") + SwiftAssertIsOnMainThread(#function) + } + private func handleLocalFailure(call: SignalCall) { Logger.debug("\(self.logTag) \(#function)") SwiftAssertIsOnMainThread(#function) diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index 2e08d0ae6..4c24807a6 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -547,7 +547,7 @@ protocol CallServiceObserver: class { // If both users are trying to call each other at the same time, // both should see busy. handleRemoteBusy(thread: existingCall.thread, callId: existingCall.signalingId) - case .answering, .localRinging, .connected, .localFailure, .localHangup, .remoteHangup, .remoteBusy: + case .answering, .localRinging, .connected, .localFailure, .localHangup, .remoteHangup, .remoteBusy, .reconnecting: // If one user calls another while the other has a "vestigial" call with // that same user, fail the old call. terminateCall() @@ -769,8 +769,31 @@ protocol CallServiceObserver: class { Logger.info("\(self.logTag) call already ringing. Ignoring \(#function): \(call.identifiersForLogs).") case .connected: Logger.info("\(self.logTag) Call reconnected \(#function): \(call.identifiersForLogs).") + case .reconnecting: + call.state = .connected + case .idle, .localRinging, .localFailure, .localHangup, .remoteHangup, .remoteBusy: + owsFail("\(self.logTag) unexpected call state for \(#function): \(call.state): \(call.identifiersForLogs).") + } + } + + private func handleIceDisconnected() { + SwiftAssertIsOnMainThread(#function) + + guard let call = self.call else { + // This will only be called for the current peerConnectionClient, so + // fail the current call. + OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(self.logTag) ignoring \(#function) since there is no current call.")) + return + } + + Logger.info("\(self.logTag) in \(#function): \(call.identifiersForLogs).") + + switch call.state { + case .connected: + call.state = .reconnecting default: - Logger.debug("\(self.logTag) unexpected call state for \(#function): \(call.state): \(call.identifiersForLogs).") + owsFail("\(self.logTag) unexpected call state for \(#function): \(call.state): \(call.identifiersForLogs).") } } @@ -804,7 +827,7 @@ protocol CallServiceObserver: class { switch call.state { case .idle, .dialing, .answering, .localRinging, .localFailure, .remoteBusy, .remoteRinging: handleMissedCall(call) - case .connected, .localHangup, .remoteHangup: + case .connected, .reconnecting, .localHangup, .remoteHangup: Logger.info("\(self.logTag) call is finished.") } @@ -1217,6 +1240,17 @@ protocol CallServiceObserver: class { self.handleIceConnected() } + func peerConnectionClientIceDisconnected(_ peerconnectionClient: PeerConnectionClient) { + SwiftAssertIsOnMainThread(#function) + + guard peerConnectionClient == self.peerConnectionClient else { + Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient") + return + } + + self.handleIceDisconnected() + } + /** * The connection failed to establish. The clients will not be able to communicate. */ diff --git a/Signal/src/call/PeerConnectionClient.swift b/Signal/src/call/PeerConnectionClient.swift index 42d89df26..8c7c37018 100644 --- a/Signal/src/call/PeerConnectionClient.swift +++ b/Signal/src/call/PeerConnectionClient.swift @@ -27,6 +27,7 @@ protocol PeerConnectionClientDelegate: class { /** * The connection has been established. The clients can now communicate. + * This can be called multiple times throughout the call in the event of temporary network disconnects. */ func peerConnectionClientIceConnected(_ peerconnectionClient: PeerConnectionClient) @@ -35,6 +36,13 @@ protocol PeerConnectionClientDelegate: class { */ func peerConnectionClientIceFailed(_ peerconnectionClient: PeerConnectionClient) + /** + * After initially connecting, the connection disconnected. + * It maybe be temporary, in which case `peerConnectionClientIceConnected` will be called again once we're reconnected. + * Otherwise, `peerConnectionClientIceFailed` will eventually called. + */ + func peerConnectionClientIceDisconnected(_ peerconnectionClient: PeerConnectionClient) + /** * During the Signaling process each client generates IceCandidates locally, which contain information about how to * reach the local client via the internet. The delegate must shuttle these IceCandates to the other (remote) client @@ -676,6 +684,12 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD } case .disconnected: Logger.warn("\(self.TAG) RTCIceConnection disconnected.") + if let delegate = self.delegate { + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return } + delegate.peerConnectionClientIceDisconnected(strongSelf) + } + } default: Logger.debug("\(self.TAG) ignoring change IceConnectionState:\(newState.debugDescription)") } diff --git a/Signal/src/call/SignalCall.swift b/Signal/src/call/SignalCall.swift index 939d5f239..909030a3f 100644 --- a/Signal/src/call/SignalCall.swift +++ b/Signal/src/call/SignalCall.swift @@ -12,6 +12,7 @@ enum CallState: String { case remoteRinging case localRinging case connected + case reconnecting case localFailure // terminal case localHangup // terminal case remoteHangup // terminal @@ -47,7 +48,7 @@ protocol CallObserver: class { switch state { case .localFailure, .localHangup, .remoteHangup, .remoteBusy: return true - case .idle, .dialing, .answering, .remoteRinging, .localRinging, .connected: + case .idle, .dialing, .answering, .remoteRinging, .localRinging, .connected, .reconnecting: return false } } @@ -87,12 +88,11 @@ protocol CallObserver: class { Logger.debug("\(TAG) state changed: \(oldValue) -> \(self.state) for call: \(self.identifiersForLogs)") // Update connectedDate - if self.state == .connected { + if case .connected = self.state { + // if it's the first time we've connected (not a reconnect) if connectedDate == nil { connectedDate = NSDate() } - } else { - connectedDate = nil } updateCallRecordType() diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 8cb00e14e..c5520df78 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -875,6 +875,9 @@ /* Call setup status label */ "IN_CALL_CONNECTING" = "Connecting…"; +/* Call setup status label */ +"IN_CALL_RECONNECTING" = "Reconnecting..."; + /* Call setup status label */ "IN_CALL_RINGING" = "Ringing…";