From 6764936d2cf14e1f47ace49551ece25f6f21668c Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 1 Feb 2019 14:42:46 +0400 Subject: [PATCH] Support updated shared context --- .../NotificationViewController.swift | 10 +- Share/ShareRootController.swift | 21 +- Telegram-iOS.xcodeproj/project.pbxproj | 8 + Telegram-iOS/AppDelegate.swift | 499 ++++++++++-------- Telegram-iOS/ApplicationContext.swift | 232 +------- Telegram-iOS/LegacyPreferencesImport.swift | 16 +- Telegram-iOS/NotificationManager.swift | 4 +- Telegram-iOS/SharedNotificationManager.swift | 361 +++++++++++++ Telegram-iOS/SharedWakeupManager.swift | 184 +++++++ Telegram-iOS/WatchCommunicationManager.swift | 10 +- Telegram-iOS/WatchRequestHandlers.swift | 6 +- submodules/Display | 2 +- submodules/Postbox | 2 +- submodules/TelegramCore | 2 +- submodules/TelegramUI | 2 +- 15 files changed, 906 insertions(+), 453 deletions(-) create mode 100644 Telegram-iOS/SharedNotificationManager.swift create mode 100644 Telegram-iOS/SharedWakeupManager.swift diff --git a/NotificationContent/NotificationViewController.swift b/NotificationContent/NotificationViewController.swift index 1cdbf8c1c3..3ac6e3e46c 100644 --- a/NotificationContent/NotificationViewController.swift +++ b/NotificationContent/NotificationViewController.swift @@ -88,6 +88,14 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi initializeAccountManagement() let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") + var initialPresentationDataAndSettings: InitialPresentationDataAndSettings? + let semaphore = DispatchSemaphore(value: 0) + let _ = currentPresentationDataAndSettings(accountManager: accountManager).start(next: { value in + initialPresentationDataAndSettings = value + semaphore.signal() + }) + semaphore.wait() + let applicationBindings = TelegramApplicationBindings(isMainApp: false, containerPath: appGroupUrl.path, appSpecificScheme: BuildConfig.shared().appSpecificUrlScheme, openUrl: { _ in }, openUniversalUrl: { _, completion in completion.completion(false) @@ -109,7 +117,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - sharedAccountContext = SharedAccountContext(accountManager: accountManager, applicationBindings: applicationBindings, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0), rootPath: rootPath, apsNotificationToken: .never(), voipNotificationToken: .never()) + sharedAccountContext = SharedAccountContext(mainWindow: nil, accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0), rootPath: rootPath, apsNotificationToken: .never(), voipNotificationToken: .never()) } } diff --git a/Share/ShareRootController.swift b/Share/ShareRootController.swift index b5b21cce3e..44c1b4940d 100644 --- a/Share/ShareRootController.swift +++ b/Share/ShareRootController.swift @@ -144,7 +144,15 @@ class ShareRootController: UIViewController { initializeAccountManagement() let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") - let sharedContext = SharedAccountContext(accountManager: accountManager, applicationBindings: applicationBindings, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0), rootPath: rootPath, apsNotificationToken: .never(), voipNotificationToken: .never()) + var initialPresentationDataAndSettings: InitialPresentationDataAndSettings? + let semaphore = DispatchSemaphore(value: 0) + let _ = currentPresentationDataAndSettings(accountManager: accountManager).start(next: { value in + initialPresentationDataAndSettings = value + semaphore.signal() + }) + semaphore.wait() + + let sharedContext = SharedAccountContext(mainWindow: nil, accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0), rootPath: rootPath, apsNotificationToken: .never(), voipNotificationToken: .never()) account = accountManager.transaction { transaction -> (SharedAccountContext, LoggingSettings) in return (sharedContext, transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings ?? LoggingSettings.defaultSettings) @@ -181,14 +189,17 @@ class ShareRootController: UIViewController { let shouldBeMaster = self.shouldBeMaster let applicationInterface = account |> mapToSignal { sharedContext, account -> Signal<(AccountContext, PostboxAccessChallengeData), ShareAuthorizationError> in - return combineLatest(sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationPasscodeSettings]), currentPresentationDataAndSettings(accountManager: sharedContext.accountManager, postbox: account.postbox), sharedContext.accountManager.accessChallengeData()) + let limitsConfiguration = account.postbox.transaction { transaction -> LimitsConfiguration in + return transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue + } + return combineLatest(sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationPasscodeSettings]), limitsConfiguration, sharedContext.accountManager.accessChallengeData()) |> take(1) |> deliverOnMainQueue |> introduceError(ShareAuthorizationError.self) - |> map { sharedData, dataAndSettings, data -> (AccountContext, PostboxAccessChallengeData) in + |> map { sharedData, limitsConfiguration, data -> (AccountContext, PostboxAccessChallengeData) in accountCache = (sharedContext, account) - updateLegacyLocalization(strings: dataAndSettings.presentationData.strings) - let context = AccountContext(sharedContext: sharedContext, account: account, initialPresentationDataAndSettings: dataAndSettings) + updateLegacyLocalization(strings: sharedContext.currentPresentationData.with({ $0 }).strings) + let context = AccountContext(sharedContext: sharedContext, account: account, limitsConfiguration: limitsConfiguration) return (context, data.data) } } diff --git a/Telegram-iOS.xcodeproj/project.pbxproj b/Telegram-iOS.xcodeproj/project.pbxproj index 37d3913a06..947989a486 100644 --- a/Telegram-iOS.xcodeproj/project.pbxproj +++ b/Telegram-iOS.xcodeproj/project.pbxproj @@ -361,6 +361,8 @@ D0AF32291FACA1920097362B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D17E891CAAD66600C4750B /* Accelerate.framework */; }; D0AF322C1FACA1B00097362B /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D0B8445F1DACF561005F29E1 /* libc++.tbd */; }; D0AF322F1FACBA280097362B /* TGShareLocationSignals.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AF322D1FACBA270097362B /* TGShareLocationSignals.m */; }; + D0B21B0D2203A9A1003F741D /* SharedWakeupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B21B0C2203A9A1003F741D /* SharedWakeupManager.swift */; }; + D0B21B0F220438E9003F741D /* SharedNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B21B0E220438E9003F741D /* SharedNotificationManager.swift */; }; D0B2F738204F4C9900D3BFB9 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E41A381D65A69C00FBFC00 /* NotificationCenter.framework */; }; D0B2F742204F4C9900D3BFB9 /* Widget.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0B2F737204F4C9900D3BFB9 /* Widget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; D0B2F74A204F4D6100D3BFB9 /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0B2F74F204F4D6100D3BFB9 /* Postbox.framework */; }; @@ -1092,6 +1094,8 @@ D0AF322A1FACA1A80097362B /* libstdc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libstdc++.tbd"; path = "usr/lib/libstdc++.tbd"; sourceTree = SDKROOT; }; D0AF322D1FACBA270097362B /* TGShareLocationSignals.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGShareLocationSignals.m; sourceTree = ""; }; D0AF322E1FACBA270097362B /* TGShareLocationSignals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGShareLocationSignals.h; sourceTree = ""; }; + D0B21B0C2203A9A1003F741D /* SharedWakeupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedWakeupManager.swift; sourceTree = ""; }; + D0B21B0E220438E9003F741D /* SharedNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedNotificationManager.swift; sourceTree = ""; }; D0B2F737204F4C9900D3BFB9 /* Widget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Widget.appex; sourceTree = BUILT_PRODUCTS_DIR; }; D0B2F74E204F4D6100D3BFB9 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0B2F74F204F4D6100D3BFB9 /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1975,6 +1979,8 @@ D0ADF956212B56C200310BBC /* Legacy Data Import */, D0ECCB8B1FE9CE2B00609802 /* Snapshots */, D008599F1B28189D00EAF753 /* Supporting Files */, + D0B21B0C2203A9A1003F741D /* SharedWakeupManager.swift */, + D0B21B0E220438E9003F741D /* SharedNotificationManager.swift */, ); path = "Telegram-iOS"; sourceTree = ""; @@ -3212,6 +3218,7 @@ D039FB172170F06A00BD1BAD /* PreFetchedLegacyResource.swift in Sources */, 09D304292174343300C00567 /* TGBridgeBotInfo.m in Sources */, 09D304272174341E00C00567 /* TGBridgeChatMessages.m in Sources */, + D0B21B0F220438E9003F741D /* SharedNotificationManager.swift in Sources */, D02E31231BD803E800CD3F01 /* main.m in Sources */, D05B37FD1FEA8D870041D2A5 /* SnapshotResources.swift in Sources */, D0EB243B201B77C400F6CC13 /* ClearNotificationsManager.swift in Sources */, @@ -3221,6 +3228,7 @@ D06E4C2F21347D9200088087 /* UIImage+ImageEffects.m in Sources */, D0B3B53B21666C0000FC60A0 /* LegacyFileImport.swift in Sources */, D0ADF95E212C818F00310BBC /* LegacyPreferencesImport.swift in Sources */, + D0B21B0D2203A9A1003F741D /* SharedWakeupManager.swift in Sources */, 09D304282174342E00C00567 /* TGBridgeChat.m in Sources */, 09C50E8A2173AEDB009E676F /* WatchRequestHandlers.swift in Sources */, 09D304302174344900C00567 /* TGBridgeForwardedMessageMediaAttachment.m in Sources */, diff --git a/Telegram-iOS/AppDelegate.swift b/Telegram-iOS/AppDelegate.swift index a173303392..7204215291 100644 --- a/Telegram-iOS/AppDelegate.swift +++ b/Telegram-iOS/AppDelegate.swift @@ -120,6 +120,18 @@ private enum QueuedWakeup: Int32 { case backgroundLocation } +private final class SharedApplicationContext { + let sharedContext: SharedAccountContext + let notificationManager: SharedNotificationManager + let wakeupManager: SharedWakeupManager + + init(sharedContext: SharedAccountContext, notificationManager: SharedNotificationManager, wakeupManager: SharedWakeupManager) { + self.sharedContext = sharedContext + self.notificationManager = notificationManager + self.wakeupManager = wakeupManager + } +} + @objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, BITHockeyManagerDelegate, UNUserNotificationCenterDelegate, UIAlertViewDelegate { @objc var window: UIWindow? var nativeWindow: (UIWindow & WindowHost)? @@ -134,13 +146,17 @@ private enum QueuedWakeup: Int32 { private var isActiveValue = false let hasActiveAudioSession = Promise(false) - private let sharedContextPromise = Promise() + private let sharedContextPromise = Promise() private let watchCommunicationManagerPromise = Promise() - private var contextValue: ApplicationContext? - private let context = Promise() + private var contextValue: AuthorizedApplicationContext? + private let context = Promise() private let contextDisposable = MetaDisposable() + private var authContextValue: UnauthorizedApplicationContext? + private let authContext = Promise() + private let authContextDisposable = MetaDisposable() + private let openChatWhenReadyDisposable = MetaDisposable() private let openUrlWhenReadyDisposable = MetaDisposable() @@ -193,8 +209,8 @@ private enum QueuedWakeup: Int32 { } } - private var queuedNotifications: [PKPushPayload] = [] - private var queuedNotificationRequests: [(String, String, String?, NotificationManagedNotificationRequestId)] = [] + //private var queuedNotifications: [[AnyHashable: Any]] = [] + //private var queuedNotificationRequests: [(String, String, String?, NotificationManagedNotificationRequestId)] = [] private var queuedMutePolling = false private var queuedAnnouncements: [String] = [] private var queuedWakeups = Set() @@ -220,7 +236,7 @@ private enum QueuedWakeup: Int32 { self.window = window self.nativeWindow = window - self.clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in + let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in if #available(iOS 10.0, *) { UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in var result: [(String, NotificationManagedNotificationRequestId)] = [] @@ -323,6 +339,7 @@ private enum QueuedWakeup: Int32 { } } }) + self.clearNotificationsManager = clearNotificationsManager #if DEBUG for argument in ProcessInfo.processInfo.arguments { @@ -543,7 +560,7 @@ private enum QueuedWakeup: Int32 { UIApplication.shared.openURL(url) } }, registerForNotifications: { completion in - let _ = (self.currentAuthorizedContext() + let _ = (self.context.get() |> take(1) |> deliverOnMainQueue).start(next: { context in if let context = context { @@ -584,18 +601,51 @@ private enum QueuedWakeup: Int32 { }) let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") - let sharedContext = SharedAccountContext(accountManager: accountManager, applicationBindings: applicationBindings, networkArguments: networkArguments, rootPath: rootPath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init)) + var initialPresentationDataAndSettings: InitialPresentationDataAndSettings? + let semaphore = DispatchSemaphore(value: 0) + let _ = currentPresentationDataAndSettings(accountManager: accountManager).start(next: { value in + initialPresentationDataAndSettings = value + semaphore.signal() + }) + semaphore.wait() - self.sharedContextPromise.set( - accountManager.transaction { transaction -> (SharedAccountContext, LoggingSettings) in - return (sharedContext, transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings ?? LoggingSettings.defaultSettings) + let sharedContext = SharedAccountContext(mainWindow: self.mainWindow, accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: networkArguments, rootPath: rootPath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init)) + sharedContext.presentGlobalController = { [weak self] c, a in + guard let strongSelf = self else { + return + } + strongSelf.mainWindow.present(c, on: .root) } - |> mapToSignal { sharedContext, loggingSettings -> Signal in + sharedContext.presentCrossfadeController = { [weak self] in + guard let strongSelf = self else { + return + } + var exists = false + strongSelf.mainWindow.forEachViewController { controller in + if controller is ThemeSettingsCrossfadeController { + exists = true + } + return true + } + + if !exists { + strongSelf.mainWindow.present(ThemeSettingsCrossfadeController(), on: .root) + } + } + + let notificationManager = SharedNotificationManager(episodeId: self.episodeId, clearNotificationsManager: clearNotificationsManager, inForeground: applicationBindings.applicationInForeground, accounts: sharedContext.activeAccounts |> map { primary, accounts, _ in Array(accounts.values.map({ ($0, $0.id == primary?.id) })) }) + let wakeupManager = SharedWakeupManager(activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1) }, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: hasActiveAudioSession.get(), notificationManager: notificationManager) + let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: notificationManager, wakeupManager: wakeupManager) + self.sharedContextPromise.set( + accountManager.transaction { transaction -> (SharedApplicationContext, LoggingSettings) in + return (sharedApplicationContext, transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings ?? LoggingSettings.defaultSettings) + } + |> mapToSignal { sharedApplicationContext, loggingSettings -> Signal in Logger.shared.logToFile = loggingSettings.logToFile Logger.shared.logToConsole = loggingSettings.logToConsole Logger.shared.redactSensitiveData = loggingSettings.redactSensitiveData - return importedLegacyAccount(basePath: appGroupUrl.path, accountManager: sharedContext.accountManager, present: { controller in + return importedLegacyAccount(basePath: appGroupUrl.path, accountManager: sharedApplicationContext.sharedContext.accountManager, present: { controller in self.window?.rootViewController?.present(controller, animated: true, completion: nil) }) |> `catch` { _ -> Signal in @@ -615,7 +665,7 @@ private enum QueuedWakeup: Int32 { return EmptyDisposable } |> runOn(Queue.mainQueue()) } - |> mapToSignal { event -> Signal in + |> mapToSignal { event -> Signal in switch event { case let .progress(type, value): Queue.mainQueue().async { @@ -642,7 +692,7 @@ private enum QueuedWakeup: Int32 { let _ = try? FileManager.default.createDirectory(atPath: appGroupUrl.path + "/Documents", withIntermediateDirectories: true, attributes: nil) let _ = try? Data().write(to: URL(fileURLWithPath: statusPath)) }) - return sharedContext.accountManager.transaction { transaction -> SharedAccountContext in + return sharedApplicationContext.sharedContext.accountManager.transaction { transaction -> SharedApplicationContext in transaction.setCurrentId(temporaryId) transaction.updateRecord(temporaryId, { record in if let record = record { @@ -650,10 +700,10 @@ private enum QueuedWakeup: Int32 { } return record }) - return sharedContext + return sharedApplicationContext } } else { - return .single(sharedContext) + return .single(sharedApplicationContext) } } } @@ -672,10 +722,55 @@ private enum QueuedWakeup: Int32 { self.context.set(self.sharedContextPromise.get() |> deliverOnMainQueue - |> mapToSignal { sharedContext -> Signal in - return sharedContext.activeAccounts - |> map { primary, _, auth -> (AnyObject?, Bool) in - return (auth ?? primary, primary != nil) + |> mapToSignal { sharedApplicationContext -> Signal in + return sharedApplicationContext.sharedContext.activeAccounts + |> map { primary, _, _ -> Account? in + return primary + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs !== rhs { + return false + } + return true + }) + |> mapToSignal { account -> Signal<(Account, LimitsConfiguration, CallListSettings)?, NoError> in + return sharedApplicationContext.sharedContext.accountManager.transaction { transaction -> CallListSettings in + return transaction.getSharedData(ApplicationSpecificSharedDataKeys.callListSettings) as? CallListSettings ?? CallListSettings.defaultSettings + } + |> mapToSignal { callListSettings -> Signal<(Account, LimitsConfiguration, CallListSettings)?, NoError> in + if let account = account { + return account.postbox.transaction { transaction -> (Account, LimitsConfiguration, CallListSettings)? in + let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue + return (account, limitsConfiguration, callListSettings) + } + } else { + return .single(nil) + } + } + } + |> deliverOnMainQueue + |> map { accountAndSettings -> AuthorizedApplicationContext? in + return accountAndSettings.flatMap { account, limitsConfiguration, callListSettings in + let context = AccountContext(sharedContext: sharedApplicationContext.sharedContext, account: account, limitsConfiguration: limitsConfiguration) + return AuthorizedApplicationContext(mainWindow: self.mainWindow, replyFromNotificationsActive: replyFromNotificationsActive, backgroundAudioActive: backgroundAudioActive, watchManagerArguments: watchManagerArgumentsPromise.get(), context: context, accountManager: sharedApplicationContext.sharedContext.accountManager, legacyBasePath: appGroupUrl.path, showCallsTab: callListSettings.showTab, reinitializedNotificationSettings: { + let _ = (self.context.get() + |> take(1) + |> deliverOnMainQueue).start(next: { context in + if let context = context { + self.registerForNotifications(context: context.context, authorize: false) + } + }) + }) + } + } + }) + + self.authContext.set(self.sharedContextPromise.get() + |> deliverOnMainQueue + |> mapToSignal { sharedApplicationContext -> Signal in + return sharedApplicationContext.sharedContext.activeAccounts + |> map { _, accounts, auth -> (UnauthorizedAccount?, Bool) in + return (auth, !accounts.isEmpty) } |> distinctUntilChanged(isEqual: { lhs, rhs in if lhs.0 !== rhs.0 { @@ -683,102 +778,64 @@ private enum QueuedWakeup: Int32 { } return true }) - |> mapToSignal { account, hasOther -> Signal<(AnyObject, InitialPresentationDataAndSettings, Bool)?, NoError> in - if let account = account as? Account { - return currentPresentationDataAndSettings(accountManager: sharedContext.accountManager, postbox: account.postbox) - |> map { initialSettings in - return (account, initialSettings, hasOther) + |> mapToSignal { account, hasOther -> Signal<(UnauthorizedAccount, LimitsConfiguration, CallListSettings, Bool)?, NoError> in + return sharedApplicationContext.sharedContext.accountManager.transaction { transaction -> CallListSettings in + return transaction.getSharedData(ApplicationSpecificSharedDataKeys.callListSettings) as? CallListSettings ?? CallListSettings.defaultSettings } - } else if let account = account as? UnauthorizedAccount { - return currentPresentationDataAndSettings(accountManager: sharedContext.accountManager, postbox: account.postbox) - |> map { initialSettings in - return (account, initialSettings, hasOther) + |> mapToSignal { callListSettings -> Signal<(UnauthorizedAccount, LimitsConfiguration, CallListSettings, Bool)?, NoError> in + if let account = account { + return account.postbox.transaction { transaction -> (UnauthorizedAccount, LimitsConfiguration, CallListSettings, Bool)? in + let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue + return (account, limitsConfiguration, callListSettings, hasOther) + } + } else { + return .single(nil) } - } else { - return .single(nil) } } |> deliverOnMainQueue - |> map { accountAndSettings -> ApplicationContext? in - return accountAndSettings.flatMap { account, initialSettings, hasOther in - if let account = account as? Account { - let context = AccountContext(sharedContext: sharedContext, account: account, initialPresentationDataAndSettings: initialSettings) - return ApplicationContext.authorized(AuthorizedApplicationContext(mainWindow: self.mainWindow, replyFromNotificationsActive: replyFromNotificationsActive, backgroundAudioActive: backgroundAudioActive, watchManagerArguments: watchManagerArgumentsPromise.get(), context: context, accountManager: sharedContext.accountManager, legacyBasePath: appGroupUrl.path, showCallsTab: initialSettings.callListSettings.showTab, reinitializedNotificationSettings: { - let _ = (self.context.get() - |> take(1) - |> deliverOnMainQueue).start(next: { value in - if let value = value, case let .authorized(context) = value { - self.registerForNotifications(context: context.context, authorize: false) - } - }) - })) - } else if let account = account as? UnauthorizedAccount { - return ApplicationContext.unauthorized(UnauthorizedApplicationContext(sharedContext: sharedContext, account: account, hasOtherAccounts: hasOther)) - } else { - return nil - } + |> map { accountAndSettings -> UnauthorizedApplicationContext? in + return accountAndSettings.flatMap { account, limitsConfiguration, callListSettings, hasOther in + return UnauthorizedApplicationContext(sharedContext: sharedApplicationContext.sharedContext, account: account, hasOtherAccounts: hasOther) } } }) let contextReadyDisposable = MetaDisposable() - self.contextDisposable.set(self.context.get().start(next: { context in - assert(Queue.mainQueue().isCurrent()) + self.contextDisposable.set((self.context.get() + |> deliverOnMainQueue).start(next: { context in var network: Network? if let context = context { - switch context { - case let .unauthorized(unauthorized): - network = unauthorized.account.network - case let .authorized(authorized): - network = authorized.context.account.network - } + network = context.context.account.network } - Logger.shared.log("App \(self.episodeId)", "received context \(String(describing: context)) account \(String(describing: context?.accountId)) network \(String(describing: network))") + Logger.shared.log("App \(self.episodeId)", "received context \(String(describing: context)) account \(String(describing: context?.context.account.id)) network \(String(describing: network))") if let contextValue = self.contextValue { - switch contextValue { - case let .unauthorized(unauthorized): - unauthorized.account.shouldBeServiceTaskMaster.set(.single(.never)) - case let .authorized(authorized): - authorized.context.isCurrent = false - authorized.context.account.shouldBeServiceTaskMaster.set(.single(.never)) - authorized.context.account.shouldKeepOnlinePresence.set(.single(false)) - authorized.context.account.shouldExplicitelyKeepWorkerConnections.set(.single(false)) - authorized.context.account.shouldKeepBackgroundDownloadConnections.set(.single(false)) - } + contextValue.context.isCurrent = false + contextValue.context.account.shouldExplicitelyKeepWorkerConnections.set(.single(false)) + contextValue.context.account.shouldKeepBackgroundDownloadConnections.set(.single(false)) } self.contextValue = context if let context = context { - let isReady: Signal - switch context { - case let .authorized(authorized): - setupLegacyComponents(context: authorized.context) - authorized.context.isCurrent = true - isReady = authorized.isReady.get() - default: - isReady = .single(true) - } + setupLegacyComponents(context: context.context) + context.context.isCurrent = true + let isReady = context.isReady.get() contextReadyDisposable.set((isReady |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { _ in self.mainWindow.viewController = context.rootController - self.mainWindow.topLevelOverlayControllers = context.overlayControllers - self.maybeDequeueNotificationPayloads() + self.mainWindow.topLevelOverlayControllers = [context.overlayMediaController, context.notificationController] + /*self.maybeDequeueNotificationPayloads() self.maybeDequeueNotificationRequests() - self.maybeDequeueWakeups() - switch context { - case let .authorized(context): - var authorizeNotifications = true - if #available(iOS 10.0, *) { - authorizeNotifications = false - } - self.registerForNotifications(context: context.context, authorize: authorizeNotifications) - case .unauthorized: - break + self.maybeDequeueWakeups()*/ + var authorizeNotifications = true + if #available(iOS 10.0, *) { + authorizeNotifications = false } + self.registerForNotifications(context: context.context, authorize: authorizeNotifications) })) } else { self.mainWindow.viewController = nil @@ -787,6 +844,38 @@ private enum QueuedWakeup: Int32 { } })) + let authContextReadyDisposable = MetaDisposable() + + self.authContextDisposable.set((self.authContext.get() + |> deliverOnMainQueue).start(next: { context in + var network: Network? + if let context = context { + network = context.account.network + } + + Logger.shared.log("App \(self.episodeId)", "received auth context \(String(describing: context)) account \(String(describing: context?.account.id)) network \(String(describing: network))") + + if let authContextValue = self.authContextValue { + authContextValue.account.shouldBeServiceTaskMaster.set(.single(.never)) + authContextValue.rootController.view.endEditing(true) + authContextValue.rootController.dismiss() + } + self.authContextValue = context + if let context = context { + let isReady: Signal = .single(true) + authContextReadyDisposable.set((isReady + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { _ in + self.mainWindow.present(context.rootController, on: .root) + //self.mainWindow.viewController = context.rootController + //self.mainWindow.topLevelOverlayControllers = context.overlayControllers + })) + } else { + authContextReadyDisposable.set(nil) + } + })) + self.watchCommunicationManagerPromise.set(watchCommunicationManager(context: self.context)) let _ = self.watchCommunicationManagerPromise.get().start(next: { manager in if let manager = manager { @@ -804,14 +893,9 @@ private enum QueuedWakeup: Int32 { self.badgeDisposable.set((self.context.get() |> mapToSignal { context -> Signal in if let context = context { - switch context { - case let .authorized(context): - return context.applicationBadge - case .unauthorized: - return .single(0) - } + return context.applicationBadge } else { - return .never() + return .single(0) } } |> deliverOnMainQueue).start(next: { count in @@ -822,15 +906,10 @@ private enum QueuedWakeup: Int32 { self.quickActionsDisposable.set((self.context.get() |> mapToSignal { context -> Signal<[ApplicationShortcutItem], NoError> in if let context = context { - switch context { - case let .authorized(context): - let presentationData = context.context.currentPresentationData.with { $0 } - return .single(applicationShortcutItems(strings: presentationData.strings)) - case .unauthorized: - return .single([]) - } + let presentationData = context.context.sharedContext.currentPresentationData.with { $0 } + return .single(applicationShortcutItems(strings: presentationData.strings)) } else { - return .never() + return .single([]) } } |> distinctUntilChanged @@ -876,14 +955,8 @@ private enum QueuedWakeup: Int32 { } })) } - if let contextValue = strongSelf.contextValue { - if case let .authorized(context) = contextValue { - let presentationData = context.context.currentPresentationData.with { $0 } - strongSelf.mainWindow.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: alert.title, text: alert.message ?? "", actions: actions), on: .root) - } else if case let .unauthorized(context) = contextValue { - strongSelf.mainWindow.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: context.rootController.theme), title: alert.title, text: alert.message ?? "", actions: actions), on: .root) - } - } + let presentationData = sharedContext.currentPresentationData.with { $0 } + strongSelf.mainWindow.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: alert.title, text: alert.message ?? "", actions: actions), on: .root) } }) @@ -916,6 +989,12 @@ private enum QueuedWakeup: Int32 { } func applicationDidEnterBackground(_ application: UIApplication) { + let _ = (self.sharedContextPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { sharedApplicationContext in + sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0) + }) + self.isInForegroundValue = false self.isInForegroundPromise.set(false) self.isActiveValue = false @@ -964,6 +1043,12 @@ private enum QueuedWakeup: Int32 { } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + let _ = (self.sharedContextPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { sharedApplicationContext in + sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0) + }) + var redactedPayload = userInfo if var aps = redactedPayload["aps"] as? [AnyHashable: Any] { if Logger.shared.redactSensitiveData { @@ -977,7 +1062,6 @@ private enum QueuedWakeup: Int32 { redactedPayload["aps"] = aps } - Logger.shared.log("App \(self.episodeId)", "remoteNotification: \(redactedPayload)") completionHandler(UIBackgroundFetchResult.noData) } @@ -996,28 +1080,24 @@ private enum QueuedWakeup: Int32 { } } - private var pushCnt = 0 public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { - if case PKPushType.voIP = type { - Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)") - /*#if DEBUG - self.pushCnt += 1 - if self.pushCnt % 2 != 0 { - Logger.shared.log("App \(self.episodeId)", "pushRegistry payload drop") - return - } - #endif*/ + let _ = (self.sharedContextPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { sharedApplicationContext in + sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0) - self.queuedNotifications.append(payload) - self.maybeDequeueNotificationPayloads() - } + if case PKPushType.voIP = type { + Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)") + sharedApplicationContext.notificationManager.addEncryptedNotification(payload.dictionaryPayload) + } + }) } - private func processPushPayload(_ payload: PKPushPayload, account: Account) { + /*private func processPushPayload(_ payload: [AnyHashable: Any], account: Account) { let decryptedPayload: Signal<[AnyHashable: Any]?, NoError> - if let _ = payload.dictionaryPayload["aps"] as? [AnyHashable: Any] { - decryptedPayload = .single(payload.dictionaryPayload as [AnyHashable: Any]) - } else if var encryptedPayload = payload.dictionaryPayload["p"] as? String { + if let _ = payload["aps"] as? [AnyHashable: Any] { + decryptedPayload = .single(payload) + } else if var encryptedPayload = payload["p"] as? String { encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+") encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/") while encryptedPayload.count % 4 != 0 { @@ -1234,7 +1314,7 @@ private enum QueuedWakeup: Int32 { self.clearNotificationsManager?.append(readMessageId) self.clearNotificationsManager?.commitNow() - let signal = self.currentAuthorizedContext() + let signal = self.context.get() |> take(1) |> mapToSignal { context -> Signal in if let context = context { @@ -1249,7 +1329,7 @@ private enum QueuedWakeup: Int32 { } if let (datacenterId, host, port, secret) = configurationUpdate { - let signal = self.currentAuthorizedContext() + let signal = self.context.get() |> take(1) |> mapToSignal { context -> Signal in if let context = context { @@ -1261,28 +1341,16 @@ private enum QueuedWakeup: Int32 { } } }) - } + }*/ public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { Logger.shared.log("App \(self.episodeId)", "invalidated token for \(type)") } - - private func currentAuthorizedContext() -> Signal { - return self.context.get() - |> take(1) - |> mapToSignal { contextValue -> Signal in - if let contextValue = contextValue, case let .authorized(context) = contextValue { - return .single(context) - } else { - return .single(nil) - } - } - } private func authorizedContext() -> Signal { return self.context.get() - |> mapToSignal { contextValue -> Signal in - if let contextValue = contextValue, case let .authorized(context) = contextValue { + |> mapToSignal { context -> Signal in + if let context = context { return .single(context) } else { return .complete() @@ -1311,30 +1379,35 @@ private enum QueuedWakeup: Int32 { } private func openUrl(url: URL) { - let _ = (self.context.get() - |> flatMap { $0 } + let _ = (self.sharedContextPromise.get() |> take(1) - |> deliverOnMainQueue).start(next: { contextValue in - switch contextValue { - case let .authorized(context): - context.openUrl(url) - case let .unauthorized(context): - if let proxyData = parseProxyUrl(url) { - context.rootController.view.endEditing(true) - let strings = context.strings - let controller = ProxyServerActionSheetController(theme: defaultPresentationTheme, strings: strings, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, server: proxyData, presentationData: nil) - context.rootController.currentWindow?.present(controller, on: PresentationSurfaceLevel.root, blockInteraction: false, completion: {}) - } else if let secureIdData = parseSecureIdUrl(url) { - let strings = context.strings - let theme = context.rootController.theme - context.rootController.currentWindow?.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: theme), title: nil, text: strings.Passport_NotLoggedInMessage, actions: [TextAlertAction(type: .genericAction, title: strings.Calls_NotNow, action: { - if let callbackUrl = URL(string: secureIdCallbackUrl(with: secureIdData.callbackUrl, peerId: secureIdData.peerId, result: .cancel, parameters: [:])) { - UIApplication.shared.openURL(callbackUrl) - } - }), TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]), on: .root, blockInteraction: false, completion: {}) - } else if let confirmationCode = parseConfirmationCodeUrl(url) { - context.rootController.applyConfirmationCode(confirmationCode) - } + |> mapToSignal { sharedApplicationContext -> Signal<(SharedAccountContext, AuthorizedApplicationContext?, UnauthorizedApplicationContext?), NoError> in + combineLatest(self.context.get(), self.authContext.get()) + |> filter { $0 != nil || $1 != nil } + |> take(1) + |> map { context, authContext -> (SharedAccountContext, AuthorizedApplicationContext?, UnauthorizedApplicationContext?) in + return (sharedApplicationContext.sharedContext, context, authContext) + } + } + |> deliverOnMainQueue).start(next: { _, context, authContext in + if let context = context { + context.openUrl(url) + } else if let authContext = authContext { + if let proxyData = parseProxyUrl(url) { + authContext.rootController.view.endEditing(true) + let presentationData = authContext.sharedContext.currentPresentationData.with { $0 } + let controller = ProxyServerActionSheetController(theme: presentationData.theme, strings: presentationData.strings, accountManager: authContext.sharedContext.accountManager, postbox: authContext.account.postbox, network: authContext.account.network, server: proxyData, presentationData: nil) + authContext.rootController.currentWindow?.present(controller, on: PresentationSurfaceLevel.root, blockInteraction: false, completion: {}) + } else if let secureIdData = parseSecureIdUrl(url) { + let presentationData = authContext.sharedContext.currentPresentationData.with { $0 } + authContext.rootController.currentWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Passport_NotLoggedInMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Calls_NotNow, action: { + if let callbackUrl = URL(string: secureIdCallbackUrl(with: secureIdData.callbackUrl, peerId: secureIdData.peerId, result: .cancel, parameters: [:])) { + UIApplication.shared.openURL(callbackUrl) + } + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), on: .root, blockInteraction: false, completion: {}) + } else if let confirmationCode = parseConfirmationCodeUrl(url) { + authContext.rootController.applyConfirmationCode(confirmationCode) + } } }) } @@ -1345,8 +1418,8 @@ private enum QueuedWakeup: Int32 { if let contact = startCallIntent.contacts?.first { if let handle = contact.personHandle?.value { if let userId = Int32(handle) { - if let contextValue = self.contextValue, case let .authorized(context) = contextValue { - let _ = context.context.callManager?.requestCall(account: context.context.account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), endCurrentIfAny: false) + if let context = self.contextValue { + let _ = context.context.sharedContext.callManager?.requestCall(account: context.context.account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), endCurrentIfAny: false) } } } @@ -1364,25 +1437,21 @@ private enum QueuedWakeup: Int32 { @available(iOS 9.0, *) func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { let _ = (self.context.get() - |> mapToSignal { value -> Signal in - if let value = value { - if case let .authorized(context) = value { - return context.unlockedState - |> filter { $0 } - |> take(1) - |> map { _ -> ApplicationContext? in - return value - } - } else { - return .single(nil) + |> mapToSignal { context -> Signal in + if let context = context { + return context.unlockedState + |> filter { $0 } + |> take(1) + |> map { _ -> AuthorizedApplicationContext? in + return context } } else { return .complete() } } |> take(1) - |> deliverOnMainQueue).start(next: { contextValue in - if let contextValue = contextValue, case let .authorized(context) = contextValue { + |> deliverOnMainQueue).start(next: { context in + if let context = context { if let type = ApplicationShortcutItemType(rawValue: shortcutItem.type) { switch type { case .search: @@ -1402,9 +1471,9 @@ private enum QueuedWakeup: Int32 { private func openChatWhenReady(accountId: AccountRecordId?, peerId: PeerId, messageId: MessageId? = nil) { let signal = self.sharedContextPromise.get() |> take(1) - |> mapToSignal { sharedContext -> Signal in + |> mapToSignal { sharedApplicationContext -> Signal in if let accountId = accountId { - sharedContext.switchToAccount(id: accountId) + sharedApplicationContext.sharedContext.switchToAccount(id: accountId) return self.authorizedContext() |> filter { context in context.context.account.id == accountId @@ -1425,7 +1494,7 @@ private enum QueuedWakeup: Int32 { self.openUrlWhenReadyDisposable.set((self.authorizedContext() |> take(1) |> deliverOnMainQueue).start(next: { context in - let presentationData = context.context.currentPresentationData.with { $0 } + let presentationData = context.context.sharedContext.currentPresentationData.with { $0 } openExternalUrl(context: context.context, url: url, presentationData: presentationData, navigationController: context.rootController, dismissInput: { }) })) @@ -1511,7 +1580,7 @@ private enum QueuedWakeup: Int32 { } private func registerForNotifications(context: AccountContext, authorize: Bool = true, completion: @escaping (Bool) -> Void = { _ in }) { - let presentationData = context.currentPresentationData.with { $0 } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } let _ = (context.sharedContext.accountManager.transaction { transaction -> Bool in let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.inAppNotificationSettings) as? InAppNotificationSettings ?? InAppNotificationSettings.defaultSettings return settings.displayNameOnLockscreen @@ -1582,8 +1651,8 @@ private enum QueuedWakeup: Int32 { } } - private func maybeDequeueNotificationPayloads() { - if let contextValue = self.contextValue, case let .authorized(context) = contextValue, !self.queuedNotifications.isEmpty { + /*private func maybeDequeueNotificationPayloads() { + if let context = self.contextValue, !self.queuedNotifications.isEmpty { let queuedNotifications = self.queuedNotifications self.queuedNotifications = [] for payload in queuedNotifications { @@ -1593,7 +1662,7 @@ private enum QueuedWakeup: Int32 { } private func maybeDequeueNotificationRequests() { - if let contextValue = self.contextValue, case let .authorized(context) = contextValue { + if let context = self.contextValue { let requests = self.queuedNotificationRequests self.queuedNotificationRequests = [] let queuedMutePolling = self.queuedMutePolling @@ -1603,20 +1672,20 @@ private enum QueuedWakeup: Int32 { return transaction.getAccessChallengeData() }) |> deliverOnMainQueue).start(next: { accessChallengeData in - guard let contextValue = self.contextValue, case let .authorized(context) = contextValue else { + guard let context = self.contextValue else { Logger.shared.log("App \(self.episodeId)", "Couldn't process remote notification request") return } - let strings = context.context.currentPresentationData.with({ $0 }).strings + let strings = context.context.sharedContext.currentPresentationData.with({ $0 }).strings for (title, body, apnsSound, requestId) in requests { if handleVoipNotifications { - context.notificationManager.enqueueRemoteNotification(title: title, text: body, apnsSound: apnsSound, requestId: requestId, strings: strings, accessChallengeData: accessChallengeData) + //context.notificationManager.enqueueRemoteNotification(title: title, text: body, apnsSound: apnsSound, requestId: requestId, strings: strings, accessChallengeData: accessChallengeData) } - context.wakeupManager.wakeupForIncomingMessages(account: context.context.account, completion: { messageIds -> Signal in - if let contextValue = self.contextValue, case let .authorized(context) = contextValue { + /*context.wakeupManager.wakeupForIncomingMessages(account: context.context.account, completion: { messageIds -> Signal in + if let context = self.contextValue { if handleVoipNotifications { return context.notificationManager.commitRemoteNotification(context: context.context, originalRequestId: requestId, messageIds: messageIds) } else { @@ -1626,17 +1695,17 @@ private enum QueuedWakeup: Int32 { Logger.shared.log("App \(self.episodeId)", "Couldn't process remote notifications wakeup result") return .complete() } - }) + })*/ } if queuedMutePolling { - context.wakeupManager.wakeupForIncomingMessages(account: context.context.account, completion: { messageIds -> Signal in - if let contextValue = self.contextValue, case .authorized = contextValue { + /*context.wakeupManager.wakeupForIncomingMessages(account: context.context.account, completion: { messageIds -> Signal in + if let context = self.contextValue { return .single(Void()) } else { Logger.shared.log("App \(self.episodeId)", "Couldn't process remote notifications wakeup result") return .single(Void()) } - }) + })*/ } }) } else { @@ -1645,7 +1714,7 @@ private enum QueuedWakeup: Int32 { } private func maybeDequeueAnnouncements() { - if let contextValue = self.contextValue, case let .authorized(context) = contextValue, !self.queuedAnnouncements.isEmpty { + if let context = self.contextValue, !self.queuedAnnouncements.isEmpty { let queuedAnnouncements = self.queuedAnnouncements self.queuedAnnouncements = [] let _ = (context.context.account.postbox.transaction(ignoreDisabled: true, { transaction -> [MessageId: String] in @@ -1672,9 +1741,9 @@ private enum QueuedWakeup: Int32 { } return result }) |> deliverOnMainQueue).start(next: { result in - if let contextValue = self.contextValue, case let .authorized(context) = contextValue { + if let context = self.contextValue { for (id, text) in result { - context.notificationManager.enqueueRemoteNotification(title: "", text: text, apnsSound: nil, requestId: .messageId(id), strings: context.context.currentPresentationData.with({ $0 }).strings, accessChallengeData: .none) + //context.notificationManager.enqueueRemoteNotification(title: "", text: text, apnsSound: nil, requestId: .messageId(id), strings: context.context.sharedContext.currentPresentationData.with({ $0 }).strings, accessChallengeData: .none) } } }) @@ -1685,12 +1754,12 @@ private enum QueuedWakeup: Int32 { for wakeup in self.queuedWakeups { switch wakeup { case .call: - if let contextValue = self.contextValue, case let .authorized(context) = contextValue { - context.wakeupManager.wakeupForIncomingMessages(account: context.context.account) + if let context = self.contextValue { + //context.wakeupManager.wakeupForIncomingMessages(account: context.context.account) } case .backgroundLocation: if UIApplication.shared.applicationState == .background { - if let contextValue = self.contextValue, case let .authorized(context) = contextValue { + if let context = self.contextValue { context.context.liveLocationManager?.pollOnce() } } @@ -1698,19 +1767,19 @@ private enum QueuedWakeup: Int32 { } self.queuedWakeups.removeAll() - } + }*/ @available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - #if DEBUG - completionHandler([.alert]) - #else - completionHandler([]) - #endif + if let context = self.contextValue { + if let accountId = accountIdFromNotification(notification), context.context.account.id != accountId { + completionHandler([.alert]) + } + } } override var next: UIResponder? { - if let contextValue = self.contextValue, case let .authorized(context) = contextValue, let controller = context.context.keyShortcutsController { + if let context = self.contextValue, let controller = context.context.keyShortcutsController { return controller } return super.next diff --git a/Telegram-iOS/ApplicationContext.swift b/Telegram-iOS/ApplicationContext.swift index e8cf63aecd..0dbba5a865 100644 --- a/Telegram-iOS/ApplicationContext.swift +++ b/Telegram-iOS/ApplicationContext.swift @@ -87,29 +87,6 @@ private struct PasscodeState: Equatable { let enableBiometrics: Bool } -private enum CallStatusText: Equatable { - case none - case inProgress(Double?) - - static func ==(lhs: CallStatusText, rhs: CallStatusText) -> Bool { - switch lhs { - case .none: - if case .none = rhs { - return true - } else { - return false - } - case let .inProgress(lhsReferenceTime): - if case let .inProgress(rhsReferenceTime) = rhs, lhsReferenceTime == rhsReferenceTime { - return true - } else { - return false - } - - } - } -} - final class AuthorizedApplicationContext { let mainWindow: Window1 let lockedCoveringView: LockedWindowCoveringView @@ -125,8 +102,7 @@ final class AuthorizedApplicationContext { private var scheduledOperChatWithPeerId: PeerId? private var scheduledOpenExternalUrl: URL? - let wakeupManager: WakeupManager - let notificationManager: NotificationManager + //let notificationManager: NotificationManager private let passcodeStatusDisposable = MetaDisposable() private let passcodeLockDisposable = MetaDisposable() @@ -142,9 +118,6 @@ final class AuthorizedApplicationContext { private var isLocked: Bool = true private var passcodeController: ViewController? - private var callController: CallController? - private let hasOngoingCall = ValuePromise(false) - private let callState = Promise(nil) private var currentTermsOfServiceUpdate: TermsOfServiceUpdate? private var currentPermissionsController: PermissionController? @@ -166,10 +139,6 @@ final class AuthorizedApplicationContext { private var presentationDataDisposable: Disposable? private var displayAlertsDisposable: Disposable? private var removeNotificationsDisposable: Disposable? - private var callDisposable: Disposable? - private var callStateDisposable: Disposable? - private var currentCallStatusText: CallStatusText = .none - private var currentCallStatusTextTimer: SwiftSignalKit.Timer? private var applicationInForegroundDisposable: Disposable? @@ -179,7 +148,7 @@ final class AuthorizedApplicationContext { init(mainWindow: Window1, replyFromNotificationsActive: Signal, backgroundAudioActive: Signal, watchManagerArguments: Signal, context: AccountContext, accountManager: AccountManager, legacyBasePath: String, showCallsTab: Bool, reinitializedNotificationSettings: @escaping () -> Void) { setupLegacyComponents(context: context) - let presentationData = context.currentPresentationData.with { $0 } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.mainWindow = mainWindow self.lockedCoveringView = LockedWindowCoveringView(theme: presentationData.theme) @@ -207,29 +176,18 @@ final class AuthorizedApplicationContext { } |> distinctUntilChanged - self.wakeupManager = WakeupManager(accountManager: context.sharedContext.accountManager, inForeground: context.sharedContext.applicationBindings.applicationInForeground, runningServiceTasks: context.account.importantTasksRunning, runningBackgroundLocationTasks: runningBackgroundLocationTasks, runningWatchTasks: runningWatchTasksPromise.get(), runningDownloadTasks: runningDownloadTasks) + //self.wakeupManager = WakeupManager(accountManager: context.sharedContext.accountManager, inForeground: context.sharedContext.applicationBindings.applicationInForeground, runningServiceTasks: context.account.importantTasksRunning, runningBackgroundLocationTasks: runningBackgroundLocationTasks, runningWatchTasks: runningWatchTasksPromise.get(), runningDownloadTasks: runningDownloadTasks) self.showCallsTab = showCallsTab - self.notificationManager = NotificationManager() - self.notificationManager.isApplicationInForeground = false + //self.notificationManager = NotificationManager() + //self.notificationManager.isApplicationInForeground = false self.overlayMediaController = OverlayMediaController() context.attachOverlayMediaController(self.overlayMediaController) - var presentImpl: ((ViewController, Any?) -> Void)? - var openSettingsImpl: (() -> Void)? - let callManager = PresentationCallManager(accountManager: context.sharedContext.accountManager, account: context.account, getDeviceAccessData: { - return (context.currentPresentationData.with { $0 }, { c, a in - presentImpl?(c, a) - }, { - openSettingsImpl?() - }) - }, networkType: context.account.networkType, audioSession: context.sharedContext.mediaManager.audioSession, activeAccounts: .single([context.account])) - context.callManager = callManager - context.hasOngoingCall = self.hasOngoingCall.get() - let shouldBeServiceTaskMaster = combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.wakeupManager.isWokenUp, replyFromNotificationsActive, backgroundAudioActive, callManager.hasActiveCalls) + /*let shouldBeServiceTaskMaster = combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.wakeupManager.isWokenUp, replyFromNotificationsActive, backgroundAudioActive, callManager.hasActiveCalls) |> map { foreground, wokenUp, replyFromNotificationsActive, backgroundAudioActive, hasActiveCalls -> AccountServiceTaskMasterMode in if foreground || wokenUp || replyFromNotificationsActive || hasActiveCalls { return .always @@ -256,10 +214,8 @@ final class AuthorizedApplicationContext { Logger.shared.log("ApplicationContext", "setting canBeginTransactions to \(next)") context.account.postbox.setCanBeginTransactions(next) } - }) + })*/ context.account.shouldExplicitelyKeepWorkerConnections.set(backgroundAudioActive) - context.account.shouldKeepBackgroundDownloadConnections.set(context.fetchManager.hasUserInitiatedEntries) - context.account.shouldKeepOnlinePresence.set(context.sharedContext.applicationBindings.applicationInForeground) let cache = TGCache(cachesPath: legacyBasePath + "/Caches")! @@ -295,56 +251,11 @@ final class AuthorizedApplicationContext { context.keyShortcutsController = keyShortcutsController } - self.applicationInForegroundDisposable = context.sharedContext.applicationBindings.applicationInForeground.start(next: { [weak self] value in + /*self.applicationInForegroundDisposable = context.sharedContext.applicationBindings.applicationInForeground.start(next: { [weak self] value in Queue.mainQueue().async { self?.notificationManager.isApplicationInForeground = value } - }) - - self.mainWindow.inCallNavigate = { [weak self] in - if let strongSelf = self, let callController = strongSelf.callController { - if callController.isNodeLoaded && callController.view.superview == nil { - strongSelf.rootController.view.endEditing(true) - strongSelf.mainWindow.present(callController, on: .calls) - } - } - } - - context.presentGlobalController = { [weak self] c, a in - self?.mainWindow.present(c, on: .root) - } - context.presentCrossfadeController = { [weak self] in - guard let strongSelf = self else { - return - } - var exists = false - strongSelf.mainWindow.forEachViewController { controller in - if controller is ThemeSettingsCrossfadeController { - exists = true - } - return true - } - - if !exists { - mainWindow.present(ThemeSettingsCrossfadeController(), on: .root) - } - } - - context.navigateToCurrentCall = { [weak self] in - if let strongSelf = self, let callController = strongSelf.callController { - if callController.isNodeLoaded && callController.view.superview == nil { - strongSelf.rootController.view.endEditing(true) - strongSelf.mainWindow.present(callController, on: .calls) - } - } - } - - presentImpl = { [weak self] c, _ in - self?.mainWindow.present(c, on: .root) - } - openSettingsImpl = { [weak context] in - context?.sharedContext.applicationBindings.openSettings() - } + })*/ let previousPasscodeState = Atomic(value: nil) @@ -400,7 +311,7 @@ final class AuthorizedApplicationContext { } strongSelf.isLocked = isLocked - strongSelf.notificationManager.isApplicationLocked = isLocked + //strongSelf.notificationManager.isApplicationLocked = isLocked if isLocked { if updatedState.isActive { @@ -418,7 +329,7 @@ final class AuthorizedApplicationContext { case .plaintextPassword: mode = TGPasscodeEntryControllerModeVerifyComplex } - let presentationData = strongSelf.context.currentPresentationData.with { $0 } + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let presentAnimated = previousState != nil && previousState!.isActive let legacyController = LegacyController(presentation: LegacyControllerPresentation.modal(animateIn: presentAnimated), theme: presentationData.theme) let controller = TGPasscodeEntryController(context: legacyController.context, style: TGPasscodeEntryControllerStyleDefault, mode: mode, cancelEnabled: false, allowTouchId: updatedState.enableBiometrics, attemptData: attemptData, completion: { value in @@ -543,7 +454,7 @@ final class AuthorizedApplicationContext { if #available(iOS 10.0, *) { } else { - DeviceAccess.authorizeAccess(to: .contacts, presentationData: strongSelf.context.currentPresentationData.with { $0 }, present: { c, a in + DeviceAccess.authorizeAccess(to: .contacts, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { c, a in }, openSettings: {}, { _ in }) } @@ -657,7 +568,7 @@ final class AuthorizedApplicationContext { } if inAppNotificationSettings.displayPreviews { - let presentationData = strongSelf.context.currentPresentationData.with { $0 } + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } strongSelf.notificationController.enqueue(ChatMessageNotificationItem(context: strongSelf.context, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, messages: messages, tapAction: { if let strongSelf = self { var foundOverlay = false @@ -713,7 +624,7 @@ final class AuthorizedApplicationContext { strongSelf.currentTermsOfServiceUpdate = termsOfServiceUpdate if let termsOfServiceUpdate = termsOfServiceUpdate { - let presentationData = strongSelf.context.currentPresentationData.with { $0 } + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } var acceptImpl: ((String?) -> Void)? var declineImpl: (() -> Void)? let controller = TermsOfServiceController(theme: TermsOfServiceControllerTheme(presentationTheme: presentationData.theme), strings: presentationData.strings, text: termsOfServiceUpdate.text, entities: termsOfServiceUpdate.entities, ageConfirmation: termsOfServiceUpdate.ageConfirmation, signingUp: false, accept: { proccedBot in @@ -892,7 +803,7 @@ final class AuthorizedApplicationContext { |> deliverOnMainQueue).start(next: { [weak self] alerts in if let strongSelf = self{ for text in alerts { - let presentationData = strongSelf.context.currentPresentationData.with { $0 } + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) (strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root)) } @@ -906,84 +817,12 @@ final class AuthorizedApplicationContext { } }) - self.callDisposable = (callManager.currentCallSignal - |> deliverOnMainQueue).start(next: { [weak self] call in - if let strongSelf = self { - if call !== strongSelf.callController?.call { - strongSelf.callController?.dismiss() - strongSelf.callController = nil - strongSelf.hasOngoingCall.set(false) - - if let call = call { - let callController = CallController(context: strongSelf.context, call: call) - strongSelf.callController = callController - strongSelf.rootController.view?.endEditing(true) - strongSelf.mainWindow.present(callController, on: .calls) - strongSelf.callState.set(call.state - |> map(Optional.init)) - strongSelf.hasOngoingCall.set(true) - strongSelf.notificationManager.setNotificationCall(call, strings: strongSelf.context.currentPresentationData.with({ $0 }).strings) - } else { - strongSelf.callState.set(.single(nil)) - strongSelf.hasOngoingCall.set(false) - strongSelf.notificationManager.setNotificationCall(nil, strings: strongSelf.context.currentPresentationData.with({ $0 }).strings) - } - } - } - }) - - self.callStateDisposable = (self.callState.get() - |> deliverOnMainQueue).start(next: { [weak self] state in - if let strongSelf = self { - let resolvedText: CallStatusText - if let state = state { - switch state { - case .connecting, .requesting, .terminating, .ringing, .waiting: - resolvedText = .inProgress(nil) - case .terminated: - resolvedText = .none - case let .active(timestamp, _, _): - resolvedText = .inProgress(timestamp) - } - } else { - resolvedText = .none - } - - if strongSelf.currentCallStatusText != resolvedText { - strongSelf.currentCallStatusText = resolvedText - - var referenceTimestamp: Double? - if case let .inProgress(timestamp) = resolvedText, let concreteTimestamp = timestamp { - referenceTimestamp = concreteTimestamp - } - - if let _ = referenceTimestamp { - if strongSelf.currentCallStatusTextTimer == nil { - let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { - if let strongSelf = self { - strongSelf.updateStatusBarText() - } - }, queue: Queue.mainQueue()) - strongSelf.currentCallStatusTextTimer = timer - timer.start() - } - } else { - strongSelf.currentCallStatusTextTimer?.invalidate() - strongSelf.currentCallStatusTextTimer = nil - } - - strongSelf.updateStatusBarText() - } - } - }) - self.context.account.resetStateManagement() - let contactSynchronizationPreferencesKey = PostboxViewKey.preferences(keys: Set([ApplicationSpecificPreferencesKeys.contactSynchronizationSettings])) let importableContacts = self.context.sharedContext.contactDataManager?.importable() ?? .single([:]) - self.context.account.importableContacts.set(self.context.account.postbox.combinedView(keys: [contactSynchronizationPreferencesKey]) - |> mapToSignal { preferences -> Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError> in - let settings: ContactSynchronizationSettings = ((preferences.views[contactSynchronizationPreferencesKey] as? PreferencesView)?.values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings) ?? .defaultSettings + self.context.account.importableContacts.set(self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]) + |> mapToSignal { sharedData -> Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError> in + let settings: ContactSynchronizationSettings = (sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings) ?? .defaultSettings if settings.synchronizeDeviceContacts { return importableContacts } else { @@ -992,7 +831,7 @@ final class AuthorizedApplicationContext { }) let previousTheme = Atomic(value: nil) - self.presentationDataDisposable = (context.presentationData + self.presentationDataDisposable = (context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { if previousTheme.swap(presentationData.theme) !== presentationData.theme { @@ -1051,42 +890,18 @@ final class AuthorizedApplicationContext { if chatIsVisible { navigateToMessage() } else { - let presentationData = strongSelf.context.currentPresentationData.with { $0 } + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.WatchRemote_AlertTitle, text: presentationData.strings.WatchRemote_AlertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.WatchRemote_AlertOpen, action:navigateToMessage)]) (strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root)) } } else { - strongSelf.notificationManager.presentWatchContinuityNotification(context: strongSelf.context, messageId: messageId) + //strongSelf.notificationManager.presentWatchContinuityNotification(context: strongSelf.context, messageId: messageId) } } })) }) } - private func updateStatusBarText() { - if case let .inProgress(timestamp) = self.currentCallStatusText { - let text: String - let presentationData = self.context.currentPresentationData.with { $0 } - if let timestamp = timestamp { - let duration = Int32(CFAbsoluteTimeGetCurrent() - timestamp) - let durationString: String - if duration > 60 * 60 { - durationString = String(format: "%02d:%02d:%02d", arguments: [duration / 3600, (duration / 60) % 60, duration % 60]) - } else { - durationString = String(format: "%02d:%02d", arguments: [(duration / 60) % 60, duration % 60]) - } - - text = presentationData.strings.Call_StatusBar(durationString).0 - } else { - text = presentationData.strings.Call_StatusBar("").0 - } - - self.mainWindow.setForceInCallStatusBar(text) - } else { - self.mainWindow.setForceInCallStatusBar(nil) - } - } - deinit { self.context.account.postbox.clearCaches() self.context.account.shouldKeepOnlinePresence.set(.single(false)) @@ -1099,9 +914,6 @@ final class AuthorizedApplicationContext { self.passcodeStatusDisposable.dispose() self.displayAlertsDisposable?.dispose() self.removeNotificationsDisposable?.dispose() - self.callDisposable?.dispose() - self.callStateDisposable?.dispose() - self.currentCallStatusTextTimer?.invalidate() self.presentationDataDisposable?.dispose() self.enablePostboxTransactionsDiposable?.dispose() self.termsOfServiceProceedToBotDisposable.dispose() @@ -1126,7 +938,7 @@ final class AuthorizedApplicationContext { func openUrl(_ url: URL) { if self.rootController.rootTabController != nil { - let presentationData = self.context.currentPresentationData.with { $0 } + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } openExternalUrl(context: self.context, url: url.absoluteString, presentationData: presentationData, navigationController: self.rootController, dismissInput: { [weak self] in self?.rootController.view.endEditing(true) }) diff --git a/Telegram-iOS/LegacyPreferencesImport.swift b/Telegram-iOS/LegacyPreferencesImport.swift index 500994470b..e5ca6287fc 100644 --- a/Telegram-iOS/LegacyPreferencesImport.swift +++ b/Telegram-iOS/LegacyPreferencesImport.swift @@ -476,15 +476,15 @@ func importLegacyPreferences(accountManager: AccountManager, account: TemporaryA return settings }) + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.contactSynchronizationSettings, { current in + var settings: ContactSynchronizationSettings = current as? ContactSynchronizationSettings ?? .defaultSettings + if let contactsInhibitSync = contactsInhibitSync, contactsInhibitSync { + settings.synchronizeDeviceContacts = false + } + return settings + }) + return account.postbox.transaction { transaction -> Void in - transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.contactSynchronizationSettings, { current in - var settings: ContactSynchronizationSettings = current as? ContactSynchronizationSettings ?? .defaultSettings - if let contactsInhibitSync = contactsInhibitSync, contactsInhibitSync { - settings.synchronizeDeviceContacts = false - } - return settings - }) - if let secretInlineBotsInitialized = secretInlineBotsInitialized, secretInlineBotsInitialized { ApplicationSpecificNotice.setSecretChatInlineBotUsage(transaction: transaction) } diff --git a/Telegram-iOS/NotificationManager.swift b/Telegram-iOS/NotificationManager.swift index e0e0bee9c3..29be3f22ea 100644 --- a/Telegram-iOS/NotificationManager.swift +++ b/Telegram-iOS/NotificationManager.swift @@ -323,7 +323,7 @@ final class NotificationManager { } private func processNotificationMessages(context: AccountContext, messageList: [([Message], PeerMessageSound, Bool, Bool)], isLocked: Bool) { - let presentationData = (context.currentPresentationData.with { $0 }) + let presentationData = (context.sharedContext.currentPresentationData.with { $0 }) let strings = presentationData.strings let nameDisplayOrder = presentationData.nameDisplayOrder @@ -801,7 +801,7 @@ final class NotificationManager { } } } - let presentationData = context.currentPresentationData.with { $0 } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } var userInfo: [AnyHashable : Any] = [:] userInfo["peerId"] = messageId.peerId.toInt64() diff --git a/Telegram-iOS/SharedNotificationManager.swift b/Telegram-iOS/SharedNotificationManager.swift new file mode 100644 index 0000000000..38e0d83db6 --- /dev/null +++ b/Telegram-iOS/SharedNotificationManager.swift @@ -0,0 +1,361 @@ +import Foundation +import SwiftSignalKit +import Postbox +import TelegramCore + +private final class PollStateContext { + let subscribers = Bag<(Bool) -> Void>() + var disposable: Disposable? + + deinit { + self.disposable?.dispose() + } + + var isEmpty: Bool { + return self.disposable == nil && self.subscribers.isEmpty + } +} + +final class SharedNotificationManager { + private let episodeId: UInt32 + + private let clearNotificationsManager: ClearNotificationsManager + + private var inForeground: Bool = false + private var inForegroundDisposable: Disposable? + + private var accountsAndKeys: [(Account, Bool, MasterNotificationKey)]? + private var accountsAndKeysDisposable: Disposable? + + private var encryptedNotifications: [[AnyHashable: Any]] = [] + + private var pollStateContexts: [AccountRecordId: PollStateContext] = [:] + + init(episodeId: UInt32, clearNotificationsManager: ClearNotificationsManager, inForeground: Signal, accounts: Signal<[(Account, Bool)], NoError>) { + assert(Queue.mainQueue().isCurrent()) + + self.episodeId = episodeId + self.clearNotificationsManager = clearNotificationsManager + + self.inForegroundDisposable = (inForeground + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.inForeground = value + }) + + self.accountsAndKeysDisposable = (accounts + |> mapToSignal { accounts -> Signal<[(Account, Bool, MasterNotificationKey)], NoError> in + let signals = accounts.map { account, isCurrent -> Signal<(Account, Bool, MasterNotificationKey), NoError> in + return masterNotificationsKey(account: account, ignoreDisabled: true) + |> map { key -> (Account, Bool, MasterNotificationKey) in + return (account, isCurrent, key) + } + } + return combineLatest(signals) + } + |> deliverOnMainQueue).start(next: { [weak self] accountsAndKeys in + guard let strongSelf = self else { + return + } + let shouldProcess = strongSelf.accountsAndKeys == nil + strongSelf.accountsAndKeys = accountsAndKeys + if shouldProcess { + strongSelf.process() + } + }) + } + + deinit { + self.inForegroundDisposable?.dispose() + self.accountsAndKeysDisposable?.dispose() + } + + func isPollingState(accountId: AccountRecordId) -> Signal { + return Signal { subscriber in + let context: PollStateContext + if let current = self.pollStateContexts[accountId] { + context = current + } else { + context = PollStateContext() + self.pollStateContexts[accountId] = context + } + subscriber.putNext(context.disposable != nil) + let index = context.subscribers.add({ value in + subscriber.putNext(value) + }) + + return ActionDisposable { [weak context] in + Queue.mainQueue().async { + if let current = self.pollStateContexts[accountId], current === context { + current.subscribers.remove(index) + if current.isEmpty { + self.pollStateContexts.removeValue(forKey: accountId) + } + } + } + } + } + } + + private func beginPollingState(account: Account) { + let accountId = account.id + let context: PollStateContext + if let current = self.pollStateContexts[accountId] { + context = current + } else { + context = PollStateContext() + self.pollStateContexts[accountId] = context + } + let previousDisposable = context.disposable + context.disposable = (account.stateManager.pollStateUpdateCompletion() + |> deliverOnMainQueue).start(next: { [weak self, weak context] _ in + guard let strongSelf = self else { + return + } + if let current = strongSelf.pollStateContexts[accountId], current === context { + if let disposable = current.disposable { + disposable.dispose() + current.disposable = nil + for f in current.subscribers.copyItems() { + f(false) + } + } + if current.isEmpty { + strongSelf.pollStateContexts.removeValue(forKey: accountId) + } + } + }) + previousDisposable?.dispose() + if previousDisposable == nil { + for f in context.subscribers.copyItems() { + f(true) + } + } + } + + func addEncryptedNotification(_ dict: [AnyHashable: Any]) { + self.encryptedNotifications.append(dict) + + if self.accountsAndKeys != nil { + self.process() + } + } + + private func process() { + guard let accountsAndKeys = self.accountsAndKeys else { + return + } + var decryptedNotifications: [(Account, Bool, [AnyHashable: Any])] = [] + for dict in self.encryptedNotifications { + if var encryptedPayload = dict["p"] as? String { + encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+") + encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/") + while encryptedPayload.count % 4 != 0 { + encryptedPayload.append("=") + } + if let data = Data(base64Encoded: encryptedPayload) { + inner: for (account, isCurrent, key) in accountsAndKeys { + if let decryptedData = decryptedNotificationPayload(key: key, data: data) { + if let decryptedDict = (try? JSONSerialization.jsonObject(with: decryptedData, options: [])) as? [AnyHashable: Any] { + decryptedNotifications.append((account, isCurrent, decryptedDict)) + } + break inner + } + } + } + } + } + self.encryptedNotifications.removeAll() + + for (account, isCurrent, payload) in decryptedNotifications { + var redactedPayload = payload + if var aps = redactedPayload["aps"] as? [AnyHashable: Any] { + if Logger.shared.redactSensitiveData { + if aps["alert"] != nil { + aps["alert"] = "[[redacted]]" + } + if aps["body"] != nil { + aps["body"] = "[[redacted]]" + } + } + redactedPayload["aps"] = aps + } + Logger.shared.log("Apns \(self.episodeId)", "\(redactedPayload)") + + let aps = payload["aps"] as? [AnyHashable: Any] + + var readMessageId: MessageId? + var isCall = false + var isAnnouncement = false + var isLocationPolling = false + var notificationRequestId: NotificationManagedNotificationRequestId? + var isMutePolling = false + var title: String = "" + var body: String? + var apnsSound: String? + var configurationUpdate: (Int32, String, Int32, Data?)? + if let aps = aps, let alert = aps["alert"] as? String { + if let range = alert.range(of: ": ") { + title = String(alert[.. Void in + transaction.applyIncomingReadMaxId(readMessageId) + }).start() + } + + if let (datacenterId, host, port, secret) = configurationUpdate { + account.network.mergeBackupDatacenterAddress(datacenterId: datacenterId, host: host, port: port, secret: secret) + } + } + } +} diff --git a/Telegram-iOS/SharedWakeupManager.swift b/Telegram-iOS/SharedWakeupManager.swift new file mode 100644 index 0000000000..95b97d5014 --- /dev/null +++ b/Telegram-iOS/SharedWakeupManager.swift @@ -0,0 +1,184 @@ +import Foundation +import UIKit +import SwiftSignalKit +import Postbox +import TelegramCore + +private struct AccountTasks { + let stateSynchronization: Bool + let importantTasks: AccountRunningImportantTasks + let backgroundLocation: Bool + let backgroundDownloads: Bool + + var isEmpty: Bool { + if self.stateSynchronization { + return false + } + if !self.importantTasks.isEmpty { + return false + } + if self.backgroundLocation { + return false + } + if self.backgroundDownloads { + return false + } + return true + } +} + +final class SharedWakeupManager { + private var inForeground: Bool = false + private var hasActiveAudioSession: Bool = false + private var allowBackgroundTimeExtensionDeadline: Double? + private var isInBackgroundExtension: Bool = false + + private var inForegroundDisposable: Disposable? + private var hasActiveAudioSessionDisposable: Disposable? + private var tasksDisposable: Disposable? + private var currentTask: (UIBackgroundTaskIdentifier, Double, SwiftSignalKit.Timer)? + + private var accountsAndTasks: [(Account, Bool, AccountTasks)] = [] + + init(activeAccounts: Signal<(primary: Account?, accounts: [AccountRecordId: Account]), NoError>, inForeground: Signal, hasActiveAudioSession: Signal, notificationManager: SharedNotificationManager) { + assert(Queue.mainQueue().isCurrent()) + + self.inForegroundDisposable = (inForeground + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.inForeground = value + strongSelf.checkTasks() + }) + + self.hasActiveAudioSessionDisposable = (hasActiveAudioSession + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.hasActiveAudioSession = value + strongSelf.checkTasks() + }) + + self.tasksDisposable = (activeAccounts + |> deliverOnMainQueue + |> mapToSignal { primary, accounts -> Signal<[(Account, Bool, AccountTasks)], NoError> in + let signals: [Signal<(Account, Bool, AccountTasks), NoError>] = accounts.values.map { account in + return combineLatest(queue: .mainQueue(), account.importantTasksRunning, notificationManager.isPollingState(accountId: account.id)) + |> map { importantTasksRunning, isPollingState -> (Account, Bool, AccountTasks) in + return (account, primary?.id == account.id, AccountTasks(stateSynchronization: isPollingState, importantTasks: importantTasksRunning, backgroundLocation: false, backgroundDownloads: false)) + } + } + return combineLatest(signals) + } + |> deliverOnMainQueue).start(next: { [weak self] accountsAndTasks in + guard let strongSelf = self else { + return + } + strongSelf.accountsAndTasks = accountsAndTasks + strongSelf.checkTasks() + }) + } + + deinit { + self.inForegroundDisposable?.dispose() + self.hasActiveAudioSessionDisposable?.dispose() + self.tasksDisposable?.dispose() + if let (taskId, _, timer) = self.currentTask { + timer.invalidate() + UIApplication.shared.endBackgroundTask(taskId) + } + } + + func allowBackgroundTimeExtension(timeout: Double) { + let shouldCheckTasks = self.allowBackgroundTimeExtensionDeadline == nil + self.allowBackgroundTimeExtensionDeadline = CACurrentMediaTime() + timeout + if shouldCheckTasks { + self.checkTasks() + } + } + + func checkTasks() { + if self.inForeground || self.hasActiveAudioSession { + if let (taskId, _, timer) = self.currentTask { + self.currentTask = nil + timer.invalidate() + UIApplication.shared.endBackgroundTask(taskId) + self.isInBackgroundExtension = false + } + } else { + var hasTasksForBackgroundExtension = false + for (_, _, tasks) in self.accountsAndTasks { + if !tasks.isEmpty { + hasTasksForBackgroundExtension = true + break + } + } + + let canBeginBackgroundExtensionTasks = self.allowBackgroundTimeExtensionDeadline.flatMap({ CACurrentMediaTime() < $0 }) ?? false + if hasTasksForBackgroundExtension { + if canBeginBackgroundExtensionTasks { + var endTaskId: UIBackgroundTaskIdentifier? + + let currentTime = CACurrentMediaTime() + if let (taskId, startTime, timer) = self.currentTask { + if startTime < currentTime + 1.0 { + self.currentTask = nil + timer.invalidate() + endTaskId = taskId + } + } + + if self.currentTask == nil { + let handleExpiration:() -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.isInBackgroundExtension = false + strongSelf.checkTasks() + } + let taskId = UIApplication.shared.beginBackgroundTask(withName: "background-wakeup", expirationHandler: { + handleExpiration() + }) + let timer = SwiftSignalKit.Timer(timeout: min(30.0, UIApplication.shared.backgroundTimeRemaining), repeat: false, completion: { + handleExpiration() + }, queue: Queue.mainQueue()) + self.currentTask = (taskId, currentTime, timer) + timer.start() + + endTaskId.flatMap(UIApplication.shared.endBackgroundTask) + + self.isInBackgroundExtension = true + } + } + } else if let (taskId, _, timer) = self.currentTask { + self.currentTask = nil + timer.invalidate() + UIApplication.shared.endBackgroundTask(taskId) + self.isInBackgroundExtension = false + } + } + self.updateAccounts() + } + + private func updateAccounts() { + if self.inForeground || self.hasActiveAudioSession || self.isInBackgroundExtension { + for (account, primary, tasks) in self.accountsAndTasks { + if primary || !tasks.isEmpty { + account.shouldBeServiceTaskMaster.set(.single(.always)) + } else { + account.shouldBeServiceTaskMaster.set(.single(.never)) + } + account.shouldKeepOnlinePresence.set(.single(primary && self.inForeground)) + account.shouldKeepBackgroundDownloadConnections.set(.single(tasks.backgroundDownloads)) + } + } else { + for (account, _, _) in self.accountsAndTasks { + account.shouldBeServiceTaskMaster.set(.single(.never)) + account.shouldKeepOnlinePresence.set(.single(false)) + account.shouldKeepBackgroundDownloadConnections.set(.single(false)) + } + } + } +} diff --git a/Telegram-iOS/WatchCommunicationManager.swift b/Telegram-iOS/WatchCommunicationManager.swift index ae07043f86..5d4c42fe67 100644 --- a/Telegram-iOS/WatchCommunicationManager.swift +++ b/Telegram-iOS/WatchCommunicationManager.swift @@ -15,7 +15,7 @@ final class WatchCommunicationManager { private let presets = Promise(nil) private let navigateToMessagePipe = ValuePipe() - init(queue: Queue, context: Promise) { + init(queue: Queue, context: Promise) { self.queue = queue let handlers = allWatchRequestHandlers.reduce([String : AnyClass]()) { (map, handler) -> [String : AnyClass] in @@ -57,7 +57,7 @@ final class WatchCommunicationManager { guard let strongSelf = self, appInstalled else { return } - if let appContext = appContext, case let .authorized(context) = appContext { + if let context = appContext { strongSelf.accountContext.set(.single(context.context)) strongSelf.server.setAuthorized(true, userId: context.context.account.peerId.id) strongSelf.server.setMicAccessAllowed(false) @@ -79,10 +79,10 @@ final class WatchCommunicationManager { })) self.presetsDisposable.set((combineLatest(self.watchAppInstalled, self.presets.get() |> distinctUntilChanged |> deliverOn(self.queue), context.get() |> deliverOn(self.queue))).start(next: { [weak self] appInstalled, presets, appContext in - guard let strongSelf = self, let presets = presets, let appContext = appContext, case let .authorized(context) = appContext, appInstalled, let tempPath = strongSelf.watchTemporaryStorePath else { + guard let strongSelf = self, let presets = presets, let context = appContext, appInstalled, let tempPath = strongSelf.watchTemporaryStorePath else { return } - let presentationData = context.context.currentPresentationData.with { $0 } + let presentationData = context.context.sharedContext.currentPresentationData.with { $0 } let defaultSuggestions: [String : String] = [ "OK": presentationData.strings.Watch_Suggestion_OK, "Thanks": presentationData.strings.Watch_Suggestion_Thanks, @@ -173,7 +173,7 @@ final class WatchCommunicationManager { } } -func watchCommunicationManager(context: Promise) -> Signal { +func watchCommunicationManager(context: Promise) -> Signal { return Signal { subscriber in let queue = Queue() queue.async { diff --git a/Telegram-iOS/WatchRequestHandlers.swift b/Telegram-iOS/WatchRequestHandlers.swift index ebb616435b..fb19aae68d 100644 --- a/Telegram-iOS/WatchRequestHandlers.swift +++ b/Telegram-iOS/WatchRequestHandlers.swift @@ -39,7 +39,7 @@ final class WatchChatListHandler: WatchRequestHandler { if let context = context { return context.account.viewTracker.tailChatListView(groupId: nil, count: limit) |> map { chatListView, _ -> (ChatListView, PresentationData) in - return (chatListView, context.currentPresentationData.with { $0 }) + return (chatListView, context.sharedContext.currentPresentationData.with { $0 }) } } else { return .complete() @@ -87,7 +87,7 @@ final class WatchChatMessagesHandler: WatchRequestHandler { if let context = context { return context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: limit, fixedCombinedReadStates: nil) |> map { messageHistoryView, _, _ -> (MessageHistoryView, Bool, PresentationData) in - return (messageHistoryView, peerId == context.account.peerId, context.currentPresentationData.with { $0 }) + return (messageHistoryView, peerId == context.account.peerId, context.sharedContext.currentPresentationData.with { $0 }) } } else { return .complete() @@ -140,7 +140,7 @@ final class WatchChatMessagesHandler: WatchRequestHandler { let messageSignal = downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: args.messageId)) |> map { message -> (Message, PresentationData)? in if let message = message { - return (message, context.currentPresentationData.with { $0 }) + return (message, context.sharedContext.currentPresentationData.with { $0 }) } else { return nil } diff --git a/submodules/Display b/submodules/Display index 709df1faef..076af3926c 160000 --- a/submodules/Display +++ b/submodules/Display @@ -1 +1 @@ -Subproject commit 709df1faeffdd6fefc72bb8a1853b65a5e803bdc +Subproject commit 076af3926c4a5d55bcad4ffece41464059c891d9 diff --git a/submodules/Postbox b/submodules/Postbox index 88bc22e119..57821c9c36 160000 --- a/submodules/Postbox +++ b/submodules/Postbox @@ -1 +1 @@ -Subproject commit 88bc22e1192569ae4c0111b6d371119a87a91f08 +Subproject commit 57821c9c362f56e8ea652b51086fda516e791999 diff --git a/submodules/TelegramCore b/submodules/TelegramCore index 84231fb6fc..3e4cc1a26f 160000 --- a/submodules/TelegramCore +++ b/submodules/TelegramCore @@ -1 +1 @@ -Subproject commit 84231fb6fcbb39f72b50b2812c728cab8c659b52 +Subproject commit 3e4cc1a26fb677682de98117cf4c918331f5160d diff --git a/submodules/TelegramUI b/submodules/TelegramUI index 0d8163db0b..867d73ba99 160000 --- a/submodules/TelegramUI +++ b/submodules/TelegramUI @@ -1 +1 @@ -Subproject commit 0d8163db0b4d763090b44e3df750ae6b4632ff31 +Subproject commit 867d73ba99f1caac56d997762415948de633ca8d