AppDelegate.swift 15.5 KB
Newer Older
Guillaume Roguez's avatar
Guillaume Roguez committed
1
/*
2
 *  Copyright (C) 2017 Savoir-faire Linux Inc.
Guillaume Roguez's avatar
Guillaume Roguez committed
3 4
 *
 *  Author: Edric Ladent-Milaret <edric.ladent-milaret@savoirfairelinux.com>
5
 *  Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
6
 *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
Guillaume Roguez's avatar
Guillaume Roguez committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 *  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
24
import SwiftyBeaver
25
import RxSwift
26
import Chameleon
27
import Contacts
28
import PushKit
Guillaume Roguez's avatar
Guillaume Roguez committed
29 30

@UIApplicationMain
31
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
Guillaume Roguez's avatar
Guillaume Roguez committed
32
    var window: UIWindow?
33 34 35 36 37
    private let daemonService = DaemonService(dRingAdaptor: DRingAdapter())
    private let accountService = AccountsService(withAccountAdapter: AccountAdapter())
    private let nameService = NameService(withNameRegistrationAdapter: NameRegistrationAdapter())
    private let conversationsService = ConversationsService(withMessageAdapter: MessagesAdapter())
    private let contactsService = ContactsService(withContactsAdapter: ContactsAdapter())
38
    private let presenceService = PresenceService(withPresenceAdapter: PresenceAdapter())
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
39
    private let callService = CallsService(withCallsAdapter: CallsAdapter())
40
    private let videoService = VideoService(withVideoAdapter: VideoAdapter())
41
    private let audioService = AudioService(withAudioAdapter: AudioAdapter())
42
    private let dataTransferService = DataTransferService(withDataTransferAdapter: DataTransferAdapter())
43
    private let networkService = NetworkService()
44
    private let profileService = ProfilesService()
45
    private var conversationManager: ConversationsManager?
46
    private var interactionsManager: GeneratedInteractionsManager?
47

48 49
    private let voipRegistry = PKPushRegistry(queue: DispatchQueue.main)

50 51 52 53 54
    public lazy var injectionBag: InjectionBag = {
        return InjectionBag(withDaemonService: self.daemonService,
                            withAccountService: self.accountService,
                            withNameService: self.nameService,
                            withConversationService: self.conversationsService,
55
                            withContactsService: self.contactsService,
56
                            withPresenceService: self.presenceService,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
57
                            withNetworkService: self.networkService,
58
                            withCallService: self.callService,
59
                            withVideoService: self.videoService,
60
                            withAudioService: self.audioService,
61
                            withDataTransferService: self.dataTransferService,
62
                            withProfileService: self.profileService)
63 64 65 66
    }()
    private lazy var appCoordinator: AppCoordinator = {
        return AppCoordinator(with: self.injectionBag)
    }()
67

68
    private let log = SwiftyBeaver.self
Guillaume Roguez's avatar
Guillaume Roguez committed
69

70 71
    fileprivate let disposeBag = DisposeBag()

72
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
73

74 75 76 77 78
        // ignore sigpipe
        typealias SigHandler = @convention(c) (Int32) -> Void
        let SIG_IGN = unsafeBitCast(OpaquePointer(bitPattern: 1), to: SigHandler.self)
        signal(SIGPIPE, SIG_IGN)

79 80
        self.window = UIWindow(frame: UIScreen.main.bounds)

81
        UserDefaults.standard.setValue(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
82 83 84
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = self
        }
85 86 87 88 89
        // initialize log format
        let console = ConsoleDestination()
        console.format = "$Dyyyy-MM-dd HH:mm:ss.SSS$d $C$L$c: $M"
        log.addDestination(console)

90
        // starts the daemon
91
        SystemAdapter().registerConfigurationHandler()
92
        self.startDaemon()
93

94 95 96
        // sets output device to whatever is currently available (either spk / headset)
        self.audioService.startAVAudioSession()

97 98 99 100 101 102 103 104
        // disables hardware decoding
        self.videoService.setDecodingAccelerated(withState: false)

        // requests permission to use the camera
        // will enumerate and add devices once permission has been granted
        self.videoService.setupInputs()

        // start monitoring for network changes
105 106
        self.networkService.monitorNetworkType()

107 108 109 110 111 112 113
        // Observe connectivity changes and reconnect DHT
        self.networkService.connectionStateObservable
            .subscribe(onNext: { _ in
                self.daemonService.connectivityChanged()
            })
            .disposed(by: self.disposeBag)

114
        // themetize the app
115 116
        Chameleon.setGlobalThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
        Chameleon.setRingThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
117 118 119

        UIApplication.shared.statusBarStyle = .default

120 121 122 123
        self.interactionsManager = GeneratedInteractionsManager(accountService: self.accountService,
                                                                contactService: self.contactsService,
                                                                conversationService: self.conversationsService,
                                                                callService: self.callService)
124

125 126
        // load accounts during splashscreen
        // and ask the AppCoordinator to handle the first screen once loading is finished
127 128 129 130
        self.conversationManager = ConversationsManager(with: self.conversationsService,
                                                        accountsService: self.accountService,
                                                        nameService: self.nameService,
                                                        dataTransferService: self.dataTransferService)
131
        self.startDB()
132
        self.accountService.loadAccounts().subscribe { [unowned self] (_) in
133 134 135 136 137 138 139 140 141 142
            guard let currentAccount = self.accountService.currentAccount else {
                self.log.error("Can't get current account!")
                return
            }
            self.contactsService.loadContacts(withAccount: currentAccount)
            self.contactsService.loadContactRequests(withAccount: currentAccount)
            self.presenceService.subscribeBuddies(withAccount: currentAccount, withContacts: self.contactsService.contacts.value)
            if let ringID = AccountModelHelper(withAccount: currentAccount).ringId {
                self.conversationManager?
                    .prepareConversationsForAccount(accountId: currentAccount.id, accountUri: ringID)
143
            }
144 145 146 147
            // make sure video is enabled
            let accountDetails = self.accountService.getAccountDetails(fromAccountId: currentAccount.id)
            accountDetails.set(withConfigKeyModel: ConfigKeyModel(withKey: ConfigKey.videoEnabled), withValue: "true")
            self.accountService.setAccountDetails(forAccountId: currentAccount.id, withDetails: accountDetails)
148 149 150
            if self.accountService.getCurrentProxyState(accountID: currentAccount.id) {
                self.registerVoipNotifications()
            }
151
        }.disposed(by: self.disposeBag)
152

153 154 155
        self.window?.rootViewController = self.appCoordinator.rootViewController
        self.window?.makeKeyAndVisible()
        self.appCoordinator.start()
156 157 158 159 160 161 162
        self.voipRegistry.delegate = self
        NotificationCenter.default.addObserver(self, selector: #selector(registerVoipNotifications),
                                               name: NSNotification.Name(rawValue: NotificationName.enablePushNotifications.rawValue),
                                               object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(unregisterVoipNotifications),
                                               name: NSNotification.Name(rawValue: NotificationName.disablePushNotifications.rawValue),
                                               object: nil)
163
        self.clearBadgeNumber()
Guillaume Roguez's avatar
Guillaume Roguez committed
164 165
        return true
    }
Edric Milaret's avatar
Edric Milaret committed
166

167 168 169 170 171 172 173 174 175
    func applicationDidEnterBackground(_ application: UIApplication) {
        self.log.warning("entering background")
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        self.log.warning("entering foreground")
        self.daemonService.connectivityChanged()
    }

176
    func applicationWillTerminate(_ application: UIApplication) {
177
        self.stopDaemon()
Guillaume Roguez's avatar
Guillaume Roguez committed
178 179
    }

180 181 182 183
    func applicationDidBecomeActive(_ application: UIApplication) {
        self.clearBadgeNumber()
    }

184 185
    // MARK: - Ring Daemon
    fileprivate func startDaemon() {
186

187
        do {
188
            try self.daemonService.startDaemon()
189
        } catch StartDaemonError.initializationFailure {
190
            log.error("Daemon failed to initialize.")
191
        } catch StartDaemonError.startFailure {
192
            log.error("Daemon failed to start.")
193
        } catch StartDaemonError.daemonAlreadyRunning {
194
            log.error("Daemon already running.")
195
        } catch {
196
            log.error("Unknown error in Daemon start.")
197 198 199 200 201
        }
    }

    fileprivate func stopDaemon() {
        do {
202
            try self.daemonService.stopDaemon()
203
        } catch StopDaemonError.daemonNotRunning {
204
            log.error("Daemon failed to stop because it was not already running.")
205
        } catch {
206
            log.error("Unknown error in Daemon stop.")
207 208
        }
    }
209 210 211

    private func startDB() {
        do {
212
            let dbManager = DBManager(profileHepler: ProfileDataHelper(),
213 214 215 216 217 218 219 220 221 222
                                       conversationHelper: ConversationDataHelper(),
                                       interactionHepler: InteractionDataHelper())
            try dbManager.start()
        } catch {
            let time = DispatchTime.now() + 1
            DispatchQueue.main.asyncAfter(deadline: time) {
                self.appCoordinator.showDatabaseError()
            }
        }
    }
223 224 225 226 227 228 229 230 231 232 233

    @objc private func registerVoipNotifications() {
        self.requestNotificationAuthorization()
        self.voipRegistry.desiredPushTypes = Set([PKPushType.voIP])
    }

    @objc private func unregisterVoipNotifications() {
       self.voipRegistry.desiredPushTypes = nil
       self.accountService.setPushNotificationToken(token: "")
    }

234
    private func requestNotificationAuthorization() {
235 236 237 238 239 240 241 242 243 244
        let application = UIApplication.shared
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = application.delegate as? UNUserNotificationCenterDelegate
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler: {_, _ in })
        } else {
            let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }
    }
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288

    private func clearBadgeNumber() {
        UIApplication.shared.applicationIconBadgeNumber = 0
        if #available(iOS 10.0, *) {
            let center = UNUserNotificationCenter.current()
            center.removeAllDeliveredNotifications()
            center.removeAllPendingNotificationRequests()
        } else {
            UIApplication.shared.cancelAllLocalNotifications()
        }
    }

    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let data = response.notification.request.content.userInfo
        self.handleNotificationActions(data: data, responseIdentifier: response.actionIdentifier)
        completionHandler()
    }

    func handleNotificationActions(data: [AnyHashable: Any], responseIdentifier: String) {
        guard let callID = data[NotificationUserInfoKeys.callID.rawValue] as? String else {
            return
        }
        switch responseIdentifier {
        case CallAcition.accept.rawValue:
            NotificationCenter.default.post(name: NSNotification.Name(NotificationName.answerCallFromNotifications.rawValue),
                                            object: nil,
                                            userInfo: data)
        case CallAcition.refuse.rawValue:
            self.callService.refuse(callId: callID)
                .subscribe({_ in
                    print("Call ignored")
                }).disposed(by: self.disposeBag)
        default:
            print("Other Action")
        }
    }

    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
        if let identifier = identifier, let data = notification.userInfo {
            self.handleNotificationActions(data: data, responseIdentifier: identifier)
        }
        completionHandler()
    }
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311

    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController) {
            if (rootViewController.responds(to: Selector(("canRotate")))) {
                return .all
            }
        }
        return .portrait
    }

    private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
        if rootViewController == nil {
            return nil
        }
        if rootViewController.isKind(of: (UITabBarController).self) {
            return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
        } else if rootViewController.isKind(of: (UINavigationController).self) {
            return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
        } else if rootViewController.presentedViewController != nil {
            return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
        }
        return rootViewController
    }
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
}

extension AppDelegate: PKPushRegistryDelegate {

    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
        self.accountService.setPushNotificationToken(token: "")
    }

    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
        self.accountService.pushNotificationReceived(data: payload.dictionaryPayload)
    }

    func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        if type == PKPushType.voIP {
            let deviceTokenString = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
            self.accountService.updatePushTokenForCurrentAccount(token: deviceTokenString)
            self.accountService.setPushNotificationToken(token: deviceTokenString)
        }
    }
Guillaume Roguez's avatar
Guillaume Roguez committed
331
}