NotificationManager.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2018-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "NotificationManager.h"
  21. #import "UserSettings.h"
  22. #import <UserNotifications/UserNotifications.h>
  23. #import <PushKit/PushKit.h>
  24. #import "NSString+Hex.h"
  25. #import "AppDelegate.h"
  26. #import "EntityFetcher.h"
  27. #import "UIDefines.h"
  28. #import <AudioToolbox/AudioToolbox.h>
  29. #import "BundleUtil.h"
  30. #import "PushPayloadDecryptor.h"
  31. #import "ContactStore.h"
  32. #import "AppGroup.h"
  33. #import "ServerConnector.h"
  34. #import "TextStyleUtils.h"
  35. #import "Threema-Swift.h"
  36. #import "AbstractGroupMessage.h"
  37. #import "Conversation.h"
  38. #import "Contact.h"
  39. #import "GroupImageMessage.h"
  40. #import "GroupVideoMessage.h"
  41. #import "ImageMessage.h"
  42. #import "VideoMessage.h"
  43. #import "BoxImageMessage.h"
  44. #import "BoxVideoMessage.h"
  45. #import "ValidationLogger.h"
  46. @implementation NotificationManager {
  47. SystemSoundID receivedMessageSound;
  48. CFTimeInterval lastReceivedMessageSound;
  49. }
  50. + (NotificationManager *)sharedInstance {
  51. static NotificationManager *sharedInstance;
  52. static dispatch_once_t pred;
  53. dispatch_once(&pred, ^{
  54. sharedInstance = [[NotificationManager alloc] init];
  55. });
  56. return sharedInstance;
  57. }
  58. - (id)init
  59. {
  60. self = [super init];
  61. if (self) {
  62. /* Get sounds ready */
  63. NSString *soundPath = [BundleUtil pathForResource:@"received_message" ofType:@"caf"];
  64. CFURLRef baseURL = (__bridge CFURLRef)[NSURL fileURLWithPath:soundPath];
  65. AudioServicesCreateSystemSoundID(baseURL, &receivedMessageSound);
  66. }
  67. return self;
  68. }
  69. - (void)updateUnreadMessagesCount:(BOOL)unloadedMessage {
  70. NSNumber *unread = [self unreadMessagesCount:unloadedMessage];
  71. [[NSNotificationCenter defaultCenter] postNotificationName:@"ThreemaUnreadMessagesCountChanged" object:nil userInfo:[NSDictionary dictionaryWithObject:unread forKey:@"unread"]];
  72. dispatch_async(dispatch_get_main_queue(), ^{
  73. [UIApplication sharedApplication].applicationIconBadgeNumber = [unread integerValue];
  74. });
  75. }
  76. - (NSNumber *)unreadMessagesCount:(BOOL)unloadedMessage {
  77. EntityManager *entityManager = [[EntityManager alloc] init];
  78. NSArray *conversations = [entityManager.entityFetcher allConversations];
  79. int unread = 0;
  80. if (unloadedMessage)
  81. unread++;
  82. for (Conversation *conversation in conversations) {
  83. int count = [conversation.unreadMessageCount intValue];
  84. if (count > 0) {
  85. unread += [conversation.unreadMessageCount intValue];
  86. }
  87. }
  88. NSString *badgeValue = nil;
  89. if (unread > 0)
  90. badgeValue = [NSString stringWithFormat:@"%d", unread];
  91. __block UITabBarController *mainTabBar;
  92. if ([NSThread isMainThread]) {
  93. mainTabBar = [AppDelegate getMainTabBarController];
  94. if (mainTabBar && [mainTabBar isKindOfClass:[UITabBarController class]]) {
  95. [[mainTabBar.tabBar.items objectAtIndex:kChatTabBarIndex] setBadgeValue:badgeValue];
  96. }
  97. } else {
  98. dispatch_sync(dispatch_get_main_queue(), ^{
  99. mainTabBar = [AppDelegate getMainTabBarController];
  100. if (mainTabBar && [mainTabBar isKindOfClass:[UITabBarController class]]) {
  101. [[mainTabBar.tabBar.items objectAtIndex:kChatTabBarIndex] setBadgeValue:badgeValue];
  102. }
  103. });
  104. }
  105. return [NSNumber numberWithInt:unread];
  106. }
  107. - (void)handleVoIPPush:(NSDictionary *)payload withCompletionHandler:(void (^)(void))completion {
  108. if ([[MyIdentityStore sharedMyIdentityStore] isKeychainLocked]) {
  109. if (payload[@"threema"] != nil) {
  110. [NotificationManager showNoAccessToDatabaseNotification];
  111. }
  112. [self waitForSeconds:2 finish:^{
  113. exit(0);
  114. }];
  115. // The keychain is locked; we cannot proceed. The UI will show the ProtectedDataUnavailable screen
  116. // at this point. To prevent this screen from appearing when the user unlocks their device after we
  117. // have processed the push, we exit now so that the process will restart after the device is unlocked.
  118. }
  119. else {
  120. WebClientSession *currentSession = nil;
  121. if (payload[@"3mw"] != nil) {
  122. NSDictionary *webPayload = payload[@"3mw"];
  123. if (webPayload[@"wcs"] != nil) {
  124. currentSession = [[WebClientSessionStore shared] webClientSessionForHash:webPayload[@"wcs"]];
  125. }
  126. if (currentSession != nil) {
  127. int webClientProtocolVersion = [webPayload[@"wcv"] intValue];
  128. if (![currentSession.version isEqualToNumber:[NSNumber numberWithInt:webClientProtocolVersion]]) {
  129. // show error
  130. NSString *title;
  131. NSString *body;
  132. if (webClientProtocolVersion > [currentSession.version intValue]) {
  133. title = NSLocalizedString(@"webClientSession_error_updateApp_title", nil);
  134. body = NSLocalizedString(@"webClientSession_error_updateApp_message", nil);
  135. } else {
  136. if ([currentSession.selfHosted boolValue] == YES) {
  137. title = NSLocalizedString(@"webClientSession_error_updateServer_title", nil);
  138. body = NSLocalizedString(@"webClientSession_error_updateServer_message", nil);
  139. } else {
  140. title = NSLocalizedString(@"webClientSession_error_wrongVersion_title", nil);
  141. body = NSLocalizedString(@"webClientSession_error_wrongVersion_message", nil);
  142. }
  143. }
  144. [self showThreemaWebErrorWithTitle:title body:body];
  145. [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
  146. completion();
  147. return;
  148. }
  149. [self loadMessages:payload currentSession:currentSession withCompletionHandler:completion];
  150. } else {
  151. [[ValidationLogger sharedValidationLogger] logString:@"Threema Web: Unknown session try to connect; Session blocked"];
  152. [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
  153. completion();
  154. }
  155. } else {
  156. [self loadMessages:payload currentSession:currentSession withCompletionHandler:completion];
  157. }
  158. }
  159. }
  160. - (void)loadMessages:(NSDictionary *)payload currentSession:(WebClientSession *)currentSession withCompletionHandler:(void (^)(void))completion {
  161. NSDictionary *threemaDict = [PushPayloadDecryptor decryptPushPayload:payload[ThreemaPushNotificationDictionaryKey]];
  162. NSString *messageId = threemaDict[ThreemaPushNotificationDictionaryMessageIdKey];
  163. NSString *senderId = threemaDict[ThreemaPushNotificationDictionaryFromKey];
  164. [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Push: Received Push Notification for %@", messageId]];
  165. if (payload[@"3mw"] != nil) {
  166. if (currentSession != nil) {
  167. [[WCSessionManager shared] connectWithAuthToken:nil wca: payload[@"wca"] webClientSession:currentSession];
  168. [[DatabaseManager dbManager] refreshDirtyObjects];
  169. } else {
  170. // there is no local connection
  171. [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
  172. completion();
  173. return;
  174. }
  175. [[ServerConnector sharedServerConnector] connect];
  176. if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive && [[WCSessionManager shared] isRunningWCSession]) {
  177. [[BackgroundTaskManager shared] newBackgroundTaskWithKey:kAppWCBackgroundTask timeout:kAppWCBackgroundTaskTime completionHandler:^{
  178. [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
  179. completion();
  180. }];
  181. } else {
  182. [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
  183. completion();
  184. }
  185. } else {
  186. if (senderId == nil && messageId == nil && threemaDict == nil) {
  187. [PendingMessage createTestNotificationWithPayload:payload completion:^{
  188. [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
  189. completion();
  190. }];
  191. } else {
  192. [[ValidationLogger sharedValidationLogger] logString:@"Threema Web: loadMessages --> connect all running sessions"];
  193. [[WCSessionManager shared] connectAllRunningSessions];
  194. [[DatabaseManager dbManager] refreshDirtyObjects];
  195. [[PendingMessagesManager shared] pendingMessageWithSenderId:senderId messageId:messageId abstractMessage:nil threemaDict:threemaDict completion:^(PendingMessage *pendingMessage) {
  196. [[BackgroundTaskManager shared] newBackgroundTaskWithKey:kAppPushBackgroundTask timeout:kAppPushBackgroundTaskTime completionHandler:^{
  197. if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive && [[WCSessionManager shared] isRunningWCSession]) {
  198. [[BackgroundTaskManager shared] newBackgroundTaskWithKey:kAppWCBackgroundTask timeout:kAppWCBackgroundTaskTime completionHandler:nil];
  199. }
  200. if (pendingMessage != nil) {
  201. pendingMessage.completionHandler = completion;
  202. } else {
  203. completion();
  204. }
  205. [[ServerConnector sharedServerConnector] connect];
  206. }];
  207. }];
  208. }
  209. }
  210. }
  211. #pragma mark - Private functions
  212. - (void)showThreemaWebErrorWithTitle:(NSString *)title body:(NSString *)body {
  213. if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) {
  214. UNMutableNotificationContent *notification = [[UNMutableNotificationContent alloc] init];
  215. notification.title = title;
  216. notification.body = body;
  217. if (![[UserSettings sharedUserSettings].pushSound isEqualToString:@"none"]) {
  218. notification.sound = [UNNotificationSound soundNamed:[NSString stringWithFormat:@"%@.caf", [UserSettings sharedUserSettings].pushSound]];
  219. }
  220. UNNotificationRequest *notificationRequest = [UNNotificationRequest requestWithIdentifier:@"ThreemaWebError" content:notification trigger:nil];
  221. UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  222. [center addNotificationRequest:notificationRequest withCompletionHandler:^(NSError * _Nullable error) {
  223. }];
  224. } else {
  225. [UIAlertTemplate showAlertWithOwner:[[AppDelegate sharedAppDelegate] currentTopViewController] title:title message:body actionOk:nil];
  226. }
  227. }
  228. - (void)playReceivedMessageSound {
  229. CFTimeInterval curTime = CACurrentMediaTime();
  230. /* play sound only twice per second */
  231. if (curTime - lastReceivedMessageSound > 0.5) {
  232. if ([UserSettings sharedUserSettings].inAppSounds && [UserSettings sharedUserSettings].inAppVibrate)
  233. AudioServicesPlayAlertSound(receivedMessageSound);
  234. else if ([UserSettings sharedUserSettings].inAppSounds)
  235. AudioServicesPlaySystemSound(receivedMessageSound);
  236. else if ([UserSettings sharedUserSettings].inAppVibrate)
  237. AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
  238. }
  239. lastReceivedMessageSound = curTime;
  240. }
  241. - (void)waitForSeconds:(int)count finish:(void(^)(void))finish {
  242. if (count > 0 && [AppGroup getActiveType] == AppGroupTypeApp) {
  243. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  244. [self waitForSeconds:count-1 finish:finish];
  245. });
  246. } else {
  247. finish();
  248. }
  249. }
  250. #pragma mark - Static functions
  251. + (void)showNoAccessToDatabaseNotification {
  252. UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
  253. content.title = NSLocalizedString(@"new_message_no_access_title", @"");
  254. content.body = NSLocalizedString(@"new_message_no_access_message", @"");
  255. content.badge = @1;
  256. UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"NoAccessToDB" content:content trigger:nil];
  257. [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil];
  258. }
  259. /**
  260. Generate push settings for all groups, will be run once when upgrade app.
  261. */
  262. + (void)generatePushSettingForAllGroups {
  263. if ([UserSettings sharedUserSettings].pushGroupGenerated == NO) {
  264. NSMutableOrderedSet *pushSettings = [[NSMutableOrderedSet alloc] initWithOrderedSet:[UserSettings sharedUserSettings].pushSettingsList];
  265. EntityManager *entityManager = [[EntityManager alloc] init];
  266. NSArray *allGroupConversations = [entityManager.entityFetcher allGroupConversations];
  267. for (Conversation *conversation in allGroupConversations) {
  268. NSString *identity = [NSString stringWithHexData:conversation.groupId];
  269. PushSetting *pushSetting = [PushSetting findPushSettingForIdentity:identity pushSettingList:pushSettings];
  270. if (pushSetting == nil) {
  271. PushSetting *tmpPushSetting = [PushSetting new];
  272. tmpPushSetting.identity = identity;
  273. tmpPushSetting.type = kPushSettingTypeOn;
  274. tmpPushSetting.periodOffTime = 0;
  275. tmpPushSetting.periodOffTillDate = nil;
  276. tmpPushSetting.silent = false;
  277. tmpPushSetting.mentions = false;
  278. [pushSettings addObject:tmpPushSetting.buildDict];
  279. }
  280. }
  281. [[UserSettings sharedUserSettings] setPushSettingsList:pushSettings];
  282. [[UserSettings sharedUserSettings] setPushGroupGenerated:YES];
  283. }
  284. }
  285. @end