Wakeup for watch tasks

This commit is contained in:
Peter 2019-02-19 00:06:46 +03:00
parent c481383e05
commit b2962b0dce
9 changed files with 126 additions and 53 deletions

View File

@ -13,7 +13,7 @@ private final class SharedExtensionContext {
init(sharedContext: SharedAccountContext) { init(sharedContext: SharedAccountContext) {
self.sharedContext = sharedContext self.sharedContext = sharedContext
self.wakeupManager = SharedWakeupManager(beginBackgroundTask: { _, _ in nil }, endBackgroundTask: { _ in }, backgroundTimeRemaining: { 0.0 }, activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) }, liveLocationPolling: .single(nil), inForeground: inForeground.get(), hasActiveAudioSession: .single(false), notificationManager: nil, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in self.wakeupManager = SharedWakeupManager(beginBackgroundTask: { _, _ in nil }, endBackgroundTask: { _ in }, backgroundTimeRemaining: { 0.0 }, activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) }, liveLocationPolling: .single(nil), watchTasks: .single(nil), inForeground: inForeground.get(), hasActiveAudioSession: .single(false), notificationManager: nil, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in
return sharedContext.accountUserInterfaceInUse(id) return sharedContext.accountUserInterfaceInUse(id)
}) })
} }

View File

@ -661,7 +661,25 @@ private final class SharedApplicationContext {
return .single(nil) return .single(nil)
} }
} }
let wakeupManager = SharedWakeupManager(beginBackgroundTask: { name, expiration in application.beginBackgroundTask(withName: name, expirationHandler: expiration) }, endBackgroundTask: { id in application.endBackgroundTask(id) }, backgroundTimeRemaining: { application.backgroundTimeRemaining }, activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) }, liveLocationPolling: liveLocationPolling, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in let watchTasks = self.context.get()
|> mapToSignal { context -> Signal<AccountRecordId?, NoError> in
if let context = context, let watchManager = context.context.watchManager {
let accountId = context.context.account.id
return watchManager.runningTasks
|> distinctUntilChanged
|> map { value -> AccountRecordId? in
if let value = value, value.running {
return accountId
} else {
return nil
}
}
|> distinctUntilChanged
} else {
return .single(nil)
}
}
let wakeupManager = SharedWakeupManager(beginBackgroundTask: { name, expiration in application.beginBackgroundTask(withName: name, expirationHandler: expiration) }, endBackgroundTask: { id in application.endBackgroundTask(id) }, backgroundTimeRemaining: { application.backgroundTimeRemaining }, activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) }, liveLocationPolling: liveLocationPolling, watchTasks: watchTasks, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in
return sharedContext.accountUserInterfaceInUse(id) return sharedContext.accountUserInterfaceInUse(id)
}) })
let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: notificationManager, wakeupManager: wakeupManager) let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: notificationManager, wakeupManager: wakeupManager)
@ -891,6 +909,29 @@ private final class SharedApplicationContext {
} }
}) })
} }
self.mainWindow.forEachViewController({ controller in
if let controller = controller as? TabBarAccountSwitchController {
var dismissed = false
if let rootController = self.mainWindow.viewController as? TelegramRootController {
if let tabsController = rootController.viewControllers.first as? TabBarController {
for i in 0 ..< tabsController.controllers.count {
if let _ = tabsController.controllers[i] as? (SettingsController & ViewController) {
let sourceNodes = tabsController.sourceNodesForController(at: i)
if let sourceNodes = sourceNodes {
dismissed = true
controller.dismiss(sourceNodes: sourceNodes)
}
return false
}
}
}
}
if dismissed {
controller.dismiss()
}
}
return true
})
self.mainWindow.topLevelOverlayControllers = [sharedApplicationContext.overlayMediaController, context.notificationController] self.mainWindow.topLevelOverlayControllers = [sharedApplicationContext.overlayMediaController, context.notificationController]
var authorizeNotifications = true var authorizeNotifications = true
if #available(iOS 10.0, *) { if #available(iOS 10.0, *) {
@ -937,7 +978,9 @@ private final class SharedApplicationContext {
} }
})) }))
self.watchCommunicationManagerPromise.set(watchCommunicationManager(context: self.context)) self.watchCommunicationManagerPromise.set(watchCommunicationManager(context: self.context, allowBackgroundTimeExtension: { timeout in
wakeupManager.allowBackgroundTimeExtension(timeout: timeout)
}))
let _ = self.watchCommunicationManagerPromise.get().start(next: { manager in let _ = self.watchCommunicationManagerPromise.get().start(next: { manager in
if let manager = manager { if let manager = manager {
watchManagerArgumentsPromise.set(.single(manager.arguments)) watchManagerArgumentsPromise.set(.single(manager.arguments))
@ -1578,16 +1621,33 @@ private final class SharedApplicationContext {
self.openChatWhenReady(accountId: accountIdFromNotification(response.notification), peerId: peerId, messageId: messageId) self.openChatWhenReady(accountId: accountIdFromNotification(response.notification), peerId: peerId, messageId: messageId)
} }
completionHandler() completionHandler()
} else if response.actionIdentifier == "reply", let peerId = peerIdFromNotification(response.notification) { } else if response.actionIdentifier == "reply", let peerId = peerIdFromNotification(response.notification), let accountId = accountIdFromNotification(response.notification) {
if let response = response as? UNTextInputNotificationResponse, !response.userText.isEmpty { guard let response = response as? UNTextInputNotificationResponse, !response.userText.isEmpty else {
let text = response.userText completionHandler()
let signal = self.authorizedContext() return
|> take(1) }
|> mapToSignal { context -> Signal<Void, NoError> in let text = response.userText
if let messageId = messageIdFromNotification(peerId: peerId, notification: response.notification) { let signal = self.sharedContextPromise.get()
let _ = applyMaxReadIndexInteractively(postbox: context.context.account.postbox, stateManager: context.context.account.stateManager, index: MessageIndex(id: messageId, timestamp: 0)).start() |> take(1)
|> deliverOnMainQueue
|> mapToSignal { sharedContext -> Signal<Void, NoError> in
sharedContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0)
return sharedContext.sharedContext.activeAccounts
|> mapToSignal { _, accounts, _ -> Signal<Account, NoError> in
for account in accounts {
if account.1.id == accountId {
return .single(account.1)
}
} }
return enqueueMessages(account: context.context.account, peerId: peerId, messages: [EnqueueMessage.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)]) return .complete()
}
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { account -> Signal<Void, NoError> in
if let messageId = messageIdFromNotification(peerId: peerId, notification: response.notification) {
let _ = applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0)).start()
}
return enqueueMessages(account: account, peerId: peerId, messages: [EnqueueMessage.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)])
|> map { messageIds -> MessageId? in |> map { messageIds -> MessageId? in
if messageIds.isEmpty { if messageIds.isEmpty {
return nil return nil
@ -1597,7 +1657,7 @@ private final class SharedApplicationContext {
} }
|> mapToSignal { messageId -> Signal<Void, NoError> in |> mapToSignal { messageId -> Signal<Void, NoError> in
if let messageId = messageId { if let messageId = messageId {
return context.context.account.postbox.unsentMessageIdsView() return account.postbox.unsentMessageIdsView()
|> filter { view in |> filter { view in
return !view.ids.contains(messageId) return !view.ids.contains(messageId)
} }
@ -1610,34 +1670,20 @@ private final class SharedApplicationContext {
} }
} }
} }
|> deliverOnMainQueue
|> timeout(15.0, queue: Queue.mainQueue(), alternate: .complete() |> beforeCompleted {
/*let content = UNMutableNotificationContent()
content.body = "Please open the app to continue sending messages"
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "error"
content.userInfo = ["peerId": peerId as NSNumber]
let request = UNNotificationRequest(identifier: "reply-error", content: content, trigger: nil)
let center = UNUserNotificationCenter.current()
center.add(request)*/
})
let disposable = MetaDisposable()
disposable.set((signal
|> afterDisposed { [weak disposable] in
Queue.mainQueue().async {
if let disposable = disposable {
self.replyFromNotificationsDisposables.remove(disposable)
}
completionHandler()
}
}).start())
self.replyFromNotificationsDisposables.add(disposable)
} else {
completionHandler()
} }
|> deliverOnMainQueue
let disposable = MetaDisposable()
disposable.set((signal
|> afterDisposed { [weak disposable] in
Queue.mainQueue().async {
if let disposable = disposable {
self.replyFromNotificationsDisposables.remove(disposable)
}
completionHandler()
}
}).start())
self.replyFromNotificationsDisposables.add(disposable)
} else { } else {
completionHandler() completionHandler()
} }

View File

@ -389,7 +389,11 @@ final class AuthorizedApplicationContext {
}*/ }*/
if let tabsController = strongSelf.rootController.viewControllers.first as? TabBarController, !tabsController.controllers.isEmpty, tabsController.selectedIndex >= 0 { if let tabsController = strongSelf.rootController.viewControllers.first as? TabBarController, !tabsController.controllers.isEmpty, tabsController.selectedIndex >= 0 {
let controller = tabsController.controllers[tabsController.selectedIndex] let controller = tabsController.controllers[tabsController.selectedIndex]
strongSelf.isReady.set(controller.ready.get()) let combinedReady = combineLatest(tabsController.ready.get(), controller.ready.get())
|> map { $0 && $1 }
|> filter { $0 }
|> take(1)
strongSelf.isReady.set(combinedReady)
} else { } else {
strongSelf.isReady.set(.single(true)) strongSelf.isReady.set(.single(true))
} }
@ -762,7 +766,8 @@ final class AuthorizedApplicationContext {
} }
}) })
let _ = (watchManagerArguments |> deliverOnMainQueue).start(next: { [weak self] arguments in let _ = (watchManagerArguments
|> deliverOnMainQueue).start(next: { [weak self] arguments in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -12,6 +12,7 @@ private struct AccountTasks {
let backgroundDownloads: Bool let backgroundDownloads: Bool
let backgroundAudio: Bool let backgroundAudio: Bool
let activeCalls: Bool let activeCalls: Bool
let watchTasks: Bool
let userInterfaceInUse: Bool let userInterfaceInUse: Bool
var isEmpty: Bool { var isEmpty: Bool {
@ -33,6 +34,9 @@ private struct AccountTasks {
if self.activeCalls { if self.activeCalls {
return false return false
} }
if self.watchTasks {
return false
}
if self.userInterfaceInUse { if self.userInterfaceInUse {
return false return false
} }
@ -57,7 +61,7 @@ final class SharedWakeupManager {
private var accountsAndTasks: [(Account, Bool, AccountTasks)] = [] private var accountsAndTasks: [(Account, Bool, AccountTasks)] = []
init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal<AccountRecordId?, NoError>, inForeground: Signal<Bool, NoError>, hasActiveAudioSession: Signal<Bool, NoError>, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal<Bool, NoError>) { init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal<AccountRecordId?, NoError>, watchTasks: Signal<AccountRecordId?, NoError>, inForeground: Signal<Bool, NoError>, hasActiveAudioSession: Signal<Bool, NoError>, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal<Bool, NoError>) {
assert(Queue.mainQueue().isCurrent()) assert(Queue.mainQueue().isCurrent())
self.beginBackgroundTask = beginBackgroundTask self.beginBackgroundTask = beginBackgroundTask
@ -120,11 +124,17 @@ final class SharedWakeupManager {
} }
|> distinctUntilChanged |> distinctUntilChanged
let hasWatchTasks = watchTasks
|> map { id in
return id == account.id
}
|> distinctUntilChanged
let userInterfaceInUse = accountUserInterfaceInUse(account.id) let userInterfaceInUse = accountUserInterfaceInUse(account.id)
return combineLatest(queue: .mainQueue(), account.importantTasksRunning, notificationManager?.isPollingState(accountId: account.id) ?? .single(false), hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, userInterfaceInUse) return combineLatest(queue: .mainQueue(), account.importantTasksRunning, notificationManager?.isPollingState(accountId: account.id) ?? .single(false), hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, hasWatchTasks, userInterfaceInUse)
|> map { importantTasksRunning, isPollingState, hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, userInterfaceInUse -> (Account, Bool, AccountTasks) in |> map { importantTasksRunning, isPollingState, hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, hasWatchTasks, userInterfaceInUse -> (Account, Bool, AccountTasks) in
return (account, primary?.id == account.id, AccountTasks(stateSynchronization: isPollingState, importantTasks: importantTasksRunning, backgroundLocation: hasActiveLiveLocationPolling, backgroundDownloads: false, backgroundAudio: hasActiveAudio, activeCalls: hasActiveCalls, userInterfaceInUse: userInterfaceInUse)) return (account, primary?.id == account.id, AccountTasks(stateSynchronization: isPollingState, importantTasks: importantTasksRunning, backgroundLocation: hasActiveLiveLocationPolling, backgroundDownloads: false, backgroundAudio: hasActiveAudio, activeCalls: hasActiveCalls, watchTasks: hasWatchTasks, userInterfaceInUse: userInterfaceInUse))
} }
} }
return combineLatest(signals) return combineLatest(signals)

View File

@ -9,7 +9,7 @@
@property (nonatomic, readonly) bool isRunning; @property (nonatomic, readonly) bool isRunning;
- (instancetype)initWithHandler:(SSignal *(^)(TGBridgeSubscription *))handler fileHandler:(void (^)(NSString *, NSDictionary *))fileHandler dispatchOnQueue:(void (^)(void (^)(void)))dispatchOnQueue logFunction:(void (^)(NSString *))logFunction; - (instancetype)initWithHandler:(SSignal *(^)(TGBridgeSubscription *))handler fileHandler:(void (^)(NSString *, NSDictionary *))fileHandler dispatchOnQueue:(void (^)(void (^)(void)))dispatchOnQueue logFunction:(void (^)(NSString *))logFunction allowBackgroundTimeExtension:(void (^)())allowBackgroundTimeExtension;
- (void)startRunning; - (void)startRunning;
- (SSignal *)watchAppInstalledSignal; - (SSignal *)watchAppInstalledSignal;

View File

@ -49,6 +49,8 @@
NSMutableDictionary *_runningTasks; NSMutableDictionary *_runningTasks;
SVariable *_hasRunningTasks; SVariable *_hasRunningTasks;
void (^_allowBackgroundTimeExtension)();
} }
@property (nonatomic, readonly) WCSession *session; @property (nonatomic, readonly) WCSession *session;
@ -57,7 +59,7 @@
@implementation TGBridgeServer @implementation TGBridgeServer
- (instancetype)initWithHandler:(SSignal *(^)(TGBridgeSubscription *))handler fileHandler:(void (^)(NSString *, NSDictionary *))fileHandler dispatchOnQueue:(void (^)(void (^)(void)))dispatchOnQueue logFunction:(void (^)(NSString *))logFunction - (instancetype)initWithHandler:(SSignal *(^)(TGBridgeSubscription *))handler fileHandler:(void (^)(NSString *, NSDictionary *))fileHandler dispatchOnQueue:(void (^)(void (^)(void)))dispatchOnQueue logFunction:(void (^)(NSString *))logFunction allowBackgroundTimeExtension:(void (^)())allowBackgroundTimeExtension
{ {
self = [super init]; self = [super init];
if (self != nil) if (self != nil)
@ -66,6 +68,7 @@
_fileHandler = [fileHandler copy]; _fileHandler = [fileHandler copy];
_dispatch = [dispatchOnQueue copy]; _dispatch = [dispatchOnQueue copy];
_logFunction = [logFunction copy]; _logFunction = [logFunction copy];
_allowBackgroundTimeExtension = [allowBackgroundTimeExtension copy];
_runningTasks = [[NSMutableDictionary alloc] init]; _runningTasks = [[NSMutableDictionary alloc] init];
_hasRunningTasks = [[SVariable alloc] init]; _hasRunningTasks = [[SVariable alloc] init];
@ -162,6 +165,10 @@
- (void)handleMessageData:(NSData *)messageData task:(id<SDisposable>)task replyHandler:(void (^)(NSData *))replyHandler completion:(void (^)(void))completion - (void)handleMessageData:(NSData *)messageData task:(id<SDisposable>)task replyHandler:(void (^)(NSData *))replyHandler completion:(void (^)(void))completion
{ {
if (_allowBackgroundTimeExtension) {
_allowBackgroundTimeExtension();
}
__block id<SDisposable> runningTask = task; __block id<SDisposable> runningTask = task;
void (^finishTask)(NSTimeInterval) = ^(NSTimeInterval delay) void (^finishTask)(NSTimeInterval) = ^(NSTimeInterval delay)
{ {

View File

@ -6,6 +6,8 @@ import TelegramUI
final class WatchCommunicationManager { final class WatchCommunicationManager {
private let queue: Queue private let queue: Queue
private let allowBackgroundTimeExtension: (Double) -> Void
private var server: TGBridgeServer! private var server: TGBridgeServer!
private let contextDisposable = MetaDisposable() private let contextDisposable = MetaDisposable()
@ -15,8 +17,9 @@ final class WatchCommunicationManager {
private let presets = Promise<WatchPresetSettings?>(nil) private let presets = Promise<WatchPresetSettings?>(nil)
private let navigateToMessagePipe = ValuePipe<MessageId>() private let navigateToMessagePipe = ValuePipe<MessageId>()
init(queue: Queue, context: Promise<AuthorizedApplicationContext?>) { init(queue: Queue, context: Promise<AuthorizedApplicationContext?>, allowBackgroundTimeExtension: @escaping (Double) -> Void) {
self.queue = queue self.queue = queue
self.allowBackgroundTimeExtension = allowBackgroundTimeExtension
let handlers = allWatchRequestHandlers.reduce([String : AnyClass]()) { (map, handler) -> [String : AnyClass] in let handlers = allWatchRequestHandlers.reduce([String : AnyClass]()) { (map, handler) -> [String : AnyClass] in
var map = map var map = map
@ -50,6 +53,8 @@ final class WatchCommunicationManager {
if let value = value { if let value = value {
Logger.shared.log("WatchBridge", value) Logger.shared.log("WatchBridge", value)
} }
}, allowBackgroundTimeExtension: {
allowBackgroundTimeExtension(4.0)
}) })
self.server.startRunning() self.server.startRunning()
@ -173,12 +178,12 @@ final class WatchCommunicationManager {
} }
} }
func watchCommunicationManager(context: Promise<AuthorizedApplicationContext?>) -> Signal<WatchCommunicationManager?, NoError> { func watchCommunicationManager(context: Promise<AuthorizedApplicationContext?>, allowBackgroundTimeExtension: @escaping (Double) -> Void) -> Signal<WatchCommunicationManager?, NoError> {
return Signal { subscriber in return Signal { subscriber in
let queue = Queue() let queue = Queue()
queue.async { queue.async {
if #available(iOSApplicationExtension 9.0, *) { if #available(iOSApplicationExtension 9.0, *) {
subscriber.putNext(WatchCommunicationManager(queue: queue, context: context)) subscriber.putNext(WatchCommunicationManager(queue: queue, context: context, allowBackgroundTimeExtension: allowBackgroundTimeExtension))
} else { } else {
subscriber.putNext(nil) subscriber.putNext(nil)
} }

@ -1 +1 @@
Subproject commit 6e0355b6326c684f465c691dbaef6d147bda38f3 Subproject commit 26b55adfb35df215fe6ec3e6a9f7c83748ec7cd5

@ -1 +1 @@
Subproject commit 8ddc0b670a139a3c37be807f7ab2e612351ff813 Subproject commit 06625eea98a27e8c41a4ef74ddeb1055b5ce036d