diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 3c86df26c..de1eaa635 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -143,6 +143,8 @@ 454EBAB41F2BE14C00ACE0BB /* OWSAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */; }; 455A16DD1F1FEA0000F86704 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 455A16DB1F1FEA0000F86704 /* Metal.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 455A16DE1F1FEA0000F86704 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 455A16DC1F1FEA0000F86704 /* MetalKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 45638BDC1F3DD0D400128435 /* DebugUICalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45638BDB1F3DD0D400128435 /* DebugUICalling.swift */; }; + 45638BDF1F3DDB2200128435 /* MessageSender+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45638BDE1F3DDB2200128435 /* MessageSender+Promise.swift */; }; 4563ADF11F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4563ADF01F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift */; }; 45666EC61D99483D008FE134 /* OWSAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */; }; 45666EC91D994C0D008FE134 /* OWSGroupAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC81D994C0D008FE134 /* OWSGroupAvatarBuilder.m */; }; @@ -587,6 +589,8 @@ 454B35071D08EED80026D658 /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = translations/mk.lproj/Localizable.strings; sourceTree = ""; }; 455A16DB1F1FEA0000F86704 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; 455A16DC1F1FEA0000F86704 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; + 45638BDB1F3DD0D400128435 /* DebugUICalling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugUICalling.swift; sourceTree = ""; }; + 45638BDE1F3DDB2200128435 /* MessageSender+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageSender+Promise.swift"; sourceTree = ""; }; 4563ADF01F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS106EnsureProfileComplete.swift; sourceTree = ""; }; 45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAvatarBuilder.h; sourceTree = ""; }; 45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAvatarBuilder.m; sourceTree = ""; }; @@ -1105,6 +1109,7 @@ 452037D01EE84975004E4CDF /* DebugUISessionState.m */, 34D8C0251ED3673300188D7C /* DebugUITableViewController.h */, 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */, + 45638BDB1F3DD0D400128435 /* DebugUICalling.swift */, ); path = DebugUI; sourceTree = ""; @@ -1769,6 +1774,7 @@ 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */, 45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */, 45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */, + 45638BDE1F3DDB2200128435 /* MessageSender+Promise.swift */, ); name = "UI Categories"; path = ..; @@ -2214,6 +2220,7 @@ 45666EC91D994C0D008FE134 /* OWSGroupAvatarBuilder.m in Sources */, 3471B1DA1EB7C63600F6AEC8 /* NewNonContactConversationViewController.m in Sources */, 34B3F87C1E8DF1700035BE1A /* FingerprintViewController.m in Sources */, + 45638BDC1F3DD0D400128435 /* DebugUICalling.swift in Sources */, 76EB058218170B33006006FC /* Environment.m in Sources */, 34B3F8921E8DF1710035BE1A /* SignalAttachment.swift in Sources */, 45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */, @@ -2262,6 +2269,7 @@ 45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */, 34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */, 45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */, + 45638BDF1F3DDB2200128435 /* MessageSender+Promise.swift in Sources */, 34535D821E256BE9008A4747 /* UIView+OWS.m in Sources */, 45F3AEB61DFDE7900080CE33 /* AvatarImageView.swift in Sources */, 7038632718F70C0700D4A43F /* CryptoTools.m in Sources */, diff --git a/Signal/src/MessageSender+Promise.swift b/Signal/src/MessageSender+Promise.swift new file mode 100644 index 000000000..ee4bbf8ca --- /dev/null +++ b/Signal/src/MessageSender+Promise.swift @@ -0,0 +1,25 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit + +public extension MessageSender { + + /** + * Wrap message sending in a Promise for easier callback chaining. + */ + public func sendPromise(message: TSOutgoingMessage) -> Promise { + let promise: Promise = Promise { fulfill, reject in + self.send(message, success: fulfill, failure: reject) + } + + // Ensure sends complete before they're GC'd. + // This *should* be redundant, since we should be calling retainUntilComplete + // at all call sites where the promise isn't otherwise retained. + promise.retainUntilComplete() + + return promise + } +} diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 51393b431..d43b4bee7 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -6,6 +6,7 @@ #import "AppSettingsViewController.h" #import "AttachmentSharing.h" +#import "DebugUIPage.h" #import "Environment.h" #import "FLAnimatedImage.h" #import "FingerprintViewController.h" diff --git a/Signal/src/ViewControllers/DebugUI/DebugUICalling.swift b/Signal/src/ViewControllers/DebugUI/DebugUICalling.swift new file mode 100644 index 000000000..462d68a81 --- /dev/null +++ b/Signal/src/ViewControllers/DebugUI/DebugUICalling.swift @@ -0,0 +1,88 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation + +class DebugUICalling: DebugUIPage { + + let TAG = "[DebugUICalling]" + + // MARK: Dependencies + + var notificationsAdapter: CallNotificationsAdapter { + return Environment.getCurrent().callService.notificationsAdapter + } + var messageSender: MessageSender { + return Environment.getCurrent().messageSender + } + + // MARK: Overrides + + override func name() -> String { + return "Calling" + } + + override func section(thread aThread: TSThread?) -> OWSTableSection? { + guard let thread = aThread as? TSContactThread else { + owsFail("Calling is only valid for contact thread, got thread: \(String(describing: aThread))") + return nil + } + + let sectionItems = [ + OWSTableItem(title:"⚠️ Missed Call") { + self.delayedDispatchWithFakeCall(thread: thread) { call in + self.notificationsAdapter.presentMissedCall(call, callerName: thread.name()) + } + }, + OWSTableItem(title:"⚠️ New Safety Number (rejected)") { + self.delayedDispatchWithFakeCall(thread: thread) { call in + self.notificationsAdapter.presentMissedCallBecauseOfNewIdentity(call: call, callerName: thread.name()) + } + }, + OWSTableItem(title:"⚠️ No Longer Verified (rejected)") { + self.delayedDispatchWithFakeCall(thread: thread) { call in + self.notificationsAdapter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: call, callerName: thread.name()) + } + }, + OWSTableItem(title:"Send 'hangup' for old call") { + let kFakeCallId = UInt64(12345) + let hangupMessage = OWSCallHangupMessage(callId: kFakeCallId) + let callMessage = OWSOutgoingCallMessage(thread: thread, hangupMessage: hangupMessage) + + self.messageSender.sendPromise(message: callMessage).then { + Logger.debug("\(self.TAG) Successfully sent hangup call message to \(thread.contactIdentifier())") + }.catch { error in + Logger.error("\(self.TAG) failed to send hangup call message to \(thread.contactIdentifier()) with error: \(error)") + } + }, + OWSTableItem(title:"Send 'busy' for old call") { + let kFakeCallId = UInt64(12345) + let busyMessage = OWSCallBusyMessage(callId: kFakeCallId) + let callMessage = OWSOutgoingCallMessage(thread: thread, busyMessage: busyMessage) + + self.messageSender.sendPromise(message: callMessage).then { + Logger.debug("\(self.TAG) Successfully sent busy call message to \(thread.contactIdentifier())") + }.catch { error in + Logger.error("\(self.TAG) failed to send busy call message to \(thread.contactIdentifier()) with error: \(error)") + } + } + ] + + return OWSTableSection(title: "Call Notifications (⚠️) have delay: \(kNotificationDelay)s", items: sectionItems) + } + + // MARK: Helpers + + // After enqueing the notification you may want to background the app or lock the screen before it triggers, so + // we give a little delay. + let kNotificationDelay: TimeInterval = 5 + + func delayedDispatchWithFakeCall(thread: TSContactThread, callBlock: @escaping (SignalCall) -> Void) { + let call = SignalCall.incomingCall(localId: UUID(), remotePhoneNumber: thread.contactIdentifier(), signalingId: 0) + + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + kNotificationDelay) { + callBlock(call) + } + } +} diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIPage.h b/Signal/src/ViewControllers/DebugUI/DebugUIPage.h index 4ae463272..4fb2c075b 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIPage.h +++ b/Signal/src/ViewControllers/DebugUI/DebugUIPage.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)name; -- (nullable OWSTableSection *)sectionForThread:(nullable TSThread *)thread; +- (nullable OWSTableSection *)sectionForThread:(nullable TSThread *)thread NS_SWIFT_NAME(section(thread:)); @end diff --git a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m index 913a186ca..d00570938 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m @@ -92,77 +92,13 @@ NS_ASSUME_NONNULL_BEGIN if ([thread isKindOfClass:[TSContactThread class]]) { [subsectionItems addObject:[self itemForSubsection:[DebugUISessionState new] viewController:viewController thread:thread]]; + [subsectionItems + addObject:[self itemForSubsection:[DebugUICalling new] viewController:viewController thread:thread]]; } [subsectionItems addObject:[self itemForSubsection:[DebugUIMisc new] viewController:viewController thread:thread]]; [contents addSection:[OWSTableSection sectionWithTitle:@"Sections" items:subsectionItems]]; - if ([thread isKindOfClass:[TSContactThread class]]) { - // 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 New 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 - presentMissedCallBecauseOfNewIdentityWithCall:call - callerName:thread.name]; - }); - }], - [OWSTableItem - itemWithTitle:@"Rejected Call with No Longer Verified 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 - presentMissedCallBecauseOfNoLongerVerifiedIdentityWithCall:call - callerName: - thread - .name]; - }); - }], - ]]]; - } // end contact thread section - viewController.contents = contents; [viewController presentFromViewController:fromViewController]; } diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index 1763380b2..554caac9e 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -322,7 +322,7 @@ protocol CallServiceObserver: class { return peerConnectionClient.setLocalSessionDescription(sessionDescription).then { let offerMessage = OWSCallOfferMessage(callId: call.signalingId, sessionDescription: sessionDescription.sdp) let callMessage = OWSOutgoingCallMessage(thread: call.thread, offerMessage: offerMessage) - return self.messageSender.sendCallMessage(callMessage) + return self.messageSender.sendPromise(message: callMessage) } }.then { guard self.call == call else { @@ -410,7 +410,7 @@ protocol CallServiceObserver: class { Logger.error("\(self.TAG) Sending \(pendingIceUpdateMessages.count) pendingIceUpdateMessages") let callMessage = OWSOutgoingCallMessage(thread: thread, iceUpdateMessages: pendingIceUpdateMessages) - let sendPromise = messageSender.sendCallMessage(callMessage).catch { error in + let sendPromise = messageSender.sendPromise(message: callMessage).catch { error in Logger.error("\(self.TAG) failed to send ice updates in \(#function) with error: \(error)") } sendPromise.retainUntilComplete() @@ -471,7 +471,7 @@ protocol CallServiceObserver: class { let busyMessage = OWSCallBusyMessage(callId: call.signalingId) let callMessage = OWSOutgoingCallMessage(thread: thread, busyMessage: busyMessage) - let sendPromise = messageSender.sendCallMessage(callMessage) + let sendPromise = messageSender.sendPromise(message: callMessage) sendPromise.retainUntilComplete() handleMissedCall(call, thread: thread) @@ -629,10 +629,10 @@ protocol CallServiceObserver: class { let answerMessage = OWSCallAnswerMessage(callId: newCall.signalingId, sessionDescription: negotiatedSessionDescription.sdp) let callAnswerMessage = OWSOutgoingCallMessage(thread: thread, answerMessage: answerMessage) - return self.messageSender.sendCallMessage(callAnswerMessage) + return self.messageSender.sendPromise(message: callAnswerMessage) }.then { guard self.call == newCall else { - throw CallError.obsoleteCall(description: "sendCallMessage() response for obsolete call") + throw CallError.obsoleteCall(description: "sendPromise(message: ) response for obsolete call") } Logger.debug("\(self.TAG) successfully sent callAnswerMessage for: \(newCall.identifiersForLogs)") @@ -745,7 +745,7 @@ protocol CallServiceObserver: class { if self.sendIceUpdatesImmediately { Logger.info("\(self.TAG) in \(#function). Sending immediately.") let callMessage = OWSOutgoingCallMessage(thread: call.thread, iceUpdateMessage: iceUpdateMessage) - let sendPromise = self.messageSender.sendCallMessage(callMessage) + let sendPromise = self.messageSender.sendPromise(message: callMessage) sendPromise.retainUntilComplete() } else { // For outgoing messages, we wait to send ice updates until we're sure client received our call message. @@ -1023,7 +1023,7 @@ protocol CallServiceObserver: class { // If the call hasn't started yet, we don't have a data channel to communicate the hang up. Use Signal Service Message. let hangupMessage = OWSCallHangupMessage(callId: call.signalingId) let callMessage = OWSOutgoingCallMessage(thread: call.thread, hangupMessage: hangupMessage) - let sendPromise = self.messageSender.sendCallMessage(callMessage).then { + let sendPromise = self.messageSender.sendPromise(message: callMessage).then { Logger.debug("\(self.TAG) successfully sent hangup call message to \(call.thread.contactIdentifier())") }.catch { error in OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleLocalHungupCall(), file:#file, function:#function, line:#line) @@ -1636,14 +1636,3 @@ protocol CallServiceObserver: class { self.activeCallTimer = nil } } - -fileprivate extension MessageSender { - /** - * Wrap message sending in a Promise for easier callback chaining. - */ - fileprivate func sendCallMessage(_ message: OWSOutgoingCallMessage) -> Promise { - return Promise { fulfill, reject in - self.send(message, success: fulfill, failure: reject) - } - } -}