|
@@ -19,6 +19,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|
var window: UIWindow?
|
|
var window: UIWindow?
|
|
var appIsInForeground = false
|
|
var appIsInForeground = false
|
|
|
|
|
|
|
|
+ // `didFinishLaunchingWithOptions` is the main entry point
|
|
|
|
+ // that is called if the app is started for the first time
|
|
|
|
+ // or after the app is killed.
|
|
|
|
+ //
|
|
|
|
+ // `didFinishLaunchingWithOptions` creates the context object and sets
|
|
|
|
+ // up other global things.
|
|
|
|
+ //
|
|
|
|
+ // `didFinishLaunchingWithOptions` is _not_ called
|
|
|
|
+ // when the app wakes up from "suspended" state
|
|
|
|
+ // (app is in memory in the background but no code is executed, IO stopped)
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
// explicitly ignore SIGPIPE to avoid crashes, see https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/CommonPitfalls/CommonPitfalls.html
|
|
// explicitly ignore SIGPIPE to avoid crashes, see https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/CommonPitfalls/CommonPitfalls.html
|
|
// setupCrashReporting() may create an additional handler, but we do not want to rely on that
|
|
// setupCrashReporting() may create an additional handler, but we do not want to rely on that
|
|
@@ -66,8 +76,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|
logger.error("Unable to start notifier")
|
|
logger.error("Unable to start notifier")
|
|
}
|
|
}
|
|
|
|
|
|
- let notificationOption = launchOptions?[.remoteNotification]
|
|
|
|
- logger.info("Notifications: remoteNotification: \(String(describing: notificationOption))")
|
|
|
|
|
|
+ if let notificationOption = launchOptions?[.remoteNotification] {
|
|
|
|
+ logger.info("Notifications: remoteNotification: \(String(describing: notificationOption))")
|
|
|
|
+ increaseDebugCounter("notify-remote-launch")
|
|
|
|
+ }
|
|
|
|
|
|
if dcContext.isConfigured() && !UserDefaults.standard.bool(forKey: "notifications_disabled") {
|
|
if dcContext.isConfigured() && !UserDefaults.standard.bool(forKey: "notifications_disabled") {
|
|
registerForNotifications()
|
|
registerForNotifications()
|
|
@@ -76,6 +88,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|
return true
|
|
return true
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // `open` is called when an url should be opened by Delta Chat.
|
|
|
|
+ // we currently use that for handling oauth2 and for handing openpgp4fpr
|
|
func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
|
func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
|
// gets here when app returns from oAuth2-Setup process - the url contains the provided token
|
|
// gets here when app returns from oAuth2-Setup process - the url contains the provided token
|
|
if let params = url.queryParameters, let token = params["code"] {
|
|
if let params = url.queryParameters, let token = params["code"] {
|
|
@@ -91,13 +105,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|
return true
|
|
return true
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
|
|
+ // `performFetchWithCompletionHandler` is called on local wakeup.
|
|
|
|
+ // this requires "UIBackgroundModes: fetch" to be set in Info.plist
|
|
|
|
+ // ("App downloads content from the network" in Xcode)
|
|
|
|
+ //
|
|
|
|
+ // we have 30 seconds time for our job, leave some seconds for graceful termination.
|
|
|
|
+ // also, the faster we return, the sooner we get called again.
|
|
func application(_: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
|
func application(_: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
|
logger.info("---- background-fetch ----")
|
|
logger.info("---- background-fetch ----")
|
|
|
|
+ increaseDebugCounter("notify-local-wakeup")
|
|
|
|
+
|
|
dcContext.maybeStartIo()
|
|
dcContext.maybeStartIo()
|
|
|
|
+ dcContext.maybeNetwork()
|
|
|
|
|
|
- // we have 30 seconds time for our job, leave some seconds for graceful termination.
|
|
|
|
- // also, the faster we return, the sooner we get called again.
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
|
|
if !self.appIsInForeground {
|
|
if !self.appIsInForeground {
|
|
self.dcContext.stopIo()
|
|
self.dcContext.stopIo()
|
|
@@ -254,8 +274,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|
backgroundTask = .invalid
|
|
backgroundTask = .invalid
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
// MARK: - PushNotifications
|
|
// MARK: - PushNotifications
|
|
|
|
|
|
|
|
+ // `registerForNotifications` asks the user if they want to get notifiations shown.
|
|
|
|
+ // if so, it registers for receiving push notifications.
|
|
func registerForNotifications() {
|
|
func registerForNotifications() {
|
|
UNUserNotificationCenter.current().delegate = self
|
|
UNUserNotificationCenter.current().delegate = self
|
|
|
|
|
|
@@ -265,13 +288,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|
logger.info("Notifications: Permission granted: \(granted)")
|
|
logger.info("Notifications: Permission granted: \(granted)")
|
|
|
|
|
|
if granted {
|
|
if granted {
|
|
- // we are allowd to show notifications:
|
|
|
|
|
|
+ // we are allowed to show notifications:
|
|
// register for receiving push notifications
|
|
// register for receiving push notifications
|
|
self?.maybeRegisterForRemoteNotifications()
|
|
self?.maybeRegisterForRemoteNotifications()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // register on apple server for receiving push notifications
|
|
|
|
+ // and pass the token to the app's notification server.
|
|
|
|
+ //
|
|
|
|
+ // on success, we get a token at didRegisterForRemoteNotificationsWithDeviceToken;
|
|
|
|
+ // on failure, didFailToRegisterForRemoteNotificationsWithError is called
|
|
private func maybeRegisterForRemoteNotifications() {
|
|
private func maybeRegisterForRemoteNotifications() {
|
|
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
|
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
|
logger.info("Notifications: Settings: \(settings)")
|
|
logger.info("Notifications: Settings: \(settings)")
|
|
@@ -279,8 +307,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|
switch settings.authorizationStatus {
|
|
switch settings.authorizationStatus {
|
|
case .authorized, .provisional, .ephemeral:
|
|
case .authorized, .provisional, .ephemeral:
|
|
DispatchQueue.main.async {
|
|
DispatchQueue.main.async {
|
|
- // on success, we get a token at didRegisterForRemoteNotificationsWithDeviceToken;
|
|
|
|
- // on failure, didFailToRegisterForRemoteNotificationsWithError is called
|
|
|
|
UIApplication.shared.registerForRemoteNotifications()
|
|
UIApplication.shared.registerForRemoteNotifications()
|
|
}
|
|
}
|
|
case .denied, .notDetermined:
|
|
case .denied, .notDetermined:
|
|
@@ -288,7 +314,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ // `didRegisterForRemoteNotificationsWithDeviceToken` is called by iOS
|
|
|
|
+ // when the call to `UIApplication.shared.registerForRemoteNotifications` succeeded.
|
|
|
|
+ //
|
|
|
|
+ // we pass the received token to the app's notification server then.
|
|
func application(
|
|
func application(
|
|
_ application: UIApplication,
|
|
_ application: UIApplication,
|
|
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
|
|
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
|
|
@@ -322,26 +352,49 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // `didFailToRegisterForRemoteNotificationsWithError` is called by iOS
|
|
|
|
+ // when the call to `UIApplication.shared.registerForRemoteNotifications` failed.
|
|
func application(
|
|
func application(
|
|
_ application: UIApplication,
|
|
_ application: UIApplication,
|
|
- didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
|
|
|
|
|
+ didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
|
logger.error("Notifications: Failed to register: \(error)")
|
|
logger.error("Notifications: Failed to register: \(error)")
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ // `didReceiveRemoteNotification` is called by iOS when a push notification is received.
|
|
|
|
+ //
|
|
|
|
+ // we need to ensure IO is running as the functionb may be called from suspended state
|
|
|
|
+ // (with app in memory, but gracefully shut down before; sort of freezed).
|
|
|
|
+ // if the function was not called from suspended state,
|
|
|
|
+ // the call to maybeStartIo() did nothing, therefore, interrupt and force fetch
|
|
func application(
|
|
func application(
|
|
_ application: UIApplication,
|
|
_ application: UIApplication,
|
|
didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
|
|
didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
|
|
logger.verbose("Notifications: didReceiveRemoteNotification \(userInfo)")
|
|
logger.verbose("Notifications: didReceiveRemoteNotification \(userInfo)")
|
|
|
|
+ increaseDebugCounter("notify-remote-receive")
|
|
|
|
|
|
- // startIO as this function may be called from suspended state
|
|
|
|
- // (with app in memory, but gracefully shut down before; sort of freezed)
|
|
|
|
dcContext.maybeStartIo()
|
|
dcContext.maybeStartIo()
|
|
-
|
|
|
|
- // if the function was not called from suspended state,
|
|
|
|
- // the call to maybeStartIo() did nothing, therefore, interrupt and force fetch
|
|
|
|
dcContext.maybeNetwork()
|
|
dcContext.maybeNetwork()
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ private func increaseDebugCounter(_ name: String) {
|
|
|
|
+ let nowDate = Date()
|
|
|
|
+ let nowTimestamp = Double(nowDate.timeIntervalSince1970)
|
|
|
|
+ let startTimestamp = UserDefaults.standard.double(forKey: name + "-start")
|
|
|
|
+ if nowTimestamp > startTimestamp + 60*60*24 {
|
|
|
|
+ let cal: Calendar = Calendar(identifier: .gregorian)
|
|
|
|
+ let newStartDate: Date = cal.date(bySettingHour: 0, minute: 0, second: 0, of: nowDate)!
|
|
|
|
+ UserDefaults.standard.set(0, forKey: name + "-count")
|
|
|
|
+ UserDefaults.standard.set(Double(newStartDate.timeIntervalSince1970), forKey: name + "-start")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let cnt = UserDefaults.standard.integer(forKey: name + "-count")
|
|
|
|
+ UserDefaults.standard.set(cnt + 1, forKey: name + "-count")
|
|
|
|
+ UserDefaults.standard.set(nowTimestamp, forKey: name + "-last")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // MARK: - Handle notification banners
|
|
|
|
+
|
|
private func userNotificationCenter(_: UNUserNotificationCenter, willPresent _: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
private func userNotificationCenter(_: UNUserNotificationCenter, willPresent _: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
logger.info("forground notification")
|
|
logger.info("forground notification")
|
|
completionHandler([.alert, .sound])
|
|
completionHandler([.alert, .sound])
|