123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- // _____ _
- // |_ _| |_ _ _ ___ ___ _ __ __ _
- // | | | ' \| '_/ -_) -_) ' \/ _` |_
- // |_| |_||_|_| \___\___|_|_|_\__,_(_)
- //
- // Threema iOS Client
- // Copyright (c) 2018-2020 Threema GmbH
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License, version 3,
- // as published by the Free Software Foundation.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- #import "NotificationManager.h"
- #import "UserSettings.h"
- #import <UserNotifications/UserNotifications.h>
- #import <PushKit/PushKit.h>
- #import "NSString+Hex.h"
- #import "AppDelegate.h"
- #import "EntityFetcher.h"
- #import "UIDefines.h"
- #import <AudioToolbox/AudioToolbox.h>
- #import "BundleUtil.h"
- #import "PushPayloadDecryptor.h"
- #import "ContactStore.h"
- #import "AppGroup.h"
- #import "ServerConnector.h"
- #import "TextStyleUtils.h"
- #import "Threema-Swift.h"
- #import "AbstractGroupMessage.h"
- #import "Conversation.h"
- #import "Contact.h"
- #import "GroupImageMessage.h"
- #import "GroupVideoMessage.h"
- #import "ImageMessage.h"
- #import "VideoMessage.h"
- #import "BoxImageMessage.h"
- #import "BoxVideoMessage.h"
- #import "ValidationLogger.h"
- @implementation NotificationManager {
- SystemSoundID receivedMessageSound;
- CFTimeInterval lastReceivedMessageSound;
- }
- + (NotificationManager *)sharedInstance {
- static NotificationManager *sharedInstance;
- static dispatch_once_t pred;
- dispatch_once(&pred, ^{
- sharedInstance = [[NotificationManager alloc] init];
- });
- return sharedInstance;
- }
- - (id)init
- {
- self = [super init];
- if (self) {
- /* Get sounds ready */
- NSString *soundPath = [BundleUtil pathForResource:@"received_message" ofType:@"caf"];
- CFURLRef baseURL = (__bridge CFURLRef)[NSURL fileURLWithPath:soundPath];
- AudioServicesCreateSystemSoundID(baseURL, &receivedMessageSound);
- }
- return self;
- }
- - (void)updateUnreadMessagesCount:(BOOL)unloadedMessage {
- NSNumber *unread = [self unreadMessagesCount:unloadedMessage];
-
- [[NSNotificationCenter defaultCenter] postNotificationName:@"ThreemaUnreadMessagesCountChanged" object:nil userInfo:[NSDictionary dictionaryWithObject:unread forKey:@"unread"]];
-
- dispatch_async(dispatch_get_main_queue(), ^{
- [UIApplication sharedApplication].applicationIconBadgeNumber = [unread integerValue];
- });
- }
- - (NSNumber *)unreadMessagesCount:(BOOL)unloadedMessage {
- EntityManager *entityManager = [[EntityManager alloc] init];
- NSArray *conversations = [entityManager.entityFetcher allConversations];
- int unread = 0;
- if (unloadedMessage)
- unread++;
-
- for (Conversation *conversation in conversations) {
- int count = [conversation.unreadMessageCount intValue];
- if (count > 0) {
- unread += [conversation.unreadMessageCount intValue];
- }
- }
-
- NSString *badgeValue = nil;
- if (unread > 0)
- badgeValue = [NSString stringWithFormat:@"%d", unread];
-
- __block UITabBarController *mainTabBar;
- if ([NSThread isMainThread]) {
- mainTabBar = [AppDelegate getMainTabBarController];
-
- if (mainTabBar && [mainTabBar isKindOfClass:[UITabBarController class]]) {
- [[mainTabBar.tabBar.items objectAtIndex:kChatTabBarIndex] setBadgeValue:badgeValue];
- }
- } else {
- dispatch_sync(dispatch_get_main_queue(), ^{
- mainTabBar = [AppDelegate getMainTabBarController];
-
- if (mainTabBar && [mainTabBar isKindOfClass:[UITabBarController class]]) {
- [[mainTabBar.tabBar.items objectAtIndex:kChatTabBarIndex] setBadgeValue:badgeValue];
- }
- });
- }
-
- return [NSNumber numberWithInt:unread];
- }
- - (void)handleVoIPPush:(NSDictionary *)payload withCompletionHandler:(void (^)(void))completion {
- if ([[MyIdentityStore sharedMyIdentityStore] isKeychainLocked]) {
- if (payload[@"threema"] != nil) {
- [NotificationManager showNoAccessToDatabaseNotification];
- }
- [self waitForSeconds:2 finish:^{
- exit(0);
- }];
- // The keychain is locked; we cannot proceed. The UI will show the ProtectedDataUnavailable screen
- // at this point. To prevent this screen from appearing when the user unlocks their device after we
- // have processed the push, we exit now so that the process will restart after the device is unlocked.
- }
- else {
- WebClientSession *currentSession = nil;
-
- if (payload[@"3mw"] != nil) {
- NSDictionary *webPayload = payload[@"3mw"];
- if (webPayload[@"wcs"] != nil) {
- currentSession = [[WebClientSessionStore shared] webClientSessionForHash:webPayload[@"wcs"]];
- }
-
- if (currentSession != nil) {
- int webClientProtocolVersion = [webPayload[@"wcv"] intValue];
- if (![currentSession.version isEqualToNumber:[NSNumber numberWithInt:webClientProtocolVersion]]) {
- // show error
- NSString *title;
- NSString *body;
- if (webClientProtocolVersion > [currentSession.version intValue]) {
- title = NSLocalizedString(@"webClientSession_error_updateApp_title", nil);
- body = NSLocalizedString(@"webClientSession_error_updateApp_message", nil);
- } else {
- if ([currentSession.selfHosted boolValue] == YES) {
- title = NSLocalizedString(@"webClientSession_error_updateServer_title", nil);
- body = NSLocalizedString(@"webClientSession_error_updateServer_message", nil);
- } else {
- title = NSLocalizedString(@"webClientSession_error_wrongVersion_title", nil);
- body = NSLocalizedString(@"webClientSession_error_wrongVersion_message", nil);
- }
- }
-
- [self showThreemaWebErrorWithTitle:title body:body];
- [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
- completion();
- return;
- }
-
- [self loadMessages:payload currentSession:currentSession withCompletionHandler:completion];
- } else {
- [[ValidationLogger sharedValidationLogger] logString:@"Threema Web: Unknown session try to connect; Session blocked"];
- [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
- completion();
- }
- } else {
- [self loadMessages:payload currentSession:currentSession withCompletionHandler:completion];
- }
- }
- }
- - (void)loadMessages:(NSDictionary *)payload currentSession:(WebClientSession *)currentSession withCompletionHandler:(void (^)(void))completion {
- NSDictionary *threemaDict = [PushPayloadDecryptor decryptPushPayload:payload[ThreemaPushNotificationDictionaryKey]];
- NSString *messageId = threemaDict[ThreemaPushNotificationDictionaryMessageIdKey];
- NSString *senderId = threemaDict[ThreemaPushNotificationDictionaryFromKey];
-
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Push: Received Push Notification for %@", messageId]];
-
- if (payload[@"3mw"] != nil) {
- if (currentSession != nil) {
- [[WCSessionManager shared] connectWithAuthToken:nil wca: payload[@"wca"] webClientSession:currentSession];
- [[DatabaseManager dbManager] refreshDirtyObjects];
- } else {
- // there is no local connection
- [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
- completion();
- return;
- }
-
- [[ServerConnector sharedServerConnector] connect];
- if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive && [[WCSessionManager shared] isRunningWCSession]) {
- [[BackgroundTaskManager shared] newBackgroundTaskWithKey:kAppWCBackgroundTask timeout:kAppWCBackgroundTaskTime completionHandler:^{
- [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
- completion();
- }];
- } else {
- [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
- completion();
- }
- } else {
- if (senderId == nil && messageId == nil && threemaDict == nil) {
- [PendingMessage createTestNotificationWithPayload:payload completion:^{
- [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppPushBackgroundTask];
- completion();
- }];
- } else {
- [[ValidationLogger sharedValidationLogger] logString:@"Threema Web: loadMessages --> connect all running sessions"];
- [[WCSessionManager shared] connectAllRunningSessions];
- [[DatabaseManager dbManager] refreshDirtyObjects];
- [[PendingMessagesManager shared] pendingMessageWithSenderId:senderId messageId:messageId abstractMessage:nil threemaDict:threemaDict completion:^(PendingMessage *pendingMessage) {
- [[BackgroundTaskManager shared] newBackgroundTaskWithKey:kAppPushBackgroundTask timeout:kAppPushBackgroundTaskTime completionHandler:^{
- if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive && [[WCSessionManager shared] isRunningWCSession]) {
- [[BackgroundTaskManager shared] newBackgroundTaskWithKey:kAppWCBackgroundTask timeout:kAppWCBackgroundTaskTime completionHandler:nil];
- }
- if (pendingMessage != nil) {
- pendingMessage.completionHandler = completion;
- } else {
- completion();
- }
- [[ServerConnector sharedServerConnector] connect];
- }];
- }];
- }
- }
- }
- #pragma mark - Private functions
- - (void)showThreemaWebErrorWithTitle:(NSString *)title body:(NSString *)body {
- if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) {
- UNMutableNotificationContent *notification = [[UNMutableNotificationContent alloc] init];
-
- notification.title = title;
- notification.body = body;
-
- if (![[UserSettings sharedUserSettings].pushSound isEqualToString:@"none"]) {
- notification.sound = [UNNotificationSound soundNamed:[NSString stringWithFormat:@"%@.caf", [UserSettings sharedUserSettings].pushSound]];
- }
-
- UNNotificationRequest *notificationRequest = [UNNotificationRequest requestWithIdentifier:@"ThreemaWebError" content:notification trigger:nil];
-
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- [center addNotificationRequest:notificationRequest withCompletionHandler:^(NSError * _Nullable error) {
- }];
- } else {
- [UIAlertTemplate showAlertWithOwner:[[AppDelegate sharedAppDelegate] currentTopViewController] title:title message:body actionOk:nil];
- }
- }
- - (void)playReceivedMessageSound {
- CFTimeInterval curTime = CACurrentMediaTime();
-
- /* play sound only twice per second */
- if (curTime - lastReceivedMessageSound > 0.5) {
- if ([UserSettings sharedUserSettings].inAppSounds && [UserSettings sharedUserSettings].inAppVibrate)
- AudioServicesPlayAlertSound(receivedMessageSound);
- else if ([UserSettings sharedUserSettings].inAppSounds)
- AudioServicesPlaySystemSound(receivedMessageSound);
- else if ([UserSettings sharedUserSettings].inAppVibrate)
- AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
- }
- lastReceivedMessageSound = curTime;
- }
- - (void)waitForSeconds:(int)count finish:(void(^)(void))finish {
- if (count > 0 && [AppGroup getActiveType] == AppGroupTypeApp) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- [self waitForSeconds:count-1 finish:finish];
- });
- } else {
- finish();
- }
- }
- #pragma mark - Static functions
- + (void)showNoAccessToDatabaseNotification {
- UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
- content.title = NSLocalizedString(@"new_message_no_access_title", @"");
- content.body = NSLocalizedString(@"new_message_no_access_message", @"");
- content.badge = @1;
- UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"NoAccessToDB" content:content trigger:nil];
- [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil];
- }
- /**
- Generate push settings for all groups, will be run once when upgrade app.
- */
- + (void)generatePushSettingForAllGroups {
- if ([UserSettings sharedUserSettings].pushGroupGenerated == NO) {
- NSMutableOrderedSet *pushSettings = [[NSMutableOrderedSet alloc] initWithOrderedSet:[UserSettings sharedUserSettings].pushSettingsList];
- EntityManager *entityManager = [[EntityManager alloc] init];
- NSArray *allGroupConversations = [entityManager.entityFetcher allGroupConversations];
- for (Conversation *conversation in allGroupConversations) {
- NSString *identity = [NSString stringWithHexData:conversation.groupId];
- PushSetting *pushSetting = [PushSetting findPushSettingForIdentity:identity pushSettingList:pushSettings];
- if (pushSetting == nil) {
- PushSetting *tmpPushSetting = [PushSetting new];
- tmpPushSetting.identity = identity;
- tmpPushSetting.type = kPushSettingTypeOn;
- tmpPushSetting.periodOffTime = 0;
- tmpPushSetting.periodOffTillDate = nil;
- tmpPushSetting.silent = false;
- tmpPushSetting.mentions = false;
- [pushSettings addObject:tmpPushSetting.buildDict];
- }
- }
- [[UserSettings sharedUserSettings] setPushSettingsList:pushSettings];
- [[UserSettings sharedUserSettings] setPushGroupGenerated:YES];
- }
- }
- @end
|