diff --git a/Podfile.lock b/Podfile.lock index 3f4861a53..0baa19d26 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -136,7 +136,7 @@ CHECKOUT OPTIONS: :commit: 7054e4b13ee5bcd6d524adb6dc9a726e8c466308 :git: https://github.com/WhisperSystems/JSQMessagesViewController.git SignalServiceKit: - :commit: 0c46288cf96c6dadc775c2c2d089245d65490e78 + :commit: 05a96008e3a2955637f0886cddc5013b3fc64f12 :git: https://github.com/WhisperSystems/SignalServiceKit.git SocketRocket: :commit: 877ac7438be3ad0b45ef5ca3969574e4b97112bf diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index d8d9f524c..4c25a1163 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -482,7 +482,7 @@ 34D8C0231ED3673300188D7C /* DebugUIMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessages.h; sourceTree = ""; }; 34D8C0241ED3673300188D7C /* DebugUIMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIMessages.m; sourceTree = ""; }; 34D8C0251ED3673300188D7C /* DebugUITableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUITableViewController.h; sourceTree = ""; }; - 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUITableViewController.m; sourceTree = ""; }; + 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = DebugUITableViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 34D8C0291ED3685800188D7C /* DebugUIContacts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIContacts.h; sourceTree = ""; }; 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIContacts.m; sourceTree = ""; }; 34DFCB831E8E04B400053165 /* AddToBlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToBlockListViewController.h; sourceTree = ""; }; @@ -512,7 +512,7 @@ 4516E3FE1DD2193B00DC4206 /* OWS101ExistingUsersBlockOnIdentityChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWS101ExistingUsersBlockOnIdentityChange.m; path = Migrations/OWS101ExistingUsersBlockOnIdentityChange.m; sourceTree = ""; }; 451764281DE939FD00EDB8B9 /* ContactCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactCell.xib; sourceTree = ""; }; 451764291DE939FD00EDB8B9 /* ContactCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = ""; }; - 451A13B01E13DED2000A50FD /* CallNotificationsAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallNotificationsAdapter.swift; path = ../UserInterface/Notifications/CallNotificationsAdapter.swift; sourceTree = ""; }; + 451A13B01E13DED2000A50FD /* CallNotificationsAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = CallNotificationsAdapter.swift; path = ../UserInterface/Notifications/CallNotificationsAdapter.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 451DE9F11DC1585F00810E42 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/iOS/PromiseKit.framework; sourceTree = ""; }; 451DE9FC1DC1A28200810E42 /* SyncPushTokensJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SyncPushTokensJob.swift; path = Models/SyncPushTokensJob.swift; sourceTree = ""; }; 4520D8D41D417D8E00123472 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; @@ -548,7 +548,7 @@ 45666F7C1D9C0814008FE134 /* OWSDatabaseMigrationRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSDatabaseMigrationRunner.h; path = Migrations/OWSDatabaseMigrationRunner.h; sourceTree = ""; }; 45666F7D1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDatabaseMigrationRunner.m; path = Migrations/OWSDatabaseMigrationRunner.m; sourceTree = ""; }; 456F6E2E1E261D1000FD2210 /* PeerConnectionClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerConnectionClientTest.swift; sourceTree = ""; }; - 4574A5D51DD6704700C6B692 /* CallService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallService.swift; sourceTree = ""; }; + 4574A5D51DD6704700C6B692 /* CallService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CallService.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 4579431C1E7C8CE9008ED0C0 /* Pastelog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pastelog.h; sourceTree = ""; }; 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pastelog.m; sourceTree = ""; }; 45794E851E00620000066731 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallUIAdapter.swift; path = UserInterface/CallUIAdapter.swift; sourceTree = ""; }; @@ -601,8 +601,8 @@ 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompareSafetyNumbersActivity.swift; sourceTree = ""; }; 45E282DE1D08E67800ADD4C8 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = translations/gl.lproj/Localizable.strings; sourceTree = ""; }; 45E282DF1D08E6CC00ADD4C8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = translations/id.lproj/Localizable.strings; sourceTree = ""; }; - 45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSCallNotificationsAdaptee.h; path = UserInterface/OWSCallNotificationsAdaptee.h; sourceTree = ""; }; - 45E2E91F1E153B3D00457AA0 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Strings.swift; path = UserInterface/Strings.swift; sourceTree = ""; }; + 45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = OWSCallNotificationsAdaptee.h; path = UserInterface/OWSCallNotificationsAdaptee.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 45E2E91F1E153B3D00457AA0 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = Strings.swift; path = UserInterface/Strings.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 45E615151E8C590B0018AD52 /* DisplayableTextFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilter.swift; sourceTree = ""; }; 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilterTest.swift; sourceTree = ""; }; 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSession.swift; sourceTree = ""; }; diff --git a/Signal/src/Jobs/MarkIdentityAsSeenJob.swift b/Signal/src/Jobs/MarkIdentityAsSeenJob.swift index 3efd7c4d4..329f0b78b 100644 --- a/Signal/src/Jobs/MarkIdentityAsSeenJob.swift +++ b/Signal/src/Jobs/MarkIdentityAsSeenJob.swift @@ -8,18 +8,24 @@ import Foundation class MarkIdentityAsSeenJob: NSObject { let TAG = "[MarkIdentityAsSeenJob]" - private let thread: TSThread + private let recipientIds: [String] public class func run(thread: TSThread) { - MarkIdentityAsSeenJob(thread: thread).run() + let recipientIds = thread.recipientIdentifiers + + MarkIdentityAsSeenJob(recipientIds: recipientIds).run() + } + + public class func run(recipientId: String) { + MarkIdentityAsSeenJob(recipientIds: [recipientId]).run() } - init(thread: TSThread) { - self.thread = thread + init(recipientIds: [String]) { + self.recipientIds = recipientIds } public func run() { - for recipientId in self.thread.recipientIdentifiers { + for recipientId in self.recipientIds { markAsSeenIfNecessary(recipientId: recipientId) } } diff --git a/Signal/src/UserInterface/Notifications/CallNotificationsAdapter.swift b/Signal/src/UserInterface/Notifications/CallNotificationsAdapter.swift index d2363ab3c..8d0d95a40 100644 --- a/Signal/src/UserInterface/Notifications/CallNotificationsAdapter.swift +++ b/Signal/src/UserInterface/Notifications/CallNotificationsAdapter.swift @@ -1,5 +1,6 @@ -// Created by Michael Kirk on 12/28/16. -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// import Foundation @@ -34,4 +35,9 @@ class CallNotificationsAdapter: NSObject { Logger.debug("\(TAG) in \(#function)") adaptee.presentMissedCall(call, callerName: callerName) } + + func presentRejectedCallWithUnseenIdentityChange(_ call: SignalCall, callerName: String) { + Logger.debug("\(TAG) in \(#function)") + adaptee.presentRejectedCallWithUnseenIdentityChange(call, callerName: callerName) + } } diff --git a/Signal/src/UserInterface/Notifications/UserNotificationsAdaptee.swift b/Signal/src/UserInterface/Notifications/UserNotificationsAdaptee.swift index 744e0c69f..676a47179 100644 --- a/Signal/src/UserInterface/Notifications/UserNotificationsAdaptee.swift +++ b/Signal/src/UserInterface/Notifications/UserNotificationsAdaptee.swift @@ -13,14 +13,17 @@ import UserNotifications @available(iOS 10.0, *) struct AppNotifications { enum Category { - case missedCall + case missedCall, + rejectedCallFromUnseenIdentity // Don't forget to update this! We use it to register categories. - static let allValues = [ missedCall ] + static let allValues = [ missedCall, rejectedCallFromUnseenIdentity ] } enum Action { - case callBack + case callBack, + showThread, + confirmIdentityAndCallBack } static var allCategories: Set { @@ -35,6 +38,12 @@ struct AppNotifications { actions: [ action(.callBack) ], intentIdentifiers: [], options: []) + + case .rejectedCallFromUnseenIdentity: + return UNNotificationCategory(identifier: "org.whispersystems.signal.AppNotifications.Category.rejectedCallFromUnseenIdentity", + actions: [ action(.confirmIdentityAndCallBack), action(.showThread) ], + intentIdentifiers: [], + options: []) } } @@ -44,6 +53,14 @@ struct AppNotifications { return UNNotificationAction(identifier: "org.whispersystems.signal.AppNotifications.Action.callBack", title: CallStrings.callBackButtonTitle, options: .authenticationRequired) + case .showThread: + return UNNotificationAction(identifier: "org.whispersystems.signal.AppNotifications.Action.showThread", + title: CallStrings.showThreadButtonTitle, + options: .authenticationRequired) + case .confirmIdentityAndCallBack: + return UNNotificationAction(identifier: "org.whispersystems.signal.AppNotifications.Action.confirmIdentityAndCallBack", + title: CallStrings.confirmIdentityAndCallBackButtonTitle, + options: .authenticationRequired) } } } @@ -88,7 +105,7 @@ class UserNotificationsAdaptee: NSObject, OWSCallNotificationsAdaptee, UNUserNot public func presentIncomingCall(_ call: SignalCall, callerName: String) { Logger.debug("\(TAG) \(#function) is no-op, because it's handled with callkit.") // TODO since CallKit doesn't currently work on the simulator, - // we could implement UNNotifications for simulator testing. + // we could implement UNNotifications for simulator testing, or if people have opted out of callkit. } public func presentMissedCall(_ call: SignalCall, callerName: String) { @@ -116,4 +133,30 @@ class UserNotificationsAdaptee: NSObject, OWSCallNotificationsAdaptee, UNUserNot center.add(request) } + + func presentRejectedCallWithUnseenIdentityChange(_ call: SignalCall, callerName: String) { + Logger.debug("\(TAG) \(#function)") + + let content = UNMutableNotificationContent() + // TODO group by thread identifier + // content.threadIdentifier = threadId + + let notificationBody = { () -> String in + switch previewType { + case .noNameNoPreview: + return CallStrings.rejectedCallWithUnseenIdentityChangeNotificationBody + case .nameNoPreview, .namePreview: + return (Environment.getCurrent().preferences.isCallKitPrivacyEnabled() + ? CallStrings.rejectedCallWithUnseenIdentityChangeNotificationBodyWithoutCallerName + : String(format: CallStrings.rejectedCallWithUnseenIdentityChangeNotificationBodyWithCallerName, callerName)) + }}() + + content.body = notificationBody + content.sound = UNNotificationSound.default() + content.categoryIdentifier = AppNotifications.category(.rejectedCallFromUnseenIdentity).identifier + + let request = UNNotificationRequest.init(identifier: call.localId.uuidString, content: content, trigger: nil) + + center.add(request) + } } diff --git a/Signal/src/UserInterface/Strings.swift b/Signal/src/UserInterface/Strings.swift index 9efc38da5..36af12bdf 100644 --- a/Signal/src/UserInterface/Strings.swift +++ b/Signal/src/UserInterface/Strings.swift @@ -8,9 +8,23 @@ import Foundation * Strings re-used in multiple places should be added here. */ @objc class CallStrings: NSObject { + + static let callStatusFormat = NSLocalizedString("CALL_STATUS_FORMAT", comment: "embeds {{Call Status}} in call screen label. For ongoing calls, {{Call Status}} is a seconds timer like 01:23, otherwise {{Call Status}} is a short text like 'Ringing', 'Busy', or 'Failed Call'") + + static let confirmAndCallButtonTitle = NSLocalizedString("SAFETY_NUMBER_CHANGED_CONFIRM_CALL_ACTION", comment: "alert button text to confirm placing an outgoing call after the recipients Safety Number has changed.") + + // MARK: Notification actions static let callBackButtonTitle = NSLocalizedString("CALLBACK_BUTTON_TITLE", comment: "notification action") + static let confirmIdentityAndCallBackButtonTitle = NSLocalizedString("CONFIRM_IDENTITY_AND_CALLBACK_BUTTON_TITLE", comment: "notification action, confirming that it's OK to proceed calling after a caller's Safety Number has changed") + static let showThreadButtonTitle = NSLocalizedString("SHOW_THREAD_BUTTON_TITLE", comment: "notification action") + + // MARK: Missed Call Notification static let missedCallNotificationBody = NSLocalizedString("MISSED_CALL", comment: "notification title") static let missedCallNotificationBodyWithCallerName = NSLocalizedString("MSGVIEW_MISSED_CALL_WITH_NAME", comment: "notification title. Embeds {{Caller's Name}}") static let missedCallNotificationBodyWithoutCallerName = NSLocalizedString("MSGVIEW_MISSED_CALL_WITHOUT_NAME", comment: "notification title.") - static let callStatusFormat = NSLocalizedString("CALL_STATUS_FORMAT", comment: "embeds {{Call Status}} in call screen label. For ongoing calls, {{Call Status}} is a seconds timer like 01:23, otherwise {{Call Status}} is a short text like 'Ringing', 'Busy', or 'Failed Call'") + + // MARK: Missed with Unseen identity Notification + static let rejectedCallWithUnseenIdentityChangeNotificationBody = NSLocalizedString("MISSED_CALL_WITH_UNSEEN_IDENTITY_BODY", comment: "notification action") + static let rejectedCallWithUnseenIdentityChangeNotificationBodyWithoutCallerName = NSLocalizedString("MISSED_CALL_WITH_UNSEEN_IDENTITY_BODY_WITHOUT_CALLER_NAME", comment: "notification action") + static let rejectedCallWithUnseenIdentityChangeNotificationBodyWithCallerName = NSLocalizedString("MISSED_CALL_WITH_UNSEEN_IDENTITY_BODY_WITH_CALLER_NAME", comment: "notification action") } diff --git a/Signal/src/ViewControllers/AddToGroupViewController.m b/Signal/src/ViewControllers/AddToGroupViewController.m index e4b85f952..217140871 100644 --- a/Signal/src/ViewControllers/AddToGroupViewController.m +++ b/Signal/src/ViewControllers/AddToGroupViewController.m @@ -67,18 +67,18 @@ NS_ASSUME_NONNULL_BEGIN } BOOL didShowSNAlert = [SafetyNumberConfirmationAlert - presentAlertIfNecessaryFromViewController:self - recipientId:phoneNumber - confirmationText: - NSLocalizedString(@"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION", - @"button title to confirm adding a recipient to a group when their safety " - @"number has recently changed") - contactsManager:helper.contactsManager - completion:^(BOOL didConfirmIdentity) { - if (didConfirmIdentity) { - [weakSelf addToGroup:phoneNumber]; - } - }]; + presentAlertIfNecessaryWithRecipientId:phoneNumber + confirmationText: + NSLocalizedString(@"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION", + @"button title to confirm adding a recipient to a group when their safety " + @"number has recently changed") + contactsManager:helper.contactsManager + verifySeen:YES + completion:^(BOOL didConfirmIdentity) { + if (didConfirmIdentity) { + [weakSelf addToGroup:phoneNumber]; + } + }]; if (didShowSNAlert) { return; } @@ -119,18 +119,18 @@ NS_ASSUME_NONNULL_BEGIN } BOOL didShowSNAlert = [SafetyNumberConfirmationAlert - presentAlertIfNecessaryFromViewController:self - recipientId:signalAccount.recipientId - confirmationText: - NSLocalizedString(@"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION", - @"button title to confirm adding a recipient to a group when their safety " - @"number has recently changed") - contactsManager:helper.contactsManager - completion:^(BOOL didConfirmIdentity) { - if (didConfirmIdentity) { - [weakSelf addToGroup:signalAccount.recipientId]; - } - }]; + presentAlertIfNecessaryWithRecipientId:signalAccount.recipientId + confirmationText: + NSLocalizedString(@"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION", + @"button title to confirm adding a recipient to a group when their safety " + @"number has recently changed") + contactsManager:helper.contactsManager + verifySeen:YES + completion:^(BOOL didConfirmIdentity) { + if (didConfirmIdentity) { + [weakSelf addToGroup:signalAccount.recipientId]; + } + }]; if (didShowSNAlert) { return; } diff --git a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m index 7bd1c6361..00446267b 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m @@ -84,6 +84,51 @@ NS_ASSUME_NONNULL_BEGIN [contents addSection:[DebugUIContacts section]]; + // After enqueing the notification you may want to background the app or lock the screen before it triggers, so we + // give a little delay. + uint64_t notificationDelay = 5; + [contents + addSection:[OWSTableSection + sectionWithTitle:[NSString stringWithFormat:@"Call Notifications (%llu second delay)", + notificationDelay] + items:@[ + [OWSTableItem itemWithTitle:@"Missed Call" + actionBlock:^{ + SignalCall *call = [SignalCall + incomingCallWithLocalId:[NSUUID new] + remotePhoneNumber:thread.contactIdentifier + signalingId:0]; + + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(notificationDelay * NSEC_PER_SEC)), + dispatch_get_main_queue(), + ^{ + [[Environment getCurrent] + .callService.notificationsAdapter + presentMissedCall:call + callerName:thread.name]; + }); + }], + [OWSTableItem + itemWithTitle:@"Rejected Call with Unseen Safety Number" + actionBlock:^{ + SignalCall *call = + [SignalCall incomingCallWithLocalId:[NSUUID new] + remotePhoneNumber:thread.contactIdentifier + signalingId:0]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(notificationDelay * NSEC_PER_SEC)), + dispatch_get_main_queue(), + ^{ + [[Environment getCurrent].callService.notificationsAdapter + presentRejectedCallWithUnseenIdentityChange:call + callerName:thread.name]; + }); + }], + ]]]; + DebugUITableViewController *viewController = [DebugUITableViewController new]; viewController.contents = contents; [viewController presentFromViewController:fromViewController]; diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index 7e80ab698..a79095e05 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -1638,11 +1638,11 @@ typedef enum : NSUInteger { completion: (void (^)(BOOL didConfirmedIdentity))completionHandler { - return [SafetyNumberConfirmationAlert presentAlertIfNecessaryFromViewController:self - recipientIds:self.thread.recipientIdentifiers - confirmationText:confirmationText - contactsManager:self.contactsManager - completion:completionHandler]; + return [SafetyNumberConfirmationAlert presentAlertIfNecessaryWithRecipientIds:self.thread.recipientIdentifiers + confirmationText:confirmationText + contactsManager:self.contactsManager + verifySeen:NO + completion:completionHandler]; } - (void)showFingerprintWithTheirIdentityKey:(NSData *)theirIdentityKey theirSignalId:(NSString *)theirSignalId @@ -1694,15 +1694,13 @@ typedef enum : NSUInteger { return; } - BOOL didShowSNAlert = [self showSafetyNumberConfirmationIfNecessaryWithConfirmationText: - NSLocalizedString(@"SAFETY_NUMBER_CHANGED_CONFIRM_CALL_ACTION", - @"button title to confirm calling a recipient whose safety " - @"number recently changed") - completion:^(BOOL didConfirmIdentity) { - if (didConfirmIdentity) { - [weakSelf callAction:sender]; - } - }]; + BOOL didShowSNAlert = + [self showSafetyNumberConfirmationIfNecessaryWithConfirmationText:[CallStrings confirmAndCallButtonTitle] + completion:^(BOOL didConfirmIdentity) { + if (didConfirmIdentity) { + [weakSelf callAction:sender]; + } + }]; if (didShowSNAlert) { return; } diff --git a/Signal/src/ViewControllers/NewGroupViewController.m b/Signal/src/ViewControllers/NewGroupViewController.m index 9b7dd114f..2deb7ff2f 100644 --- a/Signal/src/ViewControllers/NewGroupViewController.m +++ b/Signal/src/ViewControllers/NewGroupViewController.m @@ -252,21 +252,21 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68; } else { BOOL didShowSNAlert = [SafetyNumberConfirmationAlert - presentAlertIfNecessaryFromViewController:self - recipientId:recipientId - confirmationText:NSLocalizedString( - @"SAFETY_NUMBER_CHANGED_CONFIRM_" - @"ADD_TO_GROUP_ACTION", - @"button title to confirm adding " - @"a recipient to a group when " - @"their safety " - @"number has recently changed") - contactsManager:contactsViewHelper.contactsManager - completion:^(BOOL didConfirmIdentity) { - if (didConfirmIdentity) { - [weakSelf addRecipientId:recipientId]; - } - }]; + presentAlertIfNecessaryWithRecipientId:recipientId + confirmationText:NSLocalizedString( + @"SAFETY_NUMBER_CHANGED_CONFIRM_" + @"ADD_TO_GROUP_ACTION", + @"button title to confirm adding " + @"a recipient to a group when " + @"their safety " + @"number has recently changed") + contactsManager:contactsViewHelper.contactsManager + verifySeen:YES + completion:^(BOOL didConfirmIdentity) { + if (didConfirmIdentity) { + [weakSelf addRecipientId:recipientId]; + } + }]; if (didShowSNAlert) { return; } @@ -342,21 +342,21 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68; }]; } else { BOOL didShowSNAlert = [SafetyNumberConfirmationAlert - presentAlertIfNecessaryFromViewController:self - recipientId:signalAccount.recipientId - confirmationText:NSLocalizedString( - @"SAFETY_NUMBER_CHANGED_CONFIRM_" - @"ADD_TO_GROUP_ACTION", - @"button title to confirm adding " - @"a recipient to a group when " - @"their safety " - @"number has recently changed") - contactsManager:contactsViewHelper.contactsManager - completion:^(BOOL didConfirmIdentity) { - if (didConfirmIdentity) { - [weakSelf addRecipientId:recipientId]; - } - }]; + presentAlertIfNecessaryWithRecipientId:signalAccount.recipientId + confirmationText:NSLocalizedString( + @"SAFETY_NUMBER_CHANGED_CONFIRM_" + @"ADD_TO_GROUP_ACTION", + @"button title to confirm adding " + @"a recipient to a group when " + @"their safety " + @"number has recently changed") + contactsManager:contactsViewHelper.contactsManager + verifySeen:YES + completion:^(BOOL didConfirmIdentity) { + if (didConfirmIdentity) { + [weakSelf addRecipientId:recipientId]; + } + }]; if (didShowSNAlert) { return; } diff --git a/Signal/src/ViewControllers/SafetyNumberConfirmationAlert.swift b/Signal/src/ViewControllers/SafetyNumberConfirmationAlert.swift index f9dae9b40..6f5515d4f 100644 --- a/Signal/src/ViewControllers/SafetyNumberConfirmationAlert.swift +++ b/Signal/src/ViewControllers/SafetyNumberConfirmationAlert.swift @@ -16,14 +16,14 @@ class SafetyNumberConfirmationAlert: NSObject { self.storageManager = TSStorageManager.shared() } - public class func presentAlertIfNecessary(fromViewController: UIViewController, recipientId: String, confirmationText: String, contactsManager: OWSContactsManager, completion: @escaping (Bool) -> Void) -> Bool { - return self.presentAlertIfNecessary(fromViewController: fromViewController, recipientIds: [recipientId], confirmationText: confirmationText, contactsManager: contactsManager, completion: completion) + public class func presentAlertIfNecessary(recipientId: String, confirmationText: String, contactsManager: OWSContactsManager, verifySeen: Bool, completion: @escaping (Bool) -> Void) -> Bool { + return self.presentAlertIfNecessary(recipientIds: [recipientId], confirmationText: confirmationText, contactsManager: contactsManager, verifySeen: verifySeen, completion: completion) } - public class func presentAlertIfNecessary(fromViewController: UIViewController, recipientIds: [String], confirmationText: String, contactsManager: OWSContactsManager, completion: @escaping (Bool) -> Void) -> Bool { - return SafetyNumberConfirmationAlert(contactsManager: contactsManager).presentIfNecessary(fromViewController: fromViewController, - recipientIds: recipientIds, + public class func presentAlertIfNecessary(recipientIds: [String], confirmationText: String, contactsManager: OWSContactsManager, verifySeen: Bool, completion: @escaping (Bool) -> Void) -> Bool { + return SafetyNumberConfirmationAlert(contactsManager: contactsManager).presentIfNecessary(recipientIds: recipientIds, confirmationText: confirmationText, + verifySeen: verifySeen, completion: completion) } @@ -33,18 +33,25 @@ class SafetyNumberConfirmationAlert: NSObject { * @returns true if an alert was shown * false if there were no unconfirmed identities */ - public func presentIfNecessary(fromViewController: UIViewController, recipientIds: [String], confirmationText: String, completion: @escaping (Bool) -> Void) -> Bool { + public func presentIfNecessary(recipientIds: [String], confirmationText: String, verifySeen: Bool, completion: @escaping (Bool) -> Void) -> Bool { - guard let unconfirmedIdentity = self.unconfirmedIdentityThatShouldBlockSending(recipientIds: recipientIds) else { + let unconfirmedIdentity = unconfirmedIdentities(recipientIds: recipientIds).first + + var unseenIdentity: OWSRecipientIdentity? + if verifySeen { + unseenIdentity = unseenIdentities(recipientIds: recipientIds).first + } + + guard let untrustedIdentity = [unseenIdentity, unconfirmedIdentity].flatMap({ $0 }).first else { // No identities to confirm, no alert to present. return false } let displayName: String = { - if let signalAccount = contactsManager.signalAccountMap[unconfirmedIdentity.recipientId] { + if let signalAccount = contactsManager.signalAccountMap[untrustedIdentity.recipientId] { return contactsManager.displayName(for: signalAccount) } else { - return contactsManager.displayName(forPhoneIdentifier: unconfirmedIdentity.recipientId) + return contactsManager.displayName(forPhoneIdentifier: untrustedIdentity.recipientId) } }() @@ -59,12 +66,14 @@ class SafetyNumberConfirmationAlert: NSObject { let actionSheetController = UIAlertController(title: title, message:body, preferredStyle: .actionSheet) let confirmAction = UIAlertAction(title: confirmationText, style: .default) { _ in - Logger.info("\(self.TAG) Confirmed identity: \(unconfirmedIdentity)") + Logger.info("\(self.TAG) Confirmed identity: \(untrustedIdentity)") + OWSDispatch.sessionStoreQueue().async { - self.storageManager.saveRemoteIdentity(unconfirmedIdentity.identityKey, - recipientId: unconfirmedIdentity.recipientId, + self.storageManager.saveRemoteIdentity(untrustedIdentity.identityKey, + recipientId: untrustedIdentity.recipientId, approvedForBlockingUse: true, approvedForNonBlockingUse: true) + MarkIdentityAsSeenJob.run(recipientId: untrustedIdentity.recipientId) DispatchQueue.main.async { completion(true) } @@ -73,11 +82,10 @@ class SafetyNumberConfirmationAlert: NSObject { actionSheetController.addAction(confirmAction) let showSafetyNumberAction = UIAlertAction(title: NSLocalizedString("VERIFY_PRIVACY", comment: "Action sheet item"), style: .default) { _ in - Logger.info("\(self.TAG) Opted to show Safety Number for identity: \(unconfirmedIdentity)") + Logger.info("\(self.TAG) Opted to show Safety Number for identity: \(untrustedIdentity)") - self.presentSafetyNumberViewController(fromViewController: fromViewController, - theirIdentityKey: unconfirmedIdentity.identityKey, - theirRecipientId: unconfirmedIdentity.recipientId, + self.presentSafetyNumberViewController(theirIdentityKey: untrustedIdentity.identityKey, + theirRecipientId: untrustedIdentity.recipientId, theirDisplayName: displayName, completion: { completion(false) }) @@ -87,11 +95,11 @@ class SafetyNumberConfirmationAlert: NSObject { let dismissAction = UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: "generic cancel text"), style: .cancel) actionSheetController.addAction(dismissAction) - fromViewController.present(actionSheetController, animated: true) + UIApplication.shared.frontmostViewController?.present(actionSheetController, animated: true) return true } - public func presentSafetyNumberViewController(fromViewController: UIViewController, theirIdentityKey: Data, theirRecipientId: String, theirDisplayName: String, completion: (() -> Void)? = nil) { + public func presentSafetyNumberViewController(theirIdentityKey: Data, theirRecipientId: String, theirDisplayName: String, completion: (() -> Void)? = nil) { let fingerprintViewController = UIStoryboard.instantiateFingerprintViewController() let fingerprintBuilder = OWSFingerprintBuilder(storageManager: self.storageManager, contactsManager: self.contactsManager) @@ -99,21 +107,19 @@ class SafetyNumberConfirmationAlert: NSObject { fingerprintViewController.configure(fingerprint: fingerprint, contactName: theirDisplayName) - fromViewController.present(fingerprintViewController, animated: true, completion: completion) + UIApplication.shared.frontmostViewController?.present(fingerprintViewController, animated: true, completion: completion) } - private func unconfirmedIdentitiesThatShouldBlockSending(recipientIds: [String]) -> [OWSRecipientIdentity] { + private func unconfirmedIdentities(recipientIds: [String]) -> [OWSRecipientIdentity] { return recipientIds.flatMap { - return self.storageManager.unconfirmedIdentityThatShouldBlockSending(forRecipientId: $0) + self.storageManager.unconfirmedIdentityThatShouldBlockSending(forRecipientId: $0) } } - private func unconfirmedIdentityThatShouldBlockSending(recipientIds: [String]) -> OWSRecipientIdentity? { - return unconfirmedIdentitiesThatShouldBlockSending(recipientIds: recipientIds).first - } - - private func shouldShow(recipientIds: [String]) -> Bool { - return !unconfirmedIdentitiesThatShouldBlockSending(recipientIds: recipientIds).isEmpty + private func unseenIdentities(recipientIds: [String]) -> [OWSRecipientIdentity] { + return recipientIds.flatMap { + self.storageManager.unseenIdentityChange(forRecipientId: $0) + } } } diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index 51d82c536..07229e4ad 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -110,9 +110,10 @@ protocol CallServiceObserver: class { private let accountManager: AccountManager private let messageSender: MessageSender private let contactsManager: OWSContactsManager - private let notificationsAdapter: CallNotificationsAdapter + private let storageManager: TSStorageManager // Exposed by environment.m + internal let notificationsAdapter: CallNotificationsAdapter internal var callUIAdapter: CallUIAdapter! // MARK: Class @@ -200,6 +201,7 @@ protocol CallServiceObserver: class { self.contactsManager = contactsManager self.messageSender = messageSender self.notificationsAdapter = notificationsAdapter + self.storageManager = TSStorageManager.shared() super.init() @@ -465,9 +467,16 @@ protocol CallServiceObserver: class { AssertIsOnMainThread() Logger.info("\(TAG) receivedCallOffer for thread:\(thread)") + let newCall = SignalCall.incomingCall(localId: UUID(), remotePhoneNumber: thread.contactIdentifier(), signalingId: callId) - guard call == nil else { + guard self.storageManager.unseenIdentityChange(forRecipientId: thread.contactIdentifier()) == nil else { + let callerName = self.contactsManager.displayName(forPhoneIdentifier: thread.contactIdentifier()) + self.notificationsAdapter.presentRejectedCallWithUnseenIdentityChange(newCall, callerName: callerName) + return + } + + guard self.call == nil else { // TODO on iOS10+ we can use CallKit to swap calls rather than just returning busy immediately. Logger.verbose("\(TAG) receivedCallOffer for thread: \(thread) but we're already in call: \(call!)") diff --git a/Signal/src/call/OutboundCallInitiator.swift b/Signal/src/call/OutboundCallInitiator.swift index c15c9e8e9..144fbc1af 100644 --- a/Signal/src/call/OutboundCallInitiator.swift +++ b/Signal/src/call/OutboundCallInitiator.swift @@ -46,6 +46,18 @@ import Foundation return false } + let showedAlert = SafetyNumberConfirmationAlert.presentAlertIfNecessary(recipientId: recipientId, + confirmationText: CallStrings.confirmAndCallButtonTitle, + contactsManager: self.contactsManager, + verifySeen: true) { didConfirmIdentity in + if didConfirmIdentity { + _ = self.initiateCall(recipientId: recipientId) + } + } + guard !showedAlert else { + return false + } + // Check for microphone permissions // Alternative way without prompting for permissions: // if AVAudioSession.sharedInstance().recordPermission() == .denied { diff --git a/Signal/src/call/UserInterface/OWSCallNotificationsAdaptee.h b/Signal/src/call/UserInterface/OWSCallNotificationsAdaptee.h index 060a58007..d80a09319 100644 --- a/Signal/src/call/UserInterface/OWSCallNotificationsAdaptee.h +++ b/Signal/src/call/UserInterface/OWSCallNotificationsAdaptee.h @@ -1,5 +1,6 @@ -// Created by Michael Kirk on 12/28/16. -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// NS_ASSUME_NONNULL_BEGIN @@ -11,6 +12,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)presentMissedCall:(SignalCall *)call callerName:(NSString *)callerName; +- (void)presentRejectedCallWithUnseenIdentityChange:(SignalCall *)call + callerName:(NSString *)callerName + NS_SWIFT_NAME(presentRejectedCallWithUnseenIdentityChange(_:callerName:)); + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/environment/NotificationsManager.m b/Signal/src/environment/NotificationsManager.m index 7fb70dd3e..dc7bb85bb 100644 --- a/Signal/src/environment/NotificationsManager.m +++ b/Signal/src/environment/NotificationsManager.m @@ -113,6 +113,43 @@ [self presentNotification:notification identifier:localCallId]; } +- (void)presentRejectedCallWithUnseenIdentityChange:(SignalCall *)call callerName:(NSString *)callerName +{ + TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:call.remotePhoneNumber]; + OWSAssert(thread != nil); + + UILocalNotification *notification = [UILocalNotification new]; + notification.category = PushManagerCategoriesRejectedCallFromUnseenIdentityChange; + NSString *localCallId = call.localId.UUIDString; + notification.userInfo = @{ + PushManagerUserInfoKeysLocalCallId : localCallId, + PushManagerUserInfoKeysCallBackSignalRecipientId : call.remotePhoneNumber, + Signal_Thread_UserInfo_Key : thread.uniqueId + }; + + NSString *alertMessage; + switch (self.notificationPreviewType) { + case NotificationNoNameNoPreview: { + alertMessage = [CallStrings rejectedCallWithUnseenIdentityChangeNotificationBody]; + break; + } + case NotificationNameNoPreview: + case NotificationNamePreview: { + alertMessage = (([UIDevice currentDevice].supportsCallKit && + [[Environment getCurrent].preferences isCallKitPrivacyEnabled]) + ? [CallStrings rejectedCallWithUnseenIdentityChangeNotificationBodyWithoutCallerName] + : [NSString + stringWithFormat:[CallStrings + rejectedCallWithUnseenIdentityChangeNotificationBodyWithCallerName], + callerName]); + break; + } + } + notification.alertBody = [NSString stringWithFormat:@"☎️ %@", alertMessage]; + + [self presentNotification:notification identifier:localCallId]; +} + #pragma mark - Signal Messages - (void)notifyUserForErrorMessage:(TSErrorMessage *)message inThread:(TSThread *)thread { diff --git a/Signal/src/network/PushManager.h b/Signal/src/network/PushManager.h index 3482fc1b8..c3a93c33d 100644 --- a/Signal/src/network/PushManager.h +++ b/Signal/src/network/PushManager.h @@ -10,25 +10,28 @@ NS_ASSUME_NONNULL_BEGIN @class UILocalNotification; -#define Signal_Thread_UserInfo_Key @"Signal_Thread_Id" -#define Signal_Message_UserInfo_Key @"Signal_Message_Id" +extern NSString *const Signal_Thread_UserInfo_Key; +extern NSString *const Signal_Message_UserInfo_Key; -#define Signal_Full_New_Message_Category @"Signal_Full_New_Message" +extern NSString *const Signal_Full_New_Message_Category; -#define Signal_Message_Reply_Identifier @"Signal_New_Message_Reply" -#define Signal_Message_MarkAsRead_Identifier @"Signal_Message_MarkAsRead" +extern NSString *const Signal_Message_Reply_Identifier; +extern NSString *const Signal_Message_MarkAsRead_Identifier; #pragma mark Signal Calls constants -FOUNDATION_EXPORT NSString *const PushManagerCategoriesIncomingCall; -FOUNDATION_EXPORT NSString *const PushManagerCategoriesMissedCall; +extern NSString *const PushManagerCategoriesIncomingCall; +extern NSString *const PushManagerCategoriesMissedCall; +extern NSString *const PushManagerCategoriesRejectedCallFromUnseenIdentityChange; -FOUNDATION_EXPORT NSString *const PushManagerActionsAcceptCall; -FOUNDATION_EXPORT NSString *const PushManagerActionsDeclineCall; -FOUNDATION_EXPORT NSString *const PushManagerActionsCallBack; +extern NSString *const PushManagerActionsAcceptCall; +extern NSString *const PushManagerActionsDeclineCall; +extern NSString *const PushManagerActionsCallBack; +extern NSString *const PushManagerActionsConfirmIdentityAndCallBack; +extern NSString *const PushManagerActionsShowThread; -FOUNDATION_EXPORT NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId; -FOUNDATION_EXPORT NSString *const PushManagerUserInfoKeysLocalCallId; +extern NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId; +extern NSString *const PushManagerUserInfoKeysLocalCallId; typedef void (^failedPushRegistrationBlock)(NSError *error); typedef void (^pushTokensSuccessBlock)(NSString *pushToken, NSString *voipToken); diff --git a/Signal/src/network/PushManager.m b/Signal/src/network/PushManager.m index 9dfe06f95..fe242de84 100644 --- a/Signal/src/network/PushManager.m +++ b/Signal/src/network/PushManager.m @@ -16,7 +16,13 @@ #import #import -#define pushManagerDomain @"org.whispersystems.pushmanager" +NSString *const Signal_Thread_UserInfo_Key = @"Signal_Thread_Id"; +NSString *const Signal_Message_UserInfo_Key = @"Signal_Message_Id"; + +NSString *const Signal_Full_New_Message_Category = @"Signal_Full_New_Message"; + +NSString *const Signal_Message_Reply_Identifier = @"Signal_New_Message_Reply"; +NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRead"; @interface PushManager () @@ -132,10 +138,9 @@ withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler { - DDLogInfo(@"received: %s", __PRETTY_FUNCTION__); + DDLogInfo(@"%@ handling action with identifier: %@", self.tag, identifier); if ([identifier isEqualToString:Signal_Message_Reply_Identifier]) { - DDLogInfo(@"%@ received reply identifier", self.tag); NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key]; if (threadId) { @@ -146,6 +151,7 @@ messageBody:responseInfo[UIUserNotificationActionResponseTypedTextKey]]; [self.messageSender sendMessage:message success:^{ + // TODO do we really want to mark them all as read? [self markAllInThreadAsRead:notification.userInfo completionHandler:completionHandler]; [[[[Environment getCurrent] signalsViewController] tableView] reloadData]; } @@ -162,10 +168,9 @@ }]; } } else if ([identifier isEqualToString:Signal_Message_MarkAsRead_Identifier]) { + // TODO mark all as read? Or just this one? [self markAllInThreadAsRead:notification.userInfo completionHandler:completionHandler]; } else if ([identifier isEqualToString:PushManagerActionsAcceptCall]) { - DDLogInfo(@"%@ received accept call action", self.tag); - NSString *localIdString = notification.userInfo[PushManagerUserInfoKeysLocalCallId]; if (!localIdString) { DDLogError(@"%@ missing localIdString.", self.tag); @@ -178,11 +183,9 @@ return; } - [self.callUIAdapter answerCallWithLocalId:localId]; + completionHandler(); } else if ([identifier isEqualToString:PushManagerActionsDeclineCall]) { - DDLogInfo(@"%@ received decline call action", self.tag); - NSString *localIdString = notification.userInfo[PushManagerUserInfoKeysLocalCallId]; if (!localIdString) { DDLogError(@"%@ missing localIdString.", self.tag); @@ -196,19 +199,35 @@ } [self.callUIAdapter declineCallWithLocalId:localId]; + completionHandler(); } else if ([identifier isEqualToString:PushManagerActionsCallBack]) { - DDLogInfo(@"%@ received call back action", self.tag); + NSString *recipientId = notification.userInfo[PushManagerUserInfoKeysCallBackSignalRecipientId]; + if (!recipientId) { + DDLogError(@"%@ missing call back id", self.tag); + return; + } + [self.callUIAdapter startAndShowOutgoingCallWithRecipientId:recipientId]; + completionHandler(); + } else if ([identifier isEqualToString:PushManagerActionsConfirmIdentityAndCallBack]) { NSString *recipientId = notification.userInfo[PushManagerUserInfoKeysCallBackSignalRecipientId]; if (!recipientId) { DDLogError(@"%@ missing call back id", self.tag); return; } + TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId]; + [MarkIdentityAsSeenJob runWithThread:thread]; + [self.callUIAdapter startAndShowOutgoingCallWithRecipientId:recipientId]; + completionHandler(); + } else if ([identifier isEqualToString:PushManagerActionsShowThread]) { + NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key]; + [Environment messageThreadId:threadId]; + completionHandler(); } else { - DDLogDebug(@"%@ Unhandled action with identifier: %@", self.tag, identifier); - + DDLogError(@"%@ Unhandled action with identifier: %@", self.tag, identifier); + OWSFail(@"Unhandled action"); NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key]; [Environment messageThreadId:threadId]; completionHandler(); @@ -338,10 +357,14 @@ NSString *const PushManagerCategoriesIncomingCall = @"PushManagerCategoriesIncomingCall"; NSString *const PushManagerCategoriesMissedCall = @"PushManagerCategoriesMissedCall"; +NSString *const PushManagerCategoriesRejectedCallFromUnseenIdentityChange = + @"PushManagerCategoriesRejectedCallFromUnseenIdentityChange"; NSString *const PushManagerActionsAcceptCall = @"PushManagerActionsAcceptCall"; NSString *const PushManagerActionsDeclineCall = @"PushManagerActionsDeclineCall"; NSString *const PushManagerActionsCallBack = @"PushManagerActionsCallBack"; +NSString *const PushManagerActionsConfirmIdentityAndCallBack = @"PushManagerActionsConfirmIdentityAndCallBack"; +NSString *const PushManagerActionsShowThread = @"PushManagerActionsShowThread"; NSString *const PushManagerUserInfoKeysLocalCallId = @"PushManagerUserInfoKeysLocalCallId"; NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManagerUserInfoKeysCallBackSignalRecipientId"; @@ -379,12 +402,38 @@ NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManager callBackAction.destructive = NO; callBackAction.authenticationRequired = YES; - UIMutableUserNotificationCategory *callCategory = [UIMutableUserNotificationCategory new]; - callCategory.identifier = PushManagerCategoriesMissedCall; - [callCategory setActions:@[ callBackAction ] forContext:UIUserNotificationActionContextMinimal]; - [callCategory setActions:@[ callBackAction ] forContext:UIUserNotificationActionContextDefault]; + UIMutableUserNotificationCategory *missedCallCategory = [UIMutableUserNotificationCategory new]; + missedCallCategory.identifier = PushManagerCategoriesMissedCall; + [missedCallCategory setActions:@[ callBackAction ] forContext:UIUserNotificationActionContextMinimal]; + [missedCallCategory setActions:@[ callBackAction ] forContext:UIUserNotificationActionContextDefault]; - return callCategory; + return missedCallCategory; +} + +- (UIUserNotificationCategory *)signalRejectedCallWithUnseenIdentityChangeCategory +{ + UIMutableUserNotificationAction *confirmAndCallBackAction = [UIMutableUserNotificationAction new]; + confirmAndCallBackAction.identifier = PushManagerActionsConfirmIdentityAndCallBack; + confirmAndCallBackAction.title = [CallStrings confirmIdentityAndCallBackButtonTitle]; + confirmAndCallBackAction.activationMode = UIUserNotificationActivationModeForeground; + confirmAndCallBackAction.destructive = NO; + confirmAndCallBackAction.authenticationRequired = YES; + + UIMutableUserNotificationAction *showThreadAction = [UIMutableUserNotificationAction new]; + showThreadAction.identifier = PushManagerActionsShowThread; + showThreadAction.title = [CallStrings showThreadButtonTitle]; + showThreadAction.activationMode = UIUserNotificationActivationModeForeground; + showThreadAction.destructive = NO; + showThreadAction.authenticationRequired = YES; + + UIMutableUserNotificationCategory *rejectedCallCategory = [UIMutableUserNotificationCategory new]; + rejectedCallCategory.identifier = PushManagerCategoriesRejectedCallFromUnseenIdentityChange; + [rejectedCallCategory setActions:@[ confirmAndCallBackAction, showThreadAction ] + forContext:UIUserNotificationActionContextMinimal]; + [rejectedCallCategory setActions:@[ confirmAndCallBackAction, showThreadAction ] + forContext:UIUserNotificationActionContextDefault]; + + return rejectedCallCategory; } #pragma mark Util @@ -408,6 +457,7 @@ NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManager categories:[NSSet setWithObjects:[self fullNewMessageNotificationCategory], [self signalIncomingCallCategory], [self signalMissedCallCategory], + [self signalRejectedCallWithUnseenIdentityChangeCategory], nil]]; [UIApplication.sharedApplication registerUserNotificationSettings:settings]; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index dfccf5130..7ff06eaa7 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2,7 +2,7 @@ "AB_PERMISSION_MISSING_ACTION_NOT_NOW" = "Not Now"; /* Action sheet item */ -"ACCEPT_NEW_IDENTITY_ACTION" = "Accept new safety number"; +"ACCEPT_NEW_IDENTITY_ACTION" = "Accept New Safety Number"; /* A label for the 'add by phone number' button in the 'add group member' view */ "ADD_GROUP_MEMBER_VIEW_BUTTON" = "Add"; @@ -212,7 +212,7 @@ "CALL_VIEW_SETTINGS_NAG_SHOW_CALL_SETTINGS" = "Show Privacy Settings"; /* notification action */ -"CALLBACK_BUTTON_TITLE" = "Call back"; +"CALLBACK_BUTTON_TITLE" = "Call Back"; /* The generic name used for calls if CallKit privacy is enabled */ "CALLKIT_ANONYMOUS_CONTACT_NAME" = "Signal User"; @@ -232,6 +232,9 @@ /* No comment provided by engineer. */ "CONFIRM_ACCOUNT_DESTRUCTION_TITLE" = "Are you sure you want to delete your account?"; +/* notification action, confirming that it's OK to proceed calling after a caller's Safety Number has changed */ +"CONFIRM_IDENTITY_AND_CALLBACK_BUTTON_TITLE" = "Confirm and Call Back"; + /* Alert body */ "CONFIRM_LEAVE_GROUP_DESCRIPTION" = "You will no longer be able to send or receive messages in this group."; @@ -242,10 +245,10 @@ "CONFIRM_LINK_NEW_DEVICE_ACTION" = "Link New Device"; /* Action sheet body presented when a user's SN have recently changed. Embeds {{contact's name or phone nubmer}} */ -"CONFIRM_SENDING_TO_CHANGED_IDENTITY_BODY_FORMAT" = "%@ may have reinstalled or changed devices. Verify your Safety Number with them to ensure privacy."; +"CONFIRM_SENDING_TO_CHANGED_IDENTITY_BODY_FORMAT" = "%@ may have reinstalled or changed devices. Verify your safety number with them to ensure privacy."; /* Action sheet title presented when a users's SN have recently changed. Embeds {{contact's name or phone number}} */ -"CONFIRM_SENDING_TO_CHANGED_IDENTITY_TITLE_FORMAT" = "Safety Number with %@ has Changed"; +"CONFIRM_SENDING_TO_CHANGED_IDENTITY_TITLE_FORMAT" = "Safety number with %@ has Changed"; /* Generic button text to proceed with an action */ "CONFIRMATION_TITLE" = "Confirm"; @@ -670,9 +673,6 @@ /* table cell label in conversation settings */ "LIST_GROUP_MEMBERS_ACTION" = "List Group Members"; -/* No comment provided by engineer. */ -"load_earlier_messages" = "load_earlier_messages"; - /* No comment provided by engineer. */ "LOGGING_SECTION" = "Logging"; @@ -736,6 +736,15 @@ /* notification title */ "MISSED_CALL" = "Missed call"; +/* notification action */ +"MISSED_CALL_WITH_UNSEEN_IDENTITY_BODY" = "Missed call because the caller's safety number changed."; + +/* notification action */ +"MISSED_CALL_WITH_UNSEEN_IDENTITY_BODY_WITH_CALLER_NAME" = "Missed call from %@ because their safety number changed."; + +/* notification action */ +"MISSED_CALL_WITH_UNSEEN_IDENTITY_BODY_WITHOUT_CALLER_NAME" = "Missed call because the caller's safety number changed."; + /* Alert body Alert body when camera is not authorized */ "MISSING_CAMERA_PERMISSION_MESSAGE" = "Signal needs access to your camera for video calls. You can grant this permission in the Settings app >> Privacy >> Camera >> Signal"; @@ -950,7 +959,7 @@ "PROCEED_BUTTON" = "Proceed"; /* No comment provided by engineer. */ -"PUSH_MANAGER_MARKREAD" = "Mark as read"; +"PUSH_MANAGER_MARKREAD" = "Mark as Read"; /* No comment provided by engineer. */ "PUSH_MANAGER_REPLY" = "Reply"; @@ -1256,7 +1265,10 @@ "SHARE_ACTION_TWEET" = "Twitter"; /* Action sheet item */ -"SHOW_SAFETY_NUMBER_ACTION" = "Show new safety number"; +"SHOW_SAFETY_NUMBER_ACTION" = "Show New Safety Number"; + +/* notification action */ +"SHOW_THREAD_BUTTON_TITLE" = "Show Conversation"; /* {{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 day}}'. See other *_TIME_AMOUNT strings */ "SINGLE_DAY_TIME_AMOUNT" = "%u day";