AppDelegate.swift 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. import AudioToolbox
  2. import Reachability
  3. import UIKit
  4. import UserNotifications
  5. import DcCore
  6. import SDWebImageWebPCoder
  7. import Intents
  8. import SDWebImageSVGKitPlugin
  9. let logger = SimpleLogger()
  10. @UIApplicationMain
  11. class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
  12. private let dcAccounts = DcAccounts()
  13. var appCoordinator: AppCoordinator!
  14. var relayHelper: RelayHelper!
  15. var locationManager: LocationManager!
  16. var notificationManager: NotificationManager!
  17. private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
  18. var reachability: Reachability?
  19. var window: UIWindow?
  20. var notifyToken: String?
  21. var applicationInForeground: Bool = false
  22. private var launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  23. private var appFullyInitialized = false
  24. // purpose of `bgIoTimestamp` is to block rapidly subsequent calls to remote- or local-wakeups:
  25. //
  26. // `bgIoTimestamp` is set to enter-background or last remote- or local-wakeup;
  27. // in the minute after these events, subsequent remote- or local-wakeups are skipped
  28. // in favor to the chance of being awakened when it makes more sense
  29. // and to avoid issues with calling concurrent series of startIo/maybeNetwork/stopIo.
  30. private var bgIoTimestamp: Double = 0.0
  31. // MARK: - app main entry point
  32. // `didFinishLaunchingWithOptions` is the main entry point
  33. // that is called if the app is started for the first time
  34. // or after the app is killed.
  35. //
  36. // - `didFinishLaunchingWithOptions` is also called before event methods as `didReceiveRemoteNotification` are called -
  37. // either _directly before_ (if the app was killed) or _long before_ (if the app was suspended).
  38. //
  39. // - in some cases `didFinishLaunchingWithOptions` is called _instead_ an event method and `launchOptions` tells the reason;
  40. // the event method may or may not be called in this case, see #1542 for some deeper information.
  41. //
  42. // `didFinishLaunchingWithOptions` is _not_ called
  43. // when the app wakes up from "suspended" state
  44. // (app is in memory in the background but no code is executed, IO stopped)
  45. func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  46. // explicitly ignore SIGPIPE to avoid crashes, see https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/CommonPitfalls/CommonPitfalls.html
  47. // setupCrashReporting() may create an additional handler, but we do not want to rely on that
  48. signal(SIGPIPE, SIG_IGN)
  49. logger.info("➡️ didFinishLaunchingWithOptions")
  50. let webPCoder = SDImageWebPCoder.shared
  51. SDImageCodersManager.shared.addCoder(webPCoder)
  52. let svgCoder = SDImageSVGKCoder.shared
  53. SDImageCodersManager.shared.addCoder(svgCoder)
  54. dcAccounts.logger = SimpleLogger()
  55. dcAccounts.openDatabase()
  56. migrateToDcAccounts()
  57. self.launchOptions = launchOptions
  58. continueDidFinishLaunchingWithOptions()
  59. return true
  60. }
  61. // finishes the app initialization which depends on the successful access to the keychain
  62. func continueDidFinishLaunchingWithOptions() {
  63. if let sharedUserDefaults = UserDefaults.shared, !sharedUserDefaults.bool(forKey: UserDefaults.hasSavedKeyToKeychain) {
  64. // we can assume a fresh install (UserDefaults are deleted on app removal)
  65. // -> reset the keychain (which survives removals of the app) in case the app was removed and reinstalled.
  66. if !KeychainManager.deleteDBSecrets() {
  67. logger.warning("Failed to delete DB secrets")
  68. }
  69. }
  70. do {
  71. self.reachability = try Reachability()
  72. } catch {
  73. // TODO: Handle
  74. }
  75. let accountIds = dcAccounts.getAll()
  76. for accountId in accountIds {
  77. let dcContext = dcAccounts.get(id: accountId)
  78. if !dcContext.isOpen() {
  79. do {
  80. let secret = try KeychainManager.getAccountSecret(accountID: accountId)
  81. if !dcContext.open(passphrase: secret) {
  82. logger.error("Failed to open database for account \(accountId)")
  83. }
  84. } catch KeychainError.accessError(let message, let status) {
  85. logger.error("Keychain error. \(message). Error status: \(status)")
  86. return
  87. } catch KeychainError.unhandledError(let message, let status) {
  88. logger.error("Keychain error. \(message). Error status: \(status)")
  89. } catch {
  90. logger.error("\(error)")
  91. }
  92. }
  93. }
  94. if dcAccounts.getAll().isEmpty, dcAccounts.add() == 0 {
  95. fatalError("Could not initialize a new account.")
  96. }
  97. window = UIWindow(frame: UIScreen.main.bounds)
  98. guard let window = window else {
  99. fatalError("window was nil in app delegate")
  100. }
  101. if #available(iOS 13.0, *) {
  102. window.backgroundColor = UIColor.systemBackground
  103. } else {
  104. window.backgroundColor = UIColor.white
  105. }
  106. installEventHandler()
  107. relayHelper = RelayHelper.setup(dcAccounts.getSelected())
  108. appCoordinator = AppCoordinator(window: window, dcAccounts: dcAccounts)
  109. locationManager = LocationManager(dcAccounts: dcAccounts)
  110. UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
  111. notificationManager = NotificationManager(dcAccounts: dcAccounts)
  112. setStockTranslations()
  113. dcAccounts.startIo()
  114. if let reachability = reachability {
  115. reachability.whenReachable = { reachability in
  116. // maybeNetwork() shall not be called in ui thread;
  117. // Reachability::reachabilityChanged uses DispatchQueue.main.async only
  118. logger.info("network: reachable", reachability.connection.description)
  119. DispatchQueue.global(qos: .background).async { [weak self] in
  120. guard let self = self else { return }
  121. self.dcAccounts.maybeNetwork()
  122. if self.notifyToken == nil &&
  123. self.dcAccounts.getSelected().isConfigured() &&
  124. !UserDefaults.standard.bool(forKey: "notifications_disabled") {
  125. self.registerForNotifications()
  126. }
  127. }
  128. }
  129. reachability.whenUnreachable = { _ in
  130. logger.info("network: not reachable")
  131. DispatchQueue.global(qos: .background).async { [weak self] in
  132. self?.dcAccounts.maybeNetworkLost()
  133. }
  134. }
  135. do {
  136. try reachability.startNotifier()
  137. } catch {
  138. logger.error("Unable to start notifier")
  139. }
  140. }
  141. if let notificationOption = launchOptions?[.remoteNotification] {
  142. logger.info("Notifications: remoteNotification: \(String(describing: notificationOption))")
  143. increaseDebugCounter("notify-remote-launch")
  144. pushToDebugArray("📡'")
  145. performFetch(completionHandler: { (_) -> Void in })
  146. }
  147. if dcAccounts.getSelected().isConfigured() && !UserDefaults.standard.bool(forKey: "notifications_disabled") {
  148. registerForNotifications()
  149. }
  150. launchOptions = nil
  151. appFullyInitialized = true
  152. }
  153. // `open` is called when an url should be opened by Delta Chat.
  154. // we currently use that for handling oauth2 and for handing openpgp4fpr.
  155. //
  156. // before `open` gets called, `didFinishLaunchingWithOptions` is called.
  157. func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
  158. logger.info("➡️ open url")
  159. // gets here when app returns from oAuth2-Setup process - the url contains the provided token
  160. // if let params = url.queryParameters, let token = params["code"] {
  161. // NotificationCenter.default.post(name: NSNotification.Name("oauthLoginApproved"), object: nil, userInfo: ["token": token])
  162. // }
  163. switch url.scheme?.lowercased() {
  164. case "dcaccount", "dclogin":
  165. appCoordinator.handleQRCode(url.absoluteString)
  166. return true
  167. case "openpgp4fpr":
  168. // Hack to format url properly
  169. let urlString = url.absoluteString
  170. .replacingOccurrences(of: "openpgp4fpr", with: "OPENPGP4FPR", options: .literal, range: nil)
  171. .replacingOccurrences(of: "%23", with: "#", options: .literal, range: nil)
  172. appCoordinator.handleQRCode(urlString)
  173. return true
  174. case "mailto":
  175. return appCoordinator.handleMailtoURL(url)
  176. case "chat.delta.deeplink":
  177. return appCoordinator.handleDeepLinkURL(url)
  178. default:
  179. return false
  180. }
  181. }
  182. // MARK: - app lifecycle
  183. // applicationWillEnterForeground() is _not_ called on initial app start
  184. func applicationWillEnterForeground(_: UIApplication) {
  185. logger.info("➡️ applicationWillEnterForeground")
  186. applicationInForeground = true
  187. dcAccounts.startIo()
  188. DispatchQueue.global(qos: .background).async { [weak self] in
  189. guard let self = self else { return }
  190. if let reachability = self.reachability {
  191. if reachability.connection != .unavailable {
  192. self.dcAccounts.maybeNetwork()
  193. }
  194. }
  195. AppDelegate.emitMsgsChangedIfShareExtensionWasUsed()
  196. }
  197. }
  198. func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication) {
  199. logger.info("➡️ applicationProtectedDataDidBecomeAvailable")
  200. if !appFullyInitialized {
  201. continueDidFinishLaunchingWithOptions()
  202. }
  203. }
  204. func applicationProtectedDataWillBecomeUnavailable(_ application: UIApplication) {
  205. logger.info("⬅️ applicationProtectedDataWillBecomeUnavailable")
  206. }
  207. static func emitMsgsChangedIfShareExtensionWasUsed() {
  208. if let userDefaults = UserDefaults.shared, userDefaults.bool(forKey: UserDefaults.hasExtensionAttemptedToSend) {
  209. userDefaults.removeObject(forKey: UserDefaults.hasExtensionAttemptedToSend)
  210. DispatchQueue.main.async {
  211. NotificationCenter.default.post(
  212. name: dcNotificationChanged,
  213. object: nil,
  214. userInfo: [
  215. "message_id": Int(0),
  216. "chat_id": Int(0),
  217. ]
  218. )
  219. }
  220. }
  221. }
  222. // applicationDidBecomeActive() is called on initial app start _and_ after applicationWillEnterForeground()
  223. func applicationDidBecomeActive(_: UIApplication) {
  224. logger.info("➡️ applicationDidBecomeActive")
  225. applicationInForeground = true
  226. }
  227. func applicationWillResignActive(_: UIApplication) {
  228. logger.info("⬅️ applicationWillResignActive")
  229. registerBackgroundTask()
  230. }
  231. func applicationDidEnterBackground(_: UIApplication) {
  232. logger.info("⬅️ applicationDidEnterBackground")
  233. applicationInForeground = false
  234. }
  235. func applicationWillTerminate(_: UIApplication) {
  236. logger.info("⬅️ applicationWillTerminate")
  237. dcAccounts.closeDatabase()
  238. if let reachability = reachability {
  239. reachability.stopNotifier()
  240. }
  241. }
  242. // MARK: - fade out app smoothly
  243. // let the app run in background for a little while
  244. // eg. to complete sending messages out and to react to responses.
  245. private func registerBackgroundTask() {
  246. logger.info("⬅️ registering background task")
  247. bgIoTimestamp = Double(Date().timeIntervalSince1970)
  248. unregisterBackgroundTask()
  249. backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
  250. // usually, the background thread is finished before in maybeStop()
  251. logger.info("⬅️ background expirationHandler called")
  252. self?.unregisterBackgroundTask()
  253. }
  254. maybeStop()
  255. }
  256. private func unregisterBackgroundTask() {
  257. if backgroundTask != .invalid {
  258. UIApplication.shared.endBackgroundTask(backgroundTask)
  259. backgroundTask = .invalid
  260. }
  261. }
  262. private func maybeStop() {
  263. DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
  264. let app = UIApplication.shared
  265. if app.applicationState != .background {
  266. logger.info("⬅️ no longer in background")
  267. self.unregisterBackgroundTask()
  268. } else if app.backgroundTimeRemaining < 10 {
  269. logger.info("⬅️ few background time, \(app.backgroundTimeRemaining), stopping")
  270. self.dcAccounts.stopIo()
  271. // to avoid 0xdead10cc exceptions, scheduled jobs need to be done before we get suspended;
  272. // we increase the probabilty that this happens by waiting a moment before calling unregisterBackgroundTask()
  273. DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
  274. logger.info("⬅️ few background time, \(app.backgroundTimeRemaining), done")
  275. self.unregisterBackgroundTask()
  276. }
  277. } else {
  278. logger.info("⬅️ remaining background time: \(app.backgroundTimeRemaining)")
  279. self.maybeStop()
  280. }
  281. }
  282. }
  283. // MARK: - background fetch and notifications
  284. // `registerForNotifications` asks the user if they want to get notifiations shown.
  285. // if so, it registers for receiving remote notifications.
  286. func registerForNotifications() {
  287. UNUserNotificationCenter.current().delegate = self
  288. notifyToken = nil
  289. // register for showing notifications
  290. //
  291. // note: the alert-dialog cannot be customized, however, since iOS 12,
  292. // it can be avoided completely by using `.provisional`,
  293. // https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications
  294. UNUserNotificationCenter.current()
  295. .requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
  296. if granted {
  297. // we are allowed to show notifications:
  298. // register for receiving remote notifications
  299. logger.info("Notifications: Permission granted: \(granted)")
  300. self?.maybeRegisterForRemoteNotifications()
  301. } else {
  302. logger.info("Notifications: Permission not granted.")
  303. }
  304. }
  305. }
  306. // register on apple server for receiving remote notifications
  307. // and pass the token to the app's notification server.
  308. //
  309. // on success, we get a token at didRegisterForRemoteNotificationsWithDeviceToken;
  310. // on failure, didFailToRegisterForRemoteNotificationsWithError is called
  311. private func maybeRegisterForRemoteNotifications() {
  312. UNUserNotificationCenter.current().getNotificationSettings { settings in
  313. logger.info("Notifications: Settings: \(settings)")
  314. switch settings.authorizationStatus {
  315. case .authorized, .provisional, .ephemeral:
  316. DispatchQueue.main.async {
  317. UIApplication.shared.registerForRemoteNotifications()
  318. }
  319. case .denied, .notDetermined:
  320. break
  321. }
  322. }
  323. }
  324. // `didRegisterForRemoteNotificationsWithDeviceToken` is called by iOS
  325. // when the call to `UIApplication.shared.registerForRemoteNotifications` succeeded.
  326. //
  327. // we pass the received token to the app's notification server then.
  328. func application(
  329. _ application: UIApplication,
  330. didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
  331. ) {
  332. let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
  333. let tokenString = tokenParts.joined()
  334. let endpoint = "https://notifications.delta.chat/register"
  335. logger.info("Notifications: POST token: \(tokenString) to \(endpoint)")
  336. if let url = URL(string: endpoint) {
  337. var request = URLRequest(url: url)
  338. request.httpMethod = "POST"
  339. let body = "{ \"token\": \"\(tokenString)\" }"
  340. request.httpBody = body.data(using: String.Encoding.utf8)
  341. let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
  342. if let error = error {
  343. logger.error("Notifications: cannot POST to notification server: \(error)")
  344. return
  345. }
  346. if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
  347. logger.info("Notifications: request to notification server succeeded")
  348. } else {
  349. logger.error("Notifications: request to notification server failed: \(String(describing: response)), \(String(describing: data))")
  350. }
  351. self.notifyToken = tokenString
  352. }
  353. task.resume()
  354. } else {
  355. logger.error("Notifications: cannot create URL for token: \(tokenString)")
  356. }
  357. }
  358. // `didFailToRegisterForRemoteNotificationsWithError` is called by iOS
  359. // when the call to `UIApplication.shared.registerForRemoteNotifications` failed.
  360. func application(
  361. _ application: UIApplication,
  362. didFailToRegisterForRemoteNotificationsWithError error: Error) {
  363. logger.error("Notifications: Failed to register: \(error)")
  364. }
  365. // `didReceiveRemoteNotification` is called by iOS when a remote notification is received.
  366. //
  367. // we need to ensure IO is running as the function may be called from suspended state
  368. // (with app in memory, but gracefully shut down before; sort of freezed).
  369. // if the function was not called from suspended state,
  370. // the call to startIo() did nothing, therefore, interrupt and force fetch.
  371. //
  372. // we have max. 30 seconds time for our job and to call the completion handler.
  373. // as the system tracks the elapsed time, power usage, and data costs, we return faster,
  374. // after 10 seconds, things should be done.
  375. // (see https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application)
  376. // (at some point it would be nice if we get a clear signal from the core)
  377. func application(
  378. _ application: UIApplication,
  379. didReceiveRemoteNotification userInfo: [AnyHashable: Any],
  380. fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
  381. ) {
  382. logger.info("➡️ Notifications: didReceiveRemoteNotification \(userInfo)")
  383. increaseDebugCounter("notify-remote-receive")
  384. pushToDebugArray("📡")
  385. performFetch(completionHandler: completionHandler)
  386. }
  387. // `performFetchWithCompletionHandler` is called by iOS on local wakeup.
  388. //
  389. // this requires "UIBackgroundModes: fetch" to be set in Info.plist
  390. // ("App downloads content from the network" in Xcode)
  391. //
  392. // we have 30 seconds time for our job, things are quite similar as in `didReceiveRemoteNotification`
  393. func application(
  394. _ application: UIApplication,
  395. performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
  396. ) {
  397. logger.info("➡️ Notifications: performFetchWithCompletionHandler")
  398. increaseDebugCounter("notify-local-wakeup")
  399. pushToDebugArray("🏠")
  400. performFetch(completionHandler: completionHandler)
  401. }
  402. private func performFetch(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  403. // `didReceiveRemoteNotification` as well as `performFetchWithCompletionHandler` might be called if we're in foreground,
  404. // in this case, there is no need to wait for things or do sth.
  405. if appIsInForeground() {
  406. logger.info("➡️ app already in foreground")
  407. pushToDebugArray("OK1")
  408. completionHandler(.newData)
  409. return
  410. }
  411. // from time to time, `didReceiveRemoteNotification` and `performFetchWithCompletionHandler`
  412. // are actually called at the same millisecond.
  413. //
  414. // therefore, if last fetch is less than a minute ago, we skip this call;
  415. // this also lets the completionHandler being called earlier so that we maybe get awakened when it makes more sense.
  416. //
  417. // nb: calling the completion handler with .noData results in less calls overall.
  418. // if at some point we do per-message-push-notifications, we need to tweak this gate.
  419. let nowTimestamp = Double(Date().timeIntervalSince1970)
  420. if nowTimestamp < bgIoTimestamp + 60 {
  421. logger.info("➡️ fetch was just executed, skipping")
  422. pushToDebugArray("OK2")
  423. completionHandler(.newData)
  424. return
  425. }
  426. bgIoTimestamp = nowTimestamp
  427. // make sure to balance each call to `beginBackgroundTask` with `endBackgroundTask`
  428. var backgroundTask: UIBackgroundTaskIdentifier = .invalid
  429. backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
  430. // usually, this handler is not used as we are taking care of timings below.
  431. logger.info("⬅️ finishing fetch by system urgency requests")
  432. self?.pushToDebugArray("ERR1")
  433. self?.dcAccounts.stopIo()
  434. completionHandler(.newData)
  435. if backgroundTask != .invalid {
  436. UIApplication.shared.endBackgroundTask(backgroundTask)
  437. backgroundTask = .invalid
  438. }
  439. }
  440. pushToDebugArray("1")
  441. // move work to non-main thread to not block UI (otherwise, in case we get suspended, the app is blocked totally)
  442. // (we are using `qos: default` as `qos: .background` or `main.asyncAfter` may be delayed by tens of minutes)
  443. DispatchQueue.global().async { [weak self] in
  444. guard let self = self else { completionHandler(.failed); return }
  445. // we're in background, run IO for a little time
  446. self.dcAccounts.startIo()
  447. self.dcAccounts.maybeNetwork()
  448. self.pushToDebugArray("2")
  449. self.addDebugFetchTimestamp()
  450. // create a new semaphore to make sure the received DC_CONNECTIVITY_CONNECTED really belongs to maybeNetwork() from above
  451. // (maybeNetwork() sets connectivity to DC_CONNECTIVITY_CONNECTING, when fetch is done, we're back at DC_CONNECTIVITY_CONNECTED)
  452. self.dcAccounts.fetchSemaphore = DispatchSemaphore(value: 0)
  453. _ = self.dcAccounts.fetchSemaphore?.wait(timeout: .now() + 20)
  454. self.dcAccounts.fetchSemaphore = nil
  455. // TOCHECK: it seems, we are not always reaching this point in code,
  456. // semaphore?.wait() does not always exit after the given timeout and the app gets suspended -
  457. // maybe that is on purpose somehow to suspend inactive apps, not sure.
  458. // this does not happen often, but still.
  459. // cmp. https://github.com/deltachat/deltachat-ios/pull/1542#pullrequestreview-951620906
  460. logger.info("⬅️ finishing fetch")
  461. self.pushToDebugArray(String(format: "3/%.3fs", Double(Date().timeIntervalSince1970)-nowTimestamp))
  462. if !self.appIsInForeground() {
  463. self.dcAccounts.stopIo()
  464. }
  465. // to avoid 0xdead10cc exceptions, scheduled jobs need to be done before we get suspended;
  466. // we increase the probabilty that this happens by waiting a moment before calling completionHandler()
  467. usleep(1_000_000)
  468. logger.info("⬅️ fetch done")
  469. completionHandler(.newData)
  470. if backgroundTask != .invalid {
  471. self.pushToDebugArray("OK3")
  472. UIApplication.shared.endBackgroundTask(backgroundTask)
  473. backgroundTask = .invalid
  474. }
  475. }
  476. }
  477. // MARK: - handle notification banners
  478. // This method will be called if an incoming message was received while the app was in foreground.
  479. // We don't show foreground notifications in the notification center because they don't get grouped properly
  480. func userNotificationCenter(_: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
  481. logger.info("Notifications: foreground notification")
  482. completionHandler([.badge])
  483. }
  484. // this method will be called if the user tapped on a notification
  485. func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
  486. if !response.notification.request.identifier.containsExact(subSequence: Constants.notificationIdentifier).isEmpty {
  487. logger.info("Notifications: notification tapped")
  488. let userInfo = response.notification.request.content.userInfo
  489. if let chatId = userInfo["chat_id"] as? Int,
  490. let msgId = userInfo["message_id"] as? Int {
  491. if !appCoordinator.isShowingChat(chatId: chatId) {
  492. appCoordinator.showChat(chatId: chatId, msgId: msgId, animated: false, clearViewControllerStack: true)
  493. }
  494. }
  495. }
  496. completionHandler()
  497. }
  498. // MARK: - misc.
  499. func migrateToDcAccounts() {
  500. let dbHelper = DatabaseHelper()
  501. if let databaseLocation = dbHelper.unmanagedDatabaseLocation {
  502. if dcAccounts.migrate(dbLocation: databaseLocation) == 0 {
  503. fatalError("Account could not be migrated")
  504. // TODO: show error message in UI
  505. }
  506. INInteraction.deleteAll(completion: nil)
  507. }
  508. }
  509. /// - Parameters:
  510. /// - accountCode: optional string representation of dcaccounts: url, used to setup a new account
  511. func reloadDcContext(accountCode: String? = nil) {
  512. setStockTranslations()
  513. locationManager.reloadDcContext()
  514. notificationManager.reloadDcContext()
  515. RelayHelper.shared.finishRelaying()
  516. _ = RelayHelper.setup(dcAccounts.getSelected())
  517. if dcAccounts.getSelected().isConfigured() {
  518. appCoordinator.resetTabBarRootViewControllers()
  519. } else {
  520. appCoordinator.presentWelcomeController(accountCode: accountCode)
  521. }
  522. }
  523. func installEventHandler() {
  524. DispatchQueue.global(qos: .background).async { [weak self] in
  525. guard let self = self else { return }
  526. let eventHandler = DcEventHandler(dcAccounts: self.dcAccounts)
  527. let eventEmitter = self.dcAccounts.getEventEmitter()
  528. while true {
  529. guard let event = eventEmitter.getNextEvent() else { break }
  530. eventHandler.handleEvent(event: event)
  531. }
  532. logger.info("⬅️ event emitter finished")
  533. }
  534. }
  535. // Values calculated for debug log view
  536. private func increaseDebugCounter(_ name: String) {
  537. let nowDate = Date()
  538. let nowTimestamp = Double(nowDate.timeIntervalSince1970)
  539. let startTimestamp = UserDefaults.standard.double(forKey: name + "-start")
  540. if nowTimestamp > startTimestamp + 60*60*24 {
  541. let cal: Calendar = Calendar(identifier: .gregorian)
  542. let newStartDate: Date = cal.date(bySettingHour: 0, minute: 0, second: 0, of: nowDate)!
  543. UserDefaults.standard.set(0, forKey: name + "-count")
  544. UserDefaults.standard.set(Double(newStartDate.timeIntervalSince1970), forKey: name + "-start")
  545. }
  546. let cnt = UserDefaults.standard.integer(forKey: name + "-count")
  547. UserDefaults.standard.set(cnt + 1, forKey: name + "-count")
  548. UserDefaults.standard.set(nowTimestamp, forKey: name + "-last")
  549. }
  550. // Values calculated for connectivity view
  551. private func addDebugFetchTimestamp() {
  552. let nowTimestamp = Double(Date().timeIntervalSince1970)
  553. let timestamps = UserDefaults.standard.array(forKey: Constants.Keys.notificationTimestamps)
  554. var slidingTimeframe: [Double]
  555. if timestamps != nil, let timestamps = timestamps as? [Double] {
  556. slidingTimeframe = timestamps.filter({ nowTimestamp < $0 + 60 * 60 * 24 })
  557. } else {
  558. slidingTimeframe = [Double]()
  559. }
  560. slidingTimeframe.append(nowTimestamp)
  561. UserDefaults.standard.set(slidingTimeframe, forKey: Constants.Keys.notificationTimestamps)
  562. }
  563. private func pushToDebugArray(_ value: String) {
  564. let name = "notify-fetch-info2"
  565. let values = UserDefaults.standard.array(forKey: name)
  566. var slidingValues = [String]()
  567. if values != nil, let values = values as? [String] {
  568. slidingValues = values.suffix(512)
  569. }
  570. slidingValues.append(value+"/"+DateUtils.getExtendedAbsTimeSpanString(timeStamp: Double(Date().timeIntervalSince1970)))
  571. UserDefaults.standard.set(slidingValues, forKey: name)
  572. }
  573. private func setStockTranslations() {
  574. let dcContext = dcAccounts.getSelected()
  575. dcContext.setStockTranslation(id: DC_STR_NOMESSAGES, localizationKey: "chat_no_messages")
  576. dcContext.setStockTranslation(id: DC_STR_SELF, localizationKey: "self")
  577. dcContext.setStockTranslation(id: DC_STR_DRAFT, localizationKey: "draft")
  578. dcContext.setStockTranslation(id: DC_STR_VOICEMESSAGE, localizationKey: "voice_message")
  579. dcContext.setStockTranslation(id: DC_STR_IMAGE, localizationKey: "image")
  580. dcContext.setStockTranslation(id: DC_STR_VIDEO, localizationKey: "video")
  581. dcContext.setStockTranslation(id: DC_STR_AUDIO, localizationKey: "audio")
  582. dcContext.setStockTranslation(id: DC_STR_FILE, localizationKey: "file")
  583. dcContext.setStockTranslation(id: DC_STR_GIF, localizationKey: "gif")
  584. dcContext.setStockTranslation(id: DC_STR_ENCRYPTEDMSG, localizationKey: "encrypted_message")
  585. dcContext.setStockTranslation(id: DC_STR_CANTDECRYPT_MSG_BODY, localizationKey: "systemmsg_cannot_decrypt")
  586. dcContext.setStockTranslation(id: DC_STR_READRCPT, localizationKey: "systemmsg_read_receipt_subject")
  587. dcContext.setStockTranslation(id: DC_STR_READRCPT_MAILBODY, localizationKey: "systemmsg_read_receipt_body")
  588. dcContext.setStockTranslation(id: DC_STR_CONTACT_VERIFIED, localizationKey: "contact_verified")
  589. dcContext.setStockTranslation(id: DC_STR_CONTACT_NOT_VERIFIED, localizationKey: "contact_not_verified")
  590. dcContext.setStockTranslation(id: DC_STR_CONTACT_SETUP_CHANGED, localizationKey: "contact_setup_changed")
  591. dcContext.setStockTranslation(id: DC_STR_ARCHIVEDCHATS, localizationKey: "chat_archived_label")
  592. dcContext.setStockTranslation(id: DC_STR_AC_SETUP_MSG_SUBJECT, localizationKey: "autocrypt_asm_subject")
  593. dcContext.setStockTranslation(id: DC_STR_AC_SETUP_MSG_BODY, localizationKey: "autocrypt_asm_general_body")
  594. dcContext.setStockTranslation(id: DC_STR_CANNOT_LOGIN, localizationKey: "login_error_cannot_login")
  595. dcContext.setStockTranslation(id: DC_STR_LOCATION, localizationKey: "location")
  596. dcContext.setStockTranslation(id: DC_STR_STICKER, localizationKey: "sticker")
  597. dcContext.setStockTranslation(id: DC_STR_DEVICE_MESSAGES, localizationKey: "device_talk")
  598. dcContext.setStockTranslation(id: DC_STR_SAVED_MESSAGES, localizationKey: "saved_messages")
  599. dcContext.setStockTranslation(id: DC_STR_DEVICE_MESSAGES_HINT, localizationKey: "device_talk_explain")
  600. dcContext.setStockTranslation(id: DC_STR_WELCOME_MESSAGE, localizationKey: "device_talk_welcome_message")
  601. dcContext.setStockTranslation(id: DC_STR_UNKNOWN_SENDER_FOR_CHAT, localizationKey: "systemmsg_unknown_sender_for_chat")
  602. dcContext.setStockTranslation(id: DC_STR_SUBJECT_FOR_NEW_CONTACT, localizationKey: "systemmsg_subject_for_new_contact")
  603. dcContext.setStockTranslation(id: DC_STR_FAILED_SENDING_TO, localizationKey: "systemmsg_failed_sending_to")
  604. dcContext.setStockTranslation(id: DC_STR_VIDEOCHAT_INVITATION, localizationKey: "videochat_invitation")
  605. dcContext.setStockTranslation(id: DC_STR_VIDEOCHAT_INVITE_MSG_BODY, localizationKey: "videochat_invitation_body")
  606. dcContext.setStockTranslation(id: DC_STR_CONFIGURATION_FAILED, localizationKey: "configuration_failed_with_error")
  607. dcContext.setStockTranslation(id: DC_STR_BAD_TIME_MSG_BODY, localizationKey: "devicemsg_bad_time")
  608. dcContext.setStockTranslation(id: DC_STR_UPDATE_REMINDER_MSG_BODY, localizationKey: "devicemsg_update_reminder")
  609. dcContext.setStockTranslation(id: DC_STR_REPLY_NOUN, localizationKey: "reply_noun")
  610. dcContext.setStockTranslation(id: DC_STR_SELF_DELETED_MSG_BODY, localizationKey: "devicemsg_self_deleted")
  611. dcContext.setStockTranslation(id: DC_STR_FORWARDED, localizationKey: "forwarded")
  612. dcContext.setStockTranslation(id: DC_STR_QUOTA_EXCEEDING_MSG_BODY, localizationKey: "devicemsg_storage_exceeding")
  613. dcContext.setStockTranslation(id: DC_STR_PARTIAL_DOWNLOAD_MSG_BODY, localizationKey: "n_bytes_message")
  614. dcContext.setStockTranslation(id: DC_STR_DOWNLOAD_AVAILABILITY, localizationKey: "download_max_available_until")
  615. dcContext.setStockTranslation(id: DC_STR_INCOMING_MESSAGES, localizationKey: "incoming_messages")
  616. dcContext.setStockTranslation(id: DC_STR_OUTGOING_MESSAGES, localizationKey: "outgoing_messages")
  617. dcContext.setStockTranslation(id: DC_STR_STORAGE_ON_DOMAIN, localizationKey: "storage_on_domain")
  618. dcContext.setStockTranslation(id: DC_STR_CONNECTED, localizationKey: "connectivity_connected")
  619. dcContext.setStockTranslation(id: DC_STR_CONNTECTING, localizationKey: "connectivity_connecting")
  620. dcContext.setStockTranslation(id: DC_STR_UPDATING, localizationKey: "connectivity_updating")
  621. dcContext.setStockTranslation(id: DC_STR_SENDING, localizationKey: "sending")
  622. dcContext.setStockTranslation(id: DC_STR_LAST_MSG_SENT_SUCCESSFULLY, localizationKey: "last_msg_sent_successfully")
  623. dcContext.setStockTranslation(id: DC_STR_ERROR, localizationKey: "error_x")
  624. dcContext.setStockTranslation(id: DC_STR_NOT_SUPPORTED_BY_PROVIDER, localizationKey: "not_supported_by_provider")
  625. dcContext.setStockTranslation(id: DC_STR_MESSAGES, localizationKey: "messages")
  626. dcContext.setStockTranslation(id: DC_STR_BROADCAST_LIST, localizationKey: "broadcast_list")
  627. dcContext.setStockTranslation(id: DC_STR_PART_OF_TOTAL_USED, localizationKey: "part_of_total_used")
  628. dcContext.setStockTranslation(id: DC_STR_SECURE_JOIN_STARTED, localizationKey: "secure_join_started")
  629. dcContext.setStockTranslation(id: DC_STR_SECURE_JOIN_REPLIES, localizationKey: "secure_join_replies")
  630. dcContext.setStockTranslation(id: DC_STR_SETUP_CONTACT_QR_DESC, localizationKey: "qrshow_join_contact_hint")
  631. dcContext.setStockTranslation(id: DC_STR_SECURE_JOIN_GROUP_QR_DESC, localizationKey: "qrshow_join_group_hint")
  632. dcContext.setStockTranslation(id: DC_STR_NOT_CONNECTED, localizationKey: "connectivity_not_connected")
  633. dcContext.setStockTranslation(id: DC_STR_AEAP_ADDR_CHANGED, localizationKey: "aeap_addr_changed")
  634. dcContext.setStockTranslation(id: DC_STR_AEAP_EXPLANATION_AND_LINK, localizationKey: "aeap_explanation")
  635. dcContext.setStockTranslation(id: DC_STR_GROUP_NAME_CHANGED_BY_YOU, localizationKey: "group_name_changed_by_you")
  636. dcContext.setStockTranslation(id: DC_STR_GROUP_NAME_CHANGED_BY_OTHER, localizationKey: "group_name_changed_by_other")
  637. dcContext.setStockTranslation(id: DC_STR_GROUP_IMAGE_CHANGED_BY_YOU, localizationKey: "group_image_changed_by_you")
  638. dcContext.setStockTranslation(id: DC_STR_GROUP_IMAGE_CHANGED_BY_OTHER, localizationKey: "group_image_changed_by_other")
  639. dcContext.setStockTranslation(id: DC_STR_ADD_MEMBER_BY_YOU, localizationKey: "add_member_by_you")
  640. dcContext.setStockTranslation(id: DC_STR_ADD_MEMBER_BY_OTHER, localizationKey: "add_member_by_other")
  641. dcContext.setStockTranslation(id: DC_STR_REMOVE_MEMBER_BY_YOU, localizationKey: "remove_member_by_you")
  642. dcContext.setStockTranslation(id: DC_STR_REMOVE_MEMBER_BY_OTHER, localizationKey: "remove_member_by_other")
  643. dcContext.setStockTranslation(id: DC_STR_GROUP_LEFT_BY_YOU, localizationKey: "group_left_by_you")
  644. dcContext.setStockTranslation(id: DC_STR_GROUP_LEFT_BY_OTHER, localizationKey: "group_left_by_other")
  645. dcContext.setStockTranslation(id: DC_STR_GROUP_IMAGE_DELETED_BY_YOU, localizationKey: "group_image_deleted_by_you")
  646. dcContext.setStockTranslation(id: DC_STR_GROUP_IMAGE_DELETED_BY_OTHER, localizationKey: "group_image_deleted_by_other")
  647. dcContext.setStockTranslation(id: DC_STR_LOCATION_ENABLED_BY_YOU, localizationKey: "location_enabled_by_you")
  648. dcContext.setStockTranslation(id: DC_STR_LOCATION_ENABLED_BY_OTHER, localizationKey: "location_enabled_by_other")
  649. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_DISABLED_BY_YOU, localizationKey: "ephemeral_timer_disabled_by_you")
  650. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_DISABLED_BY_OTHER, localizationKey: "ephemeral_timer_disabled_by_other")
  651. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_SECONDS_BY_YOU, localizationKey: "ephemeral_timer_seconds_by_you")
  652. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_SECONDS_BY_OTHER, localizationKey: "ephemeral_timer_seconds_by_other")
  653. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_1_MINUTE_BY_YOU, localizationKey: "ephemeral_timer_1_minute_by_you")
  654. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_1_MINUTE_BY_OTHER, localizationKey: "ephemeral_timer_1_minute_by_other")
  655. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_1_HOUR_BY_YOU, localizationKey: "ephemeral_timer_1_hour_by_you")
  656. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_1_HOUR_BY_OTHER, localizationKey: "ephemeral_timer_1_hour_by_other")
  657. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_1_DAY_BY_YOU, localizationKey: "ephemeral_timer_1_day_by_you")
  658. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_1_DAY_BY_OTHER, localizationKey: "ephemeral_timer_1_day_by_other")
  659. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_1_WEEK_BY_YOU, localizationKey: "ephemeral_timer_1_week_by_you")
  660. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_1_WEEK_BY_OTHER, localizationKey: "ephemeral_timer_1_week_by_other")
  661. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_MINUTES_BY_YOU, localizationKey: "ephemeral_timer_minutes_by_you")
  662. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_MINUTES_BY_OTHER, localizationKey: "ephemeral_timer_minutes_by_other")
  663. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_HOURS_BY_YOU, localizationKey: "ephemeral_timer_hours_by_you")
  664. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_HOURS_BY_OTHER, localizationKey: "ephemeral_timer_hours_by_other")
  665. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_DAYS_BY_YOU, localizationKey: "ephemeral_timer_days_by_you")
  666. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_DAYS_BY_OTHER, localizationKey: "ephemeral_timer_days_by_other")
  667. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_WEEKS_BY_YOU, localizationKey: "ephemeral_timer_weeks_by_you")
  668. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_TIMER_WEEKS_BY_OTHER, localizationKey: "ephemeral_timer_weeks_by_other")
  669. dcContext.setStockTranslation(id: DC_STR_PROTECTION_ENABLED_BY_YOU, localizationKey: "protection_enabled_by_you")
  670. dcContext.setStockTranslation(id: DC_STR_PROTECTION_ENABLED_BY_OTHER, localizationKey: "protection_enabled_by_other")
  671. dcContext.setStockTranslation(id: DC_STR_PROTECTION_DISABLED_BY_YOU, localizationKey: "protection_disabled_by_you")
  672. dcContext.setStockTranslation(id: DC_STR_PROTECTION_DISABLED_BY_OTHER, localizationKey: "protection_disabled_by_other")
  673. }
  674. func appIsInForeground() -> Bool {
  675. if Thread.isMainThread {
  676. switch UIApplication.shared.applicationState {
  677. case .background, .inactive:
  678. applicationInForeground = false
  679. case .active:
  680. applicationInForeground = true
  681. }
  682. }
  683. return applicationInForeground
  684. }
  685. }