Merge pull request #337 from RyanRory/fix-callkit-2

Fix Voice and Video Calls
pull/1055/head
Morgan Pretty 4 months ago committed by GitHub
commit b965a382db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -7819,6 +7819,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 513;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -7856,6 +7857,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 2.8.4;
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
@ -7888,6 +7890,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 513;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -7925,6 +7928,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 2.8.4;
OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = Session;

@ -156,7 +156,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
self.callId = UUID()
self.mode = mode
self.audioMode = .earpiece
self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionId, with: uuid)
self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionId, with: uuid, using: dependencies)
self.isOutgoing = outgoing
let avatarData: Data? = ProfileManager.profileAvatar(db, id: sessionId)
@ -186,7 +186,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
// stringlint:ignore_contents
func reportIncomingCallIfNeeded(completion: @escaping (Error?) -> Void) {
guard case .answer = mode else {
SessionCallManager.reportFakeCall(info: "Call not in answer mode", using: dependencies)
Singleton.callManager.reportFakeCall(info: "Call not in answer mode")
return
}
@ -268,12 +268,13 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
hasStartedConnecting = true
if let sdp = remoteSDP {
SNLog("[Calls] Got remote sdp already")
webRTCSession.handleRemoteSDP(sdp, from: sessionId) // This sends an answer message internally
}
}
func answerSessionCallInBackground(action: CXAnswerCallAction) {
answerCallAction = action
func answerSessionCallInBackground() {
SNLog("[Calls] Answering call in background")
self.answerSessionCall()
}

@ -16,20 +16,16 @@ extension SessionCallManager {
return true
}
@discardableResult
public func answerCallAction() -> Bool {
guard let call: SessionCall = (self.currentCall as? SessionCall) else { return false }
public func answerCallAction() {
guard let call: SessionCall = (self.currentCall as? SessionCall) else { return }
if Singleton.hasAppContext, Singleton.appContext.frontmostViewController is CallVC {
call.answerSessionCall()
}
else {
guard
Singleton.hasAppContext,
let presentingVC = Singleton.appContext.frontmostViewController
else { return false } // FIXME: Handle more gracefully
let callVC = CallVC(for: call)
guard Singleton.hasAppContext, let presentingVC = Singleton.appContext.frontmostViewController else { return } // FIXME: Handle more gracefully
let callVC = CallVC(for: call)
if let conversationVC = presentingVC as? ConversationVC {
callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true
@ -40,7 +36,6 @@ extension SessionCallManager {
call.answerSessionCall()
}
}
return true
}
@discardableResult

@ -25,18 +25,18 @@ extension SessionCallManager: CXProviderDelegate {
Log.assertOnMainThread()
Log.debug("[CallKit] Perform CXAnswerCallAction")
guard let call: SessionCall = (self.currentCall as? SessionCall) else { return action.fail() }
guard let call: SessionCall = (self.currentCall as? SessionCall) else {
Log.warn("[CallKit] No session call")
return action.fail()
}
call.answerCallAction = action
if Singleton.hasAppContext && Singleton.appContext.isMainAppAndActive {
if answerCallAction() {
action.fulfill()
}
else {
action.fail()
}
self.answerCallAction()
}
else {
call.answerSessionCallInBackground(action: action)
call.answerSessionCallInBackground()
}
}

@ -9,14 +9,21 @@ import SessionMessagingKit
import SignalUtilitiesKit
import SessionUtilitiesKit
// MARK: - Cache
// MARK: - CXProviderConfiguration
public extension Cache {
static let callManager: CacheInfo.Config<CallManagerCacheType, CallManagerImmutableCacheType> = CacheInfo.create(
createInstance: { SessionCallManager.Cache() },
mutableInstance: { $0 },
immutableInstance: { $0 }
)
public extension CXProviderConfiguration {
static func defaultConfiguration(_ useSystemCallLog: Bool = false) -> CXProviderConfiguration {
let iconMaskImage: UIImage = #imageLiteral(resourceName: "SessionGreen32")
let configuration = CXProviderConfiguration()
configuration.supportsVideo = true
configuration.maximumCallGroups = 1
configuration.maximumCallsPerCallGroup = 1
configuration.supportedHandleTypes = [.generic]
configuration.iconTemplateImageData = iconMaskImage.pngData()
configuration.includesCallsInRecents = useSystemCallLog
return configuration
}
}
// MARK: - SessionCallManager
@ -47,9 +54,7 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
self.dependencies = dependencies
if Preferences.isCallKitSupported {
self.provider = dependencies.caches.mutate(cache: .callManager) {
$0.getOrCreateProvider(useSystemCallLog: useSystemCallLog)
}
self.provider = CXProvider(configuration: .defaultConfiguration(useSystemCallLog))
self.callController = CXCallController()
}
else {
@ -65,18 +70,15 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
// MARK: - Report calls
public static func reportFakeCall(info: String, using dependencies: Dependencies) {
public func reportFakeCall(info: String) {
let callId = UUID()
let provider: CXProvider = dependencies.caches.mutate(cache: .callManager) {
$0.getOrCreateProvider(useSystemCallLog: false)
}
provider.reportNewIncomingCall(
self.provider?.reportNewIncomingCall(
with: callId,
update: CXCallUpdate()
) { _ in
SNLog("[Calls] Reported fake incoming call to CallKit due to: \(info)")
}
provider.reportCall(
self.provider?.reportCall(
with: callId,
endedAt: nil,
reason: .failed
@ -104,9 +106,6 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
}
public func reportIncomingCall(_ call: CurrentCallProtocol, callerName: String, completion: @escaping (Error?) -> Void) {
let provider: CXProvider = dependencies.caches.mutate(cache: .callManager) {
$0.getOrCreateProvider(useSystemCallLog: false)
}
// Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate()
update.localizedCallerName = callerName
@ -116,7 +115,7 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
disableUnsupportedFeatures(callUpdate: update)
// Report the incoming call to the system
provider.reportNewIncomingCall(with: call.callId, update: update) { error in
self.provider?.reportNewIncomingCall(with: call.callId, update: update) { error in
guard error == nil else {
self.reportCurrentCallEnded(reason: .failed)
completion(error)
@ -294,39 +293,3 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
MiniCallView.current?.dismiss()
}
}
// MARK: - SessionCallManager Cache
public extension SessionCallManager {
class Cache: CallManagerCacheType {
public var provider: CXProvider?
public func getOrCreateProvider(useSystemCallLog: Bool) -> CXProvider {
if let provider: CXProvider = self.provider {
return provider
}
let iconMaskImage: UIImage = #imageLiteral(resourceName: "SessionGreen32")
let configuration = CXProviderConfiguration()
configuration.supportsVideo = true
configuration.maximumCallGroups = 1
configuration.maximumCallsPerCallGroup = 1
configuration.supportedHandleTypes = [.generic]
configuration.iconTemplateImageData = iconMaskImage.pngData()
configuration.includesCallsInRecents = useSystemCallLog
let provider: CXProvider = CXProvider(configuration: configuration)
self.provider = provider
return provider
}
}
}
// MARK: - OGMCacheType
/// This is a read-only version of the Cache designed to avoid unintentionally mutating the instance in a non-thread-safe way
public protocol CallManagerImmutableCacheType: ImmutableCacheType {}
public protocol CallManagerCacheType: CallManagerImmutableCacheType, MutableCacheType {
func getOrCreateProvider(useSystemCallLog: Bool) -> CXProvider
}

@ -602,6 +602,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { [weak self] _ in
DispatchQueue.main.async {
self?.dismiss(animated: true, completion: {
self?.conversationVC?.becomeFirstResponder()
self?.conversationVC?.showInputAccessoryView()
})
}
@ -647,6 +648,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
@objc private func minimize() {
self.shouldRestartCamera = false
self.conversationVC?.becomeFirstResponder()
self.conversationVC?.showInputAccessoryView()
let miniCallView = MiniCallView(from: self)

@ -19,6 +19,8 @@ public protocol WebRTCSessionDelegate: AnyObject {
/// See https://webrtc.org/getting-started/overview for more information.
public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
private let dependencies: Dependencies
public weak var delegate: WebRTCSessionDelegate?
public let uuid: String
private let contactSessionId: String
@ -95,12 +97,13 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
// MARK: Initialization
public static var current: WebRTCSession?
public init(for contactSessionId: String, with uuid: String) {
public init(for contactSessionId: String, with uuid: String, using dependencies: Dependencies) {
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
self.contactSessionId = contactSessionId
self.uuid = uuid
self.dependencies = dependencies
super.init()
@ -125,8 +128,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
_ db: Database,
message: CallMessage,
interactionId: Int64?,
in thread: SessionThread,
using dependencies: Dependencies = Dependencies()
in thread: SessionThread
) throws -> AnyPublisher<Void, Error> {
SNLog("[Calls] Sending pre-offer message.")
@ -151,12 +153,12 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
public func sendOffer(
to thread: SessionThread,
isRestartingICEConnection: Bool = false,
using dependencies: Dependencies = Dependencies()
isRestartingICEConnection: Bool = false
) -> AnyPublisher<Void, Error> {
SNLog("[Calls] Sending offer message.")
let uuid: String = self.uuid
let mediaConstraints: RTCMediaConstraints = mediaConstraints(isRestartingICEConnection)
let dependencies: Dependencies = self.dependencies
return Deferred {
Future<Void, Error> { [weak self] resolver in
@ -215,13 +217,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
.eraseToAnyPublisher()
}
public func sendAnswer(
to sessionId: String,
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<Void, Error> {
public func sendAnswer(to sessionId: String) -> AnyPublisher<Void, Error> {
SNLog("[Calls] Sending answer message.")
let uuid: String = self.uuid
let mediaConstraints: RTCMediaConstraints = mediaConstraints(false)
let dependencies: Dependencies = self.dependencies
return dependencies.storage
.readPublisher { db -> SessionThread in
@ -299,10 +299,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
}
}
private func sendICECandidates(using dependencies: Dependencies = Dependencies()) {
private func sendICECandidates() {
let candidates: [RTCIceCandidate] = self.queuedICECandidates
let uuid: String = self.uuid
let contactSessionId: String = self.contactSessionId
let dependencies: Dependencies = self.dependencies
// Empty the queue
self.queuedICECandidates.removeAll()
@ -346,8 +347,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
public func endCall(
_ db: Database,
with sessionId: String,
using dependencies: Dependencies = Dependencies()
with sessionId: String
) throws {
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: sessionId) else { return }

@ -285,17 +285,14 @@ public class PushRegistrationManager: NSObject, PKPushRegistryDelegate, PushRegi
let timestampMs: UInt64 = payload["timestamp"] as? UInt64,
TimestampUtils.isWithinOneMinute(timestampMs: timestampMs)
else {
SessionCallManager.reportFakeCall(info: "Missing payload data", using: dependencies) // stringlint:ignore
Singleton.callManager.reportFakeCall(info: "Missing payload data") // stringlint:ignore
return
}
// FIXME: Initialise the `PushRegistrationManager` with a dependencies instance
let dependencies: Dependencies = Dependencies()
dependencies.storage.resumeDatabaseAccess()
LibSession.resumeNetworkAccess()
let maybeCall: SessionCall? = Storage.shared.write { db in
let maybeCall: SessionCall? = Storage.shared.read { [dependencies = self.dependencies] db in
var call: SessionCall? = nil
do {
@ -307,17 +304,8 @@ public class PushRegistrationManager: NSObject, PKPushRegistryDelegate, PushRegi
using: dependencies
)
let thread: SessionThread = try SessionThread.upsert(
db,
id: caller,
variant: .contact,
values: .existingOrDefault,
calledFromConfig: nil,
using: dependencies
)
let interaction: Interaction? = try Interaction
.filter(Interaction.Columns.threadId == thread.id)
.filter(Interaction.Columns.threadId == caller)
.filter(Interaction.Columns.messageUuid == uuid)
.fetchOne(db)
@ -330,7 +318,7 @@ public class PushRegistrationManager: NSObject, PKPushRegistryDelegate, PushRegi
}
guard let call: SessionCall = maybeCall else {
SessionCallManager.reportFakeCall(info: "Could not retrieve call from database", using: dependencies) // stringlint:ignore
Singleton.callManager.reportFakeCall(info: "Could not retrieve call from database") // stringlint:ignore
return
}

@ -22,6 +22,7 @@ public protocol CallManagerProtocol {
var currentCall: CurrentCallProtocol? { get set }
func setCurrentCall(_ call: CurrentCallProtocol?)
func reportFakeCall(info: String)
func reportIncomingCall(_ call: CurrentCallProtocol, callerName: String, completion: @escaping (Error?) -> Void)
func reportCurrentCallEnded(reason: CXCallEndedReason?)
func suspendDatabaseIfCallEndedInBackground()

@ -7,6 +7,7 @@ internal struct NoopSessionCallManager: CallManagerProtocol {
var currentCall: CurrentCallProtocol?
func setCurrentCall(_ call: CurrentCallProtocol?) {}
func reportFakeCall(info: String) {}
func reportIncomingCall(_ call: CurrentCallProtocol, callerName: String, completion: @escaping (Error?) -> Void) {}
func reportCurrentCallEnded(reason: CXCallEndedReason?) {}
func suspendDatabaseIfCallEndedInBackground() {}

@ -95,8 +95,10 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
}
}
let isCallOngoing: Bool = (UserDefaults.sharedLokiProject?[.isCallOngoing])
.defaulting(to: false)
let isCallOngoing: Bool = (
(UserDefaults.sharedLokiProject?[.isCallOngoing]).defaulting(to: false) &&
(UserDefaults.sharedLokiProject?[.lastCallPreOffer]) != nil
)
// HACK: It is important to use write synchronously here to avoid a race condition
// where the completeSilenty() is called before the local notification request

Loading…
Cancel
Save