Merge pull request #329 from RyanRory/fix-callkit

Fixed an issue where calls are not working on locked screen
pull/1053/head
Morgan Pretty 5 months ago committed by GitHub
commit 1ace299235
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -7778,7 +7778,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 496; CURRENT_PROJECT_VERSION = 505;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -7816,7 +7816,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 2.8.2; MARKETING_VERSION = 2.8.3;
OTHER_LDFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
@ -7849,7 +7849,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 496; CURRENT_PROJECT_VERSION = 505;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -7887,7 +7887,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 2.8.2; MARKETING_VERSION = 2.8.3;
OTHER_LDFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = Session; PRODUCT_NAME = Session;

@ -113,7 +113,7 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
// Construct a CXCallUpdate describing the incoming call, including the caller. // Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate() let update = CXCallUpdate()
update.localizedCallerName = callerName update.localizedCallerName = callerName
update.remoteHandle = CXHandle(type: .generic, value: call.callId.uuidString) update.remoteHandle = CXHandle(type: .generic, value: call.sessionId)
update.hasVideo = false update.hasVideo = false
disableUnsupportedFeatures(callUpdate: update) disableUnsupportedFeatures(callUpdate: update)
@ -140,11 +140,12 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
} }
func handleCallEnded() { func handleCallEnded() {
SNLog("[Calls] Call ended.")
WebRTCSession.current = nil WebRTCSession.current = nil
UserDefaults.sharedLokiProject?[.isCallOngoing] = false UserDefaults.sharedLokiProject?[.isCallOngoing] = false
UserDefaults.sharedLokiProject?[.lastCallPreOffer] = nil UserDefaults.sharedLokiProject?[.lastCallPreOffer] = nil
if Singleton.hasAppContext && Singleton.appContext.isInBackground { if Singleton.hasAppContext && Singleton.appContext.isNotInForeground {
(UIApplication.shared.delegate as? AppDelegate)?.stopPollers() (UIApplication.shared.delegate as? AppDelegate)?.stopPollers()
Log.flush() Log.flush()
} }
@ -198,6 +199,7 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
} }
public static func suspendDatabaseIfCallEndedInBackground() { public static func suspendDatabaseIfCallEndedInBackground() {
SNLog("[Calls] suspendDatabaseIfCallEndedInBackground.")
if Singleton.hasAppContext && Singleton.appContext.isInBackground { if Singleton.hasAppContext && Singleton.appContext.isInBackground {
// FIXME: Initialise the `SessionCallManager` with a dependencies instance // FIXME: Initialise the `SessionCallManager` with a dependencies instance
let dependencies: Dependencies = Dependencies() let dependencies: Dependencies = Dependencies()
@ -238,8 +240,7 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
{ {
let callVC = CallVC(for: call) let callVC = CallVC(for: call)
callVC.conversationVC = conversationVC callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true conversationVC.hideInputAccessoryView()
conversationVC.inputAccessoryView?.alpha = 0
presentingVC.present(callVC, animated: true, completion: nil) presentingVC.present(callVC, animated: true, completion: nil)
} }
else if !Preferences.isCallKitSupported { else if !Preferences.isCallKitSupported {

@ -600,8 +600,11 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
} }
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { [weak self] _ in Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { [weak self] _ in
self?.conversationVC?.showInputAccessoryView() DispatchQueue.main.async {
self?.presentingViewController?.dismiss(animated: true, completion: nil) self?.dismiss(animated: true, completion: {
self?.conversationVC?.showInputAccessoryView()
})
}
} }
} }
@ -623,9 +626,13 @@ final class CallVC: UIViewController, VideoPreviewDelegate {
AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: nil) AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: nil)
} }
DispatchQueue.main.async { Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { [weak self] _ in
self?.conversationVC?.showInputAccessoryView() DispatchQueue.main.async {
self?.presentingViewController?.dismiss(animated: true, completion: nil) self?.dismiss(animated: true, completion: {
self?.conversationVC?.becomeFirstResponder()
self?.conversationVC?.showInputAccessoryView()
})
}
} }
} }
} }

@ -791,13 +791,23 @@ extension ConversationVC:
} }
func hideInputAccessoryView() { func hideInputAccessoryView() {
DispatchQueue.main.async { guard Thread.isMainThread else {
self.inputAccessoryView?.isHidden = true DispatchQueue.main.async {
self.inputAccessoryView?.alpha = 0 self.hideInputAccessoryView()
}
return
} }
self.inputAccessoryView?.isHidden = true
self.inputAccessoryView?.alpha = 0
} }
func showInputAccessoryView() { func showInputAccessoryView() {
guard Thread.isMainThread else {
DispatchQueue.main.async {
self.showInputAccessoryView()
}
return
}
UIView.animate(withDuration: 0.25, animations: { UIView.animate(withDuration: 0.25, animations: {
self.inputAccessoryView?.isHidden = false self.inputAccessoryView?.isHidden = false
self.inputAccessoryView?.alpha = 1 self.inputAccessoryView?.alpha = 1

@ -353,7 +353,7 @@ final class HomeVC: BaseVC, LibSessionRespondingViewController, UITableViewDataS
) )
// Start polling if needed (i.e. if the user just created or restored their Session ID) // Start polling if needed (i.e. if the user just created or restored their Session ID)
if Identity.userExists(), let appDelegate: AppDelegate = UIApplication.shared.delegate as? AppDelegate { if Identity.userExists(), let appDelegate: AppDelegate = UIApplication.shared.delegate as? AppDelegate, !Singleton.appContext.isNotInForeground {
appDelegate.startPollersIfNeeded() appDelegate.startPollersIfNeeded()
} }

@ -985,6 +985,88 @@ SOFTWARE.
<key>License</key> <key>License</key>
<string>The MIT License (MIT) <string>The MIT License (MIT)
Copyright (c) 2016 swiftlyfalling (https://github.com/swiftlyfalling)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</string>
<key>Title</key>
<string>session-grdb-swift</string>
</dict>
<dict>
<key>License</key>
<string>The author disclaims copyright to this source code. In place of
a legal notice, here is a blessing:
* May you do good and not evil.
* May you find forgiveness for yourself and forgive others.
* May you share freely, never taking more than you give.
</string>
<key>Title</key>
<string>session-grdb-swift</string>
</dict>
<dict>
<key>License</key>
<string>/*
** LICENSE for the sqlite3 WebAssembly/JavaScript APIs.
**
** This bundle (typically released as sqlite3.js or sqlite3.mjs)
** is an amalgamation of JavaScript source code from two projects:
**
** 1) https://emscripten.org: the Emscripten "glue code" is covered by
** the terms of the MIT license and University of Illinois/NCSA
** Open Source License, as described at:
**
** https://emscripten.org/docs/introducing_emscripten/emscripten_license.html
**
** 2) https://sqlite.org: all code and documentation labeled as being
** from this source are released under the same terms as the sqlite3
** C library:
**
** 2022-10-16
**
** The author disclaims copyright to this source code. In place of a
** legal notice, here is a blessing:
**
** * May you do good and not evil.
** * May you find forgiveness for yourself and forgive others.
** * May you share freely, never taking more than you give.
*/
</string>
<key>Title</key>
<string>session-grdb-swift</string>
</dict>
<dict>
<key>License</key>
<string>The author disclaims copyright to this source code. In place of
a legal notice, here is a blessing:
May you do good and not evil.
May you find forgiveness for yourself and forgive others.
May you share freely, never taking more than you give.
</string>
<key>Title</key>
<string>session-grdb-swift</string>
</dict>
<dict>
<key>License</key>
<string>The MIT License (MIT)
Copyright (c) 2015 ibireme &lt;ibireme@gmail.com&gt; Copyright (c) 2015 ibireme &lt;ibireme@gmail.com&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy

@ -284,7 +284,8 @@ public enum PushRegistrationError: Error {
guard guard
let uuid: String = payload["uuid"] as? String, let uuid: String = payload["uuid"] as? String,
let caller: String = payload["caller"] as? String, let caller: String = payload["caller"] as? String,
let timestampMs: Int64 = payload["timestamp"] as? Int64 let timestampMs: UInt64 = payload["timestamp"] as? UInt64,
TimestampUtils.isWithinOneMinute(timestampMs: timestampMs)
else { else {
SessionCallManager.reportFakeCall(info: "Missing payload data") // stringlint:ignore SessionCallManager.reportFakeCall(info: "Missing payload data") // stringlint:ignore
return return
@ -297,40 +298,33 @@ public enum PushRegistrationError: Error {
LibSession.resumeNetworkAccess() LibSession.resumeNetworkAccess()
let maybeCall: SessionCall? = Storage.shared.write { db in let maybeCall: SessionCall? = Storage.shared.write { db in
let messageInfo: CallMessage.MessageInfo = CallMessage.MessageInfo( var call: SessionCall? = nil
state: (caller == getUserHexEncodedPublicKey(db) ?
.outgoing :
.incoming
)
)
let messageInfoString: String? = {
if let messageInfoData: Data = try? JSONEncoder().encode(messageInfo) {
return String(data: messageInfoData, encoding: .utf8)
} else {
return "callsIncoming"
.put(key: "name", value: caller)
.localized()
}
}()
let call: SessionCall = SessionCall(db, for: caller, uuid: uuid, mode: .answer)
let thread: SessionThread = try SessionThread
.fetchOrCreate(db, id: caller, variant: .contact, shouldBeVisible: nil)
let interaction: Interaction = try Interaction(
messageUuid: uuid,
threadId: thread.id,
threadVariant: thread.variant,
authorId: caller,
variant: .infoCall,
body: messageInfoString,
timestampMs: timestampMs
)
.withDisappearingMessagesConfiguration(db, threadVariant: thread.variant)
.inserted(db)
call.callInteractionId = interaction.id do {
call = SessionCall(
db,
for: caller,
uuid: uuid,
mode: .answer
)
let thread: SessionThread = try SessionThread
.fetchOrCreate(
db,
id: caller,
variant: .contact,
shouldBeVisible: nil
)
let interaction: Interaction? = try Interaction
.filter(Interaction.Columns.threadId == thread.id)
.filter(Interaction.Columns.messageUuid == uuid)
.fetchOne(db)
call?.callInteractionId = interaction?.id
} catch {
SNLog("[Calls] Failed to create call due to error: \(error)")
}
return call return call
} }
@ -340,12 +334,16 @@ public enum PushRegistrationError: Error {
return return
} }
JobRunner.appDidBecomeActive()
// NOTE: Just start 1-1 poller so that it won't wait for polling group messages // NOTE: Just start 1-1 poller so that it won't wait for polling group messages
(UIApplication.shared.delegate as? AppDelegate)?.startPollersIfNeeded(shouldStartGroupPollers: false) (UIApplication.shared.delegate as? AppDelegate)?.startPollersIfNeeded(shouldStartGroupPollers: false)
call.reportIncomingCallIfNeeded { error in call.reportIncomingCallIfNeeded { error in
if let error = error { if let error = error {
SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)")
} else {
SNLog("[Calls] Succeeded to report incoming call to CallKit")
} }
} }
} }

@ -109,6 +109,7 @@ extension MessageReceiver {
// Ignore pre offer message after the same call instance has been generated // Ignore pre offer message after the same call instance has been generated
if let currentCall: CurrentCallProtocol = callManager.currentCall, currentCall.uuid == message.uuid { if let currentCall: CurrentCallProtocol = callManager.currentCall, currentCall.uuid == message.uuid {
SNLog("[MessageReceiver+Calls] Ignoring pre-offer message for call[\(currentCall.uuid)] instance because it is already active.")
return return
} }

@ -117,7 +117,7 @@ extension MessageReceiver {
} }
// Update the `didApproveMe` state of the sender // Update the `didApproveMe` state of the sender
try updateContactApprovalStatusIfNeeded( let shouldInsertControlMessage: Bool = try updateContactApprovalStatusIfNeeded(
db, db,
senderSessionId: senderId, senderSessionId: senderId,
threadId: nil threadId: nil
@ -138,6 +138,7 @@ extension MessageReceiver {
) )
} }
guard shouldInsertControlMessage else { return }
// Notify the user of their approval (Note: This will always appear in the un-blinded thread) // Notify the user of their approval (Note: This will always appear in the un-blinded thread)
// //
// Note: We want to do this last as it'll mean the un-blinded thread gets updated and the // Note: We want to do this last as it'll mean the un-blinded thread gets updated and the
@ -156,11 +157,11 @@ extension MessageReceiver {
).inserted(db) ).inserted(db)
} }
internal static func updateContactApprovalStatusIfNeeded( @discardableResult internal static func updateContactApprovalStatusIfNeeded(
_ db: Database, _ db: Database,
senderSessionId: String, senderSessionId: String,
threadId: String? threadId: String?
) throws { ) throws -> Bool {
let userPublicKey: String = getUserHexEncodedPublicKey(db) let userPublicKey: String = getUserHexEncodedPublicKey(db)
// If the sender of the message was the current user // If the sender of the message was the current user
@ -171,13 +172,13 @@ extension MessageReceiver {
let threadId: String = threadId, let threadId: String = threadId,
let thread: SessionThread = try? SessionThread.fetchOne(db, id: threadId), let thread: SessionThread = try? SessionThread.fetchOne(db, id: threadId),
!thread.isNoteToSelf(db) !thread.isNoteToSelf(db)
else { return } else { return true }
// Sending a message to someone flags them as approved so create the contact record if // Sending a message to someone flags them as approved so create the contact record if
// it doesn't exist // it doesn't exist
let contact: Contact = Contact.fetchOrCreate(db, id: threadId) let contact: Contact = Contact.fetchOrCreate(db, id: threadId)
guard !contact.isApproved else { return } guard !contact.isApproved else { return false }
try? contact.save(db) try? contact.save(db)
_ = try? Contact _ = try? Contact
@ -189,12 +190,14 @@ extension MessageReceiver {
// someone without approving them) // someone without approving them)
let contact: Contact = Contact.fetchOrCreate(db, id: senderSessionId) let contact: Contact = Contact.fetchOrCreate(db, id: senderSessionId)
guard !contact.didApproveMe else { return } guard !contact.didApproveMe else { return false }
try? contact.save(db) try? contact.save(db)
_ = try? Contact _ = try? Contact
.filter(id: senderSessionId) .filter(id: senderSessionId)
.updateAllAndConfig(db, Contact.Columns.didApproveMe.set(to: true)) .updateAllAndConfig(db, Contact.Columns.didApproveMe.set(to: true))
} }
return true
} }
} }

@ -413,7 +413,9 @@ public class Poller {
job: job, job: job,
canStartJob: ( canStartJob: (
!forceSynchronousProcessing && !forceSynchronousProcessing &&
(Singleton.hasAppContext && !Singleton.appContext.isInBackground) (Singleton.hasAppContext && !Singleton.appContext.isInBackground) ||
// FIXME: Better seperate the call messages handling, since we need to handle them all the time
SessionEnvironment.shared?.callManager.wrappedValue?.currentCall != nil
), ),
using: dependencies using: dependencies
) )

@ -46,7 +46,9 @@ public extension AppContext {
var frontmostViewController: UIViewController? { nil } var frontmostViewController: UIViewController? { nil }
var backgroundTimeRemaining: TimeInterval { 0 } var backgroundTimeRemaining: TimeInterval { 0 }
// Note: CallKit will make the app state as .inactive
var isInBackground: Bool { reportedApplicationState == .background } var isInBackground: Bool { reportedApplicationState == .background }
var isNotInForeground: Bool { reportedApplicationState != .active }
var isAppForegroundAndActive: Bool { reportedApplicationState == .active } var isAppForegroundAndActive: Bool { reportedApplicationState == .active }
// MARK: - Paths // MARK: - Paths

Loading…
Cancel
Save