From cd0bda7105cc54e773bfc5b42b01583b65265869 Mon Sep 17 00:00:00 2001 From: Frederic Jacobs Date: Mon, 15 Sep 2014 01:32:19 +0200 Subject: [PATCH] iOS 8 Support - Supporting iOS 8 - Updating translations - Rewriting PushManager to extensively use futures - Pick up calls directly from the lock-screen --- .../xcshareddata/xcschemes/spandsp.xcscheme | 11 +- .../xcshareddata/xcschemes/speex.xcscheme | 11 +- Podfile | 4 +- Podfile.lock | 32 +-- Pods | 2 +- Signal.xcodeproj/project.pbxproj | 22 +- .../xcshareddata/xcschemes/Signal.xcscheme | 2 +- Signal/Signal-Info.plist | 7 +- Signal/src/AppDelegate.m | 35 ++- Signal/src/environment/VersionMigrations.m | 6 +- Signal/src/network/PushManager.h | 33 ++- Signal/src/network/PushManager.m | 260 ++++++++++++------ .../view controllers/InCallViewController.h | 1 + .../view controllers/RegisterViewController.m | 5 +- Signal/test/push/PushManagerTest.m | 38 +++ .../translations/an.lproj/Localizable.strings | Bin 15172 -> 15318 bytes .../translations/ar.lproj/Localizable.strings | Bin 15594 -> 15740 bytes .../translations/be.lproj/Localizable.strings | Bin 15172 -> 15318 bytes .../bg_BG.lproj/Localizable.strings | Bin 15172 -> 15318 bytes .../translations/ca.lproj/Localizable.strings | Bin 16016 -> 16162 bytes .../translations/cs.lproj/Localizable.strings | Bin 14770 -> 14914 bytes .../translations/da.lproj/Localizable.strings | Bin 15544 -> 15774 bytes .../translations/de.lproj/Localizable.strings | Bin 16802 -> 16966 bytes .../el_GR.lproj/Localizable.strings | Bin 16374 -> 16520 bytes .../translations/es.lproj/Localizable.strings | Bin 16556 -> 16702 bytes .../translations/eu.lproj/Localizable.strings | Bin 15586 -> 15732 bytes .../translations/fa.lproj/Localizable.strings | Bin 15384 -> 15530 bytes .../fa_IR.lproj/Localizable.strings | Bin 15172 -> 15318 bytes .../translations/fi.lproj/Localizable.strings | Bin 15624 -> 15770 bytes .../fil.lproj/Localizable.strings | Bin 17520 -> 17686 bytes .../translations/fr.lproj/Localizable.strings | Bin 16684 -> 16830 bytes .../translations/he.lproj/Localizable.strings | Bin 14482 -> 14628 bytes .../translations/hu.lproj/Localizable.strings | Bin 16528 -> 16674 bytes .../it_IT.lproj/Localizable.strings | Bin 16280 -> 16436 bytes .../ja_JP.lproj/Localizable.strings | Bin 13296 -> 13442 bytes .../ko_KR.lproj/Localizable.strings | Bin 0 -> 15318 bytes .../translations/lv.lproj/Localizable.strings | Bin 15280 -> 15426 bytes .../translations/nb.lproj/Localizable.strings | Bin 15382 -> 15528 bytes .../translations/nl.lproj/Localizable.strings | Bin 16222 -> 16406 bytes .../translations/pl.lproj/Localizable.strings | Bin 15964 -> 16110 bytes .../pt_BR.lproj/Localizable.strings | Bin 16312 -> 16458 bytes .../translations/ro.lproj/Localizable.strings | Bin 15478 -> 15624 bytes .../translations/ru.lproj/Localizable.strings | Bin 16440 -> 16586 bytes .../translations/sl.lproj/Localizable.strings | Bin 15944 -> 16098 bytes .../translations/sq.lproj/Localizable.strings | Bin 15172 -> 15318 bytes .../sv_SE.lproj/Localizable.strings | Bin 15448 -> 15596 bytes .../translations/ta.lproj/Localizable.strings | Bin 15172 -> 15318 bytes .../tr_TR.lproj/Localizable.strings | Bin 15172 -> 15286 bytes .../translations/uk.lproj/Localizable.strings | Bin 15172 -> 15318 bytes .../zh_CN.lproj/Localizable.strings | Bin 11478 -> 11740 bytes .../zh_TW.lproj/Localizable.strings | Bin 15172 -> 15318 bytes 51 files changed, 336 insertions(+), 133 deletions(-) create mode 100644 Signal/test/push/PushManagerTest.m create mode 100644 Signal/translations/ko_KR.lproj/Localizable.strings diff --git a/Libraries/spandsp/spandsp/spandsp.xcodeproj/xcshareddata/xcschemes/spandsp.xcscheme b/Libraries/spandsp/spandsp/spandsp.xcodeproj/xcshareddata/xcschemes/spandsp.xcscheme index 770451b92..60911e91c 100644 --- a/Libraries/spandsp/spandsp/spandsp.xcodeproj/xcshareddata/xcschemes/spandsp.xcscheme +++ b/Libraries/spandsp/spandsp/spandsp.xcodeproj/xcshareddata/xcschemes/spandsp.xcscheme @@ -1,6 +1,6 @@ + + + + diff --git a/Libraries/speex/speex.xcodeproj/xcshareddata/xcschemes/speex.xcscheme b/Libraries/speex/speex.xcodeproj/xcshareddata/xcschemes/speex.xcscheme index 8b110786b..0bffc41d1 100644 --- a/Libraries/speex/speex.xcodeproj/xcshareddata/xcschemes/speex.xcscheme +++ b/Libraries/speex/speex.xcodeproj/xcshareddata/xcschemes/speex.xcscheme @@ -1,6 +1,6 @@ + + + + diff --git a/Podfile b/Podfile index f119e083c..8b7c2633f 100644 --- a/Podfile +++ b/Podfile @@ -4,8 +4,8 @@ link_with ["Signal", "SignalTests"] pod 'UICKeyChainStore', :podspec => 'Podspecs/UICKeyChainStore.podspec' pod 'OpenSSL', '~> 1.0.109' -pod 'MMDrawerController', '~> 0.5.0' +pod 'MMDrawerController', '~> 0.5.7' pod 'libPhoneNumber-iOS', '~> 0.7' pod 'PastelogKit', '~> 1.1' -pod 'AFNetworking', '~> 2.3.1' +pod 'AFNetworking', '~> 2.4.1' pod 'TwistedOakCollapsingFutures','~> 1.0' diff --git a/Podfile.lock b/Podfile.lock index 94c2c96c1..37189c8b2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,23 +1,23 @@ PODS: - - AFNetworking (2.3.1): + - AFNetworking (2.4.1): - AFNetworking/NSURLConnection - AFNetworking/NSURLSession - AFNetworking/Reachability - AFNetworking/Security - AFNetworking/Serialization - AFNetworking/UIKit - - AFNetworking/NSURLConnection (2.3.1): + - AFNetworking/NSURLConnection (2.4.1): - AFNetworking/Reachability - AFNetworking/Security - AFNetworking/Serialization - - AFNetworking/NSURLSession (2.3.1): + - AFNetworking/NSURLSession (2.4.1): - AFNetworking/Reachability - AFNetworking/Security - AFNetworking/Serialization - - AFNetworking/Reachability (2.3.1) - - AFNetworking/Security (2.3.1) - - AFNetworking/Serialization (2.3.1) - - AFNetworking/UIKit (2.3.1): + - AFNetworking/Reachability (2.4.1) + - AFNetworking/Security (2.4.1) + - AFNetworking/Serialization (2.4.1) + - AFNetworking/UIKit (2.4.1): - AFNetworking/NSURLConnection - AFNetworking/NSURLSession - CocoaLumberjack (1.9.2): @@ -26,17 +26,17 @@ PODS: - CocoaLumberjack/Extensions (1.9.2): - CocoaLumberjack/Core - libPhoneNumber-iOS (0.7.3) - - MMDrawerController (0.5.6): + - MMDrawerController (0.5.7): - MMDrawerController/Core - MMDrawerController/MMDrawerBarButtonItem - MMDrawerController/MMDrawerVisualStates - MMDrawerController/Subclass - - MMDrawerController/Core (0.5.6) - - MMDrawerController/MMDrawerBarButtonItem (0.5.6): + - MMDrawerController/Core (0.5.7) + - MMDrawerController/MMDrawerBarButtonItem (0.5.7): - MMDrawerController/Core - - MMDrawerController/MMDrawerVisualStates (0.5.6): + - MMDrawerController/MMDrawerVisualStates (0.5.7): - MMDrawerController/Core - - MMDrawerController/Subclass (0.5.6): + - MMDrawerController/Subclass (0.5.7): - MMDrawerController/Core - OpenSSL (1.0.109) - PastelogKit (1.1): @@ -47,9 +47,9 @@ PODS: - UnionFind (1.0.1) DEPENDENCIES: - - AFNetworking (~> 2.3.1) + - AFNetworking (~> 2.4.1) - libPhoneNumber-iOS (~> 0.7) - - MMDrawerController (~> 0.5.0) + - MMDrawerController (~> 0.5.7) - OpenSSL (~> 1.0.109) - PastelogKit (~> 1.1) - TwistedOakCollapsingFutures (~> 1.0) @@ -60,10 +60,10 @@ EXTERNAL SOURCES: :podspec: Podspecs/UICKeyChainStore.podspec SPEC CHECKSUMS: - AFNetworking: 6d7b76aa5d04c8c37daad3eef4b7e3f2a7620da3 + AFNetworking: 0aabc6fae66d6e5d039eeb21c315843c7aae51ab CocoaLumberjack: 205769c032b5fef85b92472046bcc8b7e7c8a817 libPhoneNumber-iOS: 98fc07d70c8fdb5e6a8e3442c37e97353065c20e - MMDrawerController: 4bae84535ca7a5f4cb55a66a001e6035c3571677 + MMDrawerController: c3ab7a318ddc7e2bcd133139c3161af08c6e1197 OpenSSL: 4810adf5c99b0e2cd20670a11a987c805e8a521c PastelogKit: 32836ec27e587a8876326abeaf9a1b5e2bc484ea TwistedOakCollapsingFutures: 07aab84fd3958dc94d55ef705b12857d9fbe61d1 diff --git a/Pods b/Pods index 5d4e46748..594aa6022 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 5d4e467489501e0a8b76a3f636380e8f00296947 +Subproject commit 594aa6022c574bd1798041dd440af1aedc4d21b5 diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 23ef51c2d..00c885e70 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -396,6 +396,7 @@ B6416FD4199A0478003C5699 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6416FAB199A0478003C5699 /* Localizable.strings */; }; B67ADDC41989FF8700E1A773 /* CallServerRequestsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B67ADDC31989FF8700E1A773 /* CallServerRequestsManager.m */; }; B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; }; + B684A46D19C3446200B11029 /* PushManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B684A46C19C3446200B11029 /* PushManagerTest.m */; }; B6850E5A1995A4710068E715 /* whisperFake.cer in Resources */ = {isa = PBXBuildFile; fileRef = B6850E591995A4710068E715 /* whisperFake.cer */; }; B69CD25119773E79005CE69A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B69CD25019773E79005CE69A /* XCTest.framework */; }; B6B1013C196D213F007E3930 /* SGNKeychainUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B1013B196D213F007E3930 /* SGNKeychainUtil.m */; }; @@ -1098,6 +1099,7 @@ B67ADDC21989FF8700E1A773 /* CallServerRequestsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallServerRequestsManager.h; sourceTree = ""; }; B67ADDC31989FF8700E1A773 /* CallServerRequestsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallServerRequestsManager.m; sourceTree = ""; }; B67EBF5C19194AC60084CCFD /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Settings.bundle; path = SettingsBundle/Settings.bundle; sourceTree = SOURCE_ROOT; }; + B684A46C19C3446200B11029 /* PushManagerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PushManagerTest.m; path = Signal/test/push/PushManagerTest.m; sourceTree = SOURCE_ROOT; }; B6850E591995A4710068E715 /* whisperFake.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = whisperFake.cer; sourceTree = ""; }; B69CD25019773E79005CE69A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; B6B1013A196D213F007E3930 /* SGNKeychainUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGNKeychainUtil.h; sourceTree = ""; }; @@ -2196,6 +2198,7 @@ A157073917F0CD6D007C2BD6 /* Supporting Files */, A157073B17F0CD6D007C2BD6 /* TestUtil.h */, A157073C17F0CD6D007C2BD6 /* TestUtil.m */, + B684A46C19C3446200B11029 /* PushManagerTest.m */, A157073D17F0CD6D007C2BD6 /* util */, ); path = test; @@ -3007,12 +3010,18 @@ D221A088169C9E5E00537ABF = { DevelopmentTeam = U68MSDN6DR; SystemCapabilities = { + com.apple.DataProtection = { + enabled = 1; + }; com.apple.InAppPurchase = { enabled = 0; }; com.apple.InterAppAudio = { enabled = 0; }; + com.apple.VPNLite = { + enabled = 0; + }; }; }; D221A0A9169C9E5F00537ABF = { @@ -3739,6 +3748,7 @@ 76EB066118170B34006006FC /* RegisterViewController.m in Sources */, 76EB068D18170B34006006FC /* InboxFeedTableViewCell.m in Sources */, BFB074C119A4BCA400F2947C /* FutureUtilTest.m in Sources */, + B684A46D19C3446200B11029 /* PushManagerTest.m in Sources */, 76EB05A918170B33006006FC /* RtpSocket.m in Sources */, 765052B0182AC9B5008313E1 /* DialerButtonView.m in Sources */, 76EB062B18170B33006006FC /* BadState.m in Sources */, @@ -4194,8 +4204,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = C71793B33D9C45079F74487E /* Pods.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CURRENT_PROJECT_VERSION = 1; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4234,7 +4244,7 @@ LLVM_LTO = NO; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_NAME = Signal; - PROVISIONING_PROFILE = "036480DA-A21D-4CC6-BF48-98E8AE1EE981"; + PROVISIONING_PROFILE = "4aa31d4f-187c-470a-add6-8e949fc2cfd1"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = 1; TEST_AFTER_BUILD = YES; @@ -4483,8 +4493,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = C71793B33D9C45079F74487E /* Pods.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CURRENT_PROJECT_VERSION = 1; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4521,7 +4531,7 @@ LLVM_LTO = NO; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_NAME = Signal; - PROVISIONING_PROFILE = "7214A823-1F7A-4460-82D8-D89CA511CEA6"; + PROVISIONING_PROFILE = "4434E074-1111-40F0-A7B6-CEA8437ACFE1"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = 1; TEST_AFTER_BUILD = YES; diff --git a/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme b/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme index 66b6d1104..cefe172a8 100644 --- a/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme +++ b/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme @@ -1,6 +1,6 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.4 + 1.0.5 CFBundleSignature ???? CFBundleVersion - 1.0.4 + 1.0.5 LSApplicationCategoryType - + public.app-category.social-networking LSRequiresIPhoneOS UIAppFonts @@ -48,6 +48,7 @@ UIBackgroundModes audio + fetch remote-notification voip diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 09c4de261..37955a5fe 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -32,6 +32,8 @@ @property (nonatomic, strong) MMDrawerController *drawerController; @property (nonatomic, strong) NotificationTracker *notificationTracker; +@property (nonatomic) TOCFutureSource *callPickUpFuture; + @end @implementation AppDelegate @@ -154,7 +156,18 @@ InCallViewController *callViewController = [InCallViewController inCallViewControllerWithCallState:latestCall andOptionallyKnownContact:latestCall.potentiallySpecifiedContact]; + + if (latestCall.initiatedLocally == false){ + [self.callPickUpFuture.future thenDo:^(NSNumber *accept) { + if ([accept isEqualToNumber:@YES]) { + [callViewController answerButtonTapped]; + } else if ([accept isEqualToNumber:@NO]){ + [callViewController rejectButtonTapped]; + } + }]; + } [_drawerController.centerViewController presentViewController:callViewController animated:YES completion:nil]; + } onThread:NSThread.mainThread untilCancelled:nil]; @@ -162,12 +175,15 @@ } - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { - [PushManager.sharedManager registerForPushWithToken:deviceToken]; + [[PushManager sharedManager].pushNotificationFutureSource trySetResult:deviceToken]; } - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error { - [PushManager.sharedManager verifyPushActivated]; - DDLogError(@"Failed to register for push notifications: %@", error); + [[PushManager sharedManager].pushNotificationFutureSource trySetFailure:error]; +} + +- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{ + [[PushManager sharedManager].userNotificationFutureSource trySetResult:notificationSettings]; } -(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { @@ -184,7 +200,7 @@ DDLogError(@"Decryption of session descriptor failed"); return; } - + self.callPickUpFuture = [TOCFutureSource new]; [Environment.phoneManager incomingCallWithSession:call]; } @@ -207,11 +223,20 @@ [self removeScreenProtection]; if (Environment.isRegistered) { - [PushManager.sharedManager verifyPushActivated]; + [PushManager.sharedManager verifyPushPermissions]; [AppAudioManager.sharedInstance requestRequiredPermissionsIfNeeded]; } } +- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler{ + if ([identifier isEqualToString:Signal_Accept_Identifier]) { + [self.callPickUpFuture trySetResult:@YES]; + } else if ([identifier isEqualToString:Signal_Decline_Identifier]){ + [self.callPickUpFuture trySetResult:@NO]; + } + completionHandler(); +} + - (void)applicationWillResignActive:(UIApplication *)application{ [self protectScreen]; } diff --git a/Signal/src/environment/VersionMigrations.m b/Signal/src/environment/VersionMigrations.m index 6d028ccb9..616b6395f 100644 --- a/Signal/src/environment/VersionMigrations.m +++ b/Signal/src/environment/VersionMigrations.m @@ -43,7 +43,11 @@ // Some users push IDs were not correctly registered, by precaution, we are going to re-register all of them - [PushManager.sharedManager askForPushRegistration]; + [PushManager.sharedManager registrationWithSuccess:^{ + + } failure:^{ + DDLogError(@"Error re-registering on migration from 1.0.2"); + }]; [[NSFileManager defaultManager] removeItemAtPath:path error:&error]; diff --git a/Signal/src/network/PushManager.h b/Signal/src/network/PushManager.h index daa578b38..6af250ebe 100644 --- a/Signal/src/network/PushManager.h +++ b/Signal/src/network/PushManager.h @@ -6,19 +6,42 @@ // Copyright (c) 2014 Open Whisper Systems. All rights reserved. // +#import #import + +#define Signal_Accept_Identifier @"Signal_Call_Accept" +#define Signal_Decline_Identifier @"Signal_Call_Decline" + +/** + * The Push Manager is responsible for registering the device for Signal push notifications. + */ + @interface PushManager : NSObject + (instancetype)sharedManager; -- (void)verifyPushActivated; +/** + * Push notification token is always registered during signup. User can however revoke notifications. + * Therefore, we check on startup if mandatory permissions are granted. + */ -- (void)askForPushRegistration; +- (void)verifyPushPermissions; -- (void)askForPushRegistrationWithSuccess:(void (^)())success failure:(void (^)())failure; +/** + * Push notification registration method + * + * @param success Block to execute after succesful push notification registration + * @param failure Block to executre if push notification registration fails + */ -- (void)registerForPushWithToken:(NSData*)token; +- (void)registrationWithSuccess:(void (^)())success failure:(void (^)())failure; -@end +/** + * The pushNotification and userNotificationFutureSource are accessed by the App Delegate after requested permissions. + */ + +@property TOCFutureSource *pushNotificationFutureSource; +@property TOCFutureSource *userNotificationFutureSource; +@end diff --git a/Signal/src/network/PushManager.m b/Signal/src/network/PushManager.m index dd136eeec..bd3364721 100644 --- a/Signal/src/network/PushManager.m +++ b/Signal/src/network/PushManager.m @@ -9,13 +9,13 @@ #import "PushManager.h" #import "Environment.h" #import "CallServerRequestsManager.h" +#import "FutureUtil.h" @interface PushManager () -@property (nonatomic, copy) void (^PushRegisteringSuccessBlock)(); -@property (nonatomic, copy) void (^PushRegisteringFailureBlock)(); +@property TOCFutureSource *registerWithServerFutureSource; -@property int retries; +@property UIAlertView *missingPermissionsAlertView; @end @@ -26,123 +26,209 @@ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedManager = [self new]; + sharedManager.missingPermissionsAlertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ACTION_REQUIRED_TITLE", @"") message:NSLocalizedString(@"PUSH_SETTINGS_MESSAGE", @"") delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil, nil]; }); return sharedManager; } -- (void)verifyPushActivated{ - UIRemoteNotificationType notificationTypes = [UIApplication.sharedApplication enabledRemoteNotificationTypes]; +- (void)verifyPushPermissions{ - BOOL needsPushSettingChangeAlert = NO; - - if (notificationTypes == UIRemoteNotificationTypeNone) { - needsPushSettingChangeAlert = YES; - } else if (notificationTypes == UIRemoteNotificationTypeBadge) { - needsPushSettingChangeAlert = YES; - } else if (notificationTypes == UIRemoteNotificationTypeAlert) { - needsPushSettingChangeAlert = YES; - } else if (notificationTypes == UIRemoteNotificationTypeSound) { - needsPushSettingChangeAlert = YES; - } else if (notificationTypes == (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert)) { - needsPushSettingChangeAlert = YES; - } else if (notificationTypes == (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)) { - needsPushSettingChangeAlert = YES; - } - - if (needsPushSettingChangeAlert) { - [Environment.preferences setRevokedPushPermission:YES]; - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ACTION_REQUIRED_TITLE", @"") message:NSLocalizedString(@"PUSH_SETTINGS_MESSAGE", @"") delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil, nil]; - [alertView show]; - } else if (!needsPushSettingChangeAlert){ - if (Environment.preferences.encounteredRevokedPushPermission) { - [self askForPushRegistration]; + if (SYSTEM_VERSION_LESS_THAN(_iOS_8_0)) { + + // Displaying notifications and ringing + + if ([self isMissingMandatoryNotificationTypes:[UIApplication.sharedApplication enabledRemoteNotificationTypes]]) { + + [self registrationWithSuccess:^{ + DDLogInfo(@"Push notifications were succesfully re-enabled"); + } failure:^{ + [self.missingPermissionsAlertView show]; + }]; + } + + } else{ + + // UIUserNotificationsSettings + UIUserNotificationSettings *settings = [UIApplication.sharedApplication currentUserNotificationSettings]; + + // To use Signal, it is required to have sound notifications and alert types. + + if ([self isMissingMandatoryNotificationTypes:settings.types]) { + + [self registrationForUserNotificationWithSuccess:^{ + DDLogInfo(@"User notifications were succesfully re-enabled"); + } failure:^{ + [self.missingPermissionsAlertView show]; + }]; + + } + + // Remote Notifications + if (![UIApplication.sharedApplication isRegisteredForRemoteNotifications]) { + + [self registrationForPushWithSuccess:^{ + DDLogInfo(@"Push notification were succesfully re-enabled"); + } failure:^{ + DDLogError(@"The phone could not be re-registered for push notifications."); // Push tokens are not changing on the same phone, just user notification changes so it's not very important. + }]; + } } - -} - -- (void)askForPushRegistrationWithSuccess:(void (^)())success failure:(void (^)())failure{ - self.PushRegisteringSuccessBlock = success; - self.PushRegisteringFailureBlock = failure; - [self askForPushRegistration]; } -- (void)askForPushRegistration{ +- (void)registrationWithSuccess:(void (^)())success failure:(void (^)())failure{ - if(SYSTEM_VERSION_LESS_THAN(_iOS_8_0)){ - [UIApplication.sharedApplication registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)]; - } else{ -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_7_1 - UIMutableUserNotificationAction *action_accept = [UIMutableUserNotificationAction new]; - action_accept.identifier = @"Signal_Call_Accept"; - action_accept.title = @"Pick up"; - action_accept.activationMode = UIUserNotificationActivationModeForeground; - action_accept.destructive = YES; - action_accept.authenticationRequired = NO; - - UIMutableUserNotificationAction *action_decline = [UIMutableUserNotificationAction new]; - action_decline.identifier = @"Signal_Call_Decline"; - action_decline.title = @"Pick up"; - action_decline.activationMode = UIUserNotificationActivationModeForeground; - action_decline.destructive = YES; - action_decline.authenticationRequired = NO; - - UIMutableUserNotificationCategory *callCategory = [UIMutableUserNotificationCategory new]; - callCategory.identifier = @"Signal_IncomingCall"; - [callCategory setActions:@[action_accept, action_decline] forContext:UIUserNotificationActionContextDefault]; + if (SYSTEM_VERSION_LESS_THAN(_iOS_8_0)) { + + // On iOS7, we just need to register for Push Notifications (user notifications are enabled with them) + [self registrationForPushWithSuccess:success failure:failure]; - NSSet *categories = [NSSet setWithObject:callCategory]; + } else{ - [UIApplication.sharedApplication registerForRemoteNotifications]; - [UIApplication.sharedApplication registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound) categories:categories]]; + // On iOS 8+, both Push Notifications and User Notfications need to be registered. -#endif - + [self registrationForPushWithSuccess:^{ + [self registrationForUserNotificationWithSuccess:success failure:^{ + [self.missingPermissionsAlertView show]; + failure(); + }]; + } failure:failure]; } - - self.retries = 3; } -- (void)registerForPushWithToken:(NSData*)token{ + +#pragma mark Private Methods + +#pragma mark Register Push Notification Token with server + +-(TOCFuture*)registerForPushFutureWithToken:(NSData*)token{ + self.registerWithServerFutureSource = [TOCFutureSource new]; + [CallServerRequestsManager.sharedInstance registerPushToken:token success:^(NSURLSessionDataTask *task, id responseObject) { if ([task.response isKindOfClass: NSHTTPURLResponse.class]){ NSInteger statusCode = [(NSHTTPURLResponse*) task.response statusCode]; if (statusCode == 200) { - DDLogInfo(@"Device sent push ID to server"); - [Environment.preferences setRevokedPushPermission:NO]; - if (self.PushRegisteringSuccessBlock) { - self.PushRegisteringSuccessBlock(); - self.PushRegisteringSuccessBlock = nil; - } + [self.registerWithServerFutureSource trySetResult:@YES]; } else{ - [self registerFailureWithToken:token]; + DDLogError(@"The server returned %@ instead of a 200 status code", task.response); + [self.registerWithServerFutureSource trySetFailure:@NO]; } + } else{ + [self.registerWithServerFutureSource trySetFailure:@NO]; } } failure:^(NSURLSessionDataTask *task, NSError *error) { - [self registerFailureWithToken:token]; + [self.registerWithServerFutureSource trySetFailure:@NO]; }]; + + return self.registerWithServerFutureSource.future; +} + +#pragma mark Register device for Push Notification locally + +-(TOCFuture*)registeriOS7PushNotificationFuture{ + self.pushNotificationFutureSource = [TOCFutureSource new]; + [UIApplication.sharedApplication registerForRemoteNotificationTypes:(UIRemoteNotificationType)[self mandatoryNotificationTypes]]; + return self.pushNotificationFutureSource.future; } +-(TOCFuture*)registerPushNotificationFuture{ + self.pushNotificationFutureSource = [TOCFutureSource new]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; + return self.pushNotificationFutureSource.future; +} -/** - * Token was not sucessfully register. Try again / deal with failure - * - * @param token Token to register - */ +-(TOCFuture*)registerForUserNotificationsFuture{ + self.userNotificationFutureSource = [TOCFutureSource new]; + [UIApplication.sharedApplication registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationType)[self allNotificationTypes] categories:[NSSet setWithObject:[self userNotificationsCallCategory]]]]; + return self.userNotificationFutureSource.future; +} -- (void)registerFailureWithToken:(NSData*)token{ - if (self.retries > 0) { - [self registerForPushWithToken:token]; - self.retries--; +- (void)registrationForPushWithSuccess:(void (^)())success failure:(void (^)())failure{ + TOCFuture *requestPushTokenFuture; + + if (SYSTEM_VERSION_LESS_THAN(_iOS_8_0)) { + requestPushTokenFuture = [self registeriOS7PushNotificationFuture]; } else{ - if (self.PushRegisteringFailureBlock) { - self.PushRegisteringFailureBlock(); - self.PushRegisteringFailureBlock = nil; - } - [Environment.preferences setRevokedPushPermission:YES]; + requestPushTokenFuture = [self registerPushNotificationFuture]; } + + [requestPushTokenFuture catchDo:^(id failureObj) { + failure(); + if (SYSTEM_VERSION_LESS_THAN(_iOS_8_0)) { + [self.missingPermissionsAlertView show]; + } else{ + 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) { + UIAlertView *failureToRegisterWithServerAlert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"REGISTRATION_ERROR", @"") message:NSLocalizedString(@"REGISTRATION_BODY", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil, nil]; + [failureToRegisterWithServerAlert show]; + failure(); + }]; + + [registerPushTokenFuture thenDo:^(id value) { + success(); + }]; + }]; } +- (void)registrationForUserNotificationWithSuccess:(void (^)())success failure:(void (^)())failure{ + TOCFuture *registrerUserNotificationFuture = [self registerForUserNotificationsFuture]; + + [registrerUserNotificationFuture catchDo:^(id failureObj) { + [self.missingPermissionsAlertView show]; + failure(); + }]; + + [registrerUserNotificationFuture thenDo:^(id types) { + if ([self isMissingMandatoryNotificationTypes:[UIApplication.sharedApplication currentUserNotificationSettings].types]) { + [self.missingPermissionsAlertView show]; + failure(); + } else{ + success(); + } + }]; +} + + +-(UIUserNotificationCategory*)userNotificationsCallCategory{ + UIMutableUserNotificationAction *action_accept = [UIMutableUserNotificationAction new]; + action_accept.identifier = Signal_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_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_IncomingCall"; + [callCategory setActions:@[action_accept, action_decline] forContext:UIUserNotificationActionContextMinimal]; + [callCategory setActions:@[action_accept, action_decline] forContext:UIUserNotificationActionContextDefault]; + + return callCategory; +} + +-(BOOL)isMissingMandatoryNotificationTypes:(int)notificationTypes{ + int mandatoryTypes = [self mandatoryNotificationTypes]; + return ((mandatoryTypes & notificationTypes) == mandatoryTypes)?NO:YES; +} +-(int)allNotificationTypes{ + return (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge); +} + +-(int)mandatoryNotificationTypes{ + return (UIUserNotificationTypeAlert | UIUserNotificationTypeSound); +} @end diff --git a/Signal/src/view controllers/InCallViewController.h b/Signal/src/view controllers/InCallViewController.h index 83cd8c49b..9baf13a38 100644 --- a/Signal/src/view controllers/InCallViewController.h +++ b/Signal/src/view controllers/InCallViewController.h @@ -37,4 +37,5 @@ - (IBAction)answerButtonTapped; - (IBAction)rejectButtonTapped; + @end diff --git a/Signal/src/view controllers/RegisterViewController.m b/Signal/src/view controllers/RegisterViewController.m index 3e5cc2023..cc004f839 100644 --- a/Signal/src/view controllers/RegisterViewController.m +++ b/Signal/src/view controllers/RegisterViewController.m @@ -199,19 +199,16 @@ }]; [futureChallengeAcceptedSource.future thenDo:^(id value) { - [PushManager.sharedManager askForPushRegistrationWithSuccess:^{ + [PushManager.sharedManager registrationWithSuccess:^{ [Environment setRegistered:YES]; [registered trySetResult:@YES]; [Environment.getCurrent.phoneDirectoryManager forceUpdate]; [self dismissView]; } failure:^{ - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:REGISTER_ERROR_ALERT_VIEW_TITLE message:REGISTER_ERROR_ALERT_VIEW_BODY delegate:nil cancelButtonTitle:REGISTER_ERROR_ALERT_VIEW_DISMISS otherButtonTitles:nil, nil]; - [alertView show]; _challengeButton.enabled = YES; [_challengeActivityIndicator stopAnimating]; }]; }]; - } - (void)showViewNumber:(NSInteger)viewNumber { diff --git a/Signal/test/push/PushManagerTest.m b/Signal/test/push/PushManagerTest.m new file mode 100644 index 000000000..933450a36 --- /dev/null +++ b/Signal/test/push/PushManagerTest.m @@ -0,0 +1,38 @@ +// +// PushManagerTest.m +// Signal +// +// Created by Frederic Jacobs on 12/09/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// + +#import +#import + +@interface PushManagerTest : XCTestCase + +@end + +@implementation PushManagerTest + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +/** + * This test verifies that the enum containing the notifications types doesn't change for iOS7 support. + */ + +- (void)testNotificationTypesForiOS7 { + XCTAssert(UIRemoteNotificationTypeAlert == UIUserNotificationTypeAlert, @"iOS 7 <-> 8 compatibility"); + XCTAssert(UIRemoteNotificationTypeSound == UIUserNotificationTypeSound, @"iOS 7 <-> 8 compatibility"); + XCTAssert(UIRemoteNotificationTypeBadge == UIUserNotificationTypeBadge, @"iOS 7 <-> 8 compatibility"); +} + +@end diff --git a/Signal/translations/an.lproj/Localizable.strings b/Signal/translations/an.lproj/Localizable.strings index 90854834654ae8fa4cef237e351337768471b645..9d962fc0c3489e97321e6afecd929b53052cba44 100644 GIT binary patch delta 88 zcmX?7cCCCvh%&z;gAWkKGdMGVS(6o|geRX9U}Mc>$YV&KY^W+bc^f~UEJT$Og93vs e5GyeRF{Co+0`(*^$YV&KY^W+bc^f~UEJT$Og93vs e5GyeRF{Co+0`(*^$YV&KY^W+bc^f~UEJT$Og93vs e5GyeRF{Co+0`(*^j_8gAWkKGdMFqSe^`i4DJj{3eIOl^x2URYR#VNA1OVT#6+i$0 delta 16 XcmZ2fH=%X|i|S+#HKolvRI?-jI;93Z diff --git a/Signal/translations/cs.lproj/Localizable.strings b/Signal/translations/cs.lproj/Localizable.strings index c3414069ebfb4c1dc767f5ec6d32bbf8308c036b..914197ee92f2461a53601fa11f080b194013175a 100644 GIT binary patch delta 94 zcmdl~e5hnYn-aexgAWkKGdMGVS(7(P3Nz<3#+EWi_cOxQcdhDJr1KVwO9i!$*-ho8rko zWytGUE8ahw>ZVfTmhoT{8a%oti|YDqBhgz!t^|H1h^_@J8e*zT9Z|#`hMkAL?|?vP zIa@uUmB9NF4=`62`8FtphW)i;a=T7I&lcMJ9~j#`y6g|qj}fat$G9$N53cgt5uMT@ zrNL(hcN%$iBKRVDu$0tKbFqZ!{p~sjuw{Dntrgd!%u2tTd5CE@ADs^L=-KSSwgb4T z=nDVZGDdH(AXII%dVq6bn9dr3|S3aPDA*;tCDkjB69 zH!OWKmz)Y`Aq%^+lX)}mz0LFed-3LbWl9lEX-1YL%81EOh$XSya!Rll*k`C=RVBRd=|jbd9@fDk7X?pbwC{)E*490gCYjGfEvGp)?uk>9D6dNGMnx!Z-!g z)(lXSV@Wn{XgiE$I>ut^ql2Ye6;mv-XUP2~??v^+%G1^o8yo~@V8ob^=%k^djm9Ee ztQsrVykRh*D`YbjD)G00fhvoDG88CED(B|Hwi0yz@~yAs2F=8z5R}BfSZ@|B{%g(o z5;?1LJty_X_M=zt?Ywr@Pca~X2QkIf$6>E)L7?T%(6;#6ya9t|B5zSmk2b3Ze|Nw4 cLoL%losWPM=j8oD{iQ@g0Zh04^k3G#0Bc@^zyJUM diff --git a/Signal/translations/de.lproj/Localizable.strings b/Signal/translations/de.lproj/Localizable.strings index 5c891dbea1ea09ca441f41c2975a00cacf588383..74bec8707454f97c4607e729a346b68a92ea9a54 100644 GIT binary patch delta 116 zcmZ3~%y_JYaRZB*up@&H5XLh&GeB6L41SY08j4TmVdk0ape14plMiBWg{x6wP++hH wVkL%fhD3%8h8%`+hD?T3h7=$!0`l{LBBcy@4C#~OHDo7mQ&ZZkrdA^j0DImTbpQYW delta 18 ZcmX@s!nmlJaRZCm4Nwm_@|6i;Nx0m4j%JcjhiiR!YG b!vy$bA!-q-f*4X6bYW^isx}``HIo7WJlhmy delta 17 YcmeBZWc*gY;ehJo2Wm>2OVmW90814IO8@`> diff --git a/Signal/translations/es.lproj/Localizable.strings b/Signal/translations/es.lproj/Localizable.strings index 863d5a941acde72d6f1a33c2e0cca086257562f1..458f10341e68a6b341e44f07469ceb602e54f41c 100644 GIT binary patch delta 118 zcmZ3}$hfbGal%vsVGXycX!u26^1TmyC=)&}YbWBcAQ`vk*^??)seFhj* delta 14 Vcmdnj#JHxBalNj(!+>!tQ;OrI> delta 12 TcmexT^{8?Khsx$zDz_v6EPe(F diff --git a/Signal/translations/fa.lproj/Localizable.strings b/Signal/translations/fa.lproj/Localizable.strings index 472f80de475ab474bcf11ccf16f967ab77a2ffcf..2696d5fa7d39f2a3eac67af5e1bdb0101fd25e0d 100644 GIT binary patch delta 114 zcmbPHv8r-|i;AuzgAWkKGdMFqSe^`i4DJj{3ayA} f9YGAPa9s%XK@6!3x-eZJ^^@$YV&KY^W+bc^f~UEJT$Og93vs e5GyeRF{Co+0`(*^BXM%RuKMI38Y-K+G@eNV00ycVxBvhE delta 18 acmbQ%#rUCvaYKg2%vsVGXycX!u26^1TmyC=)&}YbWC2Lrm~q&O->pBoU<2t delta 14 Vcmdnj%($kBal;$c&2?%H(f~001?2z$ diff --git a/Signal/translations/he.lproj/Localizable.strings b/Signal/translations/he.lproj/Localizable.strings index 4c0c886da22fef4e7d4eaa0355c74e7b561ad984..270b73014449e88bb978d5501acbfedee79fccd4 100644 GIT binary patch delta 110 zcmbPKxTI*qHAP)V1|J}dXK-eKusj+37~C0@7!(+6fmjJBp2&~`gqaL^4C#{(YDjCt fRK_y|F}T9@A#?;Wq%!D2^iBS#DZcrf;t??b;6@gJ delta 16 YcmZ2dG^udIHO0yAl$17iDa{fC07oAOl>h($ diff --git a/Signal/translations/hu.lproj/Localizable.strings b/Signal/translations/hu.lproj/Localizable.strings index 45a41a77e60fa48c9b9a4465e68b4afd84101095..bb6fdfe3bf5800da4caa96f85aebe4eb05d57bb6 100644 GIT binary patch delta 116 zcmbQx$hfG9aYK@tt|NmF5XLh&GeB6L41Nsm3`z_N47Nb51QbtX$N|DkhCGJ!$%X2& h+AtkK46blp2=ze>sSLU>T_E+7Mbwoxx2W+*0RRBR6@&l) delta 18 ZcmZ3~#5kdmaYK^YH4={qu%0AVK3 mjfp_{VjwFSC|?9rR|SL+v1En}un%$>@`3s^H>;TyaRLA`JTu7v delta 169 zcmdnez&N9R0|)P9EdxIGG=@Zm9EP097kQO8+wrQfNjn3%nGC56c?=~C3Jg$bphyWr z{^n}oi=3PNWSuxRtEt`MS55*dO$91S0g_1!r3~pnm$Op))Oc> diff --git a/Signal/translations/ja_JP.lproj/Localizable.strings b/Signal/translations/ja_JP.lproj/Localizable.strings index 27404eb12d07661df6e01824652701cdd8dad6dd..b1c5360a8364b6b24d9d77b59243a1330c7512bf 100644 GIT binary patch delta 114 zcmey6-juoFm%OecgAWkKGdMFqSe^`i4DJj{3L=$ZC~cNexFQSyW#$*W delta 15 XcmZq5{E)ukm;59}rOoRUt_TAFKW_&~ diff --git a/Signal/translations/ko_KR.lproj/Localizable.strings b/Signal/translations/ko_KR.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..9d962fc0c3489e97321e6afecd929b53052cba44 GIT binary patch literal 15318 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 delta 12 TcmX?9v7vm!4&}{WDnG;lE6)ZU diff --git a/Signal/translations/nb.lproj/Localizable.strings b/Signal/translations/nb.lproj/Localizable.strings index 00df4652d6f6477fb4f3ea0a3d4cff583bd77806..3f0b01b5c64fe9744f699ec1bbef09be54979710 100644 GIT binary patch delta 98 zcmbPMv7&Or3uRqL1|J}dXK-eKusj+37~C0@7!(+6fmjJBp2&~`gqaL^4C#|Es>n`e c6X27Es70s>Vn}7sg{Yl;UPXTM3uQeC01apqa{vGU delta 12 TcmZ2cIjv&D3+2sKDtZzCDkKGh diff --git a/Signal/translations/nl.lproj/Localizable.strings b/Signal/translations/nl.lproj/Localizable.strings index 5ef6d6c294dc496830b7affc8230ad4e54a73d17..217238251569eff499a1fe1d7b135cfacd8a57b1 100644 GIT binary patch delta 222 zcmcatH?4tjgN>@LBZChR#xpoGKv4Nwm_@|6i;Nx0m4j%JcjhiiJG#L z-30h#A!-q-f*4X6bYW^isy5rG=J4yL0yQTylmIoC0TrY&6akG)0`jsLG8pn1N*Pjs zVtGKZGN8J=$&IF>lP{RKm<9v&WB^67fwDP3*Kr)G;lp!4mGa2%LqykVTA4nHZ zE;NyzJi}B&+Z8B|&=Jg#%uoc>l?oJ9Ug_jAQ#~dD017x3_y7O^ delta 16 YcmaD?d#7eYiR$DDYD$|m)Q(9207&}>R{#J2 diff --git a/Signal/translations/pt_BR.lproj/Localizable.strings b/Signal/translations/pt_BR.lproj/Localizable.strings index 88a65e3bce58314a5e9f0f869f30b1a844f44c10..0e6e7c819f94a9f50d677cdf611392352416f580 100644 GIT binary patch delta 107 zcmdl{f2x6TL!GLwBZChR#xpoGKv4Nwm_@|6i;Nx0m4j%JcjhiiK?>N ZFdab*u5eul^+61&47xB~n-f*Tr2v3D6UP7m delta 13 UcmX@rz__D+L!Iho0W~Wr04k6L6951J diff --git a/Signal/translations/ro.lproj/Localizable.strings b/Signal/translations/ro.lproj/Localizable.strings index 6affa430baf85947ec23e31ea0ce38c49b707027..e4a5232682eb4b3659d4a027be2680c22b22091e 100644 GIT binary patch delta 116 zcmexX(NVSGm9nlQgAWkKGdMFqSe^`i4DJj{3eIOl^`&3jm^QdG=004Uv7UBQ^ delta 12 TcmeCE`c|>wmGb5ql^zKIEkOn) diff --git a/Signal/translations/ru.lproj/Localizable.strings b/Signal/translations/ru.lproj/Localizable.strings index a266988b44152d113a8caf6173f90b60db35b696..2771ea3d02f237401feadd623a17e36574009ee4 100644 GIT binary patch delta 116 zcmdndz<8>Waf67Ot|NmF5XLh&GeB6L41Nsm3`z_N47Nb51QbtX$N|DkhCGJ!$%>k? h+AtkK46blp2=ze>sSLU>T_E+7PpBzvHdAAg0s!#66+-|3 delta 18 ZcmX@r$hf0{af683$YV&KY^W+bc^f~UEJT$Og93vs e5GyeRF{Co+0`(*^dE^xR5#11SV;f?KZ_I& delta 16 XcmaD;d81;3g34qIRi(|>RKz3zLl_2^ diff --git a/Signal/translations/ta.lproj/Localizable.strings b/Signal/translations/ta.lproj/Localizable.strings index 90854834654ae8fa4cef237e351337768471b645..9d962fc0c3489e97321e6afecd929b53052cba44 100644 GIT binary patch delta 88 zcmX?7cCCCvh%&z;gAWkKGdMGVS(6o|geRX9U}Mc>$YV&KY^W+bc^f~UEJT$Og93vs e5GyeRF{Co+0`(*^-~uhqQ*xym?%$tO#zX>gE-Fqej7 zUlUIcNe^iYM<5*BT&igoIQ&nhX(|xLt&7VWd>F?mQINDMlQuxf!?4$)Z!13a%|Kos_~;}fnS|i;P=s)!B+IGzV``< zb6TKEc!wlqeZ84%3$!klQs#IQ z5kgC%bNt1a=Lfy{gEM~tBWuf7 delta 719 zcmZuvO=}ZT6g_4pX>B@~MU&L1%_L2&A5lnAs1y|&T5w@or1;T=kW9vQNSnlIlG2s9 zbR){$xDvNQaT9UjLN~4j@fQd#`U?b$3(tLNwF`O7eRtly=broZe(Q7h`AgZ!443kl z!4UGefh8>Pc5#(o9yif|hbA@t)p+RcQKv5FpN@th&8lX#hj|?uZ@wmA4 zABIo4q`WQ;N+D8|oy6e4esS`(kd!b|jNic=ZnLy`+?O|zD->Ta#>>N12QrX4)p7 z^5}n!d1vk8GGa~1-pNrpV^7I5D<$_XCyc&pWn^eHGq6D92CA}XU6&O*W3ey9&B!~O zzSF5g_N4r@FF6`pnY6hbd0NcMa`fEMWy4s(wmgcZ8GkK^O>g0_B1F1Gmwt)++ukFXQsu~jHQAq{|_d5(D`3S4r76uRdXQYD)GuoYdo4=P=!-JUMnse#d7WEu_m-b$jOJ omoq6JF65(gMAzhd{Dh1-G5I(=eZ<&$V@@h14+{C-?)aDVA6+e@UjP6A diff --git a/Signal/translations/uk.lproj/Localizable.strings b/Signal/translations/uk.lproj/Localizable.strings index 90854834654ae8fa4cef237e351337768471b645..9d962fc0c3489e97321e6afecd929b53052cba44 100644 GIT binary patch delta 88 zcmX?7cCCCvh%&z;gAWkKGdMGVS(6o|geRX9U}Mc>$YV&KY^W+bc^f~UEJT$Og93vs e5GyeRF{Co+0`(*^pzs6@oHK)syRF&qRDD-*EdEgN@H1JMpY?PeaxgO zEF?7;@+yHP#u1GL1hp1^Q<*p#@zb|%$JVDItl^2euP#^B8n7w#Y*~b|`eP!blNvcI z-ySQ5v@z&yc?Q-FSm35sA)^3w#S>b+;!3JHd0%mwln9)zA68pTdPkrvno6IO%AELj z{|ESu;Ez6Fb)ytNth2MEu$!$GnAKg9=Q6Y2??dot>m5ZNt7_JwwMgEMNke)Equ>$k zmlMN{HVc&6?6SJ;jW)ModyHOLS6N&*=Ll^2D!85pXE?@gkHLVUUvW29_4Qs@%fPbH zEL}AmXJq&P1kf2@ZPc-B6ky)CYAcJ)Nq_d=sYQ&ED>HXf#o;{Wkq@0gNi>dLKq*9f z-agP$EjR)V!wt{NV)_y%?X)utI}v}cW2^T`bdsDXdI~jeg9xK%krnaiaq?M-o+70P z>NvrL*)CJ>u4xo`(bId5|GRim0y*~_sf+Y?hmPr^4D6ctFw`xoYAvxpF(r?wbZh3X zi4D(+k16Fb!U8G5O#(mfc_k4D6)%5XU?yE&Q;nj)yhRVoW;5hEt(+sE#9t-OjwXg- zp;II+btlx=<)v5+ba}XrUHIqX(e!$O)=zlnsul)ce>1v=CqUM$S2n_^as2E!Trocg zVT%FQEJmgRo0d6_rcEKLWCuv_#V(h=EHX9PaBb`(@9sF^UYC8B7-oKi*yJ)hBVUAm z#mrQLVUSn2MNP8`lN``RyT!jj!1 z<1p_5zFQ0{Uz>>#Wud{HTQl>edw4GPoS!BU#Z)T~R}#NO)j_w{<>kpF&!F?@1WKbA zX=*ORqd|0zoHQ8`kDR304zzaplj$Br*v&iKpm69L`)_tEs%RvNM)#Mz3UJM#0Yi_a zp4THir)1y0zvPEvPf4DNZH=BDfhoZSUcpAUDkDVT%T_JOAJ9l!!g)p|Br6lS3|)<; JEToRS^dEiEq&)xt delta 1007 zcmXX_Z)g)|9DeU|iD_~PO-#@38k1|(n5KVBi+YJ~&04b7+tnx!-upe*65g+_m%*8?F^o=PS<$S8iS_r*~94G{n+pEk4%1bfx z#*Nc(#?lUqMSvxX4~mwkdBoB|F$4~(Eo|OpY}U3&pMX9^3Q8j#CD9n#gR6JYDEbVa z96R8K(E8q0Q3;0Z%oPOyQ#4 zGYq`910H*LuPX7zPx$SmkI(z>$W#%$-a~|R)EkDsydhZhUWA^lt8}OcQ(GHM)z?bF z)vCAcUnYj*q;r_73IM}Z^W-3(p<}AFFc#^1ETs)AZF! z2JUb3n@e0LMGqgu`zUl)AKzU6tX__)Qa+>P{ylS3Y!p%I!AwQ!rucNU7%zwdlCk_L zY{kdef?5QtZ|rwQ`pV$+SJR(Ofrj^)Y)nqW4_L3hx{y2POo9di**hzWl12YmQv6Z{&yN~Ap|C@i%> zdaBgE>Bai22w@x1vLHe>;D>PSULSe+>w}94?u&IfGYB)uICfaC&C&GaDR{9%VJ1&S z((v_;5FFik)?JWGGKpYmz^YG7K+jGdxLqX@3P<=lRq-VG`(ZKy0z3+DgIJv#9`6Tf?i>+^-vimZ(w=;#AbIeK+@h{O)BC j>J?krN4$-}5A~7%%L@&hS*kxl+1o{JWHY|U8;|}6{g-27 diff --git a/Signal/translations/zh_TW.lproj/Localizable.strings b/Signal/translations/zh_TW.lproj/Localizable.strings index 90854834654ae8fa4cef237e351337768471b645..9d962fc0c3489e97321e6afecd929b53052cba44 100644 GIT binary patch delta 88 zcmX?7cCCCvh%&z;gAWkKGdMGVS(6o|geRX9U}Mc>$YV&KY^W+bc^f~UEJT$Og93vs e5GyeRF{Co+0`(*^