AppDelegate.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "AppDelegate.h"
  6. #import "AFNetworkReachabilityManager.h"
  7. #import "AFNetworkActivityIndicatorManager.h"
  8. #import <Intents/Intents.h>
  9. #import <UserNotifications/UserNotifications.h>
  10. #import <BackgroundTasks/BGTaskScheduler.h>
  11. #import <BackgroundTasks/BGTaskRequest.h>
  12. #import <BackgroundTasks/BGTask.h>
  13. #import "NCAudioController.h"
  14. #import "NCAppBranding.h"
  15. #import "NCDatabaseManager.h"
  16. #import "NCKeyChainController.h"
  17. #import "NCNavigationController.h"
  18. #import "NCNotificationController.h"
  19. #import "NCPushNotification.h"
  20. #import "NCPushNotificationsUtils.h"
  21. #import "NCRoomsManager.h"
  22. #import "NCSettingsController.h"
  23. #import "NCUserInterfaceController.h"
  24. #import "NextcloudTalk-Swift.h"
  25. @interface AppDelegate ()
  26. @property (nonatomic, strong) NSTimer *keepAliveTimer;
  27. @property (nonatomic, strong) BGTaskHelper *keepAliveBGTask;
  28. @property (nonatomic, strong) UILabel *debugLabel;
  29. @property (nonatomic, strong) NSTimer *debugLabelTimer;
  30. @property (nonatomic, strong) NSTimer *fileDescriptorTimer;
  31. @end
  32. @implementation AppDelegate
  33. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
  34. {
  35. #if DEBUG
  36. [AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
  37. #endif
  38. [[AFNetworkReachabilityManager sharedManager] startMonitoring];
  39. [[NCNotificationController sharedInstance] requestAuthorization];
  40. [application registerForRemoteNotifications];
  41. pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
  42. pushRegistry.delegate = self;
  43. pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
  44. [[WebRTCCommon shared] dispatch:^{
  45. NSLog(@"Configure Audio Session");
  46. [NCAudioController sharedInstance];
  47. }];
  48. NSLog(@"Configure App Settings");
  49. [NCSettingsController sharedInstance];
  50. // Perform logfile cleanup only once in app lifecycle
  51. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  52. [NCUtils removeOldLogfiles];
  53. });
  54. UIDevice *currentDevice = [UIDevice currentDevice];
  55. [NCUtils log:[NSString stringWithFormat:@"Starting %@, version %@, %@ %@, model %@", NSBundle.mainBundle.bundleIdentifier, [NCAppBranding getAppVersionString], currentDevice.systemName, currentDevice.systemVersion, currentDevice.model]];
  56. //Init rooms manager to start receiving NSNotificationCenter notifications
  57. [NCRoomsManager sharedInstance];
  58. [self registerBackgroundFetchTask];
  59. [NCUserInterfaceController sharedInstance].mainViewController = (NCSplitViewController *) self.window.rootViewController;
  60. [NCUserInterfaceController sharedInstance].roomsTableViewController = [NCUserInterfaceController sharedInstance].mainViewController.viewControllers.firstObject.childViewControllers.firstObject;
  61. [NCUserInterfaceController sharedInstance].mainViewController.displayModeButtonVisibility = UISplitViewControllerDisplayModeButtonVisibilityNever;
  62. NSArray *arguments = [[NSProcessInfo processInfo] arguments];
  63. if ([arguments containsObject:@"-TestEnvironment"]) {
  64. UIView *mainView = [NCUserInterfaceController sharedInstance].mainViewController.view;
  65. self.debugLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 30, 200, 20)];
  66. self.debugLabel.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
  67. self.debugLabel.translatesAutoresizingMaskIntoConstraints = NO;
  68. [mainView addSubview:self.debugLabel];
  69. [NSLayoutConstraint activateConstraints:@[
  70. [self.debugLabel.topAnchor constraintEqualToAnchor:mainView.safeAreaLayoutGuide.topAnchor constant:-15],
  71. [self.debugLabel.leadingAnchor constraintEqualToAnchor:mainView.safeAreaLayoutGuide.leadingAnchor constant:5],
  72. [self.debugLabel.trailingAnchor constraintEqualToAnchor:mainView.safeAreaLayoutGuide.trailingAnchor]
  73. ]];
  74. __weak typeof(self) weakSelf = self;
  75. self.debugLabelTimer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
  76. [weakSelf.debugLabel setText:[AllocationTracker shared].description];
  77. }];
  78. }
  79. // Comment out the following code to log the number of open socket file descriptors
  80. /*
  81. self.fileDescriptorTimer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
  82. [[WebRTCCommon shared] printNumberOfOpenSocketDescriptors];
  83. }];
  84. */
  85. // When we include VLCKit we need to manually call this because otherwise, device rotation might not work
  86. [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
  87. return YES;
  88. }
  89. - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
  90. {
  91. BOOL audioCallIntent = [userActivity.interaction.intent isKindOfClass:[INStartAudioCallIntent class]];
  92. BOOL videoCallIntent = [userActivity.interaction.intent isKindOfClass:[INStartVideoCallIntent class]];
  93. if (audioCallIntent || videoCallIntent) {
  94. INPerson *person = [[(INStartAudioCallIntent*)userActivity.interaction.intent contacts] firstObject];
  95. NSString *roomToken = person.personHandle.value;
  96. if (roomToken) {
  97. [[NCUserInterfaceController sharedInstance] presentCallKitCallInRoom:roomToken withVideoEnabled:videoCallIntent];
  98. }
  99. }
  100. return YES;
  101. }
  102. - (void)applicationWillResignActive:(UIApplication *)application
  103. {
  104. // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
  105. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
  106. }
  107. - (void)applicationDidEnterBackground:(UIApplication *)application
  108. {
  109. // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
  110. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
  111. [self keepExternalSignalingConnectionAliveTemporarily];
  112. [self scheduleAppRefresh];
  113. }
  114. - (void)applicationWillEnterForeground:(UIApplication *)application
  115. {
  116. // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
  117. }
  118. - (void)applicationDidBecomeActive:(UIApplication *)application
  119. {
  120. // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
  121. [self checkForDisconnectedExternalSignalingConnection];
  122. [[NCNotificationController sharedInstance] removeAllNotificationsForAccountId:[[NCDatabaseManager sharedInstance] activeAccount].accountId];
  123. }
  124. - (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application
  125. {
  126. if ([[CallKitManager sharedInstance].calls count] > 0) {
  127. [NCUtils log:@"Protected data did become available"];
  128. }
  129. }
  130. - (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application
  131. {
  132. if ([[CallKitManager sharedInstance].calls count] > 0) {
  133. [NCUtils log:@"Protected data did become unavailable"];
  134. }
  135. }
  136. - (void)applicationWillTerminate:(UIApplication *)application
  137. {
  138. // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
  139. [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
  140. // Invalidate a potentially existing label timer
  141. [self.debugLabelTimer invalidate];
  142. [self.fileDescriptorTimer invalidate];
  143. }
  144. - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
  145. {
  146. NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
  147. NSString *scheme = urlComponents.scheme;
  148. if ([scheme isEqualToString:@"nextcloudtalk"]) {
  149. NSString *action = urlComponents.host;
  150. if ([action isEqualToString:@"open-conversation"]) {
  151. [[NCUserInterfaceController sharedInstance] presentChatForURL:urlComponents];
  152. return YES;
  153. } else if ([action isEqualToString:@"login"] && multiAccountEnabled) {
  154. NSArray *queryItems = urlComponents.queryItems;
  155. NSString *server = [NCUtils valueForKey:@"server" fromQueryItems:queryItems];
  156. NSString *user = [NCUtils valueForKey:@"user" fromQueryItems:queryItems];
  157. if (server) {
  158. [[NCUserInterfaceController sharedInstance] presentLoginViewControllerForServerURL:server withUser:user];
  159. }
  160. return YES;
  161. }
  162. }
  163. return NO;
  164. }
  165. - (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
  166. {
  167. if (_shouldLockInterfaceOrientation) {
  168. if (_lockedInterfaceOrientation == UIInterfaceOrientationPortrait) {
  169. return UIInterfaceOrientationMaskPortrait;
  170. } else if (_lockedInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
  171. return UIInterfaceOrientationMaskLandscapeLeft;
  172. } else if (_lockedInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
  173. return UIInterfaceOrientationMaskLandscapeRight;
  174. }
  175. }
  176. return UIInterfaceOrientationMaskAllButUpsideDown;
  177. }
  178. - (void)setShouldLockInterfaceOrientation:(BOOL)shouldLockInterfaceOrientation
  179. {
  180. _shouldLockInterfaceOrientation = shouldLockInterfaceOrientation;
  181. _lockedInterfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
  182. }
  183. #pragma mark - Push Notifications Registration
  184. - (void)checkForPushNotificationSubscription
  185. {
  186. if (!normalPushToken || !pushKitToken) {
  187. return;
  188. }
  189. // Store new Normal Push & PushKit tokens in Keychain
  190. UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:bundleIdentifier accessGroup:groupIdentifier];
  191. [keychain setString:normalPushToken forKey:kNCNormalPushTokenKey];
  192. [keychain setString:pushKitToken forKey:kNCPushKitTokenKey];
  193. BOOL isAppInBackground = [[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground;
  194. // Subscribe only if both tokens have been generated and app is not running in the background (do not try to subscribe
  195. // when the app is running in background e.g. when the app is launched due to a VoIP push notification)
  196. if (!isAppInBackground) {
  197. // Try to subscribe for push notifications in all accounts
  198. for (TalkAccount *account in [[NCDatabaseManager sharedInstance] allAccounts]) {
  199. [[NCSettingsController sharedInstance] subscribeForPushNotificationsForAccountId:account.accountId withCompletionBlock:nil];
  200. }
  201. }
  202. }
  203. #pragma mark - Normal Push Notifications Delegate Methods
  204. - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
  205. {
  206. if([deviceToken length] == 0) {
  207. NSLog(@"Failed to create Normal Push token.");
  208. return;
  209. }
  210. normalPushToken = [self stringWithDeviceToken:deviceToken];
  211. [self checkForPushNotificationSubscription];
  212. [self registerInteractivePushNotification];
  213. }
  214. - (void)registerInteractivePushNotification
  215. {
  216. // Reply directly to a chat notification action/category
  217. UNTextInputNotificationAction *replyAction = [UNTextInputNotificationAction actionWithIdentifier:NCNotificationActionReplyToChat
  218. title:NSLocalizedString(@"Reply", nil)
  219. options:UNNotificationActionOptionAuthenticationRequired];
  220. UNNotificationCategory *chatCategory = [UNNotificationCategory categoryWithIdentifier:@"CATEGORY_CHAT"
  221. actions:@[replyAction]
  222. intentIdentifiers:@[]
  223. options:UNNotificationCategoryOptionNone];
  224. // Recording actions/category
  225. UNNotificationAction *recordingShareAction = [UNNotificationAction actionWithIdentifier:NCNotificationActionShareRecording
  226. title:NSLocalizedString(@"Share to chat", nil)
  227. options:UNNotificationActionOptionAuthenticationRequired];
  228. UNNotificationAction *recordingDismissAction = [UNNotificationAction actionWithIdentifier:NCNotificationActionDismissRecordingNotification
  229. title:NSLocalizedString(@"Dismiss notification", nil)
  230. options:UNNotificationActionOptionAuthenticationRequired | UNNotificationActionOptionDestructive];
  231. UNNotificationCategory *recordingCategory = [UNNotificationCategory categoryWithIdentifier:@"CATEGORY_RECORDING"
  232. actions:@[recordingShareAction, recordingDismissAction]
  233. intentIdentifiers:@[]
  234. options:UNNotificationCategoryOptionNone];
  235. // Federation invitation
  236. UNNotificationAction *federationAccept = [UNNotificationAction actionWithIdentifier:NCNotificationActionFederationInvitationAccept
  237. title:NSLocalizedString(@"Accept", nil)
  238. options:UNNotificationActionOptionAuthenticationRequired];
  239. UNNotificationAction *federationReject = [UNNotificationAction actionWithIdentifier:NCNotificationActionFederationInvitationReject
  240. title:NSLocalizedString(@"Reject", nil)
  241. options:UNNotificationActionOptionAuthenticationRequired | UNNotificationActionOptionDestructive];
  242. UNNotificationCategory *federationCategory = [UNNotificationCategory categoryWithIdentifier:@"CATEGORY_FEDERATION"
  243. actions:@[federationAccept, federationReject]
  244. intentIdentifiers:@[]
  245. options:UNNotificationCategoryOptionNone];
  246. NSSet *categories = [NSSet setWithObjects:chatCategory, recordingCategory, federationCategory, nil];
  247. [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:categories];
  248. }
  249. - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
  250. {
  251. // Called when a background notification is delivered.
  252. NSString *message = [userInfo objectForKey:@"subject"];
  253. for (TalkAccount *account in [[NCDatabaseManager sharedInstance] allAccounts]) {
  254. NSData *pushNotificationPrivateKey = [[NCKeyChainController sharedInstance] pushNotificationPrivateKeyForAccountId:account.accountId];
  255. if (message && pushNotificationPrivateKey) {
  256. NSString *decryptedMessage = [NCPushNotificationsUtils decryptPushNotification:message withDevicePrivateKey:pushNotificationPrivateKey];
  257. if (decryptedMessage) {
  258. NCPushNotification *pushNotification = [NCPushNotification pushNotificationFromDecryptedString:decryptedMessage withAccountId:account.accountId];
  259. [[NCNotificationController sharedInstance] processBackgroundPushNotification:pushNotification];
  260. }
  261. }
  262. }
  263. completionHandler(UIBackgroundFetchResultNewData);
  264. }
  265. #pragma mark - PushKit Delegate Methods
  266. - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type
  267. {
  268. if([credentials.token length] == 0) {
  269. NSLog(@"Failed to create PushKit token.");
  270. return;
  271. }
  272. pushKitToken = [self stringWithDeviceToken:credentials.token];
  273. [self checkForPushNotificationSubscription];
  274. }
  275. - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion
  276. {
  277. [NCUtils log:@"Received PushKit notification"];
  278. NSString *message = [payload.dictionaryPayload objectForKey:@"subject"];
  279. for (TalkAccount *account in [[NCDatabaseManager sharedInstance] allAccounts]) {
  280. NSData *pushNotificationPrivateKey = [[NCKeyChainController sharedInstance] pushNotificationPrivateKeyForAccountId:account.accountId];
  281. if (!message || !pushNotificationPrivateKey) {
  282. continue;
  283. }
  284. NSString *decryptedMessage = [NCPushNotificationsUtils decryptPushNotification:message withDevicePrivateKey:pushNotificationPrivateKey];
  285. if (!decryptedMessage) {
  286. continue;
  287. }
  288. NCPushNotification *pushNotification = [NCPushNotification pushNotificationFromDecryptedString:decryptedMessage withAccountId:account.accountId];
  289. if ( pushNotification && pushNotification.type == NCPushNotificationTypeCall) {
  290. [[NCNotificationController sharedInstance] showIncomingCallForPushNotification:pushNotification];
  291. completion();
  292. return;
  293. }
  294. }
  295. [[NCNotificationController sharedInstance] showIncomingCallForOldAccount];
  296. [[NCSettingsController sharedInstance] setDidReceiveCallsFromOldAccount:YES];
  297. completion();
  298. }
  299. - (NSString *)stringWithDeviceToken:(NSData *)deviceToken
  300. {
  301. const char *data = [deviceToken bytes];
  302. NSMutableString *token = [NSMutableString string];
  303. for (NSUInteger i = 0; i < [deviceToken length]; i++) {
  304. [token appendFormat:@"%02.2hhX", data[i]];
  305. }
  306. return [token copy];
  307. }
  308. #pragma mark - BackgroundFetch / AppRefresh
  309. - (void)registerBackgroundFetchTask {
  310. NSString *refreshTaskIdentifier = [NSString stringWithFormat:@"%@.refresh", NSBundle.mainBundle.bundleIdentifier];
  311. // see: https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler?language=objc
  312. [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:refreshTaskIdentifier
  313. usingQueue:nil
  314. launchHandler:^(__kindof BGTask * _Nonnull task) {
  315. [self handleAppRefresh:task];
  316. }];
  317. }
  318. - (void)scheduleAppRefresh
  319. {
  320. NSString *refreshTaskIdentifier = [NSString stringWithFormat:@"%@.refresh", NSBundle.mainBundle.bundleIdentifier];
  321. BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:refreshTaskIdentifier];
  322. request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:UIApplicationBackgroundFetchIntervalMinimum];
  323. NSError *error = nil;
  324. [[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
  325. if (error) {
  326. NSLog(@"Failed to submit apprefresh request: %@", error);
  327. }
  328. }
  329. - (void)handleAppRefresh:(BGTask *)task
  330. {
  331. [NCUtils log:@"Performing background fetch -> handleAppRefresh"];
  332. // With BGTasks (iOS >= 13) we need to schedule another refresh when running in background
  333. [self scheduleAppRefresh];
  334. [self performBackgroundFetchWithCompletionHandler:^(BOOL errorOccurred) {
  335. [task setTaskCompletedWithSuccess:!errorOccurred];
  336. }];
  337. }
  338. // This method is called when you simulate a background fetch from the debug menu in XCode
  339. // so we keep it around, although it's deprecated on iOS 13 onwards
  340. - (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
  341. {
  342. [NCUtils log:@"Performing background fetch -> performFetchWithCompletionHandler"];
  343. [self performBackgroundFetchWithCompletionHandler:^(BOOL errorOccurred) {
  344. if (errorOccurred) {
  345. completionHandler(UIBackgroundFetchResultFailed);
  346. } else {
  347. completionHandler(UIBackgroundFetchResultNewData);
  348. }
  349. }];
  350. }
  351. - (void)performBackgroundFetchWithCompletionHandler:(void (^)(BOOL errorOccurred))completionHandler
  352. {
  353. dispatch_group_t backgroundRefreshGroup = dispatch_group_create();
  354. __block BOOL errorOccurred = NO;
  355. __block BOOL expired = NO;
  356. BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"NCBackgroundFetch" expirationHandler:^(BGTaskHelper *task) {
  357. [NCUtils log:@"ExpirationHandler called"];
  358. /*
  359. expired = YES;
  360. completionHandler(YES);
  361. [task stopBackgroundTask];
  362. */
  363. }];
  364. [NCUtils log:@"Start performBackgroundFetchWithCompletionHandler"];
  365. dispatch_group_enter(backgroundRefreshGroup);
  366. [[NCRoomsManager sharedInstance] resendOfflineMessagesWithCompletionBlock:^{
  367. [NCUtils log:@"CompletionHandler resendOfflineMessagesWithCompletionBlock"];
  368. dispatch_group_leave(backgroundRefreshGroup);
  369. }];
  370. // Check if the shown notifications are still available on the server
  371. dispatch_group_enter(backgroundRefreshGroup);
  372. [[NCNotificationController sharedInstance] checkNotificationExistanceWithCompletionBlock:^(NSError *error) {
  373. [NCUtils log:@"CompletionHandler checkNotificationExistance"];
  374. if (error) {
  375. errorOccurred = YES;
  376. }
  377. dispatch_group_leave(backgroundRefreshGroup);
  378. }];
  379. /* Disable checking for new messages for now, until we can prevent them from showing twice
  380. dispatch_group_enter(backgroundRefreshGroup);
  381. [[NCNotificationController sharedInstance] checkForNewNotificationsWithCompletionBlock:^(NSError *error) {
  382. [NCUtils log:@"CompletionHandler checkForNewNotificationsWithCompletionBlock"];
  383. if (error) {
  384. errorOccurred = YES;
  385. }
  386. dispatch_group_leave(backgroundRefreshGroup);
  387. }];
  388. */
  389. dispatch_group_enter(backgroundRefreshGroup);
  390. [[NCRoomsManager sharedInstance] updateRoomsAndChatsUpdatingUserStatus:NO onlyLastModified:YES withCompletionBlock:^(NSError *error) {
  391. [NCUtils log:@"CompletionHandler updateRoomsAndChatsUpdatingUserStatus"];
  392. if (error) {
  393. errorOccurred = YES;
  394. }
  395. dispatch_group_leave(backgroundRefreshGroup);
  396. }];
  397. NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
  398. dayComponent.day = -1;
  399. NSDate *thresholdDate = [[NSCalendar currentCalendar] dateByAddingComponents:dayComponent toDate:[NSDate date] options:0];
  400. NSInteger thresholdTimestamp = [thresholdDate timeIntervalSince1970];
  401. // Push proxy should be subscrided atleast every 24h
  402. // Check if we reached the threshold and start the subscription process
  403. for (TalkAccount *account in [[NCDatabaseManager sharedInstance] allAccounts]) {
  404. if (account.lastPushSubscription < thresholdTimestamp) {
  405. dispatch_group_enter(backgroundRefreshGroup);
  406. [[NCSettingsController sharedInstance] subscribeForPushNotificationsForAccountId:account.accountId withCompletionBlock:^(BOOL success) {
  407. if (!success) {
  408. errorOccurred = YES;
  409. }
  410. dispatch_group_leave(backgroundRefreshGroup);
  411. }];
  412. }
  413. }
  414. dispatch_group_notify(backgroundRefreshGroup, dispatch_get_main_queue(), ^{
  415. [NCUtils log:@"CompletionHandler performBackgroundFetchWithCompletionHandler dispatch_group_notify"];
  416. if (!expired) {
  417. completionHandler(errorOccurred);
  418. }
  419. [bgTask stopBackgroundTask];
  420. });
  421. }
  422. - (void)keepExternalSignalingConnectionAliveTemporarily
  423. {
  424. [_keepAliveTimer invalidate];
  425. _keepAliveBGTask = [BGTaskHelper startBackgroundTaskWithName:@"NCWebSocketKeepAlive" expirationHandler:nil];
  426. _keepAliveTimer = [NSTimer scheduledTimerWithTimeInterval:20 repeats:NO block:^(NSTimer * _Nonnull timer) {
  427. // Stop the external signaling connections only if the app keeps in the background and not in a call
  428. if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground &&
  429. ![NCRoomsManager sharedInstance].callViewController) {
  430. [[NCSettingsController sharedInstance] disconnectAllExternalSignalingControllers];
  431. }
  432. // Disconnect is dispatched to the main queue, so in theory it can happen that we stop the background task
  433. // before the disconnect is run/completed. So we dispatch the stopBackgroundTask to main as well
  434. // to be sure it's called after everything else is run.
  435. dispatch_async(dispatch_get_main_queue(), ^{
  436. [self->_keepAliveBGTask stopBackgroundTask];
  437. });
  438. }];
  439. [[NSRunLoop mainRunLoop] addTimer:_keepAliveTimer forMode:NSRunLoopCommonModes];
  440. }
  441. - (void)checkForDisconnectedExternalSignalingConnection
  442. {
  443. [_keepAliveTimer invalidate];
  444. [_keepAliveBGTask stopBackgroundTask];
  445. [[NCSettingsController sharedInstance] connectDisconnectedExternalSignalingControllers];
  446. }
  447. @end