diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 8f90cd00e..310435b7d 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -340,6 +340,8 @@ B640C4771A477B0F005C7C8A /* TSAttachementsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B640C4761A477B0F005C7C8A /* TSAttachementsTest.m */; }; B65031CF1A7862AA002EBBBD /* SignedPreKeyDeletionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B65031CE1A7862AA002EBBBD /* SignedPreKeyDeletionTests.m */; }; B65EDA1219E1BE6400AAA7CB /* RPAPICall.m in Sources */ = {isa = PBXBuildFile; fileRef = B65EDA1119E1BE6400AAA7CB /* RPAPICall.m */; }; + B66B9F721AEA6D1100E2E609 /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B66B9F711AEA6D1100E2E609 /* NotificationSettingsViewController.m */; }; + B66B9F7D1AEAF40500E2E609 /* NotificationSettingsOptionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B66B9F7C1AEAF40500E2E609 /* NotificationSettingsOptionsViewController.m */; }; B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; }; B671B2461A93B238002BBD9D /* GroupContactsResult.m in Sources */ = {isa = PBXBuildFile; fileRef = B671B2451A93B238002BBD9D /* GroupContactsResult.m */; }; B67ADDC41989FF8700E1A773 /* RPServerRequestsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B67ADDC31989FF8700E1A773 /* RPServerRequestsManager.m */; }; @@ -405,6 +407,8 @@ B6F509971AA53F760068F56A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; B6FAAAE81A41BC6C007FEC1D /* TSAttachmentPointer.m in Sources */ = {isa = PBXBuildFile; fileRef = B6FAAAE71A41BC6C007FEC1D /* TSAttachmentPointer.m */; }; B6FAAAEE1A41C918007FEC1D /* TSAttachmentStream.m in Sources */ = {isa = PBXBuildFile; fileRef = B6FAAAED1A41C918007FEC1D /* TSAttachmentStream.m */; }; + B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; }; + B6FE7EBA1ADD63AE00A6D22F /* NSData+ows_StripToken.m in Sources */ = {isa = PBXBuildFile; fileRef = B6FE7EB91ADD63AE00A6D22F /* NSData+ows_StripToken.m */; }; B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; B90418E7183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; B96A3100187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf in Resources */ = {isa = PBXBuildFile; fileRef = B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */; }; @@ -946,6 +950,10 @@ B65EDA1019E1BE6400AAA7CB /* RPAPICall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RPAPICall.h; sourceTree = ""; }; B65EDA1119E1BE6400AAA7CB /* RPAPICall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RPAPICall.m; sourceTree = ""; }; B661C211198EE2EA00548CA1 /* iOSVersions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iOSVersions.h; path = src/environment/iOSVersions.h; sourceTree = ""; }; + B66B9F701AEA6D1100E2E609 /* NotificationSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationSettingsViewController.h; sourceTree = ""; }; + B66B9F711AEA6D1100E2E609 /* NotificationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsViewController.m; sourceTree = ""; }; + B66B9F7B1AEAF40500E2E609 /* NotificationSettingsOptionsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationSettingsOptionsViewController.h; sourceTree = ""; }; + B66B9F7C1AEAF40500E2E609 /* NotificationSettingsOptionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsOptionsViewController.m; sourceTree = ""; }; B66DBF4919D5BBC8006EA940 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; B671B2441A93B238002BBD9D /* GroupContactsResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GroupContactsResult.h; sourceTree = ""; }; B671B2451A93B238002BBD9D /* GroupContactsResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GroupContactsResult.m; sourceTree = ""; }; @@ -1088,6 +1096,9 @@ B6FAAAE71A41BC6C007FEC1D /* TSAttachmentPointer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSAttachmentPointer.m; path = Attachements/TSAttachmentPointer.m; sourceTree = ""; }; B6FAAAEC1A41C918007FEC1D /* TSAttachmentStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TSAttachmentStream.h; path = Attachements/TSAttachmentStream.h; sourceTree = ""; }; B6FAAAED1A41C918007FEC1D /* TSAttachmentStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSAttachmentStream.m; path = Attachements/TSAttachmentStream.m; sourceTree = ""; }; + B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; }; + B6FE7EB81ADD63AE00A6D22F /* NSData+ows_StripToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+ows_StripToken.h"; sourceTree = ""; }; + B6FE7EB91ADD63AE00A6D22F /* NSData+ows_StripToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+ows_StripToken.m"; sourceTree = ""; }; B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = ""; }; B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = ""; }; B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "HelveticaNeueLTStd-Bd.otf"; sourceTree = ""; }; @@ -1227,6 +1238,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */, FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */, FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */, 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */, @@ -1750,6 +1762,8 @@ 76EB04C818170B33006006FC /* util */ = { isa = PBXGroup; children = ( + B6FE7EB81ADD63AE00A6D22F /* NSData+ows_StripToken.h */, + B6FE7EB91ADD63AE00A6D22F /* NSData+ows_StripToken.m */, A59E6D701A79E5D100D98E2E /* MIMETypeUtil.h */, A59E6D711A79E5D100D98E2E /* MIMETypeUtil.m */, FCFA64B11A24F29E0007FB87 /* UI Categories */, @@ -2449,6 +2463,7 @@ D221A08C169C9E5E00537ABF /* Frameworks */ = { isa = PBXGroup; children = ( + B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */, FC3BD9871A30A790005B96BB /* Social.framework */, B60EDE031A05A01700D73516 /* AudioToolbox.framework */, FCB11D8B1A129A76002F93FB /* CoreMedia.framework */, @@ -2594,6 +2609,10 @@ FCD274E11A5AFD8000202277 /* PrivacySettingsTableViewController.m */, FCD274E61A5AFDC900202277 /* AdvancedSettingsTableViewController.h */, FCD274E71A5AFDC900202277 /* AdvancedSettingsTableViewController.m */, + B66B9F701AEA6D1100E2E609 /* NotificationSettingsViewController.h */, + B66B9F711AEA6D1100E2E609 /* NotificationSettingsViewController.m */, + B66B9F7B1AEAF40500E2E609 /* NotificationSettingsOptionsViewController.h */, + B66B9F7C1AEAF40500E2E609 /* NotificationSettingsOptionsViewController.m */, FCD274E91A5AFDDB00202277 /* AboutTableViewController.h */, FCD274EA1A5AFDDB00202277 /* AboutTableViewController.m */, ); @@ -2981,6 +3000,7 @@ B63AF5C71A1F757900D01AAD /* TSContactsIntersectionRequest.m in Sources */, B6B0968B1A1D25ED008BFAA6 /* TSStorageManager+SignedPreKeyStore.m in Sources */, 76EB05AC18170B33006006FC /* SrtpSocket.m in Sources */, + B6FE7EBA1ADD63AE00A6D22F /* NSData+ows_StripToken.m in Sources */, B6A5D05C1A7827ED0043D837 /* TSAvailablePreKeysCountRequest.m in Sources */, FCB11D931A12A4AA002F93FB /* FullImageViewController.m in Sources */, B6B096871A1D25ED008BFAA6 /* TSStorageManager+IdentityKeyStore.m in Sources */, @@ -3058,6 +3078,7 @@ FCAC963C19FEF9280046DFC5 /* SignalsViewController.m in Sources */, 76EB05DA18170B33006006FC /* LowLatencyConnector.m in Sources */, 76EB05EE18170B33006006FC /* CallTermination.m in Sources */, + B66B9F7D1AEAF40500E2E609 /* NotificationSettingsOptionsViewController.m in Sources */, E1CD329618BCFF9900B1A496 /* SoundInstance.m in Sources */, B6FAAAEE1A41C918007FEC1D /* TSAttachmentStream.m in Sources */, 76EB05B418170B33006006FC /* HashChain.m in Sources */, @@ -3128,6 +3149,7 @@ B6FAAAE81A41BC6C007FEC1D /* TSAttachmentPointer.m in Sources */, B6B096881A1D25ED008BFAA6 /* TSStorageManager+keyFromIntLong.m in Sources */, B6B50AAB1A4192C500F8F607 /* TSMessagesManager+attachments.m in Sources */, + B66B9F721AEA6D1100E2E609 /* NotificationSettingsViewController.m in Sources */, 76EB059018170B33006006FC /* IgnoredPacketFailure.m in Sources */, B60FB9B01A4711D4006A5A66 /* TSAttachmentEncryptionResult.m in Sources */, 76EB05D418170B33006006FC /* ZrtpManager.m in Sources */, diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 313f455da..61399b663 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -21,7 +21,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.3 + 2.1 CFBundleSignature ???? CFBundleURLTypes @@ -38,7 +38,7 @@ CFBundleVersion - 2.0.20 + 2.0.21 LOGS_EMAIL support@whispersystems.org LOGS_URL diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 8e5d02c72..e2cd5014b 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -5,15 +5,15 @@ #import "DebugLogger.h" #import "DiscardingLog.h" #import "Environment.h" -#import "InCallViewController.h" +#import "PhoneNumberDirectoryFilterManager.h" #import "PreferencesUtil.h" -#import "NotificationTracker.h" #import "PushManager.h" #import "PriorityQueue.h" #import "Release.h" #import "SignalsViewController.h" #import "TSAccountManager.h" #import "TSPreKeyManager.h" +#import "TSMessagesManager.h" #import "TSSocketManager.h" #import "TSStorageManager.h" #import "Util.h" @@ -28,10 +28,7 @@ @interface AppDelegate () -@property (nonatomic, retain) UIWindow *blankWindow; -@property (nonatomic, strong) NotificationTracker *notificationTracker; - -@property (nonatomic) TOCFutureSource *callPickUpFuture; +@property (nonatomic, retain) UIWindow *blankWindow; @end @@ -68,12 +65,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self setupAppearance]; + [[PushManager sharedManager] registerPushKitNotificationFuture]; + if (getenv("runningTests_dontStartApp")) { return YES; } - self.notificationTracker = [NotificationTracker notificationTracker]; - CategorizingLogger* logger = [CategorizingLogger categorizingLogger]; [logger addLoggingCallback:^(NSString *category, id details, NSUInteger index) {}]; [Environment setCurrent:[Release releaseEnvironmentWithLogging:logger]]; @@ -119,7 +116,9 @@ [self prepareScreenshotProtection]; if ([TSAccountManager isRegistered]) { - [TSSocketManager becomeActive]; + if ([self applicationIsActive]) { + [TSSocketManager becomeActiveFromForeground]; + } [[PushManager sharedManager] validateUserNotificationSettings]; [self refreshContacts]; [TSPreKeyManager refreshPreKeys]; @@ -172,85 +171,27 @@ return NO; } --(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { - if ([self isRedPhonePush:userInfo]) { - ResponderSessionDescriptor* call; - if (![self.notificationTracker shouldProcessNotification:userInfo]){ - return; - } - - @try { - call = [ResponderSessionDescriptor responderSessionDescriptorFromEncryptedRemoteNotification:userInfo]; - DDLogDebug(@"Received remote notification. Parsed session descriptor: %@.", call); - self.callPickUpFuture = [TOCFutureSource new]; - } @catch (OperationFailed* ex) { - DDLogError(@"Error parsing remote notification. Error: %@.", ex); - return; - } - - if (!call) { - DDLogError(@"Decryption of session descriptor failed"); - return; - } - - [Environment.phoneManager incomingCallWithSession:call]; - } -} - -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - - if ([self isRedPhonePush:userInfo]) { - [self application:application didReceiveRemoteNotification:userInfo]; - } else { - [TSSocketManager becomeActive]; +-(void)applicationDidBecomeActive:(UIApplication *)application { + if ([TSAccountManager isRegistered] && [self applicationIsActive]) { + // We're double checking that the app is active, to be sure since we can't verify in production env due to code signing. + [TSSocketManager becomeActiveFromForeground]; } - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC), - dispatch_get_main_queue(), ^{ - completionHandler(UIBackgroundFetchResultNewData); - }); -} - -- (BOOL)isRedPhonePush:(NSDictionary*)pushDict { - NSDictionary *aps = [pushDict objectForKey:@"aps"]; - NSString *category = [aps objectForKey:@"category"]; - - if ([category isEqualToString:Signal_Call_Category]) { - return YES; - } else{ - return NO; - } -} - --(void) applicationDidBecomeActive:(UIApplication *)application { - if ([TSAccountManager isRegistered]) { - [TSSocketManager becomeActive]; - } - // Hacky way to clear notification center after processed push - [UIApplication.sharedApplication setApplicationIconBadgeNumber:1]; - [UIApplication.sharedApplication setApplicationIconBadgeNumber:0]; - [self removeScreenProtection]; } -- (void)applicationWillResignActive:(UIApplication *)application{ +- (void)applicationWillResignActive:(UIApplication *)application { [self protectScreen]; + + if ([TSAccountManager isRegistered]) { + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; + [self updateBadge]; + } } -- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler{ - - [self application:application didReceiveRemoteNotification:userInfo]; - if ([identifier isEqualToString:Signal_Call_Accept_Identifier]) { - [self.callPickUpFuture trySetResult:@YES]; - completionHandler(); - } else if ([identifier isEqualToString:Signal_Call_Decline_Identifier]){ - [self.callPickUpFuture trySetResult:@NO]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), - dispatch_get_main_queue(), ^{ - completionHandler(); - }); - } else{ - completionHandler(); +- (void)updateBadge { + if ([TSAccountManager isRegistered]) { + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:(NSInteger)[[TSMessagesManager sharedManager] unreadMessagesCount]]; } } @@ -329,4 +270,31 @@ [manager forceUpdate]; } +#pragma mark Push Notifications Delegate Methods + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { + [[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo]; +} +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + [[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; +} + +- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{ + [[PushManager sharedManager] application:application didReceiveLocalNotification:notification]; +} + +- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler { + [[PushManager sharedManager] application:application handleActionWithIdentifier:identifier forLocalNotification:notification completionHandler:completionHandler]; +} + +- (BOOL)applicationIsActive { + UIApplication *app = [UIApplication sharedApplication]; + + if (app.applicationState == UIApplicationStateActive) { + return YES; + } + + return NO; +} + @end diff --git a/Signal/src/Storyboard/Storyboard.storyboard b/Signal/src/Storyboard/Storyboard.storyboard index afacc8b2a..893076fc2 100755 --- a/Signal/src/Storyboard/Storyboard.storyboard +++ b/Signal/src/Storyboard/Storyboard.storyboard @@ -1,7 +1,7 @@ - + - + @@ -4160,6 +4160,69 @@ A0 09 9A FF A8 8A 09 99 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4404,6 +4467,7 @@ A0 09 9A FF A8 8A 09 99 + diff --git a/Signal/src/audio/AppAudioManager.m b/Signal/src/audio/AppAudioManager.m index 40c62965c..8c07539c2 100644 --- a/Signal/src/audio/AppAudioManager.m +++ b/Signal/src/audio/AppAudioManager.m @@ -1,5 +1,6 @@ #import "AppAudioManager.h" +#import #import "AudioRouter.h" #import "SoundBoard.h" #import "SoundPlayer.h" diff --git a/Signal/src/call/RecentCallManager.m b/Signal/src/call/RecentCallManager.m index 63db42842..11135a49b 100644 --- a/Signal/src/call/RecentCallManager.m +++ b/Signal/src/call/RecentCallManager.m @@ -39,7 +39,7 @@ - (void)addCall:(CallState*)call { require(call != nil); - [call.futureCallLocallyAcceptedOrRejected finallyDo:^(TOCFuture* interactionCompletion) { + [call.futureTermination finallyDo:^(TOCFuture* interactionCompletion) { bool isOutgoingCall = call.initiatedLocally; bool isMissedCall = interactionCompletion.hasFailed; Contact* contact = [self tryGetContactForCall:call]; diff --git a/Signal/src/environment/Environment.h b/Signal/src/environment/Environment.h index 63a240440..ba1d5036c 100644 --- a/Signal/src/environment/Environment.h +++ b/Signal/src/environment/Environment.h @@ -85,7 +85,7 @@ andCurrentRegionCodeForPhoneNumbers:(NSString*)currentRegionCodeForPhoneNumbers - (void)setSignalsViewController:(SignalsViewController *)signalsViewController; - (void)setSignUpFlowNavigationController:(UINavigationController *)signUpFlowNavigationController; -+ (void)messageIdentifier:(NSString*)identifier; -+ (void)messageGroupModel:(TSGroupModel*)model; - ++ (void)messageThreadId:(NSString*)threadId; ++ (void)messageIdentifier:(NSString*)identifier withCompose:(BOOL)compose; ++ (void)messageGroupModel:(TSGroupModel*)model withCompose:(BOOL)compose; @end diff --git a/Signal/src/environment/Environment.m b/Signal/src/environment/Environment.m index 6dce37161..f226f7175 100644 --- a/Signal/src/environment/Environment.m +++ b/Signal/src/environment/Environment.m @@ -11,6 +11,8 @@ #import "PhoneNumberDirectoryFilterManager.h" #import "SignalKeyingStorage.h" #import "SignalsViewController.h" +#import "TSContactThread.h" +#import "TSGroupThread.h" #import "TSStorageManager.h" static NSString* const kCallSegue = @"2.0_6.0_Call_Segue"; @@ -184,7 +186,22 @@ phoneDirectoryManager; _signUpFlowNavigationController = navigationController; } -+ (void)messageIdentifier:(NSString*)identifier{ ++ (void)messageThreadId:(NSString*)threadId { + TSThread *thread = [TSThread fetchObjectWithUniqueID:threadId]; + + if (!thread) { + DDLogWarn(@"We get UILocalNotifications with unknown threadId: %@", threadId); + return; + } + + if ([thread isGroupThread]) { + [self messageGroup:(TSGroupThread*)thread]; + } else { + [self messageIdentifier:((TSContactThread*)thread).contactIdentifier withCompose:YES]; + } +} + ++ (void)messageIdentifier:(NSString*)identifier withCompose:(BOOL)compose { Environment *env = [self getCurrent]; SignalsViewController *vc = env.signalsViewController; @@ -194,10 +211,23 @@ phoneDirectoryManager; [vc.navigationController popToRootViewControllerAnimated:YES]; vc.contactIdentifierFromCompose = identifier; + vc.composeMessage = compose; [vc performSegueWithIdentifier:@"showSegue" sender:nil]; } -+ (void)messageGroupModel:(TSGroupModel*)model { ++ (void)messageGroup:(TSGroupThread*)groupThread { + Environment *env = [self getCurrent]; + SignalsViewController *vc = env.signalsViewController; + + if (vc.presentedViewController) { + [vc.presentedViewController dismissViewControllerAnimated:YES completion:nil]; + } + + [vc.navigationController popToRootViewControllerAnimated:YES]; + [vc performSegueWithIdentifier:@"showSegue" sender:groupThread]; +} + ++ (void)messageGroupModel:(TSGroupModel*)model withCompose:(BOOL)compose { Environment *env = [self getCurrent]; SignalsViewController *vc = env.signalsViewController; @@ -207,6 +237,7 @@ phoneDirectoryManager; [vc.navigationController popToRootViewControllerAnimated:YES]; vc.groupFromCompose = model; + vc.composeMessage = compose; [vc performSegueWithIdentifier:@"showSegue" sender:nil]; } diff --git a/Signal/src/environment/PreferencesUtil.h b/Signal/src/environment/PreferencesUtil.h index 5eabe7e25..fe1ee7bcb 100644 --- a/Signal/src/environment/PreferencesUtil.h +++ b/Signal/src/environment/PreferencesUtil.h @@ -20,10 +20,10 @@ typedef NS_ENUM(NSUInteger, TSImageQuality) { @interface PropertyListPreferences (PropertyUtil) --(PhoneNumberDirectoryFilter*) tryGetSavedPhoneNumberDirectory; --(void) setSavedPhoneNumberDirectory:(PhoneNumberDirectoryFilter*)phoneNumberDirectoryFilter; --(NSTimeInterval) getCachedOrDefaultDesiredBufferDepth; --(void) setCachedDesiredBufferDepth:(double)value; +- (PhoneNumberDirectoryFilter*) tryGetSavedPhoneNumberDirectory; +- (void) setSavedPhoneNumberDirectory:(PhoneNumberDirectoryFilter*)phoneNumberDirectoryFilter; +- (NSTimeInterval) getCachedOrDefaultDesiredBufferDepth; +- (void) setCachedDesiredBufferDepth:(double)value; - (BOOL) getHasSentAMessage; - (void) setHasSentAMessage:(BOOL)enabled; @@ -31,20 +31,24 @@ typedef NS_ENUM(NSUInteger, TSImageQuality) { - (BOOL) getHasArchivedAMessage; - (void) setHasArchivedAMessage:(BOOL)enabled; --(BOOL)loggingIsEnabled; --(void)setLoggingEnabled:(BOOL)flag; +- (BOOL)loggingIsEnabled; +- (void)setLoggingEnabled:(BOOL)flag; --(BOOL)screenSecurityIsEnabled; --(void)setScreenSecurity:(BOOL)flag; +- (BOOL)screenSecurityIsEnabled; +- (void)setScreenSecurity:(BOOL)flag; --(NotificationType)notificationPreviewType; --(void)setNotificationPreviewType:(NotificationType)type; +- (NotificationType)notificationPreviewType; +- (void)setNotificationPreviewType:(NotificationType)type; +- (NSString*)nameForNotificationPreviewType:(NotificationType)notificationType; --(TSImageQuality)imageUploadQuality; --(void)setImageUploadQuality:(TSImageQuality)quality; +- (BOOL)soundInForeground; +- (void)setSoundInForeground:(BOOL)enabled; --(NSString*)lastRanVersion; --(NSString*)setAndGetCurrentVersion; +- (TSImageQuality)imageUploadQuality; +- (void)setImageUploadQuality:(TSImageQuality)quality; + +- (NSString*)lastRanVersion; +- (NSString*)setAndGetCurrentVersion; @end diff --git a/Signal/src/environment/PreferencesUtil.m b/Signal/src/environment/PreferencesUtil.m index faefd6c9c..78e07f020 100644 --- a/Signal/src/environment/PreferencesUtil.m +++ b/Signal/src/environment/PreferencesUtil.m @@ -27,6 +27,7 @@ #define HAS_SENT_A_MESSAGE_KEY @"User has sent a message" #define HAS_ARCHIVED_A_MESSAGE_KEY @"User archived a message" #define kSignalVersionKey @"SignalUpdateVersionKey" +#define PLAY_SOUND_IN_FOREGROUND_KEY @"NotificationSoundInForeground" #define BloomFilterCacheName @"bloomfilter" @@ -141,17 +142,6 @@ } - --(NotificationType)notificationPreviewType { - NSNumber *preference = [self tryGetValueForKey:NOTIFICATION_PREVIEW_TYPE_KEY]; - - if (preference) { - return [preference unsignedIntegerValue]; - } else { - return NotificationNamePreview; - } -} - -(TSImageQuality)imageUploadQuality { // always return average image quality return TSImageQualityMedium; @@ -161,11 +151,6 @@ [self setValueForKey:IMAGE_UPLOAD_QUALITY_KEY toValue:@(quality)]; } --(void)setNotificationPreviewType:(NotificationType)type -{ - [self setValueForKey:NOTIFICATION_PREVIEW_TYPE_KEY toValue:@(type)]; -} - -(void)setScreenSecurity:(BOOL)flag{ [self setValueForKey:SCREEN_SECURITY_KEY toValue:@(flag)]; } @@ -214,6 +199,51 @@ return currentVersion; } + +#pragma mark Notification Preferences + +- (BOOL)soundInForeground { + NSNumber *preference = [self tryGetValueForKey:PLAY_SOUND_IN_FOREGROUND_KEY]; + if (preference) { + return [preference boolValue]; + } else{ + return YES; + } +} + +- (void)setSoundInForeground:(BOOL)enabled { + [self setValueForKey:PLAY_SOUND_IN_FOREGROUND_KEY toValue:@(enabled)]; +} + +-(void)setNotificationPreviewType:(NotificationType)type +{ + [self setValueForKey:NOTIFICATION_PREVIEW_TYPE_KEY toValue:@(type)]; +} + +-(NotificationType)notificationPreviewType { + NSNumber *preference = [self tryGetValueForKey:NOTIFICATION_PREVIEW_TYPE_KEY]; + + if (preference) { + return [preference unsignedIntegerValue]; + } else { + return NotificationNamePreview; + } +} + +- (NSString*)nameForNotificationPreviewType:(NotificationType)notificationType { + switch (notificationType) { + case NotificationNamePreview: + return NSLocalizedString(@"NOTIFICATIONS_SENDER_AND_MESSAGE", nil); + case NotificationNameNoPreview: + return NSLocalizedString(@"NOTIFICATIONS_SENDER_ONLY", nil); + case NotificationNoNameNoPreview: + return NSLocalizedString(@"NOTIFICATIONS_NONE", nil); + default: + DDLogWarn(@"Undefined NotificationType in Settings"); + return @""; + } +} + #pragma mark Bloom filter - (NSData*)tryRetreiveBloomFilter { diff --git a/Signal/src/environment/VersionMigrations.m b/Signal/src/environment/VersionMigrations.m index 871351313..914700d9d 100644 --- a/Signal/src/environment/VersionMigrations.m +++ b/Signal/src/environment/VersionMigrations.m @@ -59,8 +59,8 @@ [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:waitingController animated:YES completion:nil]; - [PushManager.sharedManager registrationAndRedPhoneTokenRequestWithSuccess:^(NSData *pushToken, NSString *signupToken) { - [TSAccountManager registerWithRedPhoneToken:signupToken pushToken:pushToken success:^{ + [PushManager.sharedManager registrationAndRedPhoneTokenRequestWithSuccess:^(NSData *pushToken, NSData *voipToken, NSString *signupToken) { + [TSAccountManager registerWithRedPhoneToken:signupToken pushToken:pushToken voipToken:voipToken success:^{ [UIApplication.sharedApplication setNetworkActivityIndicatorVisible:NO]; [self clearMigrationFlag]; Environment *env = [Environment getCurrent]; diff --git a/Signal/src/network/PushManager.h b/Signal/src/network/PushManager.h index 184cd6b0a..3767d7615 100644 --- a/Signal/src/network/PushManager.h +++ b/Signal/src/network/PushManager.h @@ -9,6 +9,8 @@ #import #import +#define Signal_Thread_UserInfo_Key @"Signal_Thread_Id" + #define Signal_Call_Accept_Identifier @"Signal_Call_Accept" #define Signal_Call_Decline_Identifier @"Signal_Call_Decline" @@ -19,6 +21,8 @@ #define Signal_Message_MarkAsRead_Identifier @"Signal_Message_MarkAsRead" typedef void(^failedPushRegistrationBlock)(NSError *error); +typedef void (^pushTokensSuccessBlock)(NSData *pushToken, NSData *voipToken); +typedef void (^registrationTokensSuccessBlock)(NSData *pushToken, NSData *voipToken, NSString *signupToken); /** * The Push Manager is responsible for registering the device for Signal push notifications. @@ -35,7 +39,7 @@ typedef void(^failedPushRegistrationBlock)(NSError *error); * @param failure Failure completion block */ -- (void)registrationAndRedPhoneTokenRequestWithSuccess:(void (^)(NSData* pushToken, NSString* signupToken))success failure:(failedPushRegistrationBlock)failure; +- (void)registrationAndRedPhoneTokenRequestWithSuccess:(registrationTokensSuccessBlock)success failure:(failedPushRegistrationBlock)failure; /** * Returns the Push Notification Token of this device @@ -44,7 +48,7 @@ typedef void(^failedPushRegistrationBlock)(NSError *error); * @param failure Failure block, executed when failed to get push token */ -- (void)requestPushTokenWithSuccess:(void (^)(NSData* pushToken))success failure:(void(^)(NSError *))failure; +- (void)requestPushTokenWithSuccess:(pushTokensSuccessBlock)success failure:(void(^)(NSError *))failure; /** * Registers for Users Notifications. By doing this on launch, we are sure that the correct categories of user notifications is registered. @@ -58,5 +62,15 @@ typedef void(^failedPushRegistrationBlock)(NSError *error); @property TOCFutureSource *pushNotificationFutureSource; @property TOCFutureSource *userNotificationFutureSource; +@property TOCFutureSource *pushKitNotificationFutureSource; + +-(TOCFuture*)registerPushKitNotificationFuture; + +#pragma mark Push Notifications Delegate Methods + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo; +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; +- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler; +- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification; @end diff --git a/Signal/src/network/PushManager.m b/Signal/src/network/PushManager.m index 37c441e35..203b4a130 100644 --- a/Signal/src/network/PushManager.m +++ b/Signal/src/network/PushManager.m @@ -5,19 +5,29 @@ // Created by Frederic Jacobs on 31/07/14. // Copyright (c) 2014 Open Whisper Systems. All rights reserved. // + +#import + +#import "ContactsManager.h" +#import "InCallViewController.h" +#import "NotificationTracker.h" + #import "PreferencesUtil.h" #import "PushManager.h" #import "Environment.h" #import "RPServerRequestsManager.h" #import "TSAccountManager.h" +#import "TSSocketManager.h" #define pushManagerDomain @"org.whispersystems.pushmanager" -@interface PushManager () +@interface PushManager () -@property TOCFutureSource *registerWithServerFutureSource; -@property UIAlertView *missingPermissionsAlertView; +@property TOCFutureSource *registerWithServerFutureSource; +@property UIAlertView *missingPermissionsAlertView; +@property (nonatomic, strong) NotificationTracker *notificationTracker; +@property (nonatomic) UIBackgroundTaskIdentifier callBackgroundTask; @end @implementation PushManager @@ -36,6 +46,7 @@ { self = [super init]; if (self) { + self.notificationTracker = [NotificationTracker notificationTracker]; self.missingPermissionsAlertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ACTION_REQUIRED_TITLE", @"") message:NSLocalizedString(@"PUSH_SETTINGS_MESSAGE", @"") @@ -46,6 +57,135 @@ return self; } +#pragma mark Manage Incoming Push + +-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { + + if ([self isRedPhonePush:userInfo]) { + ResponderSessionDescriptor* call; + if (![self.notificationTracker shouldProcessNotification:userInfo]){ + return; + } + + @try { + call = [ResponderSessionDescriptor responderSessionDescriptorFromEncryptedRemoteNotification:userInfo]; + DDLogDebug(@"Received remote notification. Parsed session descriptor: %@.", call); + } @catch (OperationFailed* ex) { + DDLogError(@"Error parsing remote notification. Error: %@.", ex); + return; + } + + if (!call) { + DDLogError(@"Decryption of session descriptor failed"); + return; + } + + [Environment.phoneManager incomingCallWithSession:call]; + + + if (![self applicationIsActive]) { + UILocalNotification *notification = [[UILocalNotification alloc] init]; + + NSString *callerId = call.initiatorNumber.toE164; + NSString *nameString = [[Environment getCurrent].contactsManager nameStringForPhoneIdentifier:callerId]; + + NSString *displayName = nameString?nameString:callerId; + + notification.alertBody = [NSString stringWithFormat:@"Incoming call from %@", displayName]; + notification.category = Signal_Call_Category; + notification.soundName = @"r.caf"; + + [[UIApplication sharedApplication] presentLocalNotificationNow:notification]; + + if (_callBackgroundTask == 0) { + _callBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + _callBackgroundTask = 0; + [Environment.phoneManager hangupOrDenyCall]; + }]; + } + } + } else { + if (![self applicationIsActive]) { + [TSSocketManager becomeActiveFromBackground]; + } + } +} + +/** + * This code should in principle never be called. The only cases where it would be called are with the old-style "content-available:1" pushes if there is no "voip" token registered + * + */ + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + if ([self isRedPhonePush:userInfo]) { + [self application:application didReceiveRemoteNotification:userInfo]; + } + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + completionHandler(UIBackgroundFetchResultNewData); + }); +} + +- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { + if ([notification.category isEqualToString:Signal_Message_Category]) { + NSString *threadId = [notification.userInfo objectForKey:Signal_Thread_UserInfo_Key]; + [Environment messageThreadId:threadId]; + } +} + +- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler { + + if ([identifier isEqualToString:Signal_Call_Accept_Identifier]) { + [Environment.phoneManager answerCall]; + + completionHandler(); + } else if ([identifier isEqualToString:Signal_Call_Decline_Identifier]){ + [Environment.phoneManager hangupOrDenyCall]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + completionHandler(); + }); + } else{ + NSString *threadId = [notification.userInfo objectForKey:Signal_Thread_UserInfo_Key]; + [Environment messageThreadId:threadId]; + + completionHandler(); + } +} + +- (BOOL)isRedPhonePush:(NSDictionary*)pushDict { + NSDictionary *aps = [pushDict objectForKey:@"aps"]; + NSString *category = [aps objectForKey:@"category"]; + + if ([category isEqualToString:Signal_Call_Category]) { + return YES; + } else{ + return NO; + } +} + +#pragma mark PushKit + +-(void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type +{ + [[PushManager sharedManager].pushKitNotificationFutureSource trySetResult:credentials.token]; +} + +-(void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type +{ + [self application:[UIApplication sharedApplication] didReceiveRemoteNotification:payload.dictionaryPayload]; +} + +-(TOCFuture*)registerPushKitNotificationFuture{ + self.pushKitNotificationFutureSource = [TOCFutureSource new]; + PKPushRegistry* voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; + voipRegistry.delegate = self; + voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; + return self.pushKitNotificationFutureSource.future; +} + #pragma mark Register device for Push Notification locally - (TOCFuture *)registerPushNotificationFuture @@ -56,53 +196,63 @@ return self.pushNotificationFutureSource.future; } -- (void)requestPushTokenWithSuccess:(void (^)(NSData *pushToken))success failure:(failedPushRegistrationBlock)failure +- (void)requestPushTokenWithSuccess:(pushTokensSuccessBlock)success failure:(failedPushRegistrationBlock)failure { TOCFuture *requestPushTokenFuture = [self registerPushNotificationFuture]; - + [requestPushTokenFuture catchDo:^(id failureObj) { - [self.missingPermissionsAlertView show]; + [self.missingPermissionsAlertView show]; failure(failureObj); DDLogError(@"This should not happen on iOS8. No push token was provided"); }]; [requestPushTokenFuture thenDo:^(NSData *pushToken) { - TOCFuture *registerPushTokenFuture = [self registerForPushFutureWithToken:pushToken]; - - [registerPushTokenFuture catchDo:^(id failureObj) { - failure(failureObj); - }]; - - [registerPushTokenFuture thenDo:^(id value) { - TOCFuture *userRegistration = [self registerForUserNotificationsFuture]; - - [userRegistration thenDo:^(UIUserNotificationSettings *userNotificationSettings) { - success(pushToken); + TOCFuture *voipPushTokenFuture = [self registerPushKitNotificationFuture]; + + [voipPushTokenFuture finallyDo:^(TOCFuture *completed) { + NSData *voipPushToken = completed.hasResult?completed.forceGetResult:nil; + + TOCFuture *registerPushTokenFuture = [self registerForPushFutureWithToken:pushToken voipToken:voipPushToken]; + + [registerPushTokenFuture catchDo:^(id failureObj) { + failure(failureObj); + }]; + + [registerPushTokenFuture thenDo:^(id value) { + TOCFuture *userRegistration = [self registerForUserNotificationsFuture]; + + [userRegistration thenDo:^(UIUserNotificationSettings *userNotificationSettings) { + success(pushToken, voipPushToken); + }]; + }]; }]; - }]; }]; } -- (void)registrationAndRedPhoneTokenRequestWithSuccess:(void (^)(NSData *pushToken, NSString *signupToken))success +- (void)registrationAndRedPhoneTokenRequestWithSuccess:(registrationTokensSuccessBlock)success failure:(failedPushRegistrationBlock)failure { if (!self.wantRemoteNotifications) { - [self registerTokenWithRedPhoneServer:[@"Fake PushToken" dataUsingEncoding:NSUTF8StringEncoding] + NSData *fakeToken = [@"Fake PushToken" dataUsingEncoding:NSUTF8StringEncoding]; + [self registerTokenWithRedPhoneServer:fakeToken + voipToken:fakeToken withSuccess:success failure:failure]; + return; } - [self requestPushTokenWithSuccess:^(NSData *pushToken) { - [self registerTokenWithRedPhoneServer:pushToken withSuccess:success failure:failure]; + [self requestPushTokenWithSuccess:^(NSData *pushToken, NSData *voipToken) { + [self registerTokenWithRedPhoneServer:pushToken voipToken:voipToken withSuccess:success failure:failure]; } failure:^(NSError *error) { [self.missingPermissionsAlertView show]; failure([NSError errorWithDomain:pushManagerDomain code:400 userInfo:@{}]); }]; } -- (void)registerTokenWithRedPhoneServer:(NSData *)pushToken - withSuccess:(void (^)(NSData *pushToken, NSString *signupToken))success +- (void)registerTokenWithRedPhoneServer:(NSData*)pushToken + voipToken:(NSData*)voipToken + withSuccess:(registrationTokensSuccessBlock)success failure:(failedPushRegistrationBlock)failure { [RPServerRequestsManager.sharedInstance performRequest:[RPAPICall requestTextSecureVerificationCode] @@ -117,7 +267,7 @@ return; } - success(pushToken, tsToken); + success(pushToken, voipToken, tsToken); } failure:^(NSURLSessionDataTask *task, NSError *error) { failure(error); @@ -127,13 +277,61 @@ - (TOCFuture *)registerForUserNotificationsFuture { self.userNotificationFutureSource = [TOCFutureSource new]; + UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationType)[self allNotificationTypes] - categories:nil]; + categories:[NSSet setWithObjects:[self userNotificationsCallCategory], [self userNotificationsMessageCategory], nil]]; + [UIApplication.sharedApplication registerUserNotificationSettings:settings]; return self.userNotificationFutureSource.future; } +- (UIUserNotificationCategory*)userNotificationsMessageCategory{ + UIMutableUserNotificationAction *action_accept = [UIMutableUserNotificationAction new]; + action_accept.identifier = Signal_Message_View_Identifier; + action_accept.title = NSLocalizedString(@"View", @""); + action_accept.activationMode = UIUserNotificationActivationModeForeground; + action_accept.destructive = NO; + action_accept.authenticationRequired = YES; + + UIMutableUserNotificationAction *action_decline = [UIMutableUserNotificationAction new]; + action_decline.identifier = Signal_Message_MarkAsRead_Identifier; + action_decline.title = NSLocalizedString(@"Mark as read", @""); + action_decline.activationMode = UIUserNotificationActivationModeBackground; + action_decline.destructive = NO; + action_decline.authenticationRequired = NO; + + UIMutableUserNotificationCategory *messageCategory = [UIMutableUserNotificationCategory new]; + messageCategory.identifier = Signal_Message_Category; + [messageCategory setActions:@[action_accept, action_decline] forContext:UIUserNotificationActionContextMinimal]; + [messageCategory setActions:@[action_accept, action_decline] forContext:UIUserNotificationActionContextDefault]; + + return messageCategory; +} + +- (UIUserNotificationCategory*)userNotificationsCallCategory{ + UIMutableUserNotificationAction *action_accept = [UIMutableUserNotificationAction new]; + action_accept.identifier = Signal_Call_Accept_Identifier; + action_accept.title = NSLocalizedString(@"ANSWER_CALL_BUTTON_TITLE", @""); + action_accept.activationMode = UIUserNotificationActivationModeForeground; + action_accept.destructive = NO; + action_accept.authenticationRequired = NO; + + UIMutableUserNotificationAction *action_decline = [UIMutableUserNotificationAction new]; + action_decline.identifier = Signal_Call_Decline_Identifier; + action_decline.title = NSLocalizedString(@"REJECT_CALL_BUTTON_TITLE", @""); + action_decline.activationMode = UIUserNotificationActivationModeBackground; + action_decline.destructive = NO; + action_decline.authenticationRequired = NO; + + UIMutableUserNotificationCategory *callCategory = [UIMutableUserNotificationCategory new]; + callCategory.identifier = Signal_Call_Category; + [callCategory setActions:@[action_accept, action_decline] forContext:UIUserNotificationActionContextMinimal]; + [callCategory setActions:@[action_accept, action_decline] forContext:UIUserNotificationActionContextDefault]; + + return callCategory; +} + - (BOOL)needToRegisterForRemoteNotifications { return self.wantRemoteNotifications && (!UIApplication.sharedApplication.isRegisteredForRemoteNotifications); @@ -165,11 +363,11 @@ #pragma mark Register Push Notification Token with RedPhone server -- (TOCFuture *)registerForPushFutureWithToken:(NSData *)token +- (TOCFuture *)registerForPushFutureWithToken:(NSData *)pushToken voipToken:(NSData*)voipToken { self.registerWithServerFutureSource = [TOCFutureSource new]; - [RPServerRequestsManager.sharedInstance performRequest:[RPAPICall registerPushNotificationWithPushToken:token] + [RPServerRequestsManager.sharedInstance performRequest:[RPAPICall registerPushNotificationWithPushToken:pushToken voipToken:voipToken] success:^(NSURLSessionDataTask *task, id responseObject) { if ([task.response isKindOfClass:NSHTTPURLResponse.class]) { NSInteger statusCode = [(NSHTTPURLResponse *)task.response statusCode]; @@ -192,4 +390,14 @@ return self.registerWithServerFutureSource.future; } +- (BOOL)applicationIsActive { + UIApplication *app = [UIApplication sharedApplication]; + + if (app.applicationState == UIApplicationStateActive) { + return YES; + } + + return NO; +} + @end diff --git a/Signal/src/network/http/RPAPICall.h b/Signal/src/network/http/RPAPICall.h index dc8720f41..4e08c475d 100644 --- a/Signal/src/network/http/RPAPICall.h +++ b/Signal/src/network/http/RPAPICall.h @@ -34,7 +34,7 @@ typedef NS_ENUM(NSInteger, HTTPMethod) { + (RPAPICall*)requestVerificationCode; + (RPAPICall*)requestVerificationCodeWithVoice; + (RPAPICall*)verifyVerificationCode:(NSString*)verificationCode; -+ (RPAPICall*)registerPushNotificationWithPushToken:(NSData*)pushToken; ++ (RPAPICall*)registerPushNotificationWithPushToken:(NSData*)pushToken voipToken:(NSData*)voipToken; + (RPAPICall*)requestTextSecureVerificationCode; + (RPAPICall*)unregisterWithPushToken:(NSData*)pushToken; + (RPAPICall*)fetchBloomFilter; diff --git a/Signal/src/network/http/RPAPICall.m b/Signal/src/network/http/RPAPICall.m index 0219d54ce..98cc0064c 100644 --- a/Signal/src/network/http/RPAPICall.m +++ b/Signal/src/network/http/RPAPICall.m @@ -13,6 +13,7 @@ #import "SignalUtil.h" #import "SignalKeyingStorage.h" #import "Util.h" +#import "NSData+ows_StripToken.h" #define CLAIMED_INTEROP_VERSION_IN_INITIATE_SIGNAL 1 @@ -64,10 +65,15 @@ return apiCall; } -+ (RPAPICall*)registerPushNotificationWithPushToken:(NSData*)pushToken { ++ (RPAPICall*)registerPushNotificationWithPushToken:(NSData*)pushToken voipToken:(NSData*)voipToken { RPAPICall *apiCall = [self defaultAPICall]; + if (voipToken) { + apiCall.parameters = @{@"voip":[voipToken ows_tripToken]}; + } else { + DDLogWarn(@"No VoIP push token registered, might experience some issues while in background."); + } apiCall.method = HTTP_PUT; - apiCall.endPoint = [NSString stringWithFormat:@"/apn/%@", pushToken.encodedAsHexString]; + apiCall.endPoint = [NSString stringWithFormat:@"/apn/%@", [pushToken ows_tripToken]]; return apiCall; } diff --git a/Signal/src/textsecure/Account/TSAccountManager.h b/Signal/src/textsecure/Account/TSAccountManager.h index c9ab4f65c..c5923365c 100644 --- a/Signal/src/textsecure/Account/TSAccountManager.h +++ b/Signal/src/textsecure/Account/TSAccountManager.h @@ -60,7 +60,7 @@ typedef void(^failedVerificationBlock)(NSError *error); #if TARGET_OS_IPHONE -+ (void)registerWithRedPhoneToken:(NSString*)tsToken pushToken:(NSData*)pushToken success:(successCompletionBlock)successBlock failure:(failedVerificationBlock)failureBlock; ++ (void)registerWithRedPhoneToken:(NSString*)tsToken pushToken:(NSData*)pushToken voipToken:(NSData*)voipToken success:(successCompletionBlock)successBlock failure:(failedVerificationBlock)failureBlock; /** * Register's the device's push notification token with the server @@ -68,7 +68,7 @@ typedef void(^failedVerificationBlock)(NSError *error); * @param pushToken Apple's Push Token */ -+ (void)registerForPushNotifications:(NSData*)pushToken success:(successCompletionBlock)success failure:(failedVerificationBlock)failureBlock; ++ (void)registerForPushNotifications:(NSData*)pushToken voipToken:(NSData*)voipToken success:(successCompletionBlock)success failure:(failedVerificationBlock)failureBlock; #endif diff --git a/Signal/src/textsecure/Account/TSAccountManager.m b/Signal/src/textsecure/Account/TSAccountManager.m index 1f5fdc9bd..586f5e83b 100644 --- a/Signal/src/textsecure/Account/TSAccountManager.m +++ b/Signal/src/textsecure/Account/TSAccountManager.m @@ -11,6 +11,7 @@ #import "Constraints.h" #import "NSData+Base64.h" #import "NSData+hexString.h" +#import "NSData+ows_StripToken.h" #import "NSURLSessionDataTask+StatusCode.h" #import "SecurityUtils.h" @@ -71,11 +72,12 @@ return registrationID; } -+ (void)registerForPushNotifications:(NSData *)pushToken success:(successCompletionBlock)success failure:(failedVerificationBlock)failureBlock{ ++ (void)registerForPushNotifications:(NSData *)pushToken voipToken:(NSData*)voipToken success:(successCompletionBlock)success failure:(failedVerificationBlock)failureBlock{ - NSString *stringToken = [[pushToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<> "]]; - - [[TSNetworkManager sharedManager] queueAuthenticatedRequest:[[TSRegisterForPushRequest alloc] initWithPushIdentifier:stringToken] success:^(NSURLSessionDataTask *task, id responseObject) { + NSString *pushTokenString = [pushToken ows_tripToken]; + NSString *voipTokenString = [voipToken ows_tripToken]; + + [[TSNetworkManager sharedManager] queueAuthenticatedRequest:[[TSRegisterForPushRequest alloc] initWithPushIdentifier:pushTokenString voipIdentifier:voipTokenString] success:^(NSURLSessionDataTask *task, id responseObject) { success(); } failure:^(NSURLSessionDataTask *task, NSError *error) { NSLog(@"NSError: %@", error.debugDescription); @@ -96,7 +98,12 @@ }]; } -+ (void)registerWithRedPhoneToken:(NSString*)tsToken pushToken:(NSData*)pushToken success:(successCompletionBlock)successBlock failure:(failedVerificationBlock)failureBlock{ ++ (void)registerWithRedPhoneToken:(NSString*)tsToken + pushToken:(NSData*)pushToken + voipToken:(NSData*)voipToken + success:(successCompletionBlock)successBlock + failure:(failedVerificationBlock)failureBlock +{ NSString *authToken = [self generateNewAccountAuthenticationToken]; NSString *signalingKey = [self generateNewSignalingKeyToken]; @@ -118,10 +125,10 @@ [TSStorageManager storeServerToken:authToken signalingKey:signalingKey phoneNumber:phoneNumber]; - [self registerForPushNotifications:pushToken success:^{ + [self registerForPushNotifications:pushToken voipToken:voipToken success:^{ [self registerPreKeys:^{ successBlock(); - [TSSocketManager becomeActive]; + [TSSocketManager becomeActiveFromForeground]; } failure:failureBlock]; } failure:^(NSError *error) { failureBlock([self errorForRegistrationFailure:kTSRegistrationFailureNetwork HTTPStatusCode:0]); @@ -140,10 +147,11 @@ } + (void)registerPreKeysAfterPush:(NSData*)pushToken + voipToken:(NSData*)voipToken success:(successCompletionBlock)successBlock failure:(failedVerificationBlock)failureBlock { - [self registerForPushNotifications:pushToken success:^{ + [self registerForPushNotifications:pushToken voipToken:voipToken success:^{ [self registerPreKeys:successBlock failure:failureBlock]; } failure:failureBlock]; diff --git a/Signal/src/textsecure/Messages/TSMessagesManager.h b/Signal/src/textsecure/Messages/TSMessagesManager.h index 905f18595..09ce526de 100644 --- a/Signal/src/textsecure/Messages/TSMessagesManager.h +++ b/Signal/src/textsecure/Messages/TSMessagesManager.h @@ -27,4 +27,6 @@ - (void)handleSendToMyself:(TSOutgoingMessage*)outgoingMessage; +- (NSUInteger)unreadMessagesCount; + @end diff --git a/Signal/src/textsecure/Messages/TSMessagesManager.m b/Signal/src/textsecure/Messages/TSMessagesManager.m index 394de0e17..91a15e052 100644 --- a/Signal/src/textsecure/Messages/TSMessagesManager.m +++ b/Signal/src/textsecure/Messages/TSMessagesManager.m @@ -23,6 +23,7 @@ #import "TSInvalidIdentityKeyReceivingErrorMessage.h" #import "TSInfoMessage.h" +#import "TSDatabaseView.h" #import "TSStorageManager+keyingMaterial.h" #import "TSStorageManager+IdentityKeyStore.h" #import "TSStorageManager+SessionStore.h" @@ -349,7 +350,7 @@ } NSString *name = [thread name]; - [self notifyUserForIncomingMessage:incomingMessage from:name]; + [self notifyUserForIncomingMessage:incomingMessage from:name inThread:thread]; }]; } @@ -399,8 +400,57 @@ }]; } -- (void)notifyUserForIncomingMessage:(TSIncomingMessage*)message from:(NSString*)name { - AudioServicesPlayAlertSound(_newMessageSound); +- (NSUInteger)unreadMessagesCount { + __block NSUInteger numberOfItems; + [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + numberOfItems = [[transaction ext:TSUnreadDatabaseViewExtensionName] numberOfItemsInAllGroups]; + }]; + + return numberOfItems; +} + +- (void)notifyUserForIncomingMessage:(TSIncomingMessage*)message from:(NSString*)name inThread:(TSThread*)thread { + if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) { + UILocalNotification *notification = [[UILocalNotification alloc] init]; + notification.category = Signal_Message_Category; + notification.userInfo = @{Signal_Thread_UserInfo_Key:thread.uniqueId}; + + switch ([[Environment preferences] notificationPreviewType]) { + case NotificationNamePreview: + if ([thread isGroupThread]) { + NSString *sender = [[Environment getCurrent].contactsManager nameStringForPhoneIdentifier:message.authorId]; + if (!sender) { + sender = message.authorId; + } + + notification.alertBody = [NSString stringWithFormat:@"New message from %@ in group \"%@\": %@", sender, name, message.description]; + } else { + notification.alertBody = [NSString stringWithFormat:@"%@: %@", name, message.description]; + } + break; + case NotificationNameNoPreview:{ + if ([thread isGroupThread]) { + notification.alertBody = [NSString stringWithFormat:@"%@ \"%@\"", NSLocalizedString(@"APN_MESSAGE_IN_GROUP",nil), name]; + } else { + notification.alertBody = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"APN_MESSAGE_FROM", nil), name]; + } + break; + } + case NotificationNoNameNoPreview: + notification.alertBody = NSLocalizedString(@"APN_Message", nil); + break; + default: + notification.alertBody = NSLocalizedString(@"APN_Message", nil); + break; + } + + [[UIApplication sharedApplication] presentLocalNotificationNow:notification]; + AudioServicesPlayAlertSound(_newMessageSound); + } else { + if ([Environment.preferences soundInForeground]) { + AudioServicesPlayAlertSound(_newMessageSound); + } + } } - (void)dealloc { diff --git a/Signal/src/textsecure/Network/API/Requests/TSRegisterForPushRequest.h b/Signal/src/textsecure/Network/API/Requests/TSRegisterForPushRequest.h index a5d6dd4cb..fa4cca013 100644 --- a/Signal/src/textsecure/Network/API/Requests/TSRegisterForPushRequest.h +++ b/Signal/src/textsecure/Network/API/Requests/TSRegisterForPushRequest.h @@ -9,5 +9,7 @@ #import "TSRequest.h" @interface TSRegisterForPushRequest : TSRequest -- (id) initWithPushIdentifier:(NSString*)identifier; + +- (id) initWithPushIdentifier:(NSString*)identifier voipIdentifier:(NSString*)voipId; + @end diff --git a/Signal/src/textsecure/Network/API/Requests/TSRegisterForPushRequest.m b/Signal/src/textsecure/Network/API/Requests/TSRegisterForPushRequest.m index bb698c89d..267ce0a15 100644 --- a/Signal/src/textsecure/Network/API/Requests/TSRegisterForPushRequest.m +++ b/Signal/src/textsecure/Network/API/Requests/TSRegisterForPushRequest.m @@ -12,12 +12,13 @@ @implementation TSRegisterForPushRequest -- (id) initWithPushIdentifier:(NSString*)identifier{ +- (id) initWithPushIdentifier:(NSString*)identifier voipIdentifier:(NSString*)voipId +{ self = [super initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", textSecureAccountsAPI, @"apn"]]]; self.HTTPMethod = @"PUT"; - self.parameters = [NSMutableDictionary dictionaryWithObjects:@[identifier] forKeys:@[@"apnRegistrationId"]]; + self.parameters = [NSMutableDictionary dictionaryWithObjects:@[identifier, voipId] forKeys:@[@"apnRegistrationId", @"voipRegistrationId"]]; return self; } diff --git a/Signal/src/textsecure/Network/WebSockets/TSSocketManager.h b/Signal/src/textsecure/Network/WebSockets/TSSocketManager.h index cd2435143..5b7a5033a 100644 --- a/Signal/src/textsecure/Network/WebSockets/TSSocketManager.h +++ b/Signal/src/textsecure/Network/WebSockets/TSSocketManager.h @@ -23,7 +23,9 @@ extern NSString * const SocketConnectingNotification; @interface TSSocketManager : NSObject -+ (void)becomeActive; ++ (void)becomeActiveFromForeground; ++ (void)becomeActiveFromBackground; + + (void)resignActivity; + (void)sendNotification; diff --git a/Signal/src/textsecure/Network/WebSockets/TSSocketManager.m b/Signal/src/textsecure/Network/WebSockets/TSSocketManager.m index 2fec82864..9663f57ef 100644 --- a/Signal/src/textsecure/Network/WebSockets/TSSocketManager.m +++ b/Signal/src/textsecure/Network/WebSockets/TSSocketManager.m @@ -19,8 +19,9 @@ #import "Cryptography.h" #import "IncomingPushMessageSignal.pb.h" -#define kWebSocketHeartBeat 30 -#define kWebSocketReconnectTry 5 +#define kWebSocketHeartBeat 30 +#define kWebSocketReconnectTry 5 +#define kBackgroundConnectTimer 120 NSString * const SocketOpenedNotification = @"SocketOpenedNotification"; NSString * const SocketClosedNotification = @"SocketClosedNotification"; @@ -32,11 +33,15 @@ NSString * const SocketConnectingNotification = @"SocketConnectingNotification"; @property (nonatomic, retain) SRWebSocket *websocket; @property (nonatomic) SocketStatus status; + +@property (nonatomic, retain) NSTimer *backgroundConnectTimer; +@property (nonatomic) UIBackgroundTaskIdentifier fetchingTaskIdentifier; + @end @implementation TSSocketManager -- (id)init{ +- (instancetype)init{ self = [super init]; if (self) { @@ -47,7 +52,7 @@ NSString * const SocketConnectingNotification = @"SocketConnectingNotification"; return self; } -+ (id)sharedManager { ++ (instancetype)sharedManager { static TSSocketManager *sharedMyManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -106,7 +111,11 @@ NSString * const SocketConnectingNotification = @"SocketConnectingNotification"; #pragma mark - Delegate methods - (void) webSocketDidOpen:(SRWebSocket *)webSocket { - self.pingTimer = [NSTimer scheduledTimerWithTimeInterval:kWebSocketHeartBeat target:self selector:@selector(webSocketHeartBeat) userInfo:nil repeats:YES]; + self.pingTimer = [NSTimer scheduledTimerWithTimeInterval:kWebSocketHeartBeat + target:self + selector:@selector(webSocketHeartBeat) + userInfo:nil + repeats:YES]; self.status = kSocketStatusOpen; [self.reconnectTimer invalidate]; @@ -192,16 +201,77 @@ NSString * const SocketConnectingNotification = @"SocketConnectingNotification"; } } -- (NSString*)webSocketAuthenticationString{ +- (NSString*)webSocketAuthenticationString { return [NSString stringWithFormat:@"?login=%@&password=%@", [[TSAccountManager registeredNumber] stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"],[TSStorageManager serverAuthToken]]; } -- (void)scheduleRetry{ +- (void)scheduleRetry { if (!self.reconnectTimer || ![self.reconnectTimer isValid]) { - self.reconnectTimer = [NSTimer scheduledTimerWithTimeInterval:kWebSocketReconnectTry target:[self class] selector:@selector(becomeActive) userInfo:nil repeats:YES]; + self.reconnectTimer = [NSTimer scheduledTimerWithTimeInterval:kWebSocketReconnectTry + target:[self class] + selector:@selector(becomeActive) + userInfo:nil + repeats:YES]; } } +#pragma mark - Background Connect + ++ (void)becomeActiveFromForeground { + TSSocketManager *sharedInstance = [self sharedManager]; + + [sharedInstance.backgroundConnectTimer invalidate]; + sharedInstance.backgroundConnectTimer = nil; + sharedInstance.fetchingTaskIdentifier = 0; + + [self becomeActive]; +} + + ++ (void)becomeActiveFromBackground { + TSSocketManager *sharedInstance = [TSSocketManager sharedManager]; + + if (sharedInstance.fetchingTaskIdentifier == 0) { + sharedInstance.fetchingTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + sharedInstance.fetchingTaskIdentifier = 0; + [TSSocketManager resignActivity]; + }]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSTimer * timer = [NSTimer timerWithTimeInterval:kBackgroundConnectTimer + target:sharedInstance + selector:@selector(closeBackgroundTask) + userInfo:nil + repeats:NO]; + + [[NSRunLoop mainRunLoop] addTimer:timer + forMode:NSDefaultRunLoopMode]; + }); + + [self becomeActive]; + } +} + +- (void)closeBackgroundTask { + UIBackgroundTaskIdentifier identifier = self.fetchingTaskIdentifier; + self.fetchingTaskIdentifier = 0; + + if ([_websocket readyState] != SR_OPEN) { + [self backgroundConnectTimedOut]; + } + + [TSSocketManager resignActivity]; + + [[UIApplication sharedApplication] endBackgroundTask:identifier]; +} + +- (void)backgroundConnectTimedOut { + UILocalNotification *notification = [[UILocalNotification alloc] init]; + notification.alertBody = NSLocalizedString(@"APN_FETCHED_FAILED", nil); + [[UIApplication sharedApplication] presentLocalNotificationNow:notification]; +} + + #pragma mark UI Delegates - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { @@ -213,7 +283,7 @@ NSString * const SocketConnectingNotification = @"SocketConnectingNotification"; } } -- (void)notifyStatusChange{ +- (void)notifyStatusChange { switch (self.status) { case kSocketStatusOpen: [[NSNotificationCenter defaultCenter] postNotificationName:SocketOpenedNotification object:self]; @@ -229,7 +299,7 @@ NSString * const SocketConnectingNotification = @"SocketConnectingNotification"; } } -+ (void)sendNotification{ ++ (void)sendNotification { [[self sharedManager] notifyStatusChange]; } diff --git a/Signal/src/util/NSData+ows_StripToken.h b/Signal/src/util/NSData+ows_StripToken.h new file mode 100644 index 000000000..80900bf3b --- /dev/null +++ b/Signal/src/util/NSData+ows_StripToken.h @@ -0,0 +1,15 @@ +// +// NSData+ows_StripToken.h +// Signal +// +// Created by Frederic Jacobs on 14/04/15. +// Copyright (c) 2015 Open Whisper Systems. All rights reserved. +// + +#import + +@interface NSData (ows_StripToken) + +- (NSString*)ows_tripToken; + +@end diff --git a/Signal/src/util/NSData+ows_StripToken.m b/Signal/src/util/NSData+ows_StripToken.m new file mode 100644 index 000000000..23cbd7272 --- /dev/null +++ b/Signal/src/util/NSData+ows_StripToken.m @@ -0,0 +1,19 @@ +// +// NSData+ows_StripToken.m +// Signal +// +// Created by Frederic Jacobs on 14/04/15. +// Copyright (c) 2015 Open Whisper Systems. All rights reserved. +// + +#import "NSData+ows_StripToken.h" + +@implementation NSData (ows_StripToken) + +- (NSString*)ows_tripToken { + return [[[NSString stringWithFormat:@"%@", self] + stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] + stringByReplacingOccurrencesOfString:@" " withString:@""]; +} + +@end diff --git a/Signal/src/view controllers/CodeVerificationViewController.m b/Signal/src/view controllers/CodeVerificationViewController.m index 0fbfaf563..66a9a0cdd 100644 --- a/Signal/src/view controllers/CodeVerificationViewController.m +++ b/Signal/src/view controllers/CodeVerificationViewController.m @@ -78,10 +78,10 @@ [[RPServerRequestsManager sharedInstance] performRequest:[RPAPICall verifyVerificationCode:_challengeTextField.text] success:^(NSURLSessionDataTask *task, id responseObject) { - [PushManager.sharedManager registrationAndRedPhoneTokenRequestWithSuccess:^(NSData *pushToken, - NSString *signupToken) { - [TSAccountManager registerWithRedPhoneToken:signupToken + [PushManager.sharedManager registrationAndRedPhoneTokenRequestWithSuccess:^(NSData *pushToken, NSData *voipToken, NSString *signupToken) { + [TSAccountManager registerWithRedPhoneToken:signupToken pushToken:pushToken + voipToken:voipToken success:^{ success(); } diff --git a/Signal/src/view controllers/InCallViewController.h b/Signal/src/view controllers/InCallViewController.h index d4f68c8b1..222bd2d6e 100644 --- a/Signal/src/view controllers/InCallViewController.h +++ b/Signal/src/view controllers/InCallViewController.h @@ -5,6 +5,10 @@ #import "PhoneNumber.h" #import "PhoneNumberDirectoryFilterManager.h" +#define PICK_UP_NOTIFICATION @"RedPhoneCallPickUpNotification" +#define HANG_UP_NOTIFICATION @"RedPhoneCallHangUpNotification" + + @interface InCallViewController : UIViewController @property (nonatomic, strong) IBOutlet UIView *conversationContactView; diff --git a/Signal/src/view controllers/MessageComposeTableViewController.m b/Signal/src/view controllers/MessageComposeTableViewController.m index d2983d3bf..ff62c5ab0 100644 --- a/Signal/src/view controllers/MessageComposeTableViewController.m +++ b/Signal/src/view controllers/MessageComposeTableViewController.m @@ -418,7 +418,7 @@ NSString *identifier = [[[self contactForIndexPath:indexPath] textSecureIdentifiers] firstObject]; [self dismissViewControllerAnimated:YES completion:^(){ - [Environment messageIdentifier:identifier]; + [Environment messageIdentifier:identifier withCompose:YES]; }]; } diff --git a/Signal/src/view controllers/MessagesViewController.h b/Signal/src/view controllers/MessagesViewController.h index d9ecec270..e6516931e 100644 --- a/Signal/src/view controllers/MessagesViewController.h +++ b/Signal/src/view controllers/MessagesViewController.h @@ -28,4 +28,6 @@ - (void)setupWithTSIdentifier:(NSString*)identifier; - (void)setupWithTSGroup:(TSGroupModel*)model; +- (void)setComposeOnOpen:(BOOL)compose; + @end diff --git a/Signal/src/view controllers/MessagesViewController.m b/Signal/src/view controllers/MessagesViewController.m index 89e59a11f..c69f93062 100644 --- a/Signal/src/view controllers/MessagesViewController.m +++ b/Signal/src/view controllers/MessagesViewController.m @@ -108,6 +108,7 @@ typedef enum : NSUInteger { @property NSUInteger page; @property BOOL isVisible; +@property (nonatomic) BOOL composeOnOpen; @end @@ -135,6 +136,10 @@ typedef enum : NSUInteger { isGroupConversation = YES; } +- (void)setComposeOnOpen:(BOOL)compose { + _composeOnOpen = compose; +} + - (void)setupWithThread:(TSThread *)thread { self.thread = thread; isGroupConversation = [self.thread isKindOfClass:[TSGroupThread class]]; @@ -232,9 +237,14 @@ typedef enum : NSUInteger { - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; + [self markAllMessagesAsRead]; [self startReadTimer]; _isVisible = YES; [self initializeTitleLabelGestureRecognizer]; + + if (_composeOnOpen) { + [self popKeyBoard]; + } } - (void)viewWillDisappear:(BOOL)animated { @@ -1593,6 +1603,10 @@ typedef enum : NSUInteger { [self.collectionView reloadData]; } +- (void)popKeyBoard { + [self.inputToolbar.contentView.textView becomeFirstResponder]; +} + - (void)dismissKeyBoard { [self.inputToolbar.contentView.textView resignFirstResponder]; } diff --git a/Signal/src/view controllers/NewGroupViewController.m b/Signal/src/view controllers/NewGroupViewController.m index 0a53cb2f5..29d3e5fc8 100644 --- a/Signal/src/view controllers/NewGroupViewController.m +++ b/Signal/src/view controllers/NewGroupViewController.m @@ -119,7 +119,7 @@ static NSString* const kUnwindToMessagesViewSegue = @"UnwindToMessagesViewSegue" #pragma mark - Actions -(void)createGroup { TSGroupModel* model = [self makeGroup]; - [Environment messageGroupModel:model]; + [Environment messageGroupModel:model withCompose:YES]; } diff --git a/Signal/src/view controllers/NotificationSettingsOptionsViewController.h b/Signal/src/view controllers/NotificationSettingsOptionsViewController.h new file mode 100644 index 000000000..28f306df3 --- /dev/null +++ b/Signal/src/view controllers/NotificationSettingsOptionsViewController.h @@ -0,0 +1,13 @@ +// +// NotificationSettingsOptionsViewController.h +// Signal +// +// Created by Frederic Jacobs on 24/04/15. +// Copyright (c) 2015 Open Whisper Systems. All rights reserved. +// + +#import + +@interface NotificationSettingsOptionsViewController : UITableViewController + +@end diff --git a/Signal/src/view controllers/NotificationSettingsOptionsViewController.m b/Signal/src/view controllers/NotificationSettingsOptionsViewController.m new file mode 100644 index 000000000..7352c03bb --- /dev/null +++ b/Signal/src/view controllers/NotificationSettingsOptionsViewController.m @@ -0,0 +1,55 @@ +// +// NotificationSettingsOptionsViewController.m +// Signal +// +// Created by Frederic Jacobs on 24/04/15. +// Copyright (c) 2015 Open Whisper Systems. All rights reserved. +// + +#import "NotificationSettingsOptionsViewController.h" +#import "Environment.h" +#import "PropertyListPreferences.h" +#import "PreferencesUtil.h" + +@interface NotificationSettingsOptionsViewController () +@property NSArray *options; +@end + +@implementation NotificationSettingsOptionsViewController + +- (void)viewDidLoad +{ + self.options = @[@(NotificationNamePreview), + @(NotificationNameNoPreview), + @(NotificationNoNameNoPreview)]; + [super viewDidLoad]; +} + +#pragma mark - Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return (NSInteger)[self.options count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"NotificationSettingsOption"]; + PropertyListPreferences *prefs = [Environment preferences]; + [[cell textLabel] setText:[prefs nameForNotificationPreviewType:[[self.options objectAtIndex:(NSUInteger)indexPath.row] unsignedIntegerValue]]]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [Environment.preferences setNotificationPreviewType:[[self.options objectAtIndex:(NSUInteger)indexPath.row] unsignedIntegerValue]]; + [self.navigationController popViewControllerAnimated:YES]; +} + +@end diff --git a/Signal/src/view controllers/NotificationSettingsViewController.h b/Signal/src/view controllers/NotificationSettingsViewController.h new file mode 100755 index 000000000..98efaf3ae --- /dev/null +++ b/Signal/src/view controllers/NotificationSettingsViewController.h @@ -0,0 +1,13 @@ +// +// NotificationPreviewViewController.h +// Signal +// +// Created by Dylan Bourgeois on 09/12/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// + +#import + +@interface NotificationSettingsViewController : UITableViewController + +@end diff --git a/Signal/src/view controllers/NotificationSettingsViewController.m b/Signal/src/view controllers/NotificationSettingsViewController.m new file mode 100755 index 000000000..9d81d1117 --- /dev/null +++ b/Signal/src/view controllers/NotificationSettingsViewController.m @@ -0,0 +1,110 @@ +// +// NotificationPreviewViewController.m +// Signal +// +// Created by Frederic Jacobs on 09/12/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// + +#import "NotificationSettingsViewController.h" + +#import "Environment.h" +#import "PreferencesUtil.h" +#import "NotificationSettingsOptionsViewController.h" +#import "UIUtil.h" + +@interface NotificationSettingsViewController () + +@property NSArray *notificationsSections; + +@end + +@implementation NotificationSettingsViewController + +- (instancetype)init { + return [super initWithStyle:UITableViewStyleGrouped]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + [self setTitle:NSLocalizedString(@"SETTINGS_NOTIFICATIONS", nil)]; + + + self.notificationsSections = @[NSLocalizedString(@"NOTIFICATIONS_SECTION_BACKGROUND", nil), + NSLocalizedString(@"NOTIFICATIONS_SECTION_INAPP",nil)]; +} + +- (void)viewDidAppear:(BOOL)animated +{ + [self.tableView reloadData]; +} + +#pragma mark - Table view data source + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ + return [self.notificationsSections objectAtIndex:(NSUInteger)section]; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return (NSInteger)self.notificationsSections.count; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return 1; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSString *cellIdentifier = @"SignalTableViewCellIdentifier"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier]; + } + + PropertyListPreferences *prefs = Environment.preferences; + if (indexPath.section == 0) { + NotificationType notifType = [prefs notificationPreviewType]; + NSString *detailString = [prefs nameForNotificationPreviewType:notifType]; + + [[cell textLabel]setText:NSLocalizedString(@"NOTIFICATIONS_SHOW", nil)]; + [[cell detailTextLabel] setText:detailString]; + [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; + } else { + BOOL soundEnabled = [prefs soundInForeground]; + + [[cell textLabel]setText:NSLocalizedString(@"NOTIFICATIONS_SOUND", nil)]; + [[cell detailTextLabel] setText:nil]; + UISwitch *switchv = [[UISwitch alloc] initWithFrame:CGRectZero]; + switchv.on = soundEnabled; + [switchv addTarget:self + action:@selector(didToggleSoundNotificationsSwitch:) + forControlEvents:UIControlEventValueChanged]; + + cell.accessoryView = switchv; + } + + return cell; +} + +- (void)didToggleSoundNotificationsSwitch:(UISwitch*)sender +{ + [Environment.preferences setSoundInForeground:sender.on]; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + NotificationSettingsOptionsViewController *vc = [[NotificationSettingsOptionsViewController alloc] initWithStyle:UITableViewStyleGrouped]; + [self.navigationController pushViewController:vc animated:YES]; +} + +- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; + cell.accessoryType = UITableViewCellAccessoryNone; +} + +@end diff --git a/Signal/src/view controllers/SettingsTableViewController.h b/Signal/src/view controllers/SettingsTableViewController.h index 8ae66f334..b0d84d233 100644 --- a/Signal/src/view controllers/SettingsTableViewController.h +++ b/Signal/src/view controllers/SettingsTableViewController.h @@ -16,6 +16,7 @@ @property IBOutlet UILabel *networkStatusHeader; @property IBOutlet UILabel *settingsPrivacyTitle; +@property IBOutlet UILabel *settingsNotifications; @property IBOutlet UILabel *settingsAdvancedTitle; @property IBOutlet UILabel *settingsAboutTitle; @property IBOutlet UIButton *destroyAccountButton; diff --git a/Signal/src/view controllers/SettingsTableViewController.m b/Signal/src/view controllers/SettingsTableViewController.m index 82c26db0f..9f41b01b0 100644 --- a/Signal/src/view controllers/SettingsTableViewController.m +++ b/Signal/src/view controllers/SettingsTableViewController.m @@ -37,6 +37,7 @@ #import "AdvancedSettingsTableViewController.h" #import "AboutTableViewController.h" #import "PushManager.h" +#import "NotificationSettingsViewController.h" #define kProfileCellHeight 87.0f #define kStandardCellHeight 44.0f @@ -45,22 +46,23 @@ #define kRegisteredNumberRow 0 #define kPrivacyRow 0 -#define kAdvancedRow 1 -#define kAboutRow 2 +#define kNotificationRow 1 +#define kAdvancedRow 2 +#define kAboutRow 3 #define kNetworkRow 0 #define kUnregisterRow 0 typedef enum { kRegisteredRows = 1, - kGeneralRows = 3, + kGeneralRows = 4, kNetworkStatusRows = 1, kUnregisterRows = 1, } kRowsForSection; typedef enum { kRegisteredNumberSection=0, - kGeneralSection=2, kNetworkStatusSection=1, + kGeneralSection=2, kUnregisterSection=3, } kSection; @@ -86,6 +88,7 @@ typedef enum { _settingsPrivacyTitle.text = NSLocalizedString(@"SETTINGS_PRIVACY_TITLE",@""); _settingsAdvancedTitle.text = NSLocalizedString(@"SETTINGS_ADVANCED_TITLE",@""); _settingsAboutTitle.text = NSLocalizedString(@"SETTINGS_ABOUT",@""); + _settingsNotifications.text = NSLocalizedString(@"SETTINGS_NOTIFICATIONS", nil); [_destroyAccountButton setTitle:NSLocalizedString(@"SETTINGS_DELETE_ACCOUNT_BUTTON", @"") forState:UIControlStateNormal]; } @@ -134,15 +137,21 @@ typedef enum { switch (indexPath.row) { case kPrivacyRow: { - PrivacySettingsTableViewController * vc = [[PrivacySettingsTableViewController alloc]init]; + PrivacySettingsTableViewController *vc = [[PrivacySettingsTableViewController alloc]init]; NSAssert(self.navigationController != nil, @"Navigation controller must not be nil"); NSAssert(vc != nil, @"Privacy Settings View Controller must not be nil"); [self.navigationController pushViewController:vc animated:YES]; break; } + case kNotificationRow: + { + NotificationSettingsViewController *vc = [[NotificationSettingsViewController alloc] init]; + [self.navigationController pushViewController:vc animated:YES]; + break; + } case kAdvancedRow: { - AdvancedSettingsTableViewController * vc = [[AdvancedSettingsTableViewController alloc]init]; + AdvancedSettingsTableViewController *vc = [[AdvancedSettingsTableViewController alloc]init]; NSAssert(self.navigationController != nil, @"Navigation controller must not be nil"); NSAssert(vc != nil, @"Advanced Settings View Controller must not be nil"); [self.navigationController pushViewController:vc animated:YES]; @@ -193,7 +202,7 @@ typedef enum { - (void)proceedToUnregistration{ [TSAccountManager unregisterTextSecureWithSuccess:^{ - [PushManager.sharedManager requestPushTokenWithSuccess:^(NSData* pushToken){ + [PushManager.sharedManager requestPushTokenWithSuccess:^(NSData* pushToken, NSData *voipToken){ [[RPServerRequestsManager sharedInstance]performRequest:[RPAPICall unregisterWithPushToken:pushToken] success:^(NSURLSessionDataTask *task, id responseObject) { [Environment resetAppData]; exit(0); diff --git a/Signal/src/view controllers/UITests/SignalsViewController.h b/Signal/src/view controllers/UITests/SignalsViewController.h index 2d1b49e75..2f8a9fa76 100644 --- a/Signal/src/view controllers/UITests/SignalsViewController.h +++ b/Signal/src/view controllers/UITests/SignalsViewController.h @@ -15,8 +15,10 @@ @interface SignalsViewController : UIViewController -@property (nonatomic) NSString *contactIdentifierFromCompose; -@property (nonatomic) TSGroupModel *groupFromCompose; +@property (nonatomic) NSString *contactIdentifierFromCompose; +@property (nonatomic) TSGroupModel *groupFromCompose; +@property (nonatomic) BOOL composeMessage; + @property (nonatomic, retain) IBOutlet UITableView *tableView; @property (nonatomic, retain) IBOutlet UIButton *inboxButton; @property (nonatomic, retain) IBOutlet UIButton *archiveButton; diff --git a/Signal/src/view controllers/UITests/SignalsViewController.m b/Signal/src/view controllers/UITests/SignalsViewController.m index 4c001d9b4..13a9e0aad 100644 --- a/Signal/src/view controllers/UITests/SignalsViewController.m +++ b/Signal/src/view controllers/UITests/SignalsViewController.m @@ -211,12 +211,7 @@ static NSString* const kShowSignupFlowSegue = @"showSignupFlow"; -(void) updateInboxCountLabel { - - __block NSUInteger numberOfItems; - [_editingDbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - numberOfItems = [[transaction ext:TSUnreadDatabaseViewExtensionName] numberOfItemsInAllGroups]; - }]; - + NSUInteger numberOfItems = [[TSMessagesManager sharedManager] unreadMessagesCount]; NSNumber *badgeNumber = [NSNumber numberWithUnsignedInteger:numberOfItems]; NSString *badgeValue = nil; @@ -224,6 +219,7 @@ static NSString* const kShowSignupFlowSegue = @"showSignupFlow"; badgeValue = [badgeNumber stringValue]; } + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:badgeNumber.integerValue]; self.inboxCountLabel.text = badgeValue; } @@ -235,23 +231,30 @@ static NSString* const kShowSignupFlowSegue = @"showSignupFlow"; #pragma mark - Navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - if ([segue.identifier isEqualToString:kSegueIndentifier]){ MessagesViewController * vc = [segue destinationViewController]; NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow]; TSThread *thread = [self threadForIndexPath:selectedIndexPath]; if (self.contactIdentifierFromCompose){ [vc setupWithTSIdentifier:self.contactIdentifierFromCompose]; + [vc setComposeOnOpen:self.composeMessage]; self.contactIdentifierFromCompose = nil; + self.composeMessage = NO; } else if (self.groupFromCompose) { [vc setupWithTSGroup:self.groupFromCompose]; + [vc setComposeOnOpen:self.composeMessage]; self.groupFromCompose = nil; + self.composeMessage = NO; } else if (thread) { [vc setupWithThread:thread]; + [vc setComposeOnOpen:NO]; + } + else if([sender isKindOfClass:[TSGroupThread class]]){ + [vc setupWithThread:sender]; + [vc setComposeOnOpen:YES]; } - } else if ([segue.identifier isEqualToString:kCallSegue]) { InCallViewController* vc = [segue destinationViewController]; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index f0ac6226d..1ce40bed3 100644 Binary files a/Signal/translations/en.lproj/Localizable.strings and b/Signal/translations/en.lproj/Localizable.strings differ