| /* |
| * Copyright (C) 2017-2020 Savoir-faire Linux Inc. |
| * |
| * Author: Edric Ladent-Milaret <edric.ladent-milaret@savoirfairelinux.com> |
| * Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com> |
| * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> |
| * Author: Quentin Muret <quentin.muret@savoirfairelinux.com> |
| * Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| import UIKit |
| import SwiftyBeaver |
| import RxSwift |
| import PushKit |
| import ContactsUI |
| import os |
| |
| // swiftlint:disable identifier_name type_body_length |
| @UIApplicationMain |
| class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { |
| var window: UIWindow? |
| let dBManager = DBManager(profileHepler: ProfileDataHelper(), |
| conversationHelper: ConversationDataHelper(), |
| interactionHepler: InteractionDataHelper(), |
| dbConnections: DBContainer()) |
| private let daemonService = DaemonService(dRingAdaptor: DRingAdapter()) |
| private let nameService = NameService(withNameRegistrationAdapter: NameRegistrationAdapter()) |
| private let presenceService = PresenceService(withPresenceAdapter: PresenceAdapter()) |
| private let videoService = VideoService(withVideoAdapter: VideoAdapter()) |
| private let audioService = AudioService(withAudioAdapter: AudioAdapter()) |
| private let systemService = SystemService(withSystemAdapter: SystemAdapter()) |
| private let networkService = NetworkService() |
| private let callsProvider: CallsProviderService = CallsProviderService(provider: CXProvider(configuration: CallsHelpers.providerConfiguration()), controller: CXCallController()) |
| private var conversationManager: ConversationsManager? |
| private var interactionsManager: GeneratedInteractionsManager? |
| private var videoManager: VideoManager? |
| private lazy var callService: CallsService = { |
| CallsService(withCallsAdapter: CallsAdapter(), dbManager: self.dBManager) |
| }() |
| private lazy var accountService: AccountsService = { |
| AccountsService(withAccountAdapter: AccountAdapter(), dbManager: self.dBManager) |
| }() |
| private lazy var contactsService: ContactsService = { |
| ContactsService(withContactsAdapter: ContactsAdapter(), dbManager: self.dBManager) |
| }() |
| private lazy var profileService: ProfilesService = { |
| ProfilesService(withProfilesAdapter: ProfilesAdapter(), dbManager: self.dBManager) |
| }() |
| private lazy var dataTransferService: DataTransferService = { |
| DataTransferService(withDataTransferAdapter: DataTransferAdapter(), |
| dbManager: self.dBManager) |
| }() |
| private lazy var conversationsService: ConversationsService = { |
| ConversationsService(withConversationsAdapter: ConversationsAdapter(), dbManager: self.dBManager) |
| }() |
| private lazy var locationSharingService: LocationSharingService = { |
| LocationSharingService(dbManager: self.dBManager) |
| }() |
| private lazy var requestsService: RequestsService = { |
| RequestsService(withRequestsAdapter: RequestsAdapter(), dbManager: self.dBManager) |
| }() |
| |
| private let voipRegistry = PKPushRegistry(queue: DispatchQueue.main) |
| /* |
| When the app is in the background, but the call screen is present, notifications |
| should be handled by Jami.app and not by the notification extension. |
| */ |
| private var presentingCallScreen = false |
| |
| lazy var injectionBag: InjectionBag = { |
| return InjectionBag(withDaemonService: self.daemonService, |
| withAccountService: self.accountService, |
| withNameService: self.nameService, |
| withConversationService: self.conversationsService, |
| withContactsService: self.contactsService, |
| withPresenceService: self.presenceService, |
| withNetworkService: self.networkService, |
| withCallService: self.callService, |
| withVideoService: self.videoService, |
| withAudioService: self.audioService, |
| withDataTransferService: self.dataTransferService, |
| withProfileService: self.profileService, |
| withCallsProvider: self.callsProvider, |
| withLocationSharingService: self.locationSharingService, |
| withRequestsService: self.requestsService, |
| withSystemService: self.systemService) |
| }() |
| private lazy var appCoordinator: AppCoordinator = { |
| return AppCoordinator(with: self.injectionBag) |
| }() |
| |
| private let log = SwiftyBeaver.self |
| |
| private let disposeBag = DisposeBag() |
| |
| private let center = CFNotificationCenterGetDarwinNotifyCenter() |
| private static let shouldHandleNotification = NSNotification.Name("com.savoirfairelinux.jami.shouldHandleNotification") |
| private let backgrounTaskQueue = DispatchQueue(label: "backgrounTaskQueue") |
| |
| func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { |
| |
| // ignore sigpipe |
| typealias SigHandler = @convention(c) (Int32) -> Void |
| let SIG_IGN = unsafeBitCast(OpaquePointer(bitPattern: 1), to: SigHandler.self) |
| signal(SIGPIPE, SIG_IGN) |
| // swiftlint:enable nesting |
| |
| self.window = UIWindow(frame: UIScreen.main.bounds) |
| |
| UserDefaults.standard.setValue(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable") |
| if UserDefaults.standard.value(forKey: automaticDownloadFilesKey) == nil { |
| UserDefaults.standard.set(true, forKey: automaticDownloadFilesKey) |
| } |
| UNUserNotificationCenter.current().delegate = self |
| // initialize log format |
| let console = ConsoleDestination() |
| console.format = "$Dyyyy-MM-dd HH:mm:ss.SSS$d $C$L$c: $M" |
| #if DEBUG |
| log.addDestination(console) |
| #else |
| log.removeAllDestinations() |
| #endif |
| |
| // move files from the app container to the group container, so it could be accessed by notification extension |
| if !self.moveDataToGroupContainer() { |
| self.window?.rootViewController = self.appCoordinator.rootViewController |
| self.window?.makeKeyAndVisible() |
| let alertController = UIAlertController(title: "There was an error starting Jami", message: "Please try again", preferredStyle: .alert) |
| let okAction = UIAlertAction(title: "Ok", style: UIAlertAction.Style.default) |
| alertController.addAction(okAction) |
| self.window?.rootViewController?.present(alertController, animated: true, completion: nil) |
| return true |
| } |
| PreferenceManager.registerDonationsDefaults() |
| |
| self.addListenerForNotification() |
| |
| // starts the daemon |
| self.startDaemon() |
| |
| // requests permission to use the camera |
| // will enumerate and add devices once permission has been granted |
| self.videoService.setupInputs() |
| |
| self.audioService.connectAudioSignal() |
| |
| // Observe connectivity changes and reconnect DHT |
| self.networkService.connectionStateObservable |
| .skip(1) |
| .subscribe(onNext: { _ in |
| self.daemonService.connectivityChanged() |
| }) |
| .disposed(by: self.disposeBag) |
| |
| // start monitoring for network changes |
| self.networkService.monitorNetworkType() |
| |
| self.interactionsManager = GeneratedInteractionsManager(accountService: self.accountService, |
| requestsService: self.requestsService, |
| conversationService: self.conversationsService, |
| callService: self.callService) |
| |
| // load accounts during splashscreen |
| // and ask the AppCoordinator to handle the first screen once loading is finished |
| self.conversationManager = ConversationsManager(with: self.conversationsService, |
| accountsService: self.accountService, |
| nameService: self.nameService, |
| dataTransferService: self.dataTransferService, |
| callService: self.callService, |
| locationSharingService: self.locationSharingService, contactsService: self.contactsService, |
| callsProvider: self.callsProvider, requestsService: self.requestsService) |
| self.videoManager = VideoManager(with: self.callService, videoService: self.videoService) |
| self.window?.rootViewController = self.appCoordinator.rootViewController |
| self.window?.makeKeyAndVisible() |
| |
| prepareVideoAcceleration() |
| prepareAccounts() |
| self.voipRegistry.delegate = self |
| NotificationCenter.default.addObserver(self, selector: #selector(registerNotifications), |
| name: NSNotification.Name(rawValue: NotificationName.enablePushNotifications.rawValue), |
| object: nil) |
| self.clearBadgeNumber() |
| if let path = self.certificatePath() { |
| setenv("CA_ROOT_FILE", path, 1) |
| } |
| self.window?.backgroundColor = UIColor.systemBackground |
| return true |
| } |
| |
| func moveDataToGroupContainer() -> Bool { |
| let usingGroupConatinerKey = "usingGroupConatiner" |
| if UserDefaults.standard.bool(forKey: usingGroupConatinerKey) { |
| return true |
| } |
| guard let groupDocUrl = Constants.documentsPath, |
| let groupCachesUrl = Constants.cachesPath, |
| let appDocURL = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false), |
| let appLibrURL = try? FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false) else { |
| return false |
| } |
| if FileManager.default.fileExists(atPath: groupDocUrl.path) { |
| try? FileManager.default.removeItem(atPath: groupDocUrl.path) |
| } |
| if FileManager.default.fileExists(atPath: groupCachesUrl.path) { |
| try? FileManager.default.removeItem(atPath: groupCachesUrl.path) |
| } |
| let appCacheDir = appLibrURL.appendingPathComponent("Caches") |
| do { |
| try FileManager.default.copyItem(at: appDocURL, to: groupDocUrl) |
| try FileManager.default.copyItem(at: appCacheDir, to: groupCachesUrl) |
| } catch { |
| print(error.localizedDescription) |
| try? FileManager.default.removeItem(atPath: groupDocUrl.path) |
| try? FileManager.default.removeItem(atPath: groupCachesUrl.path) |
| return false |
| } |
| if let fileURLs = try? FileManager.default.contentsOfDirectory(at: appDocURL, |
| includingPropertiesForKeys: nil, |
| options: .skipsHiddenFiles) { |
| for fileURL in fileURLs { |
| try? FileManager.default.removeItem(at: fileURL) |
| } |
| } |
| UserDefaults.standard.setValue(true, forKey: usingGroupConatinerKey) |
| return UserDefaults.standard.bool(forKey: usingGroupConatinerKey) |
| } |
| |
| func addListenerForNotification() { |
| NotificationCenter.default.addObserver(self, selector: #selector(handleNotification), |
| name: AppDelegate.shouldHandleNotification, |
| object: nil) |
| CFNotificationCenterAddObserver(self.center, |
| nil, { (_, _, _, _, _) in |
| // emit signal so notification could be handeled by daemon |
| NotificationCenter.default.post(name: AppDelegate.shouldHandleNotification, object: nil, userInfo: nil) |
| }, |
| Constants.notificationReceived, |
| nil, |
| .deliverImmediately) |
| } |
| |
| func certificatePath() -> String? { |
| let fileName = "cacert" |
| let filExtension = "pem" |
| guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil } |
| let certPath = documentsURL.appendingPathComponent(fileName).appendingPathExtension(filExtension) |
| let fileManager = FileManager.default |
| if fileManager.fileExists(atPath: certPath.path) { |
| return certPath.path |
| } |
| guard let certSource = Bundle.main.url(forResource: fileName, withExtension: filExtension) else { |
| return nil |
| } |
| do { |
| try fileManager.copyItem(at: certSource, to: certPath) |
| return certPath.path |
| } catch { |
| return nil |
| } |
| } |
| |
| func prepareAccounts() { |
| self.accountService |
| .needMigrateCurrentAccount |
| .subscribe(onNext: { account in |
| DispatchQueue.main.async { |
| self.appCoordinator.migrateAccount(accountId: account) |
| } |
| }) |
| .disposed(by: self.disposeBag) |
| self.accountService.initialAccountsLoading() |
| .subscribe(onCompleted: { |
| // set selected account if exists |
| self.appCoordinator.start() |
| if !self.accountService.hasAccounts() { |
| // Set default download transfer limit to 20MB. |
| let userDefaults = UserDefaults.standard |
| if userDefaults.object(forKey: acceptTransferLimitKey) == nil { |
| userDefaults.set(20, forKey: acceptTransferLimitKey) |
| } |
| if userDefaults.object(forKey: hardareAccelerationKey) == nil { |
| self.videoService.setHardwareAccelerated(withState: true) |
| UserDefaults.standard.set(true, forKey: hardareAccelerationKey) |
| } |
| if userDefaults.object(forKey: limitLocationSharingDurationKey) == nil { |
| UserDefaults.standard.set(true, forKey: limitLocationSharingDurationKey) |
| } |
| if userDefaults.object(forKey: locationSharingDurationKey) == nil { |
| UserDefaults.standard.set(15, forKey: locationSharingDurationKey) |
| } |
| return |
| } |
| if self.accountService.hasAccountWithProxyEnabled() { |
| self.registerNotifications() |
| } else { |
| self.unregisterNotifications() |
| } |
| if let selectedAccountId = UserDefaults.standard.string(forKey: self.accountService.selectedAccountID), |
| let account = self.accountService.getAccount(fromAccountId: selectedAccountId) { |
| self.accountService.currentAccount = account |
| } |
| guard let currentAccount = self.accountService.currentAccount else { |
| self.log.error("Can't get current account!") |
| return |
| } |
| DispatchQueue.global(qos: .background).async {[weak self] in |
| guard let self = self else { return } |
| self.reloadDataFor(account: currentAccount) |
| } |
| }, onError: { _ in |
| self.appCoordinator.showInitialLoading() |
| let time = DispatchTime.now() + 1 |
| DispatchQueue.main.asyncAfter(deadline: time) { |
| self.appCoordinator.showDatabaseError() |
| } |
| }) |
| .disposed(by: self.disposeBag) |
| |
| self.accountService.currentWillChange |
| .subscribe(onNext: { account in |
| guard let currentAccount = account else { return } |
| self.conversationsService.clearConversationsData(accountId: currentAccount.id) |
| self.presenceService.subscribeBuddies(withAccount: currentAccount.id, withContacts: self.contactsService.contacts.value, subscribe: false) |
| }) |
| .disposed(by: self.disposeBag) |
| |
| self.accountService.currentAccountChanged |
| .subscribe(onNext: { account in |
| guard let currentAccount = account else { return } |
| self.reloadDataFor(account: currentAccount) |
| }) |
| .disposed(by: self.disposeBag) |
| } |
| |
| func updateCallScreenState(presenting: Bool) { |
| self.presentingCallScreen = presenting |
| } |
| |
| func reloadDataFor(account: AccountModel) { |
| self.requestsService.loadRequests(withAccount: account.id, accountURI: account.jamiId) |
| self.conversationManager? |
| .prepareConversationsForAccount(accountId: account.id, accountURI: account.jamiId) |
| self.contactsService.loadContacts(withAccount: account) |
| self.presenceService.subscribeBuddies(withAccount: account.id, withContacts: self.contactsService.contacts.value, subscribe: true) |
| } |
| |
| func applicationDidEnterBackground(_ application: UIApplication) { |
| self.log.warning("entering background") |
| guard let account = self.accountService.currentAccount else { return } |
| self.presenceService.subscribeBuddies(withAccount: account.id, withContacts: self.contactsService.contacts.value, subscribe: false) |
| } |
| |
| func applicationWillEnterForeground(_ application: UIApplication) { |
| self.log.warning("entering foreground") |
| self.updateNotificationAvailability() |
| guard let account = self.accountService.currentAccount else { return } |
| self.presenceService.subscribeBuddies(withAccount: account.id, withContacts: self.contactsService.contacts.value, subscribe: true) |
| } |
| |
| func applicationWillTerminate(_ application: UIApplication) { |
| self.callsProvider.stopAllUnhandeledCalls() |
| self.stopDaemon() |
| } |
| |
| func applicationDidBecomeActive(_ application: UIApplication) { |
| self.clearBadgeNumber() |
| guard let account = self.accountService.currentAccount else { return } |
| self.presenceService.subscribeBuddies(withAccount: account.id, withContacts: self.contactsService.contacts.value, subscribe: true) |
| } |
| |
| func applicationWillResignActive(_ application: UIApplication) { |
| guard let account = self.accountService.currentAccount else { return } |
| self.presenceService.subscribeBuddies(withAccount: account.id, withContacts: self.contactsService.contacts.value, subscribe: false) |
| } |
| |
| func prepareVideoAcceleration() { |
| // we want enable hardware acceleration by default so if key does not exists, |
| // means it was not disabled by user |
| let keyExists = UserDefaults.standard.object(forKey: hardareAccelerationKey) != nil |
| let enable = keyExists ? UserDefaults.standard.bool(forKey: hardareAccelerationKey) : true |
| self.videoService.setHardwareAccelerated(withState: enable) |
| } |
| |
| // MARK: - Ring Daemon |
| private func startDaemon() { |
| do { |
| try self.daemonService.startDaemon() |
| } catch StartDaemonError.initializationFailure { |
| log.error("Daemon failed to initialize.") |
| } catch StartDaemonError.startFailure { |
| log.error("Daemon failed to start.") |
| } catch StartDaemonError.daemonAlreadyRunning { |
| log.error("Daemon already running.") |
| } catch { |
| log.error("Unknown error in Daemon start.") |
| } |
| } |
| |
| private func stopDaemon() { |
| do { |
| try self.daemonService.stopDaemon() |
| } catch StopDaemonError.daemonNotRunning { |
| log.error("Daemon failed to stop because it was not already running.") |
| } catch { |
| log.error("Unknown error in Daemon stop.") |
| } |
| } |
| |
| func updateNotificationAvailability() { |
| let enabled = LocalNotificationsHelper.isEnabled() |
| let currentSettings = UNUserNotificationCenter.current() |
| currentSettings.getNotificationSettings(completionHandler: { settings in |
| switch settings.authorizationStatus { |
| case .notDetermined: |
| break |
| case .denied: |
| if enabled { LocalNotificationsHelper.setNotification(enable: false) } |
| case .authorized: |
| if !enabled { LocalNotificationsHelper.setNotification(enable: true) } |
| case .provisional: |
| if !enabled { LocalNotificationsHelper.setNotification(enable: true) } |
| case .ephemeral: |
| if enabled { LocalNotificationsHelper.setNotification(enable: false) } |
| @unknown default: |
| break |
| } |
| }) |
| } |
| |
| @objc |
| private func handleNotification() { |
| DispatchQueue.main.async {[weak self] in |
| guard let self = self else { return } |
| // If the app is running in the background and there are no waiting calls, the extension should handle the notification. |
| if UIApplication.shared.applicationState == .background && !self.presentingCallScreen && !self.callsProvider.hasActiveCalls() { |
| return |
| } |
| // emit signal that app is active for notification extension |
| CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFNotificationName(Constants.notificationAppIsActive), nil, nil, true) |
| |
| guard let userDefaults = UserDefaults(suiteName: Constants.appGroupIdentifier), |
| let notificationData = userDefaults.object(forKey: Constants.notificationData) as? [[String: String]] else { |
| return |
| } |
| userDefaults.set([[String: String]](), forKey: Constants.notificationData) |
| for data in notificationData { |
| self.accountService.pushNotificationReceived(data: data) |
| } |
| } |
| } |
| |
| @objc |
| private func registerNotifications() { |
| self.requestNotificationAuthorization() |
| DispatchQueue.main.async { |
| UIApplication.shared.registerForRemoteNotifications() |
| } |
| self.voipRegistry.desiredPushTypes = Set([PKPushType.voIP]) |
| } |
| |
| private func unregisterNotifications() { |
| DispatchQueue.main.async { |
| UIApplication.shared.unregisterForRemoteNotifications() |
| } |
| self.voipRegistry.desiredPushTypes = nil |
| self.accountService.setPushNotificationToken(token: "") |
| } |
| |
| private func requestNotificationAuthorization() { |
| let application = UIApplication.shared |
| DispatchQueue.main.async { |
| UNUserNotificationCenter.current().delegate = application.delegate as? UNUserNotificationCenterDelegate |
| let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] |
| UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler: { (enable, _) in |
| if enable { |
| LocalNotificationsHelper.setNotification(enable: true) |
| } else { |
| LocalNotificationsHelper.setNotification(enable: false) |
| } |
| }) |
| } |
| } |
| |
| private func clearBadgeNumber() { |
| if let userDefaults = UserDefaults(suiteName: Constants.appGroupIdentifier) { |
| userDefaults.set(0, forKey: Constants.notificationsCount) |
| } |
| |
| UIApplication.shared.applicationIconBadgeNumber = 0 |
| let center = UNUserNotificationCenter.current() |
| center.removeAllDeliveredNotifications() |
| center.removeAllPendingNotificationRequests() |
| } |
| |
| } |
| |
| // MARK: notification actions |
| extension AppDelegate { |
| |
| func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { |
| let data = response.notification.request.content.userInfo |
| self.handleNotificationActions(data: data) |
| completionHandler() |
| } |
| |
| func handleNotificationActions(data: [AnyHashable: Any]) { |
| guard let accountId = data[Constants.NotificationUserInfoKeys.accountID.rawValue] as? String, |
| let account = self.accountService.getAccount(fromAccountId: accountId) else { return } |
| self.accountService.updateCurrentAccount(account: account) |
| if let conversationId = data[Constants.NotificationUserInfoKeys.conversationID.rawValue] as? String { |
| self.conversationsService.updateConversationMessages(conversationId: conversationId) |
| self.appCoordinator.openConversation(conversationId: conversationId, accountId: accountId) |
| } else if let participantID = data[Constants.NotificationUserInfoKeys.participantID.rawValue] as? String { |
| self.appCoordinator.openConversation(participantID: participantID) |
| } |
| } |
| |
| func findContactAndStartCall(hash: String, isVideo: Bool) { |
| // if saved jami hash |
| if hash.isSHA1() { |
| let contactUri = JamiURI(schema: URIType.ring, infoHash: hash) |
| self.findAccountAndStartCall(uri: contactUri, isVideo: isVideo, type: AccountType.ring) |
| return |
| } |
| // if saved jami registered name |
| self.nameService.usernameLookupStatus |
| .observe(on: MainScheduler.instance) |
| .filter({ usernameLookupStatus in |
| usernameLookupStatus.name == hash |
| }) |
| .take(1) |
| .subscribe(onNext: { usernameLookupStatus in |
| if usernameLookupStatus.state == .found { |
| guard let address = usernameLookupStatus.address else { return } |
| let contactUri = JamiURI(schema: URIType.ring, infoHash: address) |
| self.findAccountAndStartCall(uri: contactUri, isVideo: isVideo, type: AccountType.ring) |
| } else { |
| // if saved sip contact |
| let contactUri = JamiURI(schema: URIType.sip, infoHash: hash) |
| self.findAccountAndStartCall(uri: contactUri, isVideo: isVideo, type: AccountType.sip) |
| } |
| }) |
| .disposed(by: self.disposeBag) |
| self.nameService.lookupName(withAccount: "", nameserver: "", name: hash) |
| } |
| |
| func findAccountAndStartCall(uri: JamiURI, isVideo: Bool, type: AccountType) { |
| guard let currentAccount = self.accountService |
| .currentAccount else { return } |
| var hash = uri.hash ?? "" |
| var uriString = uri.uriString ?? "" |
| for account in self.accountService.accounts where account.type == type { |
| if type == AccountType.sip { |
| let conatactUri = JamiURI(schema: URIType.sip, |
| infoHash: hash, |
| account: account) |
| hash = conatactUri.hash ?? "" |
| uriString = conatactUri.uriString ?? "" |
| } |
| if hash.isEmpty || uriString.isEmpty { return } |
| self.contactsService |
| .getProfileForUri(uri: uriString, |
| accountId: account.id) |
| .subscribe(onNext: { (profile) in |
| if currentAccount != account { |
| self.accountService.currentAccount = account |
| } |
| self.appCoordinator |
| .startCall(participant: hash, |
| name: profile.alias ?? "", |
| isVideo: isVideo) |
| }) |
| .disposed(by: self.disposeBag) |
| } |
| } |
| |
| func application(_ application: UIApplication, |
| continue userActivity: NSUserActivity, |
| restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { |
| if self.accountService.boothMode() { |
| return false |
| } |
| /* |
| This method could be called when activating camera from CallKit. |
| In this case we will have existing call with CallKit. |
| Othervise it was called from Contacts app. |
| We need find contact and start a call |
| */ |
| if self.callsProvider.hasActiveCalls() { return false } |
| guard let handle = userActivity.startCallHandle else { |
| return false |
| } |
| self.findContactAndStartCall(hash: handle.hash, isVideo: handle.isVideo) |
| return true |
| } |
| } |
| |
| // MARK: user notifications |
| extension AppDelegate { |
| func application(_ application: UIApplication, |
| didRegisterForRemoteNotificationsWithDeviceToken |
| deviceToken: Data) { |
| let deviceTokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() |
| print(deviceTokenString) |
| if let bundleIdentifier = Bundle.main.bundleIdentifier { |
| self.accountService.setPushNotificationTopic(topic: bundleIdentifier) |
| } |
| self.accountService.setPushNotificationToken(token: deviceTokenString) |
| } |
| |
| func application( |
| _ application: UIApplication, |
| didReceiveRemoteNotification userInfo: [AnyHashable: Any], |
| fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void |
| ) { |
| var dictionary = [String: String]() |
| for key in userInfo.keys { |
| if let value = userInfo[key] { |
| let keyString = String(describing: key) |
| let valueString = String(describing: value) |
| dictionary[keyString] = valueString |
| } |
| } |
| self.accountService.pushNotificationReceived(data: dictionary) |
| completionHandler(.newData) |
| } |
| } |
| |
| // MARK: PKPushRegistryDelegate |
| extension AppDelegate: PKPushRegistryDelegate { |
| func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { |
| } |
| |
| func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { |
| self.updateCallScreenState(presenting: true) |
| let peerId: String = payload.dictionaryPayload["peerId"] as? String ?? "" |
| let hasVideo = payload.dictionaryPayload["hasVideo"] as? String ?? "true" |
| let displayName = payload.dictionaryPayload["displayName"] as? String ?? "" |
| callsProvider.previewPendingCall(peerId: peerId, withVideo: hasVideo.boolValue, displayName: displayName) { error in |
| if error != nil { |
| self.updateCallScreenState(presenting: false) |
| } |
| completion() |
| } |
| } |
| } |