From 647b2b37e9d02e5e7b317ea0f15cb572b83a8428 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 12 Nov 2016 12:22:29 -0500 Subject: [PATCH] WIP: WebRTC calling * Ensure NotificationsManager has dependencies Otherwise it's easy to mess up the order of the required dependencies. * move AccountManager into Environment, it's heavy to construct // FREEBIE --- MAINTAINING.md | 30 + Podfile | 4 +- Podfile.lock | 14 +- Signal.xcodeproj/project.pbxproj | 185 ++- .../xcshareddata/xcschemes/Signal.xcscheme | 2 +- Signal/.swiftlint.yml | 5 + Signal/src/AppDelegate.m | 47 +- Signal/src/Models/AccountManager.swift | 75 +- Signal/src/Models/SyncPushTokensJob.swift | 1 + Signal/src/Signal-Bridging-Header.h | 14 + Signal/src/Storyboard/Main.storyboard | 260 +++- .../CallNotificationsAdapter.swift | 34 + .../UserNotificationsAdaptee.swift | 117 ++ Signal/src/UserInterface/Strings.swift | 12 + Signal/src/audio/AppAudioManager.h | 11 +- Signal/src/audio/AppAudioManager.m | 55 +- Signal/src/call/CallService.swift | 929 ++++++++++++++ Signal/src/call/DataChannelMessage.swift | 113 ++ Signal/src/call/NonCallKitCallUIAdaptee.swift | 65 + Signal/src/call/OWSWebRTCDataProtos.pb.h | 305 +++++ Signal/src/call/OWSWebRTCDataProtos.pb.m | 1073 +++++++++++++++++ Signal/src/call/PeerConnectionClient.swift | 313 +++++ Signal/src/call/RecentCallManager.m | 2 +- Signal/src/call/SignalCall.swift | 69 ++ .../call/Speakerbox/CallKitCallManager.swift | 91 ++ .../Speakerbox/CallKitCallUIAdaptee.swift | 58 + .../Speakerbox/CallKitProviderDelegate.swift | 259 ++++ Signal/src/call/TurnServerInfo.swift | 32 + .../call/UserInterface/CallUIAdapter.swift | 119 ++ .../OWSCallNotificationsAdaptee.h | 16 + .../src/call/WebRTCCallMessageHandler.swift | 81 ++ Signal/src/contact/OWSContactsManager.h | 2 + Signal/src/contact/OWSContactsManager.m | 24 +- Signal/src/environment/Environment.h | 13 +- Signal/src/environment/Environment.m | 75 +- Signal/src/environment/NotificationsManager.h | 21 +- Signal/src/environment/NotificationsManager.m | 160 ++- .../src/environment/PropertyListPreferences.h | 3 + Signal/src/environment/Release.m | 3 + Signal/src/network/PushManager.h | 22 +- Signal/src/network/PushManager.m | 127 +- Signal/src/util/Platform.swift | 14 + .../AdvancedSettingsTableViewController.m | 10 +- .../view controllers/CallViewController.swift | 321 +++++ .../CodeVerificationViewController.m | 8 +- .../view controllers/InCallViewController.m | 2 +- .../view controllers/MessagesViewController.m | 40 +- .../view controllers/SignalsViewController.m | 72 +- Signal/src/views/AvatarImageView.swift | 14 + Signal/translations/TRANSLATIONS.md | 19 +- .../translations/en.lproj/Localizable.strings | 12 +- protobuf/Makefile | 15 + protobuf/OWSWebRtcDataProtos.proto | 39 + 53 files changed, 5244 insertions(+), 163 deletions(-) create mode 100644 MAINTAINING.md create mode 100644 Signal/.swiftlint.yml create mode 100644 Signal/src/UserInterface/Notifications/CallNotificationsAdapter.swift create mode 100644 Signal/src/UserInterface/Notifications/UserNotificationsAdaptee.swift create mode 100644 Signal/src/UserInterface/Strings.swift create mode 100644 Signal/src/call/CallService.swift create mode 100644 Signal/src/call/DataChannelMessage.swift create mode 100644 Signal/src/call/NonCallKitCallUIAdaptee.swift create mode 100644 Signal/src/call/OWSWebRTCDataProtos.pb.h create mode 100644 Signal/src/call/OWSWebRTCDataProtos.pb.m create mode 100644 Signal/src/call/PeerConnectionClient.swift create mode 100644 Signal/src/call/SignalCall.swift create mode 100644 Signal/src/call/Speakerbox/CallKitCallManager.swift create mode 100644 Signal/src/call/Speakerbox/CallKitCallUIAdaptee.swift create mode 100644 Signal/src/call/Speakerbox/CallKitProviderDelegate.swift create mode 100644 Signal/src/call/TurnServerInfo.swift create mode 100644 Signal/src/call/UserInterface/CallUIAdapter.swift create mode 100644 Signal/src/call/UserInterface/OWSCallNotificationsAdaptee.h create mode 100644 Signal/src/call/WebRTCCallMessageHandler.swift create mode 100644 Signal/src/util/Platform.swift create mode 100644 Signal/src/view controllers/CallViewController.swift create mode 100644 Signal/src/views/AvatarImageView.swift create mode 100644 protobuf/Makefile create mode 100644 protobuf/OWSWebRtcDataProtos.proto diff --git a/MAINTAINING.md b/MAINTAINING.md new file mode 100644 index 000000000..b02478774 --- /dev/null +++ b/MAINTAINING.md @@ -0,0 +1,30 @@ +Apart from the general `BUILDING.md` there are certain things that have +to be done by Signal-iOS maintainers. + +For transperancy and bus factor, they are outlined here. + +## Dependencies + +Keeping cocoapods based dependencies is easy enough. + +`pod update` + +### WebRTC + +We don't currently have an automated build (cocoapod/carthage) setup for +the WebRTC.framework. Instead, read the WebRTC upstream source and build +setup instructions here: + +https://webrtc.org/native-code/ios/ + +Once you have your build environment set up and the WebRTC source downloaded: + + cd webrtc + # build a fat framework + src/webrtc/build/ios/build_ios_libs.sh + # Put it in our frameworks search path + mv src/webrtc/ios_libs_out/WebRTC.framework ../Signal-iOS/Carthage/Builds + +## Translations + +Read more about translations in [TRANSLATIONS.md](signal/translations/TRANSLATIONS.md) diff --git a/Podfile b/Podfile index 69ef648f8..6265aa059 100644 --- a/Podfile +++ b/Podfile @@ -4,8 +4,8 @@ source 'https://github.com/CocoaPods/Specs.git' target 'Signal' do pod 'SocketRocket', :git => 'https://github.com/facebook/SocketRocket.git' pod 'AxolotlKit', git: 'https://github.com/WhisperSystems/SignalProtocolKit.git' - pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git', branch: 'master' - #pod 'SignalServiceKit', path: '../SignalServiceKit' + #pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git' + pod 'SignalServiceKit', path: '../SignalServiceKit' pod 'OpenSSL' pod 'PastelogKit', '~> 1.3' pod 'FFCircularProgressView', '~> 0.5' diff --git a/Podfile.lock b/Podfile.lock index 8408db4e7..c834b3bf6 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -32,7 +32,7 @@ PODS: - JSQMessagesViewController (7.3.4): - JSQSystemSoundPlayer (~> 2.0.1) - JSQSystemSoundPlayer (2.0.1) - - libPhoneNumber-iOS (0.9.2) + - libPhoneNumber-iOS (0.9.1) - Mantle (2.1.0): - Mantle/extobjc (= 2.1.0) - Mantle/extobjc (2.1.0) @@ -121,7 +121,7 @@ DEPENDENCIES: - OpenSSL - PastelogKit (~> 1.3) - SCWaveformView (~> 1.0) - - SignalServiceKit (from `https://github.com/WhisperSystems/SignalServiceKit.git`, branch `master`) + - SignalServiceKit (from `../SignalServiceKit`) - SocketRocket (from `https://github.com/facebook/SocketRocket.git`) - ZXingObjC @@ -129,8 +129,7 @@ EXTERNAL SOURCES: AxolotlKit: :git: https://github.com/WhisperSystems/SignalProtocolKit.git SignalServiceKit: - :branch: master - :git: https://github.com/WhisperSystems/SignalServiceKit.git + :path: "../SignalServiceKit" SocketRocket: :git: https://github.com/facebook/SocketRocket.git @@ -138,9 +137,6 @@ CHECKOUT OPTIONS: AxolotlKit: :commit: 714f5ebe199ecc999b33c6f97a4bb57e2db90e75 :git: https://github.com/WhisperSystems/SignalProtocolKit.git - SignalServiceKit: - :commit: 7b7b338075f3b4615755fcc1f1fb8a8c67b0bd0a - :git: https://github.com/WhisperSystems/SignalServiceKit.git SocketRocket: :commit: 41b57bb2fc292a814f758441a05243eb38457027 :git: https://github.com/facebook/SocketRocket.git @@ -154,7 +150,7 @@ SPEC CHECKSUMS: HKDFKit: c058305d6f64b84f28c50bd7aa89574625bcb62a JSQMessagesViewController: 39fed975e3c9f8eba7292071e29eeb541d105e66 JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d - libPhoneNumber-iOS: a8bffdec18c37728360f6771fe021302f1e0b497 + libPhoneNumber-iOS: 81ad1e6bfcf46e668636333269afbfe60399a55b Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b OpenSSL: 246ffb948e9d56466727fd318134af35f5aa764e PastelogKit: 7b475be4cf577713506a943dd940bcc0499c8bca @@ -170,6 +166,6 @@ SPEC CHECKSUMS: YapDatabase: b1e43555a34a5298e23a045be96817a5ef0da58f ZXingObjC: bf15b3814f7a105b6d99f47da2333c93a063650a -PODFILE CHECKSUM: cb24c78080551874a45d1a20de4a1bef7427b41f +PODFILE CHECKSUM: 05b247199157a4742765e0ea45fe671dbf48b58e COCOAPODS: 1.0.1 diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index ec89800b4..f47386c18 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -14,10 +14,16 @@ 450873C41D9D5149006B54F2 /* OWSExpirationTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 450873C21D9D5149006B54F2 /* OWSExpirationTimerView.m */; }; 450873C71D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 450873C61D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m */; }; 450873C81D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 450873C61D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m */; }; + 4509E79A1DD653700025A59F /* WebRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4509E7991DD653700025A59F /* WebRTC.framework */; }; + 4509E79C1DD6545B0025A59F /* CallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4509E79B1DD6545B0025A59F /* CallViewController.swift */; }; + 450DF2051E0D74AC003D14BE /* Platform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450DF2041E0D74AC003D14BE /* Platform.swift */; }; + 450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */; }; 4516E3FF1DD2193B00DC4206 /* OWS101ExistingUsersBlockOnIdentityChange.m in Sources */ = {isa = PBXBuildFile; fileRef = 4516E3FE1DD2193B00DC4206 /* OWS101ExistingUsersBlockOnIdentityChange.m */; }; 451764271DE939F300EDB8B9 /* ContactsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451764261DE939F300EDB8B9 /* ContactsPicker.swift */; }; 4517642A1DE939FD00EDB8B9 /* ContactCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 451764281DE939FD00EDB8B9 /* ContactCell.xib */; }; 4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451764291DE939FD00EDB8B9 /* ContactCell.swift */; }; + 451A13B11E13DED2000A50FD /* CallNotificationsAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451A13B01E13DED2000A50FD /* CallNotificationsAdapter.swift */; }; + 451A13B21E13DED2000A50FD /* CallNotificationsAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451A13B01E13DED2000A50FD /* CallNotificationsAdapter.swift */; }; 451DE9F81DC18C9500810E42 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD81EE1DC030E7004C9430 /* AccountManager.swift */; }; 451DE9FD1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451DE9FC1DC1A28200810E42 /* SyncPushTokensJob.swift */; }; 451DE9FE1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451DE9FC1DC1A28200810E42 /* SyncPushTokensJob.swift */; }; @@ -31,6 +37,7 @@ 453D28B71D32BA5F00D523F0 /* OWSDisplayedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B61D32BA5F00D523F0 /* OWSDisplayedMessage.m */; }; 453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */; }; 453D28BB1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */; }; + 45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */; }; 45514DE21DDFA183003EFF90 /* InviteFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45514DE11DDFA183003EFF90 /* InviteFlow.swift */; }; 45666EC61D99483D008FE134 /* OWSAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */; }; 45666EC91D994C0D008FE134 /* OWSGroupAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC81D994C0D008FE134 /* OWSGroupAvatarBuilder.m */; }; @@ -40,18 +47,24 @@ 45666F7B1D9C0533008FE134 /* OWSDatabaseMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666F7A1D9C0533008FE134 /* OWSDatabaseMigration.m */; }; 45666F7E1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666F7D1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m */; }; 456C38961DC7B882007536A7 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 451DE9F11DC1585F00810E42 /* PromiseKit.framework */; }; + 4574A5D61DD6704700C6B692 /* CallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4574A5D51DD6704700C6B692 /* CallService.swift */; }; + 45794E861E00620000066731 /* CallUIAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45794E851E00620000066731 /* CallUIAdapter.swift */; }; 45843D1F1D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; }; 45843D201D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; }; 45843D221D223BA10013E85A /* OWSContactsSearcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D211D223BA10013E85A /* OWSContactsSearcherTest.m */; }; 45855F371D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; }; 45855F381D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; }; 458967111DC117CC00E9DD21 /* AccountManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458967101DC117CC00E9DD21 /* AccountManagerTest.swift */; }; + 458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */; }; + 458DE9D91DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m in Sources */ = {isa = PBXBuildFile; fileRef = 458DE9D81DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m */; }; 458E38311D6682450094BD24 /* OWSQRCodeScanningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38301D6682450094BD24 /* OWSQRCodeScanningViewController.m */; }; 458E38341D66873D0094BD24 /* OWSLinkDeviceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38331D66873D0094BD24 /* OWSLinkDeviceViewController.m */; }; 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */; }; 458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38391D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m */; }; 459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */; }; 459C3F0D1C9B3A1B003ACF51 /* TSMessageAdapterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 459C3F0C1C9B3A1B003ACF51 /* TSMessageAdapterTest.m */; }; + 45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; }; + 45AE48521E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; }; 45B201761DAECBFE00C461E0 /* HighlightableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B201751DAECBFE00C461E0 /* HighlightableLabel.swift */; }; 45BD60821DE9547E00A8F436 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45BD60811DE9547E00A8F436 /* Contacts.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 45BFFFA81D898AF0004A12A7 /* OWSStaleNotificationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 45BFFFA71D898AF0004A12A7 /* OWSStaleNotificationObserver.m */; }; @@ -66,6 +79,8 @@ 45C681C71D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681C21D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m */; }; 45C681C81D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45C681C31D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.xib */; }; 45C681C91D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45C681C31D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.xib */; }; + 45C9DEB81DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C9DEB71DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift */; }; + 45C9DEB91DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C9DEB71DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift */; }; 45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */; }; 45CD81A61DBFF8FC004C9430 /* Registration.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45CD81A51DBFF8FC004C9430 /* Registration.storyboard */; }; 45CD81EF1DC030E7004C9430 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD81EE1DC030E7004C9430 /* AccountManager.swift */; }; @@ -76,10 +91,22 @@ 45DF5DF31DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */; }; 45E1F3A31DEF1DF000852CF1 /* NoSignalContactsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45E1F3A21DEF1DF000852CF1 /* NoSignalContactsView.xib */; }; 45E1F3A51DEF20A100852CF1 /* NoSignalContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E1F3A41DEF20A100852CF1 /* NoSignalContactsView.swift */; }; + 45E2E9201E153B3D00457AA0 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E2E91F1E153B3D00457AA0 /* Strings.swift */; }; 45EB32CF1D7465C900735B2E /* OWSLinkedDevicesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 45EB32CE1D7465C900735B2E /* OWSLinkedDevicesTableViewController.m */; }; 45F2B1941D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 45F2B1931D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m */; }; 45F2B1971D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45F2B1951D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib */; }; 45F2B1981D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45F2B1961D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib */; }; + 45F3AEB61DFDE7900080CE33 /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */; }; + 45F3AEB71DFDE7900080CE33 /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */; }; + 45F659731E1BD99C00444429 /* CallKitCallUIAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F659721E1BD99C00444429 /* CallKitCallUIAdaptee.swift */; }; + 45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */; }; + 45F659831E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */; }; + 45FBC5C01DF8575700E9B410 /* CallKitProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5951DF8575700E9B410 /* CallKitProviderDelegate.swift */; }; + 45FBC5C11DF8575700E9B410 /* CallKitProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5951DF8575700E9B410 /* CallKitProviderDelegate.swift */; }; + 45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */; }; + 45FBC5C91DF8575700E9B410 /* CallKitCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */; }; + 45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */; }; + 45FBC5D21DF8592E00E9B410 /* SignalCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */; }; 4CE0E3771B954546007210CF /* TSAnimatedAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E3761B954546007210CF /* TSAnimatedAdapter.m */; }; 701231B518ECAA4500D456C4 /* EvpMessageDigest.m in Sources */ = {isa = PBXBuildFile; fileRef = 701231B418ECAA4500D456C4 /* EvpMessageDigest.m */; }; 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; }; @@ -557,11 +584,16 @@ 450873C51D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSIncomingMessageCollectionViewCell.h; sourceTree = ""; }; 450873C61D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSIncomingMessageCollectionViewCell.m; sourceTree = ""; }; 450873C91D9D86F4006B54F2 /* OWSExpirableMessageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSExpirableMessageView.h; sourceTree = ""; }; + 4509E7991DD653700025A59F /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = Carthage/Build/iOS/WebRTC.framework; sourceTree = ""; }; + 4509E79B1DD6545B0025A59F /* CallViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallViewController.swift; sourceTree = ""; }; + 450DF2041E0D74AC003D14BE /* Platform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Platform.swift; sourceTree = ""; }; + 450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = UserNotificationsAdaptee.swift; path = UserInterface/Notifications/UserNotificationsAdaptee.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 4516E3FD1DD2193B00DC4206 /* OWS101ExistingUsersBlockOnIdentityChange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWS101ExistingUsersBlockOnIdentityChange.h; path = Migrations/OWS101ExistingUsersBlockOnIdentityChange.h; sourceTree = ""; }; 4516E3FE1DD2193B00DC4206 /* OWS101ExistingUsersBlockOnIdentityChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWS101ExistingUsersBlockOnIdentityChange.m; path = Migrations/OWS101ExistingUsersBlockOnIdentityChange.m; sourceTree = ""; }; 451764261DE939F300EDB8B9 /* ContactsPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsPicker.swift; 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 = ""; }; 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; }; @@ -577,6 +609,7 @@ 453D28B61D32BA5F00D523F0 /* OWSDisplayedMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDisplayedMessage.m; sourceTree = ""; }; 453D28B81D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessagesBubblesSizeCalculator.h; sourceTree = ""; }; 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessagesBubblesSizeCalculator.m; sourceTree = ""; }; + 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataChannelMessage.swift; sourceTree = ""; }; 454B35071D08EED80026D658 /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = translations/mk.lproj/Localizable.strings; sourceTree = ""; }; 45514DE11DDFA183003EFF90 /* InviteFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InviteFlow.swift; sourceTree = ""; }; 45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAvatarBuilder.h; sourceTree = ""; }; @@ -593,6 +626,8 @@ 45666F7A1D9C0533008FE134 /* OWSDatabaseMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDatabaseMigration.m; path = Migrations/OWSDatabaseMigration.m; sourceTree = ""; }; 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 = ""; }; + 4574A5D51DD6704700C6B692 /* CallService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallService.swift; sourceTree = ""; }; + 45794E851E00620000066731 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallUIAdapter.swift; path = UserInterface/CallUIAdapter.swift; sourceTree = ""; }; 45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSearcher.h; sourceTree = ""; }; 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcher.m; sourceTree = ""; }; 45843D211D223BA10013E85A /* OWSContactsSearcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcherTest.m; sourceTree = ""; }; @@ -600,6 +635,9 @@ 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactAvatarBuilder.m; sourceTree = ""; }; 4589670F1DC117CC00E9DD21 /* SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SignalTests-Bridging-Header.h"; sourceTree = ""; }; 458967101DC117CC00E9DD21 /* AccountManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AccountManagerTest.swift; path = Models/AccountManagerTest.swift; sourceTree = ""; }; + 458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerConnectionClient.swift; sourceTree = ""; }; + 458DE9D71DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSWebRTCDataProtos.pb.h; sourceTree = ""; }; + 458DE9D81DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSWebRTCDataProtos.pb.m; sourceTree = ""; }; 458E382F1D6682450094BD24 /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = ""; }; 458E38301D6682450094BD24 /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = ""; }; 458E38321D66873D0094BD24 /* OWSLinkDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkDeviceViewController.h; sourceTree = ""; }; @@ -612,6 +650,7 @@ 4597E94E1D8313C100040CDE /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = translations/sq.lproj/Localizable.strings; sourceTree = ""; }; 4597E94F1D8313CB00040CDE /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = translations/bg.lproj/Localizable.strings; sourceTree = ""; }; 459C3F0C1C9B3A1B003ACF51 /* TSMessageAdapterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSMessageAdapterTest.m; path = "view controllers/Signals/TSMessageAdapters/TSMessageAdapterTest.m"; sourceTree = ""; }; + 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = ""; }; 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = ""; }; 45B201751DAECBFE00C461E0 /* HighlightableLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightableLabel.swift; sourceTree = ""; }; 45BD60811DE9547E00A8F436 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; }; @@ -625,6 +664,7 @@ 45C681C11D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDisplayedMessageCollectionViewCell.h; sourceTree = ""; }; 45C681C21D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDisplayedMessageCollectionViewCell.m; sourceTree = ""; }; 45C681C31D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OWSDisplayedMessageCollectionViewCell.xib; sourceTree = ""; }; + 45C9DEB71DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebRTCCallMessageHandler.swift; sourceTree = ""; }; 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "Signal/src/util/Launch Screen.storyboard"; sourceTree = SOURCE_ROOT; }; 45CD81A51DBFF8FC004C9430 /* Registration.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Registration.storyboard; path = Storyboards/Registration.storyboard; sourceTree = ""; }; 45CD81EE1DC030E7004C9430 /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; @@ -636,12 +676,20 @@ 45E1F3A41DEF20A100852CF1 /* NoSignalContactsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoSignalContactsView.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 = ""; }; 45EB32CD1D7465C900735B2E /* OWSLinkedDevicesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkedDevicesTableViewController.h; sourceTree = ""; }; 45EB32CE1D7465C900735B2E /* OWSLinkedDevicesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkedDevicesTableViewController.m; sourceTree = ""; }; 45F2B1921D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOutgoingMessageCollectionViewCell.h; sourceTree = ""; }; 45F2B1931D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOutgoingMessageCollectionViewCell.m; sourceTree = ""; }; 45F2B1951D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OWSIncomingMessageCollectionViewCell.xib; sourceTree = ""; }; 45F2B1961D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OWSOutgoingMessageCollectionViewCell.xib; sourceTree = ""; }; + 45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarImageView.swift; sourceTree = ""; }; + 45F659721E1BD99C00444429 /* CallKitCallUIAdaptee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitCallUIAdaptee.swift; sourceTree = ""; }; + 45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonCallKitCallUIAdaptee.swift; sourceTree = ""; }; + 45FBC5951DF8575700E9B410 /* CallKitProviderDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitProviderDelegate.swift; sourceTree = ""; }; + 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitCallManager.swift; sourceTree = ""; }; + 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalCall.swift; sourceTree = ""; }; 4CE0E3751B95453C007210CF /* TSAnimatedAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSAnimatedAdapter.h; sourceTree = ""; }; 4CE0E3761B954546007210CF /* TSAnimatedAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSAnimatedAdapter.m; sourceTree = ""; }; 701231B318ECAA4500D456C4 /* EvpMessageDigest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EvpMessageDigest.h; sourceTree = ""; }; @@ -886,7 +934,7 @@ B60C16641988999D00E97A6C /* VersionMigrations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VersionMigrations.m; sourceTree = ""; }; B60EDE031A05A01700D73516 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; B6258B311C29E2E60014138E /* NotificationsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationsManager.h; sourceTree = ""; }; - B6258B321C29E2E60014138E /* NotificationsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationsManager.m; sourceTree = ""; }; + B6258B321C29E2E60014138E /* NotificationsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = NotificationsManager.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; B625CD551ABB589C00E8B23C /* NewMessage.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = NewMessage.aifc; sourceTree = ""; }; B62D53F51A23CCAD009AAF82 /* TSMessageAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSMessageAdapter.h; sourceTree = ""; }; B62D53F61A23CCAD009AAF82 /* TSMessageAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSMessageAdapter.m; sourceTree = ""; }; @@ -1017,8 +1065,8 @@ B6B1013A196D213F007E3930 /* SignalKeyingStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalKeyingStorage.h; sourceTree = ""; }; B6B1013B196D213F007E3930 /* SignalKeyingStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalKeyingStorage.m; sourceTree = ""; }; B6B226961BE4B7D200860F4D /* ContactsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ContactsUI.framework; path = System/Library/Frameworks/ContactsUI.framework; sourceTree = SDKROOT; }; - B6B9ECFA198B31BA00C620D3 /* PushManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PushManager.h; sourceTree = ""; }; - B6B9ECFB198B31BA00C620D3 /* PushManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PushManager.m; sourceTree = ""; }; + B6B9ECFA198B31BA00C620D3 /* PushManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PushManager.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + B6B9ECFB198B31BA00C620D3 /* PushManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PushManager.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; B6BADBE51B88D1AC0086A80D /* LockInteractionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LockInteractionController.h; sourceTree = ""; }; B6BADBE61B88D1AC0086A80D /* LockInteractionController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LockInteractionController.m; sourceTree = ""; }; B6BC3D0C1AA544B100C2907F /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = translations/da.lproj/Localizable.strings; sourceTree = ""; }; @@ -1165,6 +1213,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4509E79A1DD653700025A59F /* WebRTC.framework in Frameworks */, 456C38961DC7B882007536A7 /* PromiseKit.framework in Frameworks */, 4520D8D51D417D8E00123472 /* Photos.framework in Frameworks */, B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */, @@ -1219,6 +1268,37 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 450DF2061E0DD28D003D14BE /* UserInterface */ = { + isa = PBXGroup; + children = ( + 450DF2071E0DD29E003D14BE /* Notifications */, + 76EB052B18170B33006006FC /* Views */, + 76EB04FE18170B33006006FC /* View Controllers */, + 45E2E91F1E153B3D00457AA0 /* Strings.swift */, + ); + name = UserInterface; + sourceTree = ""; + }; + 450DF2071E0DD29E003D14BE /* Notifications */ = { + isa = PBXGroup; + children = ( + 450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */, + ); + name = Notifications; + sourceTree = ""; + }; + 45464DB81DFA03D8001D3FD6 /* Signaling */ = { + isa = PBXGroup; + children = ( + 45C9DEB71DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift */, + 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */, + 458DE9D71DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.h */, + 458DE9D81DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m */, + 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */, + ); + name = Signaling; + sourceTree = ""; + }; 45666F731D9BFDB9008FE134 /* Migrations */ = { isa = PBXGroup; children = ( @@ -1234,6 +1314,18 @@ name = Migrations; sourceTree = ""; }; + 45794E841E0061CF00066731 /* UserInterface */ = { + isa = PBXGroup; + children = ( + 45FBC57A1DF8575700E9B410 /* CallKit */, + 45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */, + 451A13B01E13DED2000A50FD /* CallNotificationsAdapter.swift */, + 45794E851E00620000066731 /* CallUIAdapter.swift */, + 45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */, + ); + name = UserInterface; + sourceTree = ""; + }; 457F3AC01D14A0F700C51351 /* Models */ = { isa = PBXGroup; children = ( @@ -1305,6 +1397,28 @@ name = Jobs; sourceTree = ""; }; + 45F659741E1BDA4300444429 /* Redphone */ = { + isa = PBXGroup; + children = ( + 76EB03FF18170B33006006FC /* RecentCall.h */, + 76EB040018170B33006006FC /* RecentCall.m */, + 76EB040118170B33006006FC /* RecentCallManager.h */, + 76EB040218170B33006006FC /* RecentCallManager.m */, + ); + name = Redphone; + sourceTree = ""; + }; + 45FBC57A1DF8575700E9B410 /* CallKit */ = { + isa = PBXGroup; + children = ( + 45FBC5951DF8575700E9B410 /* CallKitProviderDelegate.swift */, + 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */, + 45F659721E1BD99C00444429 /* CallKitCallUIAdaptee.swift */, + ); + name = CallKit; + path = Speakerbox; + sourceTree = ""; + }; 70B8009F190C529C0042E3F0 /* Products */ = { isa = PBXGroup; children = ( @@ -1355,8 +1469,7 @@ 76EB04C818170B33006006FC /* util */, 45D231751DC7E8C50034FA89 /* Jobs */, 457F3AC01D14A0F700C51351 /* Models */, - 76EB052B18170B33006006FC /* Views */, - 76EB04FE18170B33006006FC /* View Controllers */, + 450DF2061E0DD28D003D14BE /* UserInterface */, 45BFFFA51D898AB8004A12A7 /* Observers */, 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */, ); @@ -1384,10 +1497,12 @@ 76EB03FE18170B33006006FC /* call */ = { isa = PBXGroup; children = ( - 76EB03FF18170B33006006FC /* RecentCall.h */, - 76EB040018170B33006006FC /* RecentCall.m */, - 76EB040118170B33006006FC /* RecentCallManager.h */, - 76EB040218170B33006006FC /* RecentCallManager.m */, + 45F659741E1BDA4300444429 /* Redphone */, + 45794E841E0061CF00066731 /* UserInterface */, + 45464DB81DFA03D8001D3FD6 /* Signaling */, + 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */, + 458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */, + 4574A5D51DD6704700C6B692 /* CallService.swift */, ); path = call; sourceTree = ""; @@ -1796,6 +1911,7 @@ 45666F551D9B2827008FE134 /* OWSScrubbingLogFormatter.m */, 45CD81F01DC03A22004C9430 /* OWSLogger.h */, 45CD81F11DC03A22004C9430 /* OWSLogger.m */, + 450DF2041E0D74AC003D14BE /* Platform.swift */, ); path = util; sourceTree = ""; @@ -1885,6 +2001,7 @@ 4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */, 45E1F3A21DEF1DF000852CF1 /* NoSignalContactsView.xib */, 45E1F3A41DEF20A100852CF1 /* NoSignalContactsView.swift */, + 45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */, ); name = Views; path = views; @@ -2258,6 +2375,7 @@ isa = PBXGroup; children = ( 45BD60811DE9547E00A8F436 /* Contacts.framework */, + 4509E7991DD653700025A59F /* WebRTC.framework */, 451DE9F11DC1585F00810E42 /* PromiseKit.framework */, 4520D8D41D417D8E00123472 /* Photos.framework */, B6B226961BE4B7D200860F4D /* ContactsUI.framework */, @@ -2424,6 +2542,7 @@ FC3196321A08142D0094C78E /* Signals */ = { isa = PBXGroup; children = ( + 4509E79B1DD6545B0025A59F /* CallViewController.swift */, FC3196281A067D8F0094C78E /* MessageComposeTableViewController.h */, FC3196291A067D8F0094C78E /* MessageComposeTableViewController.m */, FCAC963A19FEF9280046DFC5 /* SignalsViewController.h */, @@ -2483,6 +2602,7 @@ buildConfigurationList = D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Signal" */; buildPhases = ( 1460156AE01E0DB0949D61FE /* [CP] Check Pods Manifest.lock */, + 45AE48531E073428004D96C2 /* Swift Lint */, D221A085169C9E5E00537ABF /* Sources */, D221A086169C9E5E00537ABF /* Frameworks */, D221A087169C9E5E00537ABF /* Resources */, @@ -2537,6 +2657,7 @@ D221A088169C9E5E00537ABF = { DevelopmentTeam = U68MSDN6DR; LastSwiftMigration = 0800; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.DataProtection = { enabled = 1; @@ -2608,6 +2729,7 @@ mk, sq, bg, + Base, ); mainGroup = D221A07E169C9E5E00537ABF; productRefGroup = D221A08A169C9E5E00537ABF /* Products */; @@ -2754,6 +2876,7 @@ ); inputPaths = ( "$(SRCROOT)/Carthage/Build/iOS/PromiseKit.framework", + "$(SRCROOT)/Carthage/Build/iOS/WebRTC.framework", ); name = "[Carthage] Copy Frameworks"; outputPaths = ( @@ -2777,6 +2900,20 @@ shellPath = /bin/sh; shellScript = "/usr/local/bin/carthage copy-frameworks\n"; }; + 45AE48531E073428004D96C2 /* Swift Lint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Swift Lint"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n# disabled for now. too many lint errors outside of the scope of this branch\n#(cd Signal && swiftlint)\n# never fail.\nexit 0\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + }; 59C9DBA462715B5C999FFB02 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2848,6 +2985,7 @@ B640BFA81C257843006038B3 /* RPAccountManager.m in Sources */, A5509ECD1A69B1D600ABA4BC /* CountryCodeTableViewCell.m in Sources */, 76EB05F618170B33006006FC /* CallConnectUtil.m in Sources */, + 45C9DEB81DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */, 76EB061218170B33006006FC /* LoggingUtil.m in Sources */, 76EB060E18170B33006006FC /* DecayingSampleEstimator.m in Sources */, 76EB05BA18170B33006006FC /* CommitPacket.m in Sources */, @@ -2855,7 +2993,9 @@ 76EB05FC18170B33006006FC /* CallConnectUtil_Server.m in Sources */, B6DA6B071B8A2F9A00CA6F98 /* AppStoreRating.m in Sources */, 458E38311D6682450094BD24 /* OWSQRCodeScanningViewController.m in Sources */, + 451A13B11E13DED2000A50FD /* CallNotificationsAdapter.swift in Sources */, 76EB062418170B33006006FC /* PriorityQueue.m in Sources */, + 450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */, B6BADBE71B88D1AC0086A80D /* LockInteractionController.m in Sources */, 76EB061A18170B33006006FC /* DiscardingLog.m in Sources */, 45CD81F21DC03A22004C9430 /* OWSLogger.m in Sources */, @@ -2877,10 +3017,12 @@ 76EB05EC18170B33006006FC /* CallState.m in Sources */, 76EB05D218170B33006006FC /* ZrtpInitiator.m in Sources */, 76EB05E018170B33006006FC /* NetworkStream.m in Sources */, + 45794E861E00620000066731 /* CallUIAdapter.swift in Sources */, FCFA64B71A24F6730007FB87 /* UIFont+OWS.m in Sources */, B6B9ECFC198B31BA00C620D3 /* PushManager.m in Sources */, 45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */, 76EB05D618170B33006006FC /* ZrtpResponder.m in Sources */, + 458DE9D91DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m in Sources */, B62D53F71A23CCAD009AAF82 /* TSMessageAdapter.m in Sources */, FCD274EB1A5AFDDB00202277 /* AboutTableViewController.m in Sources */, E197B61618BBEC1A00F073E5 /* StretchFactorController.m in Sources */, @@ -2889,7 +3031,9 @@ 76EB062218170B33006006FC /* CyclicalBuffer.m in Sources */, 76EB063C18170B33006006FC /* NumberUtil.m in Sources */, B6A3EB4B1A423B3800B2236B /* TSPhotoAdapter.m in Sources */, + 4509E79C1DD6545B0025A59F /* CallViewController.swift in Sources */, 76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */, + 45FBC5C01DF8575700E9B410 /* CallKitProviderDelegate.swift in Sources */, 76EB060A18170B33006006FC /* SignalUtil.m in Sources */, E197B61718BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.m in Sources */, 76EB05BC18170B33006006FC /* ConfirmAckPacket.m in Sources */, @@ -2926,7 +3070,9 @@ 76EB05B418170B33006006FC /* HashChain.m in Sources */, 76EB05E418170B33006006FC /* UdpSocket.m in Sources */, 76EB058218170B33006006FC /* Environment.m in Sources */, + 45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */, 76EB064418170B33006006FC /* ThreadManager.m in Sources */, + 450DF2051E0D74AC003D14BE /* Platform.swift in Sources */, 45666F561D9B2827008FE134 /* OWSScrubbingLogFormatter.m in Sources */, 45C681C61D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m in Sources */, E197B61E18BBEC6D00F073E5 /* AudioRouter.m in Sources */, @@ -2959,10 +3105,12 @@ 76EB063218170B33006006FC /* Crc32.m in Sources */, E197B62418BBF5BB00F073E5 /* SoundPlayer.m in Sources */, E197B61018BBEC1A00F073E5 /* EncodedAudioPacket.m in Sources */, + 45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */, 458E38341D66873D0094BD24 /* OWSLinkDeviceViewController.m in Sources */, 76EB063618170B33006006FC /* DataUtil.m in Sources */, E197B60C18BBEC1A00F073E5 /* AudioPacker.m in Sources */, E197B61218BBEC1A00F073E5 /* AudioStretcher.m in Sources */, + 45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */, 76EB05A218170B33006006FC /* IpEndPoint.m in Sources */, E197B61A18BBEC1A00F073E5 /* SpeexCodec.m in Sources */, 76EB05F018170B33006006FC /* PhoneManager.m in Sources */, @@ -2976,9 +3124,12 @@ 76EB061C18170B33006006FC /* ArrayUtil.m in Sources */, FCD274E81A5AFDC900202277 /* AdvancedSettingsTableViewController.m in Sources */, 76EB05C418170B33006006FC /* HandshakePacket.m in Sources */, + 45F3AEB61DFDE7900080CE33 /* AvatarImageView.swift in Sources */, 76EB05AA18170B33006006FC /* SequenceCounter.m in Sources */, 7038632718F70C0700D4A43F /* CryptoTools.m in Sources */, 76EB058C18170B33006006FC /* DnsManager.m in Sources */, + 45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */, + 45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */, B671B2461A93B238002BBD9D /* GroupContactsResult.m in Sources */, B66B9F721AEA6D1100E2E609 /* NotificationSettingsViewController.m in Sources */, 76EB059018170B33006006FC /* IgnoredPacketFailure.m in Sources */, @@ -2998,7 +3149,9 @@ 76EB063818170B33006006FC /* DictionaryUtil.m in Sources */, 76EB05CE18170B33006006FC /* ZrtpHandshakeResult.m in Sources */, 45EB32CF1D7465C900735B2E /* OWSLinkedDevicesTableViewController.m in Sources */, + 45F659731E1BD99C00444429 /* CallKitCallUIAdaptee.swift in Sources */, B63761EE19E1FBE8005735D1 /* HttpRequestUtil.m in Sources */, + 458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */, 451DE9FD1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */, 76EB05B618170B33006006FC /* MasterSecret.m in Sources */, 76EB05F418170B33006006FC /* CallConnectResult.m in Sources */, @@ -3017,6 +3170,7 @@ B68EF9BA1C0B1EBD009C3DCD /* FLAnimatedImage.m in Sources */, B68112EA1A4D9EC400BA82FF /* UIImage+normalizeImage.m in Sources */, B609597C1C2C0FC6004E8797 /* iRate.m in Sources */, + 4574A5D61DD6704700C6B692 /* CallService.swift in Sources */, 76EB05C818170B33006006FC /* HelloPacket.m in Sources */, BFB074C719A5611000F2947C /* FutureUtil.m in Sources */, 45E1F3A51DEF20A100852CF1 /* NoSignalContactsView.swift in Sources */, @@ -3027,6 +3181,7 @@ 76EB059218170B33006006FC /* UnrecognizedRequestFailure.m in Sources */, 76EB05F818170B33006006FC /* CallConnectUtil_Initiator.m in Sources */, B62F5E101C2980B4000D370C /* NSData+ows_StripToken.m in Sources */, + 45E2E9201E153B3D00457AA0 /* Strings.swift in Sources */, 45666F7E1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m in Sources */, B63761E319E1F487005735D1 /* AFHTTPSessionManager+SignalMethods.m in Sources */, 76EB05CC18170B33006006FC /* ShortAuthenticationStringGenerator.m in Sources */, @@ -3107,6 +3262,7 @@ B660F72A1C29988E00687D6E /* HttpRequest.m in Sources */, B660F72B1C29988E00687D6E /* HttpRequestOrResponse.m in Sources */, B660F72C1C29988E00687D6E /* HttpRequestUtil.m in Sources */, + 45F659831E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */, B660F72D1C29988E00687D6E /* HttpResponse.m in Sources */, B660F72E1C29988E00687D6E /* HttpManager.m in Sources */, 458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */, @@ -3145,6 +3301,7 @@ 45C681BD1D305C080050903A /* OWSCallCollectionViewCell.m in Sources */, B660F74A1C29988E00687D6E /* ZrtpHandshakeResult.m in Sources */, B660F74B1C29988E00687D6E /* ZrtpHandshakeSocket.m in Sources */, + 45AE48521E0732D6004D96C2 /* TurnServerInfo.swift in Sources */, B660F74C1C29988E00687D6E /* ZrtpInitiator.m in Sources */, B660F74D1C29988E00687D6E /* ZrtpManager.m in Sources */, B660F74E1C29988E00687D6E /* ZrtpResponder.m in Sources */, @@ -3163,10 +3320,13 @@ B660F7581C29988E00687D6E /* RPAccountManager.m in Sources */, B660F7591C29988E00687D6E /* CallController.m in Sources */, B660F75A1C29988E00687D6E /* CallFailedServerMessage.m in Sources */, + 45FBC5C11DF8575700E9B410 /* CallKitProviderDelegate.swift in Sources */, B660F75B1C29988E00687D6E /* CallProgress.m in Sources */, B660F75C1C29988E00687D6E /* CallState.m in Sources */, B660F75D1C29988E00687D6E /* CallTermination.m in Sources */, + 45FBC5D21DF8592E00E9B410 /* SignalCall.swift in Sources */, B660F75E1C29988E00687D6E /* PhoneManager.m in Sources */, + 451A13B21E13DED2000A50FD /* CallNotificationsAdapter.swift in Sources */, B660F75F1C29988E00687D6E /* CallConnectResult.m in Sources */, B660F7601C29988E00687D6E /* CallConnectUtil.m in Sources */, B660F7611C29988E00687D6E /* CallConnectUtil_Initiator.m in Sources */, @@ -3211,6 +3371,7 @@ B660F7851C29988E00687D6E /* Operation.m in Sources */, B660F7861C29988E00687D6E /* AnonymousTerminator.m in Sources */, B660F7871C29988E00687D6E /* StringUtil.m in Sources */, + 45FBC5C91DF8575700E9B410 /* CallKitCallManager.swift in Sources */, B660F7881C29988E00687D6E /* ThreadManager.m in Sources */, B660F7891C29988E00687D6E /* TimeUtil.m in Sources */, B660F78A1C29988E00687D6E /* UIUtil.m in Sources */, @@ -3227,8 +3388,10 @@ B660F6D61C29868000687D6E /* ConversionsTest.m in Sources */, B660F6C01C29868000687D6E /* RtpPacketTests.m in Sources */, B660F6C71C29868000687D6E /* ShortAuthenticationStringGeneratorTest.m in Sources */, + 45F3AEB71DFDE7900080CE33 /* AvatarImageView.swift in Sources */, B660F6DA1C29868000687D6E /* ExceptionsTest.m in Sources */, B660F6CC1C29868000687D6E /* SecureEndPointTest.m in Sources */, + 45C9DEB91DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */, B660F6BA1C29868000687D6E /* RecentCallTest.m in Sources */, B660F6DB1C29868000687D6E /* FunctionalUtilTest.m in Sources */, B660F6CF1C29868000687D6E /* SessionDescriptorTest.m in Sources */, @@ -3454,6 +3617,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = U68MSDN6DR; FRAMEWORK_SEARCH_PATHS = ( @@ -3495,6 +3659,8 @@ OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.whispersystems.signal; PRODUCT_NAME = Signal; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = "Signal/src/Signal-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -3513,6 +3679,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = U68MSDN6DR; FRAMEWORK_SEARCH_PATHS = ( diff --git a/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme b/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme index 4f3550ae9..af72b3be0 100644 --- a/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme +++ b/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme @@ -28,7 +28,7 @@ buildForAnalyzing = "YES"> diff --git a/Signal/.swiftlint.yml b/Signal/.swiftlint.yml new file mode 100644 index 000000000..17bdf3427 --- /dev/null +++ b/Signal/.swiftlint.yml @@ -0,0 +1,5 @@ +line_length: 200 +disabled_rules: + - file_length + - todo + diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 13a1aecb6..60d007ddb 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -24,6 +24,8 @@ #import #import +@import WebRTC; + NSString *const AppDelegateStoryboardMain = @"Main"; NSString *const AppDelegateStoryboardRegistration = @"Registration"; @@ -123,12 +125,10 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; DDLogWarn(@"The app was launched in an unknown way"); } - OWSAccountManager *accountManager = - [[OWSAccountManager alloc] initWithTextSecureAccountManager:[TSAccountManager sharedInstance] - redPhoneAccountManager:[RPAccountManager sharedInstance]]; + RTCInitializeSSL(); [OWSSyncPushTokensJob runWithPushManager:[PushManager sharedManager] - accountManager:accountManager + accountManager:[Environment getCurrent].accountManager preferences:[Environment preferences]].then(^{ DDLogDebug(@"%@ Successfully ran syncPushTokensJob.", self.tag); }).catch(^(NSError *_Nonnull error) { @@ -149,15 +149,20 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; gesture.numberOfTapsRequired = 8; [self.window addGestureRecognizer:gesture]; }); + RTCInitializeSSL(); }]; return YES; } - (void)setupTSKitEnv { - [TextSecureKitEnv sharedEnv].contactsManager = [Environment getCurrent].contactsManager; + + TextSecureKitEnv *sharedEnv = + [[TextSecureKitEnv alloc] initWithCallMessageHandler:[Environment getCurrent].callMessageHandler + contactsManager:[Environment getCurrent].contactsManager + notificationsManager:[Environment getCurrent].notificationsManager]; + [TextSecureKitEnv setSharedEnv:sharedEnv]; [[TSStorageManager sharedManager] setupDatabase]; - [TextSecureKitEnv sharedEnv].notificationsManager = [[NotificationsManager alloc] init]; OWSMessageSender *messageSender = [[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager @@ -293,6 +298,36 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; } } +/** + * Among other things, this is used by "call back" callkit dialog and calling from native contacts app. + */ +- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler +{ + DDLogWarn(@"%@ called %s with userActivity: %@, but not yet supported.", self.tag, __PRETTY_FUNCTION__, userActivity); + // TODO Something like... + // *phoneNumber = [[[[[[userActivity interaction] intent] contacts] firstObject] personHandle] value] + // thread = blah + // [callservice handleoutgoingCAll:thread] + // + // See Speakerbox Example for intent / NSUserActivity handling. + return NO; +} +//func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { +// guard let handle = userActivity.startCallHandle else { +// print("Could not determine start call handle from user activity: \(userActivity)") +// return false +// } +// +// guard let video = userActivity.video else { +// print("Could not determine video from user activity: \(userActivity)") +// return false +// } +// +// callManager.startCall(handle: handle, video: video) +// return true +//} + + /** * Screen protection obscures the app screen shown in the app switcher. */ diff --git a/Signal/src/Models/AccountManager.swift b/Signal/src/Models/AccountManager.swift index 17d13facb..a568036f1 100644 --- a/Signal/src/Models/AccountManager.swift +++ b/Signal/src/Models/AccountManager.swift @@ -4,17 +4,24 @@ import Foundation import PromiseKit -@objc(OWSAccountManager) +/** + * Signal is actually two services - textSecure for messages and red phone (for calls). + * AccountManager delegates to both. + */ class AccountManager : NSObject { let TAG = "[AccountManager]" let textSecureAccountManager: TSAccountManager + let networkManager: TSNetworkManager let redPhoneAccountManager: RPAccountManager required init(textSecureAccountManager:TSAccountManager, redPhoneAccountManager:RPAccountManager) { + self.networkManager = textSecureAccountManager.networkManager self.textSecureAccountManager = textSecureAccountManager self.redPhoneAccountManager = redPhoneAccountManager } + // MARK: registration + @objc func register(verificationCode: String) -> AnyPromise { return AnyPromise(register(verificationCode: verificationCode)); } @@ -44,6 +51,31 @@ class AccountManager : NSObject { } } + private func registerForTextSecure(verificationCode: String) -> Promise { + return Promise { fulfill, reject in + self.textSecureAccountManager.verifyAccount(withCode:verificationCode, + success:fulfill, + failure:reject) + } + } + + private func fetchRedPhoneToken() -> Promise { + return Promise { fulfill, reject in + self.textSecureAccountManager.obtainRPRegistrationToken(success:fulfill, + failure:reject) + } + } + + private func registerForRedPhone(tsToken: String) -> Promise { + return Promise { fulfill, reject in + self.redPhoneAccountManager.register(withTsToken:tsToken, + success:fulfill, + failure:reject) + } + } + + // MARK: Push Tokens + func updatePushTokens(pushToken: String, voipToken: String) -> Promise { return firstly { return self.updateTextSecurePushTokens(pushToken: pushToken, voipToken: voipToken) @@ -54,6 +86,7 @@ class AccountManager : NSObject { return self.updateRedPhonePushTokens(pushToken:pushToken, voipToken:voipToken) }.then { Logger.info("\(self.TAG) Successfully updated red phone push tokens.") + // TODO code cleanup - convert to `return Promise(value: nil)` and test return Promise { fulfill, reject in fulfill(); } @@ -78,27 +111,29 @@ class AccountManager : NSObject { } } - private func registerForTextSecure(verificationCode: String) -> Promise { + // MARK: Turn Server + + func getTurnServerInfo() -> Promise { return Promise { fulfill, reject in - self.textSecureAccountManager.verifyAccount(withCode:verificationCode, - success:fulfill, - failure:reject) + self.networkManager.makeRequest(TurnServerInfoRequest(), + success: { (task: URLSessionDataTask, responseObject: Any?) in + guard responseObject != nil else { + return reject(OWSErrorMakeUnableToProcessServerResponseError()) + } + + if let responseDictionary = responseObject as? [String: AnyObject] { + if let turnServerInfo = TurnServerInfo(attributes:responseDictionary) { + Logger.debug("\(self.TAG) got valid turnserver info") + return fulfill(turnServerInfo) + } + Logger.error("\(self.TAG) unexpeted server response:\(responseDictionary)") + } + return reject(OWSErrorMakeUnableToProcessServerResponseError()) + }, + failure: { (task: URLSessionDataTask, error: Error) in + return reject(error) + }) } } - private func fetchRedPhoneToken() -> Promise { - return Promise { fulfill, reject in - self.textSecureAccountManager.obtainRPRegistrationToken(success:fulfill, - failure:reject) - - } - } - - private func registerForRedPhone(tsToken: String) -> Promise { - return Promise { fulfill, reject in - self.redPhoneAccountManager.register(withTsToken:tsToken, - success:fulfill, - failure:reject) - } - } } diff --git a/Signal/src/Models/SyncPushTokensJob.swift b/Signal/src/Models/SyncPushTokensJob.swift index 103f3189f..0dafe2b17 100644 --- a/Signal/src/Models/SyncPushTokensJob.swift +++ b/Signal/src/Models/SyncPushTokensJob.swift @@ -79,6 +79,7 @@ class SyncPushTokensJob : NSObject { self.preferences.setVoipToken(voipToken); } + // TODO code cleanup: convert to `return Promise(value: nil)` and test. return Promise { fulfill, reject in fulfill(); } } } diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index b6504d326..3a03a4fce 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -3,10 +3,14 @@ // #import +#import "AppAudioManager.h" #import "Environment.h" +#import "NotificationsManager.h" +#import "OWSCallNotificationsAdaptee.h" #import "OWSContactAvatarBuilder.h" #import "OWSContactsManager.h" #import "OWSLogger.h" +#import "OWSWebRTCDataProtos.pb.h" #import "PhoneNumber.h" #import "PropertyListPreferences.h" #import "PushManager.h" @@ -19,12 +23,22 @@ #import #import #import +#import +#import +#import +#import +#import +#import #import #import #import #import +#import #import +#import +#import #import +#import #import #import #import diff --git a/Signal/src/Storyboard/Main.storyboard b/Signal/src/Storyboard/Main.storyboard index 6bf67f4ac..d555e5b30 100644 --- a/Signal/src/Storyboard/Main.storyboard +++ b/Signal/src/Storyboard/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -92,6 +92,7 @@ + @@ -121,12 +122,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -323,7 +549,7 @@ - + @@ -601,7 +827,7 @@ - + @@ -921,19 +1147,19 @@ diff --git a/Signal/src/UserInterface/Notifications/CallNotificationsAdapter.swift b/Signal/src/UserInterface/Notifications/CallNotificationsAdapter.swift new file mode 100644 index 000000000..5f0badf3d --- /dev/null +++ b/Signal/src/UserInterface/Notifications/CallNotificationsAdapter.swift @@ -0,0 +1,34 @@ +// Created by Michael Kirk on 12/28/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation + +@objc(OWSCallNotificationsAdapter) +class CallNotificationsAdapter: NSObject { + + let TAG = "[CallNotificationsAdapter]" + let adaptee: OWSCallNotificationsAdaptee + + override init() { + // TODO We can't mix UINotification (NotificationManager) with the UNNotifications + // Because registering message categories in one, clobbers the notifications in the other. + // We have to first port *all* the existing UINotifications to UNNotifications + // which is a good thing to do, but in trying to limit the scope of changes that's been + // left out for now. +// if #available(iOS 10.0, *) { +// adaptee = UserNotificationsAdaptee() +// } else { + adaptee = NotificationsManager() +// } + } + + func presentIncomingCall(_ call: SignalCall, callerName: String) { + Logger.debug("\(TAG) in \(#function)") + adaptee.presentIncomingCall(call, callerName: callerName) + } + + func presentMissedCall(_ call: SignalCall, callerName: String) { + Logger.debug("\(TAG) in \(#function)") + adaptee.presentMissedCall(call, callerName: callerName) + } +} diff --git a/Signal/src/UserInterface/Notifications/UserNotificationsAdaptee.swift b/Signal/src/UserInterface/Notifications/UserNotificationsAdaptee.swift new file mode 100644 index 000000000..62aca5054 --- /dev/null +++ b/Signal/src/UserInterface/Notifications/UserNotificationsAdaptee.swift @@ -0,0 +1,117 @@ +// Created by Michael Kirk on 12/23/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +/** + * TODO This is currently unused code. I started implenting new notifications as UserNotifications rather than the deprecated + * LocalNotifications before I realized we can't mix and match. Registering notifications for one clobbers the other. + * So, for now iOS10 continues to use LocalNotifications until we can port all the NotificationsManager stuff here. + */ +import Foundation +import UserNotifications + +@available(iOS 10.0, *) +struct AppNotifications { + enum Category { + case missedCall + + // Don't forget to update this! We use it to register categories. + static let allValues = [ missedCall ] + } + + enum Action { + case callBack + } + + static var allCategories: Set { + let categories = Category.allValues.map { category($0) } + return Set(categories) + } + + static func category(_ type: Category) -> UNNotificationCategory { + switch type { + case .missedCall: + return UNNotificationCategory(identifier: "org.whispersystems.signal.AppNotifications.Category.missedCall", + actions: [ action(.callBack) ], + intentIdentifiers: [], + options: []) + } + } + + static func action(_ type: Action) -> UNNotificationAction { + switch type { + case .callBack: + return UNNotificationAction(identifier: "org.whispersystems.signal.AppNotifications.Action.callBack", + title: Strings.Calls.callBackButtonTitle, + options: .authenticationRequired) + } + } +} + +@available(iOS 10.0, *) +class UserNotificationsAdaptee: NSObject, OWSCallNotificationsAdaptee, UNUserNotificationCenterDelegate { + let TAG = "[UserNotificationsAdaptee]" + + private let center: UNUserNotificationCenter + + var previewType: NotificationType { + return Environment.getCurrent().preferences.notificationPreviewType() + } + + override init() { + self.center = UNUserNotificationCenter.current() + super.init() + + center.delegate = self + + // FIXME TODO only do this after user has registered. + // maybe the PushManager needs a reference to the NotificationsAdapter. + requestAuthorization() + + center.setNotificationCategories(AppNotifications.allCategories) + } + + func requestAuthorization() { + center.requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in + if granted { + Logger.debug("\(self.TAG) \(#function) succeeded.") + } else if error != nil { + Logger.error("\(self.TAG) \(#function) failed with error: \(error!)") + } else { + Logger.error("\(self.TAG) \(#function) failed without error.") + } + } + } + + // MARK: - OWSCallNotificationsAdaptee + + 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. + } + + public func presentMissedCall(_ 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 Strings.Calls.missedCallNotificationBody + case .nameNoPreview, .namePreview: + let format = Strings.Calls.missedCallNotificationBodyWithCallerName + return String(format: format, callerName) + }}() + + content.body = notificationBody + content.sound = UNNotificationSound.default() + content.categoryIdentifier = AppNotifications.category(.missedCall).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 new file mode 100644 index 000000000..89bcc532f --- /dev/null +++ b/Signal/src/UserInterface/Strings.swift @@ -0,0 +1,12 @@ +// Created by Michael Kirk on 12/29/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation + +@objc class Strings: NSObject { + @objc class Calls: NSObject { + static let callBackButtonTitle = NSLocalizedString("CALLBACK_BUTTON_TITLE", comment: "notification action") + static let missedCallNotificationBody = NSLocalizedString("MISSED_CALL", comment: "notification title") + static let missedCallNotificationBodyWithCallerName = NSLocalizedString("MSGVIEW_MISSED_CALL", comment: "notification title. Embeds {{Caller's Name}}") + } +} diff --git a/Signal/src/audio/AppAudioManager.h b/Signal/src/audio/AppAudioManager.h index e3512da73..64ee64f58 100644 --- a/Signal/src/audio/AppAudioManager.h +++ b/Signal/src/audio/AppAudioManager.h @@ -14,6 +14,8 @@ @import AVFoundation; +NS_ASSUME_NONNULL_BEGIN + @interface AppAudioManager : NSObject enum AudioProfile { @@ -31,7 +33,9 @@ enum AudioProfile { - (void)respondToTerminationType:(enum CallTerminationType)terminationType; - (BOOL)toggleSpeakerPhone; -- (void)cancellAllAudio; +- (void)toggleSpeakerPhoneIsEnabled:(BOOL)enabled NS_SWIFT_NAME(toggleSpeakerPhone(isEnabled:)); + +- (void)cancelAllAudio; - (void)requestRequiredPermissionsIfNeededWithCompletion:(PermissionBlock)permissionBlock incoming:(BOOL)isIncoming; - (BOOL)requestRecordingPrivilege; @@ -42,4 +46,9 @@ enum AudioProfile { - (void)didCompleteSoundInstanceOfType:(SoundInstanceType)instanceType; +- (void)handleInboundRing; +- (void)setDefaultAudioProfile; + @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/audio/AppAudioManager.m b/Signal/src/audio/AppAudioManager.m index d901e7616..abc1bba66 100644 --- a/Signal/src/audio/AppAudioManager.m +++ b/Signal/src/audio/AppAudioManager.m @@ -3,6 +3,7 @@ #import "AudioRouter.h" #import "SoundBoard.h" +NS_ASSUME_NONNULL_BEGIN #define DEFAULT_CATEGORY AVAudioSessionCategorySoloAmbient #define RECORDING_CATEGORY AVAudioSessionCategoryPlayAndRecord @@ -34,8 +35,8 @@ AppAudioManager *sharedAppAudioManager; #pragma mark AudioState Management - (void)setAudioProfile:(enum AudioProfile)profile { - [self updateAudioRouter]; _audioProfile = profile; + [self updateAudioRouter]; } - (void)updateAudioRouter { @@ -55,17 +56,6 @@ AppAudioManager *sharedAppAudioManager; } } - -- (void)overrideAudioProfile { - isSpeakerphoneActive = YES; - [self updateAudioRouter]; -} - -- (void)resetOverride { - isSpeakerphoneActive = NO; - [self updateAudioRouter]; -} - - (enum AudioProfile)getCurrentAudioProfile { return (isSpeakerphoneActive) ? AudioProfile_ExternalSpeaker : _audioProfile; } @@ -110,26 +100,35 @@ AppAudioManager *sharedAppAudioManager; } - (void)handleInboundRing { + [self setAudioProfile:AudioProfile_ExternalSpeaker]; [_soundPlayer playSound:[SoundBoard instanceOfInboundRingtone]]; } - (void)handleOutboundRing { - [self setAudioProfile:AudioProfile_Default]; + [self setDefaultAudioProfile]; [_soundPlayer playSound:[SoundBoard instanceOfOutboundRingtone]]; } - (void)handleSecuring { [_soundPlayer stopAllAudio]; - [self setAudioProfile:AudioProfile_Default]; + [self setDefaultAudioProfile]; [_soundPlayer playSound:[SoundBoard instanceOfHandshakeSound]]; } - (void)handleCallEstablished { [_soundPlayer stopAllAudio]; - [self setAudioProfile:AudioProfile_Default]; + [self setDefaultAudioProfile]; [_soundPlayer playSound:[SoundBoard instanceOfCompletedSound]]; } +/** + * Route traffic through internal speaker, unless speakerphone is enabled. + */ +- (void)setDefaultAudioProfile +{ + [self setAudioProfile:AudioProfile_Default]; +} + - (BOOL)toggleSpeakerPhone { isSpeakerphoneActive = !isSpeakerphoneActive; [self updateAudioRouter]; @@ -137,9 +136,17 @@ AppAudioManager *sharedAppAudioManager; return isSpeakerphoneActive; } +- (void)toggleSpeakerPhoneIsEnabled:(BOOL)enabled +{ + DDLogInfo(@"%@ Toggled speaker phone: %@", self.tag, enabled ? @"ON" : @"OFF"); + isSpeakerphoneActive = enabled; + [self updateAudioRouter]; +} + #pragma mark Audio Control -- (void)cancellAllAudio { +- (void)cancelAllAudio +{ [_soundPlayer stopAllAudio]; } @@ -196,7 +203,8 @@ AppAudioManager *sharedAppAudioManager; return (nil != e); } -- (void)awake { +- (void)awake +{ [_soundPlayer awake]; } @@ -209,5 +217,18 @@ AppAudioManager *sharedAppAudioManager; } } +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift new file mode 100644 index 000000000..42be15bd6 --- /dev/null +++ b/Signal/src/call/CallService.swift @@ -0,0 +1,929 @@ +// Created by Michael Kirk on 11/11/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation +import PromiseKit +import WebRTC + +/** + * ## Call Setup (Signaling) Flow + * + * ## Key + * - SS: Message sent via Signal Service + * - DC: Message sent via WebRTC Data Channel + * + * | Caller | Callee | + * +----------------------------+-------------------------+ + * handleOutgoingCall --[SS.CallOffer]--> + * and start storing ICE updates + * + * Received call offer + * Send call answer + * <--[SS.CallAnswer]-- + * Start sending ICE updates immediately + * <--[SS.ICEUpdates]-- + * + * Received CallAnswer, + * so send any stored ice updates + * --[SS.ICEUpdates]--> + * + * Once compatible ICE updates have been exchanged... + * (ICE Connected) + * + * Show remote ringing UI + * Connect to offered Data Channel + * Show incoming call UI. + * + * Answers Call + * send connected message + * <--[DC.ConnectedMesage]-- + * Received connected message + * Show Call is connected. + */ + +enum CallError: Error { + case providerReset + case assertionError(description: String) + case disconnected + case externalError(underlyingError: Error) + case timeout(description: String) +} + +// FIXME TODO do we need to timeout? +fileprivate let timeoutSeconds = 60 + +@objc class CallService: NSObject, RTCDataChannelDelegate, RTCPeerConnectionDelegate { + + // MARK: - Properties + + let TAG = "[CallService]" + + // MARK: Dependencies + + let accountManager: AccountManager + let messageSender: MessageSender + var callUIAdapter: CallUIAdapter! + + // MARK: Class + + static let fallbackIceServer = RTCIceServer(urlStrings: ["stun:stun1.l.google.com:19302"]) + + // Synchronize call signaling on the callSignalingQueue to make sure any appropriate requisite state is set. + static let signalingQueue = DispatchQueue(label: "CallServiceSignalingQueue") + + // MARK: Ivars + + var peerConnectionClient: PeerConnectionClient? + // TODO move thread into SignalCall? Or refactor messageSender to take SignalRecipient + var thread: TSContactThread? + var call: SignalCall? + var sendIceUpdatesImmediately = true + var pendingIceUpdateMessages = [OWSCallIceUpdateMessage]() + var incomingCallPromise: Promise? + + // Used to coordinate promises across delegate methods + var fulfillCallConnectedPromise: (()->())? + + required init(accountManager: AccountManager, contactsManager: OWSContactsManager, messageSender: MessageSender, notificationsAdapter: CallNotificationsAdapter) { + self.accountManager = accountManager + self.messageSender = messageSender + + super.init() + + self.callUIAdapter = CallUIAdapter(callService: self, contactsManager: contactsManager, notificationsAdapter: notificationsAdapter) + } + + // MARK: - Class Methods + + // MARK: Notifications + + // Wrapping these class constants in a method to make it accessible to objc + class func callServiceActiveCallNotificationName() -> String { + return "CallServiceActiveCallNotification" + } + + // MARK: - Service Actions + + // Unless otherwise documented, these `handleXXX` methods expect to be called on the SignalingQueue to coordinate + // state across calls. + + /** + * Initiate an outgoing call. + */ + public func handleOutgoingCall(_ call: SignalCall) -> Promise { + assertOnSignalingQueue() + + self.call = call + + let thread = TSContactThread.getOrCreateThread(contactId: call.remotePhoneNumber) + self.thread = thread + + sendIceUpdatesImmediately = false + pendingIceUpdateMessages = [] + + let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(), withCallNumber: call.remotePhoneNumber, callType: RPRecentCallTypeOutgoing, in: thread) + callRecord.save() + + guard self.peerConnectionClient == nil else { + let errorDescription = "\(TAG) peerconnection was unexpectedly already set." + Logger.error(errorDescription) + call.state = .localFailure + return Promise(error: CallError.assertionError(description: errorDescription)) + } + + return getIceServers().then(on: CallService.signalingQueue) { iceServers -> Promise in + Logger.debug("\(self.TAG) got ice servers:\(iceServers)") + let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, peerConnectionDelegate: self) + self.peerConnectionClient = peerConnectionClient + + // When calling, it's our responsibility to create the DataChannel. Receivers will not have to do this explicitly. + self.peerConnectionClient!.createSignalingDataChannel(delegate: self) + + return self.peerConnectionClient!.createOffer() + }.then(on: CallService.signalingQueue) { (sessionDescription: HardenedRTCSessionDescription) -> Promise in + return self.peerConnectionClient!.setLocalSessionDescription(sessionDescription).then(on: CallService.signalingQueue) { + let offerMessage = OWSCallOfferMessage(callId: call.signalingId, sessionDescription: sessionDescription.sdp) + let callMessage = OWSOutgoingCallMessage(thread: thread, offerMessage: offerMessage) + return self.sendMessage(callMessage) + } + }.catch(on: CallService.signalingQueue) { error in + Logger.error("\(self.TAG) placing call failed with error: \(error)") + + if let callError = error as? CallError { + self.handleFailedCall(error: callError) + } else { + let externalError = CallError.externalError(underlyingError: error) + self.handleFailedCall(error: externalError) + } + } + } + + /** + * Called by the call initiator after receiving a CallAnswer from the callee. + */ + public func handleReceivedAnswer(thread: TSContactThread, callId: UInt64, sessionDescription: String) { + Logger.debug("\(TAG) received call answer for call: \(callId) thread: \(thread)") + assertOnSignalingQueue() + + guard let call = self.call else { + handleFailedCall(error: .assertionError(description:"call was unexpectedly nil in \(#function)")) + return + } + + guard call.signalingId == callId else { + let description: String = "received answer for call: \(callId) but current call has id: \(call.signalingId)" + handleFailedCall(error: .assertionError(description: description)) + return + } + + // Now that we know the recipient trusts our identity, we no longer need to enqueue ICE updates. + self.sendIceUpdatesImmediately = true + + if pendingIceUpdateMessages.count > 0 { + let callMessage = OWSOutgoingCallMessage(thread: thread, iceUpdateMessages: pendingIceUpdateMessages) + _ = sendMessage(callMessage).catch { error in + Logger.error("\(self.TAG) failed to send ice updates in \(#function) with error: \(error)") + } + } + + guard let peerConnectionClient = self.peerConnectionClient else { + handleFailedCall(error: CallError.assertionError(description: "peerConnectionClient was unexpectedly nil in \(#function)")) + return + } + + let sessionDescription = RTCSessionDescription(type: .answer, sdp: sessionDescription) + _ = peerConnectionClient.setRemoteSessionDescription(sessionDescription).then { + Logger.debug("\(self.TAG) successfully set remote description") + }.catch(on: CallService.signalingQueue) { error in + if let callError = error as? CallError { + self.handleFailedCall(error: callError) + } else { + let externalError = CallError.externalError(underlyingError: error) + self.handleFailedCall(error: externalError) + } + } + } + + private func handleLocalBusyCall(_ call: SignalCall, thread: TSContactThread) { + Logger.debug("\(TAG) \(#function) for call: \(call) thread: \(thread)") + assertOnSignalingQueue() + + let busyMessage = OWSCallBusyMessage(callId: call.signalingId) + let callMessage = OWSOutgoingCallMessage(thread: thread, busyMessage: busyMessage) + _ = sendMessage(callMessage) + + handleMissedCall(call, thread: thread) + } + + public func handleMissedCall(_ call: SignalCall, thread: TSContactThread) { + // Insert missed call record + let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(), + withCallNumber: thread.contactIdentifier(), + callType: RPRecentCallTypeMissed, + in: thread) + callRecord.save() + + self.callUIAdapter.reportMissedCall(call) + } + + public func handleRemoteBusy(thread: TSContactThread) { + Logger.debug("\(TAG) \(#function) for thread: \(thread)") + assertOnSignalingQueue() + + guard let call = self.call else { + handleFailedCall(error: .assertionError(description: "call unexpectedly nil in \(#function)")) + return + } + + call.state = .remoteBusy + terminateCall() + } + + private func isBusy() -> Bool { + // TODO CallManager adapter? + return false + } + + /** + * Received an incoming call offer. We still have to complete setting up the Signaling channel before we notify + * the user of an incoming call. + */ + public func handleReceivedOffer(thread: TSContactThread, callId: UInt64, sessionDescription callerSessionDescription: String) { + assertOnSignalingQueue() + + Logger.verbose("\(TAG) receivedCallOffer for thread:\(thread)") + let newCall = SignalCall.incomingCall(localId: UUID(), remotePhoneNumber: thread.contactIdentifier(), signalingId: callId) + + guard 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)") + + handleLocalBusyCall(newCall, thread: thread) + return + } + + self.thread = thread + call = newCall + + let backgroundTask = UIApplication.shared.beginBackgroundTask { + let timeout = CallError.timeout(description: "background task time ran out before call connected.") + CallService.signalingQueue.async { + self.handleFailedCall(error: timeout) + } + } + + incomingCallPromise = firstly { + return getIceServers() + }.then(on: CallService.signalingQueue) { (iceServers: [RTCIceServer]) -> Promise in + // FIXME for first time call recipients I think we'll see mic/camera permission requests here, + // even though, from the users perspective, no incoming call is yet visible. + self.peerConnectionClient = PeerConnectionClient(iceServers: iceServers, peerConnectionDelegate: self) + + let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription) + let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) + + // Find a sessionDescription compatible with my constraints and the remote sessionDescription + return self.peerConnectionClient!.negotiateSessionDescription(remoteDescription: offerSessionDescription, constraints: constraints) + }.then(on: CallService.signalingQueue) { (negotiatedSessionDescription: HardenedRTCSessionDescription) in + // TODO? WebRtcCallService.this.lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING); + Logger.debug("\(self.TAG) set the remote description") + + let answerMessage = OWSCallAnswerMessage(callId: newCall.signalingId, sessionDescription: negotiatedSessionDescription.sdp) + let callAnswerMessage = OWSOutgoingCallMessage(thread: thread, answerMessage: answerMessage) + + return self.sendMessage(callAnswerMessage) + }.then(on: CallService.signalingQueue) { + Logger.debug("\(self.TAG) successfully sent callAnswerMessage") + + let (promise, fulfill, _) = Promise.pending() + + let timeout: Promise = after(interval: TimeInterval(timeoutSeconds)).then { () -> Void in + // rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled + throw CallError.timeout(description: "timed out waiting for call to connect") + } + + // This will be fulfilled (potentially) by the RTCDataChannel delegate method + self.fulfillCallConnectedPromise = fulfill + + return race(promise, timeout) + }.catch(on: CallService.signalingQueue) { error in + if let callError = error as? CallError { + self.handleFailedCall(error: callError) + } else { + let externalError = CallError.externalError(underlyingError: error) + self.handleFailedCall(error: externalError) + } + }.always { + Logger.debug("\(self.TAG) ending background task awaiting inbound call connection") + UIApplication.shared.endBackgroundTask(backgroundTask) + } + } + + public func handleCallBack(recipientId: String) { + // TODO #function is called from objc, how to access swift defiend dispatch queue (OS_dispatch_queue) + //assertOnSignalingQueue() + + guard self.call == nil else { + Logger.error("\(TAG) unexpectedly found an existing call when trying to call back: \(recipientId)") + return + } + + // Because we may not be on signalingQueue (because this method is called from Objc which doesn't have + // access to signalingQueue (that I can find). FIXME? + type(of: self).signalingQueue.async { + let call = self.callUIAdapter.startOutgoingCall(handle: recipientId) + self.callUIAdapter.showCall(call) + } + } + + public func handleRemoteAddedIceCandidate(thread: TSContactThread, callId: UInt64, sdp: String, lineIndex: Int32, mid: String) { + assertOnSignalingQueue() + Logger.debug("\(TAG) called \(#function)") + + guard self.thread != nil else { + handleFailedCall(error: .assertionError(description: "ignoring remote ice update for thread: \(thread.uniqueId) since there is no current thread. TODO: Signaling messages out of order?")) + return + } + + guard thread.contactIdentifier() == self.thread!.contactIdentifier() else { + handleFailedCall(error: .assertionError(description: "ignoring remote ice update for thread: \(thread.uniqueId) since the current call is for thread: \(self.thread!.uniqueId)")) + return + } + + guard let call = self.call else { + handleFailedCall(error: .assertionError(description: "ignoring remote ice update for callId: \(callId), since there is no current call.")) + return + } + + guard call.signalingId == callId else { + handleFailedCall(error: .assertionError(description: "ignoring remote ice update for call: \(callId) since the current call is: \(call.signalingId)")) + return + } + + guard let peerConnectionClient = self.peerConnectionClient else { + handleFailedCall(error: .assertionError(description: "ignoring remote ice update for thread: \(thread) since the current call hasn't initialized it's peerConnectionClient")) + return + } + + peerConnectionClient.addIceCandidate(RTCIceCandidate(sdp: sdp, sdpMLineIndex: lineIndex, sdpMid: mid)) + } + + private func handleLocalAddedIceCandidate(_ iceCandidate: RTCIceCandidate) { + assertOnSignalingQueue() + + guard let call = self.call else { + handleFailedCall(error: .assertionError(description: "ignoring local ice candidate, since there is no current call.")) + return + } + + guard call.state != .idle else { + handleFailedCall(error: .assertionError(description: "ignoring local ice candidate, since call is now idle.")) + return + } + + guard let thread = self.thread else { + handleFailedCall(error: .assertionError(description: "ignoring local ice candidate, because there was no current TSContactThread.")) + return + } + + let iceUpdateMessage = OWSCallIceUpdateMessage(callId: call.signalingId, sdp: iceCandidate.sdp, sdpMLineIndex: iceCandidate.sdpMLineIndex, sdpMid: iceCandidate.sdpMid) + + if self.sendIceUpdatesImmediately { + let callMessage = OWSOutgoingCallMessage(thread: thread, iceUpdateMessage: iceUpdateMessage) + _ = sendMessage(callMessage) + } else { + // For outgoing messages, we wait to send ice updates until we're sure client received our call message. + // e.g. if the client has blocked our message due to an identity change, we'd otherwise + // bombard them with a bunch *more* undecipherable messages. + Logger.debug("\(TAG) enqueuing iceUpdate until we receive call answer") + self.pendingIceUpdateMessages.append(iceUpdateMessage) + return + } + } + + private func handleIceConnected() { + assertOnSignalingQueue() + + Logger.debug("\(TAG) in \(#function)") + + guard let call = self.call else { + handleFailedCall(error: .assertionError(description:"\(TAG) ignoring \(#function) since there is no current call.")) + return + } + + guard let thread = self.thread else { + handleFailedCall(error: .assertionError(description:"\(TAG) ignoring \(#function) since there is no current thread.")) + return + } + + guard let peerConnectionClient = self.peerConnectionClient else { + handleFailedCall(error: .assertionError(description:"\(TAG) ignoring \(#function) since there is no current peerConnectionClient.")) + return + } + + switch call.state { + case .dialing: + call.state = .remoteRinging + case .answering: + call.state = .localRinging + self.callUIAdapter.reportIncomingCall(call, thread: thread, audioManager: peerConnectionClient) + self.fulfillCallConnectedPromise?() + case .remoteRinging: + Logger.info("\(TAG) call alreading ringing. Ignoring \(#function)") + default: + Logger.debug("\(TAG) unexpected call state for \(#function): \(call.state)") + } + } + + public func handleRemoteHangup(thread: TSContactThread) { + Logger.debug("\(TAG) in \(#function)") + assertOnSignalingQueue() + + guard thread.contactIdentifier() == self.thread?.contactIdentifier() else { + // This can safely be ignored. + // We don't want to fail the current call because an old call was slow to send us the hangup message. + Logger.warn("\(TAG) ignoring hangup for thread:\(thread) which is not the current thread: \(self.thread)") + return + } + + guard let call = self.call else { + handleFailedCall(error: .assertionError(description:"\(TAG) call was unexpectedly nil in \(#function)")) + return + } + + switch call.state { + case .idle, .dialing, .answering, .localRinging, .localFailure, .remoteBusy, .remoteRinging: + handleMissedCall(call, thread: thread) + case .connected, .localHangup, .remoteHangup: + Logger.info("\(TAG) call is finished.") + } + + call.state = .remoteHangup + // Notify UI + callUIAdapter.endCall(call) + + // self.call is nil'd in `terminateCall`, so it's important we update it's state *before* calling `terminateCall` + terminateCall() + } + + public func handleAnswerCall(localId: UUID) { + // TODO #function is called from objc, how to access swift defiend dispatch queue (OS_dispatch_queue) + //assertOnSignalingQueue() + + guard let call = self.call else { + handleFailedCall(error: .assertionError(description:"\(TAG) call was unexpectedly nil in \(#function)")) + return + } + + guard call.localId == localId else { + handleFailedCall(error: .assertionError(description:"\(TAG) callLocalId:\(localId) doesn't match current calls: \(call.localId)")) + return + } + + // Because we may not be on signalingQueue (because this method is called from Objc which doesn't have + // access to signalingQueue (that I can find). FIXME? + type(of: self).signalingQueue.async { + self.handleAnswerCall(call) + } + } + + public func handleAnswerCall(_ call: SignalCall) { + assertOnSignalingQueue() + + Logger.debug("\(TAG) in \(#function)") + + guard self.call != nil else { + handleFailedCall(error: .assertionError(description:"\(TAG) ignoring \(#function) since there is no current call")) + return + } + + guard call == self.call! else { + // This could conceivably happen if the other party of an old call was slow to send us their answer + // and we've subsequently engaged in another call. Don't kill the current call, but just ignore it. + Logger.warn("\(TAG) ignoring \(#function) for call other than current call") + return + } + + guard let thread = self.thread else { + handleFailedCall(error: .assertionError(description:"\(TAG) ignoring \(#function) for call other than current call")) + return + } + + guard let peerConnectionClient = self.peerConnectionClient else { + handleFailedCall(error: .assertionError(description:"\(TAG) missing peerconnection client in \(#function)")) + return + } + + let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(), withCallNumber: call.remotePhoneNumber, callType: RPRecentCallTypeIncoming, in: thread) + callRecord.save() + + callUIAdapter.answerCall(call) + + let message = DataChannelMessage.forConnected(callId: call.signalingId) + if peerConnectionClient.sendDataChannelMessage(data: message.asData()) { + Logger.debug("\(TAG) sendDataChannelMessage returned true") + } else { + Logger.warn("\(TAG) sendDataChannelMessage returned false") + } + + handleConnectedCall(call) + } + + func handleConnectedCall(_ call: SignalCall) { + Logger.debug("\(TAG) in \(#function)") + assertOnSignalingQueue() + + guard let peerConnectionClient = self.peerConnectionClient else { + handleFailedCall(error: .assertionError(description:"\(TAG) peerConnectionClient unexpectedly nil in \(#function)")) + return + } + + call.state = .connected + + // We don't risk transmitting any media until the remote client has admitted to being connected. + peerConnectionClient.setAudioEnabled(enabled: true) + peerConnectionClient.setVideoEnabled(enabled: call.hasVideo) + } + + public func handleDeclineCall(localId: UUID) { + // #function is called from objc, how to access swift defiend dispatch queue (OS_dispatch_queue) + //assertOnSignalingQueue() + + guard let call = self.call else { + handleFailedCall(error: .assertionError(description:"\(TAG) call was unexpectedly nil in \(#function)")) + return + } + + guard call.localId == localId else { + handleFailedCall(error: .assertionError(description:"\(TAG) callLocalId:\(localId) doesn't match current calls: \(call.localId)")) + return + } + + // Because we may not be on signalingQueue (because this method is called from Objc which doesn't have + // access to signalingQueue (that I can find). FIXME? + type(of: self).signalingQueue.async { + self.handleDeclineCall(call) + } + } + + public func handleDeclineCall(_ call: SignalCall) { + assertOnSignalingQueue() + + Logger.info("\(TAG) in \(#function)") + + // Currently we just handle this as a hangup. But we could offer more descriptive action. e.g. DataChannel message + handleLocalHungupCall(call) + } + + func handleLocalHungupCall(_ call: SignalCall) { + assertOnSignalingQueue() + + guard self.call != nil else { + handleFailedCall(error: .assertionError(description:"\(TAG) ignoring \(#function) since there is no current call")) + return + } + + guard call == self.call! else { + handleFailedCall(error: .assertionError(description:"\(TAG) ignoring \(#function) for call other than current call")) + return + } + + guard let peerConnectionClient = self.peerConnectionClient else { + handleFailedCall(error: .assertionError(description:"\(TAG) missing peerconnection client in \(#function)")) + return + } + + guard let thread = self.thread else { + handleFailedCall(error: .assertionError(description:"\(TAG) missing thread in \(#function)")) + return + } + + call.state = .localHangup + + // TODO something like this lifted from Signal-Android. + // this.accountManager.cancelInFlightRequests(); + // this.messageSender.cancelInFlightRequests(); + + // If the call is connected, we can send the hangup via the data channel. + let message = DataChannelMessage.forHangup(callId: call.signalingId) + if peerConnectionClient.sendDataChannelMessage(data: message.asData()) { + Logger.debug("\(TAG) sendDataChannelMessage returned true") + } else { + Logger.warn("\(TAG) sendDataChannelMessage returned false") + } + + // 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: thread, hangupMessage: hangupMessage) + _ = sendMessage(callMessage).then(on: CallService.signalingQueue) { + Logger.debug("\(self.TAG) successfully sent hangup call message to \(thread)") + }.catch(on: CallService.signalingQueue) { error in + Logger.error("\(self.TAG) failed to send hangup call message to \(thread) with error: \(error)") + } + + terminateCall() + } + + func handleToggledMute(isMuted: Bool) { + assertOnSignalingQueue() + + guard let peerConnectionClient = self.peerConnectionClient else { + handleFailedCall(error: .assertionError(description:"\(TAG) peerConnectionClient unexpectedly nil in \(#function)")) + return + } + peerConnectionClient.setAudioEnabled(enabled: !isMuted) + } + + private func handleDataChannelMessage(_ message: OWSWebRTCProtosData) { + assertOnSignalingQueue() + + guard let call = self.call else { + handleFailedCall(error: .assertionError(description:"\(TAG) received data message, but there is no current call. Ignoring.")) + return + } + + if message.hasConnected() { + Logger.debug("\(TAG) remote participant sent Connected via data channel") + + let connected = message.connected! + + guard connected.id == call.signalingId else { + handleFailedCall(error: .assertionError(description:"\(TAG) received connected message for call with id:\(connected.id) but current call has id:\(call.signalingId)")) + return + } + + handleConnectedCall(call) + + } else if message.hasHangup() { + Logger.debug("\(TAG) remote participant sent Hangup via data channel") + + let hangup = message.hangup! + + guard hangup.id == call.signalingId else { + handleFailedCall(error: .assertionError(description:"\(TAG) received hangup message for call with id:\(hangup.id) but current call has id:\(call.signalingId)")) + return + } + + guard let thread = self.thread else { + handleFailedCall(error: .assertionError(description:"\(TAG) current contact thread is unexpectedly nil when receiving hangup DataChannelMessage")) + return + } + + handleRemoteHangup(thread: thread) + } else if message.hasVideoStreamingStatus() { + Logger.debug("\(TAG) remote participant sent VideoStreamingStatus via data channel") + + // TODO: translate from java + // Intent intent = new Intent(this, WebRtcCallService.class); + // intent.setAction(ACTION_REMOTE_VIDEO_MUTE); + // intent.putExtra(EXTRA_CALL_ID, dataMessage.getVideoStreamingStatus().getId()); + // intent.putExtra(EXTRA_MUTE, !dataMessage.getVideoStreamingStatus().getEnabled()); + // startService(intent); + } + } + + // MARK: Helpers + + private func assertOnSignalingQueue() { + if #available(iOS 10.0, *) { + dispatchPrecondition(condition: .onQueue(type(of: self).signalingQueue)) + } else { + // Skipping check on Promise<[RTCIceServer]> { + + return firstly { + return accountManager.getTurnServerInfo() + }.then(on: CallService.signalingQueue) { turnServerInfo -> [RTCIceServer] in + Logger.debug("\(self.TAG) got turn server urls: \(turnServerInfo.urls)") + + return turnServerInfo.urls.map { url in + if url.hasPrefix("turn") { + // only pass credentials for "turn:" servers. + return RTCIceServer(urlStrings: [url], username: turnServerInfo.username, credential: turnServerInfo.password) + } else { + return RTCIceServer(urlStrings: [url]) + } + } + [CallService.fallbackIceServer] + } + } + + private func sendMessage(_ message: OWSOutgoingCallMessage) -> Promise { + return Promise { fulfill, reject in + self.messageSender.send(message, success: fulfill, failure: reject) + } + } + + public func handleFailedCall(error: CallError) { + assertOnSignalingQueue() + Logger.error("\(TAG) call failed with error: \(error)") + + // It's essential to set call.state before terminateCall, because terminateCall nils self.call + call?.error = error + call?.state = .localFailure + + terminateCall() + } + + private func terminateCall() { + assertOnSignalingQueue() + +// lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING); +// NotificationBarManager.setCallEnded(this); +// +// incomingRinger.stop(); +// outgoingRinger.stop(); +// outgoingRinger.playDisconnected(); +// +// if (peerConnection != null) { +// peerConnection.dispose(); +// peerConnection = null; +// } +// +// if (eglBase != null && localRenderer != null && remoteRenderer != null) { +// localRenderer.release(); +// remoteRenderer.release(); +// eglBase.release(); +// } +// +// shutdownAudio(); +// +// this.callState = CallState.STATE_IDLE; +// this.recipient = null; +// this.callId = null; +// this.audioEnabled = false; +// this.videoEnabled = false; +// this.pendingIceUpdates = null; +// lockManager.updatePhoneState(LockManager.PhoneState.IDLE); + + peerConnectionClient?.terminate() + peerConnectionClient = nil + call = nil + thread = nil + incomingCallPromise = nil + sendIceUpdatesImmediately = true + pendingIceUpdateMessages = [] + } + + // MARK: - RTCDataChannelDelegate + + /** The data channel state changed. */ + public func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) { + Logger.debug("\(TAG) dataChannelDidChangeState: \(dataChannel)") + // SignalingQueue.dispatch.async {} + } + + /** The data channel successfully received a data buffer. */ + public func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) { + Logger.debug("\(TAG) dataChannel didReceiveMessageWith buffer:\(buffer)") + + guard let dataChannelMessage = OWSWebRTCProtosData.parse(from:buffer.data) else { + // TODO can't proto parsings throw an exception? Is it just being lost in the Objc->Swift? + Logger.error("\(TAG) failed to parse dataProto") + return + } + + CallService.signalingQueue.async { + self.handleDataChannelMessage(dataChannelMessage) + } + } + + /** The data channel's |bufferedAmount| changed. */ + public func dataChannel(_ dataChannel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) { + Logger.debug("\(TAG) didChangeBufferedAmount: \(amount)") + } + + // MARK: - RTCPeerConnectionDelegate + + /** Called when the SignalingState changed. */ + public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) { + Logger.debug("\(TAG) didChange signalingState:\(stateChanged.debugDescription)") + } + + /** Called when media is received on a new stream from remote peer. */ + public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { + Logger.debug("\(TAG) didAdd stream:\(stream)") + } + + /** Called when a remote peer closes a stream. */ + public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { + Logger.debug("\(TAG) didRemove Stream:\(stream)") + } + + /** Called when negotiation is needed, for example ICE has restarted. */ + public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { + Logger.debug("\(TAG) shouldNegotiate") + } + + /** Called any time the IceConnectionState changes. */ + public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) { + Logger.debug("\(TAG) didChange IceConnectionState:\(newState.debugDescription)") + + CallService.signalingQueue.async { + switch newState { + case .connected, .completed: + self.handleIceConnected() + case .failed: + Logger.warn("\(self.TAG) RTCIceConnection failed.") + guard let thread = self.thread else { + Logger.error("\(self.TAG) refusing to hangup for failed IceConnection because there is no current thread") + return + } + self.handleFailedCall(error: CallError.disconnected) + default: + Logger.debug("\(self.TAG) ignoring change IceConnectionState:\(newState.debugDescription)") + } + } + } + + /** Called any time the IceGatheringState changes. */ + public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) { + Logger.debug("\(TAG) didChange IceGatheringState:\(newState.debugDescription)") + } + + /** New ice candidate has been found. */ + public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { + Logger.debug("\(TAG) didGenerate IceCandidate:\(candidate.sdp)") + CallService.signalingQueue.async { + self.handleLocalAddedIceCandidate(candidate) + } + } + + /** Called when a group of local Ice candidates have been removed. */ + public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { + Logger.debug("\(TAG) didRemove IceCandidates:\(candidates)") + } + + /** New data channel has been opened. */ + public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { + Logger.debug("\(TAG) didOpen dataChannel:\(dataChannel)") + CallService.signalingQueue.async { + guard let peerConnectionClient = self.peerConnectionClient else { + Logger.error("\(self.TAG) surprised to find nil peerConnectionClient in \(#function)") + return + } + + Logger.debug("\(self.TAG) set dataChannel") + peerConnectionClient.dataChannel = dataChannel + } + } +} + +// Mark: Pretty Print Objc enums. + +fileprivate extension RTCSignalingState { + var debugDescription: String { + switch self { + case .stable: + return "stable" + case .haveLocalOffer: + return "haveLocalOffer" + case .haveLocalPrAnswer: + return "haveLocalPrAnswer" + case .haveRemoteOffer: + return "haveRemoteOffer" + case .haveRemotePrAnswer: + return "haveRemotePrAnswer" + case .closed: + return "closed" + } + } +} + +fileprivate extension RTCIceGatheringState { + var debugDescription: String { + switch self { + case .new: + return "new" + case .gathering: + return "gathering" + case .complete: + return "complete" + } + } +} + +fileprivate extension RTCIceConnectionState { + var debugDescription: String { + switch self { + case .new: + return "new" + case .checking: + return "checking" + case .connected: + return "connected" + case .completed: + return "completed" + case .failed: + return "failed" + case .disconnected: + return "disconnected" + case .closed: + return "closed" + case .count: + return "count" + } + } +} diff --git a/Signal/src/call/DataChannelMessage.swift b/Signal/src/call/DataChannelMessage.swift new file mode 100644 index 000000000..4dd1c0961 --- /dev/null +++ b/Signal/src/call/DataChannelMessage.swift @@ -0,0 +1,113 @@ +// Created by Michael Kirk on 12/8/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation + +class DataChannelMessage { + + private let connected: Connected? + private let hangup: Hangup? + private let videoStreamingStatus: VideoStreamingStatus? + + private class Connected { + let callId: UInt64 + + init(callId: UInt64) { + self.callId = callId + } + + func asProtobuf() -> OWSWebRTCProtosConnected { + let builder = OWSWebRTCProtosConnectedBuilder() + builder.setId(callId) + return builder.build() + } + } + + private class Hangup { + let callId: UInt64 + + init(callId: UInt64) { + self.callId = callId + } + + func asProtobuf() -> OWSWebRTCProtosHangup { + let builder = OWSWebRTCProtosHangupBuilder() + builder.setId(callId) + return builder.build() + } + } + + private class VideoStreamingStatus { + private let callId: UInt64 + private let enabled: Bool + + init(callId: UInt64, enabled: Bool) { + self.callId = callId + self.enabled = enabled + } + + func asProtobuf() -> OWSWebRTCProtosVideoStreamingStatus { + let builder = OWSWebRTCProtosVideoStreamingStatusBuilder() + builder.setId(callId) + builder.setEnabled(enabled) + return builder.build() + } + } + + // MARK: Init + + private init(connected: Connected) { + self.connected = connected + self.hangup = nil + self.videoStreamingStatus = nil + } + + private init(hangup: Hangup) { + self.connected = nil + self.hangup = hangup + self.videoStreamingStatus = nil + } + + private init(videoStreamingStatus: VideoStreamingStatus) { + self.connected = nil + self.hangup = nil + self.videoStreamingStatus = videoStreamingStatus + } + + // MARK: Factory + + class func forConnected(callId: UInt64) -> DataChannelMessage { + return DataChannelMessage(connected:Connected(callId: callId)) + } + + class func forHangup(callId: UInt64) -> DataChannelMessage { + return DataChannelMessage(hangup: Hangup(callId: callId)) + } + + class func forVideoStreamingStatus(callId: UInt64, enabled: Bool) -> DataChannelMessage { + return DataChannelMessage(videoStreamingStatus: VideoStreamingStatus(callId: callId, enabled: enabled)) + } + + // MARK: Serialization + + func asProtobuf() -> PBGeneratedMessage { + let builder = OWSWebRTCProtosDataBuilder() + if connected != nil { + builder.setConnected(connected!.asProtobuf()) + } + + if hangup != nil { + builder.setHangup(hangup!.asProtobuf()) + } + + if videoStreamingStatus != nil { + builder.setVideoStreamingStatus(videoStreamingStatus!.asProtobuf()) + } + + return builder.build() + } + + func asData() -> Data { + return self.asProtobuf().data() + } +} diff --git a/Signal/src/call/NonCallKitCallUIAdaptee.swift b/Signal/src/call/NonCallKitCallUIAdaptee.swift new file mode 100644 index 000000000..4dca897f0 --- /dev/null +++ b/Signal/src/call/NonCallKitCallUIAdaptee.swift @@ -0,0 +1,65 @@ +// Created by Michael Kirk on 1/3/17. +// Copyright © 2017 Open Whisper Systems. All rights reserved. + +import Foundation + +/** + * Manage call related UI in a pre-CallKit world. + */ +class NonCallKitCallUIAdaptee: CallUIAdaptee { + + let TAG = "[NonCallKitCallUIAdaptee]" + + let notificationsAdapter: CallNotificationsAdapter + let callService: CallService + + required init(callService: CallService, notificationsAdapter: CallNotificationsAdapter) { + self.callService = callService + self.notificationsAdapter = notificationsAdapter + } + + public func startOutgoingCall(_ call: SignalCall) { + CallService.signalingQueue.async { + _ = self.callService.handleOutgoingCall(call).then { + Logger.debug("\(self.TAG) handleOutgoingCall succeeded") + }.catch { error in + Logger.error("\(self.TAG) handleOutgoingCall failed with error: \(error)") + } + } + } + + public func reportIncomingCall(_ call: SignalCall, callerName: String, audioManager: SignalCallAudioManager) { + Logger.debug("\(TAG) \(#function)") + + // present Call View controller + let callNotificationName = CallService.callServiceActiveCallNotificationName() + NotificationCenter.default.post(name: NSNotification.Name(rawValue: callNotificationName), object: call) + + // present lock screen notification + if UIApplication.shared.applicationState == .active { + Logger.debug("\(TAG) skipping notification since app is already active.") + } else { + notificationsAdapter.presentIncomingCall(call, callerName: callerName) + } + } + + public func reportMissedCall(_ call: SignalCall, callerName: String) { + notificationsAdapter.presentMissedCall(call, callerName: callerName) + } + + public func answerCall(_ call: SignalCall) { + // NO-OP + } + + public func declineCall(_ call: SignalCall) { + CallService.signalingQueue.async { + self.callService.handleDeclineCall(call) + } + } + + public func endCall(_ call: SignalCall) { + CallService.signalingQueue.async { + self.callService.handleLocalHungupCall(call) + } + } +} diff --git a/Signal/src/call/OWSWebRTCDataProtos.pb.h b/Signal/src/call/OWSWebRTCDataProtos.pb.h new file mode 100644 index 000000000..e09e560d4 --- /dev/null +++ b/Signal/src/call/OWSWebRTCDataProtos.pb.h @@ -0,0 +1,305 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! + +#import + +// @@protoc_insertion_point(imports) + +@class OWSWebRTCProtosConnected; +@class OWSWebRTCProtosConnectedBuilder; +@class OWSWebRTCProtosData; +@class OWSWebRTCProtosDataBuilder; +@class OWSWebRTCProtosHangup; +@class OWSWebRTCProtosHangupBuilder; +@class OWSWebRTCProtosVideoStreamingStatus; +@class OWSWebRTCProtosVideoStreamingStatusBuilder; +@class ObjectiveCFileOptions; +@class ObjectiveCFileOptionsBuilder; +@class PBDescriptorProto; +@class PBDescriptorProtoBuilder; +@class PBDescriptorProtoExtensionRange; +@class PBDescriptorProtoExtensionRangeBuilder; +@class PBEnumDescriptorProto; +@class PBEnumDescriptorProtoBuilder; +@class PBEnumOptions; +@class PBEnumOptionsBuilder; +@class PBEnumValueDescriptorProto; +@class PBEnumValueDescriptorProtoBuilder; +@class PBEnumValueOptions; +@class PBEnumValueOptionsBuilder; +@class PBFieldDescriptorProto; +@class PBFieldDescriptorProtoBuilder; +@class PBFieldOptions; +@class PBFieldOptionsBuilder; +@class PBFileDescriptorProto; +@class PBFileDescriptorProtoBuilder; +@class PBFileDescriptorSet; +@class PBFileDescriptorSetBuilder; +@class PBFileOptions; +@class PBFileOptionsBuilder; +@class PBMessageOptions; +@class PBMessageOptionsBuilder; +@class PBMethodDescriptorProto; +@class PBMethodDescriptorProtoBuilder; +@class PBMethodOptions; +@class PBMethodOptionsBuilder; +@class PBOneofDescriptorProto; +@class PBOneofDescriptorProtoBuilder; +@class PBServiceDescriptorProto; +@class PBServiceDescriptorProtoBuilder; +@class PBServiceOptions; +@class PBServiceOptionsBuilder; +@class PBSourceCodeInfo; +@class PBSourceCodeInfoBuilder; +@class PBSourceCodeInfoLocation; +@class PBSourceCodeInfoLocationBuilder; +@class PBUninterpretedOption; +@class PBUninterpretedOptionBuilder; +@class PBUninterpretedOptionNamePart; +@class PBUninterpretedOptionNamePartBuilder; + + + +@interface OWSWebRTCProtosOwswebRtcdataProtosRoot : NSObject { +} ++ (PBExtensionRegistry*) extensionRegistry; ++ (void) registerAllExtensions:(PBMutableExtensionRegistry*) registry; +@end + +#define Connected_id @"id" +@interface OWSWebRTCProtosConnected : PBGeneratedMessage { +@private + BOOL hasId_:1; + UInt64 id; +} +- (BOOL) hasId; +@property (readonly) UInt64 id; + ++ (instancetype) defaultInstance; +- (instancetype) defaultInstance; + +- (BOOL) isInitialized; +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output; +- (OWSWebRTCProtosConnectedBuilder*) builder; ++ (OWSWebRTCProtosConnectedBuilder*) builder; ++ (OWSWebRTCProtosConnectedBuilder*) builderWithPrototype:(OWSWebRTCProtosConnected*) prototype; +- (OWSWebRTCProtosConnectedBuilder*) toBuilder; + ++ (OWSWebRTCProtosConnected*) parseFromData:(NSData*) data; ++ (OWSWebRTCProtosConnected*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (OWSWebRTCProtosConnected*) parseFromInputStream:(NSInputStream*) input; ++ (OWSWebRTCProtosConnected*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (OWSWebRTCProtosConnected*) parseFromCodedInputStream:(PBCodedInputStream*) input; ++ (OWSWebRTCProtosConnected*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; +@end + +@interface OWSWebRTCProtosConnectedBuilder : PBGeneratedMessageBuilder { +@private + OWSWebRTCProtosConnected* resultConnected; +} + +- (OWSWebRTCProtosConnected*) defaultInstance; + +- (OWSWebRTCProtosConnectedBuilder*) clear; +- (OWSWebRTCProtosConnectedBuilder*) clone; + +- (OWSWebRTCProtosConnected*) build; +- (OWSWebRTCProtosConnected*) buildPartial; + +- (OWSWebRTCProtosConnectedBuilder*) mergeFrom:(OWSWebRTCProtosConnected*) other; +- (OWSWebRTCProtosConnectedBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input; +- (OWSWebRTCProtosConnectedBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; + +- (BOOL) hasId; +- (UInt64) id; +- (OWSWebRTCProtosConnectedBuilder*) setId:(UInt64) value; +- (OWSWebRTCProtosConnectedBuilder*) clearId; +@end + +#define Hangup_id @"id" +@interface OWSWebRTCProtosHangup : PBGeneratedMessage { +@private + BOOL hasId_:1; + UInt64 id; +} +- (BOOL) hasId; +@property (readonly) UInt64 id; + ++ (instancetype) defaultInstance; +- (instancetype) defaultInstance; + +- (BOOL) isInitialized; +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output; +- (OWSWebRTCProtosHangupBuilder*) builder; ++ (OWSWebRTCProtosHangupBuilder*) builder; ++ (OWSWebRTCProtosHangupBuilder*) builderWithPrototype:(OWSWebRTCProtosHangup*) prototype; +- (OWSWebRTCProtosHangupBuilder*) toBuilder; + ++ (OWSWebRTCProtosHangup*) parseFromData:(NSData*) data; ++ (OWSWebRTCProtosHangup*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (OWSWebRTCProtosHangup*) parseFromInputStream:(NSInputStream*) input; ++ (OWSWebRTCProtosHangup*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (OWSWebRTCProtosHangup*) parseFromCodedInputStream:(PBCodedInputStream*) input; ++ (OWSWebRTCProtosHangup*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; +@end + +@interface OWSWebRTCProtosHangupBuilder : PBGeneratedMessageBuilder { +@private + OWSWebRTCProtosHangup* resultHangup; +} + +- (OWSWebRTCProtosHangup*) defaultInstance; + +- (OWSWebRTCProtosHangupBuilder*) clear; +- (OWSWebRTCProtosHangupBuilder*) clone; + +- (OWSWebRTCProtosHangup*) build; +- (OWSWebRTCProtosHangup*) buildPartial; + +- (OWSWebRTCProtosHangupBuilder*) mergeFrom:(OWSWebRTCProtosHangup*) other; +- (OWSWebRTCProtosHangupBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input; +- (OWSWebRTCProtosHangupBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; + +- (BOOL) hasId; +- (UInt64) id; +- (OWSWebRTCProtosHangupBuilder*) setId:(UInt64) value; +- (OWSWebRTCProtosHangupBuilder*) clearId; +@end + +#define VideoStreamingStatus_id @"id" +#define VideoStreamingStatus_enabled @"enabled" +@interface OWSWebRTCProtosVideoStreamingStatus : PBGeneratedMessage { +@private + BOOL hasEnabled_:1; + BOOL hasId_:1; + BOOL enabled_:1; + UInt64 id; +} +- (BOOL) hasId; +- (BOOL) hasEnabled; +@property (readonly) UInt64 id; +- (BOOL) enabled; + ++ (instancetype) defaultInstance; +- (instancetype) defaultInstance; + +- (BOOL) isInitialized; +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output; +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) builder; ++ (OWSWebRTCProtosVideoStreamingStatusBuilder*) builder; ++ (OWSWebRTCProtosVideoStreamingStatusBuilder*) builderWithPrototype:(OWSWebRTCProtosVideoStreamingStatus*) prototype; +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) toBuilder; + ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromData:(NSData*) data; ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromInputStream:(NSInputStream*) input; ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromCodedInputStream:(PBCodedInputStream*) input; ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; +@end + +@interface OWSWebRTCProtosVideoStreamingStatusBuilder : PBGeneratedMessageBuilder { +@private + OWSWebRTCProtosVideoStreamingStatus* resultVideoStreamingStatus; +} + +- (OWSWebRTCProtosVideoStreamingStatus*) defaultInstance; + +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) clear; +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) clone; + +- (OWSWebRTCProtosVideoStreamingStatus*) build; +- (OWSWebRTCProtosVideoStreamingStatus*) buildPartial; + +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) mergeFrom:(OWSWebRTCProtosVideoStreamingStatus*) other; +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input; +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; + +- (BOOL) hasId; +- (UInt64) id; +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) setId:(UInt64) value; +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) clearId; + +- (BOOL) hasEnabled; +- (BOOL) enabled; +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) setEnabled:(BOOL) value; +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) clearEnabled; +@end + +#define Data_connected @"connected" +#define Data_hangup @"hangup" +#define Data_videoStreamingStatus @"videoStreamingStatus" +@interface OWSWebRTCProtosData : PBGeneratedMessage { +@private + BOOL hasConnected_:1; + BOOL hasHangup_:1; + BOOL hasVideoStreamingStatus_:1; + OWSWebRTCProtosConnected* connected; + OWSWebRTCProtosHangup* hangup; + OWSWebRTCProtosVideoStreamingStatus* videoStreamingStatus; +} +- (BOOL) hasConnected; +- (BOOL) hasHangup; +- (BOOL) hasVideoStreamingStatus; +@property (readonly, strong) OWSWebRTCProtosConnected* connected; +@property (readonly, strong) OWSWebRTCProtosHangup* hangup; +@property (readonly, strong) OWSWebRTCProtosVideoStreamingStatus* videoStreamingStatus; + ++ (instancetype) defaultInstance; +- (instancetype) defaultInstance; + +- (BOOL) isInitialized; +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output; +- (OWSWebRTCProtosDataBuilder*) builder; ++ (OWSWebRTCProtosDataBuilder*) builder; ++ (OWSWebRTCProtosDataBuilder*) builderWithPrototype:(OWSWebRTCProtosData*) prototype; +- (OWSWebRTCProtosDataBuilder*) toBuilder; + ++ (OWSWebRTCProtosData*) parseFromData:(NSData*) data; ++ (OWSWebRTCProtosData*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (OWSWebRTCProtosData*) parseFromInputStream:(NSInputStream*) input; ++ (OWSWebRTCProtosData*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (OWSWebRTCProtosData*) parseFromCodedInputStream:(PBCodedInputStream*) input; ++ (OWSWebRTCProtosData*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; +@end + +@interface OWSWebRTCProtosDataBuilder : PBGeneratedMessageBuilder { +@private + OWSWebRTCProtosData* resultData; +} + +- (OWSWebRTCProtosData*) defaultInstance; + +- (OWSWebRTCProtosDataBuilder*) clear; +- (OWSWebRTCProtosDataBuilder*) clone; + +- (OWSWebRTCProtosData*) build; +- (OWSWebRTCProtosData*) buildPartial; + +- (OWSWebRTCProtosDataBuilder*) mergeFrom:(OWSWebRTCProtosData*) other; +- (OWSWebRTCProtosDataBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input; +- (OWSWebRTCProtosDataBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; + +- (BOOL) hasConnected; +- (OWSWebRTCProtosConnected*) connected; +- (OWSWebRTCProtosDataBuilder*) setConnected:(OWSWebRTCProtosConnected*) value; +- (OWSWebRTCProtosDataBuilder*) setConnectedBuilder:(OWSWebRTCProtosConnectedBuilder*) builderForValue; +- (OWSWebRTCProtosDataBuilder*) mergeConnected:(OWSWebRTCProtosConnected*) value; +- (OWSWebRTCProtosDataBuilder*) clearConnected; + +- (BOOL) hasHangup; +- (OWSWebRTCProtosHangup*) hangup; +- (OWSWebRTCProtosDataBuilder*) setHangup:(OWSWebRTCProtosHangup*) value; +- (OWSWebRTCProtosDataBuilder*) setHangupBuilder:(OWSWebRTCProtosHangupBuilder*) builderForValue; +- (OWSWebRTCProtosDataBuilder*) mergeHangup:(OWSWebRTCProtosHangup*) value; +- (OWSWebRTCProtosDataBuilder*) clearHangup; + +- (BOOL) hasVideoStreamingStatus; +- (OWSWebRTCProtosVideoStreamingStatus*) videoStreamingStatus; +- (OWSWebRTCProtosDataBuilder*) setVideoStreamingStatus:(OWSWebRTCProtosVideoStreamingStatus*) value; +- (OWSWebRTCProtosDataBuilder*) setVideoStreamingStatusBuilder:(OWSWebRTCProtosVideoStreamingStatusBuilder*) builderForValue; +- (OWSWebRTCProtosDataBuilder*) mergeVideoStreamingStatus:(OWSWebRTCProtosVideoStreamingStatus*) value; +- (OWSWebRTCProtosDataBuilder*) clearVideoStreamingStatus; +@end + + +// @@protoc_insertion_point(global_scope) diff --git a/Signal/src/call/OWSWebRTCDataProtos.pb.m b/Signal/src/call/OWSWebRTCDataProtos.pb.m new file mode 100644 index 000000000..020a30d46 --- /dev/null +++ b/Signal/src/call/OWSWebRTCDataProtos.pb.m @@ -0,0 +1,1073 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! + +#import "OWSWebRTCDataProtos.pb.h" +// @@protoc_insertion_point(imports) + +@implementation OWSWebRTCProtosOwswebRtcdataProtosRoot +static PBExtensionRegistry* extensionRegistry = nil; ++ (PBExtensionRegistry*) extensionRegistry { + return extensionRegistry; +} + ++ (void) initialize { + if (self == [OWSWebRTCProtosOwswebRtcdataProtosRoot class]) { + PBMutableExtensionRegistry* registry = [PBMutableExtensionRegistry registry]; + [self registerAllExtensions:registry]; + [ObjectivecDescriptorRoot registerAllExtensions:registry]; + extensionRegistry = registry; + } +} ++ (void) registerAllExtensions:(PBMutableExtensionRegistry*) registry { +} +@end + +@interface OWSWebRTCProtosConnected () +@property UInt64 id; +@end + +@implementation OWSWebRTCProtosConnected + +- (BOOL) hasId { + return !!hasId_; +} +- (void) setHasId:(BOOL) _value_ { + hasId_ = !!_value_; +} +@synthesize id; +- (instancetype) init { + if ((self = [super init])) { + self.id = 0L; + } + return self; +} +static OWSWebRTCProtosConnected* defaultOWSWebRTCProtosConnectedInstance = nil; ++ (void) initialize { + if (self == [OWSWebRTCProtosConnected class]) { + defaultOWSWebRTCProtosConnectedInstance = [[OWSWebRTCProtosConnected alloc] init]; + } +} ++ (instancetype) defaultInstance { + return defaultOWSWebRTCProtosConnectedInstance; +} +- (instancetype) defaultInstance { + return defaultOWSWebRTCProtosConnectedInstance; +} +- (BOOL) isInitialized { + return YES; +} +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output { + if (self.hasId) { + [output writeUInt64:1 value:self.id]; + } + [self.unknownFields writeToCodedOutputStream:output]; +} +- (SInt32) serializedSize { + __block SInt32 size_ = memoizedSerializedSize; + if (size_ != -1) { + return size_; + } + + size_ = 0; + if (self.hasId) { + size_ += computeUInt64Size(1, self.id); + } + size_ += self.unknownFields.serializedSize; + memoizedSerializedSize = size_; + return size_; +} ++ (OWSWebRTCProtosConnected*) parseFromData:(NSData*) data { + return (OWSWebRTCProtosConnected*)[[[OWSWebRTCProtosConnected builder] mergeFromData:data] build]; +} ++ (OWSWebRTCProtosConnected*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosConnected*)[[[OWSWebRTCProtosConnected builder] mergeFromData:data extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosConnected*) parseFromInputStream:(NSInputStream*) input { + return (OWSWebRTCProtosConnected*)[[[OWSWebRTCProtosConnected builder] mergeFromInputStream:input] build]; +} ++ (OWSWebRTCProtosConnected*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosConnected*)[[[OWSWebRTCProtosConnected builder] mergeFromInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosConnected*) parseFromCodedInputStream:(PBCodedInputStream*) input { + return (OWSWebRTCProtosConnected*)[[[OWSWebRTCProtosConnected builder] mergeFromCodedInputStream:input] build]; +} ++ (OWSWebRTCProtosConnected*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosConnected*)[[[OWSWebRTCProtosConnected builder] mergeFromCodedInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosConnectedBuilder*) builder { + return [[OWSWebRTCProtosConnectedBuilder alloc] init]; +} ++ (OWSWebRTCProtosConnectedBuilder*) builderWithPrototype:(OWSWebRTCProtosConnected*) prototype { + return [[OWSWebRTCProtosConnected builder] mergeFrom:prototype]; +} +- (OWSWebRTCProtosConnectedBuilder*) builder { + return [OWSWebRTCProtosConnected builder]; +} +- (OWSWebRTCProtosConnectedBuilder*) toBuilder { + return [OWSWebRTCProtosConnected builderWithPrototype:self]; +} +- (void) writeDescriptionTo:(NSMutableString*) output withIndent:(NSString*) indent { + if (self.hasId) { + [output appendFormat:@"%@%@: %@\n", indent, @"id", [NSNumber numberWithLongLong:self.id]]; + } + [self.unknownFields writeDescriptionTo:output withIndent:indent]; +} +- (void) storeInDictionary:(NSMutableDictionary *)dictionary { + if (self.hasId) { + [dictionary setObject: [NSNumber numberWithLongLong:self.id] forKey: @"id"]; + } + [self.unknownFields storeInDictionary:dictionary]; +} +- (BOOL) isEqual:(id)other { + if (other == self) { + return YES; + } + if (![other isKindOfClass:[OWSWebRTCProtosConnected class]]) { + return NO; + } + OWSWebRTCProtosConnected *otherMessage = other; + return + self.hasId == otherMessage.hasId && + (!self.hasId || self.id == otherMessage.id) && + (self.unknownFields == otherMessage.unknownFields || (self.unknownFields != nil && [self.unknownFields isEqual:otherMessage.unknownFields])); +} +- (NSUInteger) hash { + __block NSUInteger hashCode = 7; + if (self.hasId) { + hashCode = hashCode * 31 + [[NSNumber numberWithLongLong:self.id] hash]; + } + hashCode = hashCode * 31 + [self.unknownFields hash]; + return hashCode; +} +@end + +@interface OWSWebRTCProtosConnectedBuilder() +@property (strong) OWSWebRTCProtosConnected* resultConnected; +@end + +@implementation OWSWebRTCProtosConnectedBuilder +@synthesize resultConnected; +- (instancetype) init { + if ((self = [super init])) { + self.resultConnected = [[OWSWebRTCProtosConnected alloc] init]; + } + return self; +} +- (PBGeneratedMessage*) internalGetResult { + return resultConnected; +} +- (OWSWebRTCProtosConnectedBuilder*) clear { + self.resultConnected = [[OWSWebRTCProtosConnected alloc] init]; + return self; +} +- (OWSWebRTCProtosConnectedBuilder*) clone { + return [OWSWebRTCProtosConnected builderWithPrototype:resultConnected]; +} +- (OWSWebRTCProtosConnected*) defaultInstance { + return [OWSWebRTCProtosConnected defaultInstance]; +} +- (OWSWebRTCProtosConnected*) build { + [self checkInitialized]; + return [self buildPartial]; +} +- (OWSWebRTCProtosConnected*) buildPartial { + OWSWebRTCProtosConnected* returnMe = resultConnected; + self.resultConnected = nil; + return returnMe; +} +- (OWSWebRTCProtosConnectedBuilder*) mergeFrom:(OWSWebRTCProtosConnected*) other { + if (other == [OWSWebRTCProtosConnected defaultInstance]) { + return self; + } + if (other.hasId) { + [self setId:other.id]; + } + [self mergeUnknownFields:other.unknownFields]; + return self; +} +- (OWSWebRTCProtosConnectedBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input { + return [self mergeFromCodedInputStream:input extensionRegistry:[PBExtensionRegistry emptyRegistry]]; +} +- (OWSWebRTCProtosConnectedBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + PBUnknownFieldSetBuilder* unknownFields = [PBUnknownFieldSet builderWithUnknownFields:self.unknownFields]; + while (YES) { + SInt32 tag = [input readTag]; + switch (tag) { + case 0: + [self setUnknownFields:[unknownFields build]]; + return self; + default: { + if (![self parseUnknownField:input unknownFields:unknownFields extensionRegistry:extensionRegistry tag:tag]) { + [self setUnknownFields:[unknownFields build]]; + return self; + } + break; + } + case 8: { + [self setId:[input readUInt64]]; + break; + } + } + } +} +- (BOOL) hasId { + return resultConnected.hasId; +} +- (UInt64) id { + return resultConnected.id; +} +- (OWSWebRTCProtosConnectedBuilder*) setId:(UInt64) value { + resultConnected.hasId = YES; + resultConnected.id = value; + return self; +} +- (OWSWebRTCProtosConnectedBuilder*) clearId { + resultConnected.hasId = NO; + resultConnected.id = 0L; + return self; +} +@end + +@interface OWSWebRTCProtosHangup () +@property UInt64 id; +@end + +@implementation OWSWebRTCProtosHangup + +- (BOOL) hasId { + return !!hasId_; +} +- (void) setHasId:(BOOL) _value_ { + hasId_ = !!_value_; +} +@synthesize id; +- (instancetype) init { + if ((self = [super init])) { + self.id = 0L; + } + return self; +} +static OWSWebRTCProtosHangup* defaultOWSWebRTCProtosHangupInstance = nil; ++ (void) initialize { + if (self == [OWSWebRTCProtosHangup class]) { + defaultOWSWebRTCProtosHangupInstance = [[OWSWebRTCProtosHangup alloc] init]; + } +} ++ (instancetype) defaultInstance { + return defaultOWSWebRTCProtosHangupInstance; +} +- (instancetype) defaultInstance { + return defaultOWSWebRTCProtosHangupInstance; +} +- (BOOL) isInitialized { + return YES; +} +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output { + if (self.hasId) { + [output writeUInt64:1 value:self.id]; + } + [self.unknownFields writeToCodedOutputStream:output]; +} +- (SInt32) serializedSize { + __block SInt32 size_ = memoizedSerializedSize; + if (size_ != -1) { + return size_; + } + + size_ = 0; + if (self.hasId) { + size_ += computeUInt64Size(1, self.id); + } + size_ += self.unknownFields.serializedSize; + memoizedSerializedSize = size_; + return size_; +} ++ (OWSWebRTCProtosHangup*) parseFromData:(NSData*) data { + return (OWSWebRTCProtosHangup*)[[[OWSWebRTCProtosHangup builder] mergeFromData:data] build]; +} ++ (OWSWebRTCProtosHangup*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosHangup*)[[[OWSWebRTCProtosHangup builder] mergeFromData:data extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosHangup*) parseFromInputStream:(NSInputStream*) input { + return (OWSWebRTCProtosHangup*)[[[OWSWebRTCProtosHangup builder] mergeFromInputStream:input] build]; +} ++ (OWSWebRTCProtosHangup*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosHangup*)[[[OWSWebRTCProtosHangup builder] mergeFromInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosHangup*) parseFromCodedInputStream:(PBCodedInputStream*) input { + return (OWSWebRTCProtosHangup*)[[[OWSWebRTCProtosHangup builder] mergeFromCodedInputStream:input] build]; +} ++ (OWSWebRTCProtosHangup*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosHangup*)[[[OWSWebRTCProtosHangup builder] mergeFromCodedInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosHangupBuilder*) builder { + return [[OWSWebRTCProtosHangupBuilder alloc] init]; +} ++ (OWSWebRTCProtosHangupBuilder*) builderWithPrototype:(OWSWebRTCProtosHangup*) prototype { + return [[OWSWebRTCProtosHangup builder] mergeFrom:prototype]; +} +- (OWSWebRTCProtosHangupBuilder*) builder { + return [OWSWebRTCProtosHangup builder]; +} +- (OWSWebRTCProtosHangupBuilder*) toBuilder { + return [OWSWebRTCProtosHangup builderWithPrototype:self]; +} +- (void) writeDescriptionTo:(NSMutableString*) output withIndent:(NSString*) indent { + if (self.hasId) { + [output appendFormat:@"%@%@: %@\n", indent, @"id", [NSNumber numberWithLongLong:self.id]]; + } + [self.unknownFields writeDescriptionTo:output withIndent:indent]; +} +- (void) storeInDictionary:(NSMutableDictionary *)dictionary { + if (self.hasId) { + [dictionary setObject: [NSNumber numberWithLongLong:self.id] forKey: @"id"]; + } + [self.unknownFields storeInDictionary:dictionary]; +} +- (BOOL) isEqual:(id)other { + if (other == self) { + return YES; + } + if (![other isKindOfClass:[OWSWebRTCProtosHangup class]]) { + return NO; + } + OWSWebRTCProtosHangup *otherMessage = other; + return + self.hasId == otherMessage.hasId && + (!self.hasId || self.id == otherMessage.id) && + (self.unknownFields == otherMessage.unknownFields || (self.unknownFields != nil && [self.unknownFields isEqual:otherMessage.unknownFields])); +} +- (NSUInteger) hash { + __block NSUInteger hashCode = 7; + if (self.hasId) { + hashCode = hashCode * 31 + [[NSNumber numberWithLongLong:self.id] hash]; + } + hashCode = hashCode * 31 + [self.unknownFields hash]; + return hashCode; +} +@end + +@interface OWSWebRTCProtosHangupBuilder() +@property (strong) OWSWebRTCProtosHangup* resultHangup; +@end + +@implementation OWSWebRTCProtosHangupBuilder +@synthesize resultHangup; +- (instancetype) init { + if ((self = [super init])) { + self.resultHangup = [[OWSWebRTCProtosHangup alloc] init]; + } + return self; +} +- (PBGeneratedMessage*) internalGetResult { + return resultHangup; +} +- (OWSWebRTCProtosHangupBuilder*) clear { + self.resultHangup = [[OWSWebRTCProtosHangup alloc] init]; + return self; +} +- (OWSWebRTCProtosHangupBuilder*) clone { + return [OWSWebRTCProtosHangup builderWithPrototype:resultHangup]; +} +- (OWSWebRTCProtosHangup*) defaultInstance { + return [OWSWebRTCProtosHangup defaultInstance]; +} +- (OWSWebRTCProtosHangup*) build { + [self checkInitialized]; + return [self buildPartial]; +} +- (OWSWebRTCProtosHangup*) buildPartial { + OWSWebRTCProtosHangup* returnMe = resultHangup; + self.resultHangup = nil; + return returnMe; +} +- (OWSWebRTCProtosHangupBuilder*) mergeFrom:(OWSWebRTCProtosHangup*) other { + if (other == [OWSWebRTCProtosHangup defaultInstance]) { + return self; + } + if (other.hasId) { + [self setId:other.id]; + } + [self mergeUnknownFields:other.unknownFields]; + return self; +} +- (OWSWebRTCProtosHangupBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input { + return [self mergeFromCodedInputStream:input extensionRegistry:[PBExtensionRegistry emptyRegistry]]; +} +- (OWSWebRTCProtosHangupBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + PBUnknownFieldSetBuilder* unknownFields = [PBUnknownFieldSet builderWithUnknownFields:self.unknownFields]; + while (YES) { + SInt32 tag = [input readTag]; + switch (tag) { + case 0: + [self setUnknownFields:[unknownFields build]]; + return self; + default: { + if (![self parseUnknownField:input unknownFields:unknownFields extensionRegistry:extensionRegistry tag:tag]) { + [self setUnknownFields:[unknownFields build]]; + return self; + } + break; + } + case 8: { + [self setId:[input readUInt64]]; + break; + } + } + } +} +- (BOOL) hasId { + return resultHangup.hasId; +} +- (UInt64) id { + return resultHangup.id; +} +- (OWSWebRTCProtosHangupBuilder*) setId:(UInt64) value { + resultHangup.hasId = YES; + resultHangup.id = value; + return self; +} +- (OWSWebRTCProtosHangupBuilder*) clearId { + resultHangup.hasId = NO; + resultHangup.id = 0L; + return self; +} +@end + +@interface OWSWebRTCProtosVideoStreamingStatus () +@property UInt64 id; +@property BOOL enabled; +@end + +@implementation OWSWebRTCProtosVideoStreamingStatus + +- (BOOL) hasId { + return !!hasId_; +} +- (void) setHasId:(BOOL) _value_ { + hasId_ = !!_value_; +} +@synthesize id; +- (BOOL) hasEnabled { + return !!hasEnabled_; +} +- (void) setHasEnabled:(BOOL) _value_ { + hasEnabled_ = !!_value_; +} +- (BOOL) enabled { + return !!enabled_; +} +- (void) setEnabled:(BOOL) _value_ { + enabled_ = !!_value_; +} +- (instancetype) init { + if ((self = [super init])) { + self.id = 0L; + self.enabled = NO; + } + return self; +} +static OWSWebRTCProtosVideoStreamingStatus* defaultOWSWebRTCProtosVideoStreamingStatusInstance = nil; ++ (void) initialize { + if (self == [OWSWebRTCProtosVideoStreamingStatus class]) { + defaultOWSWebRTCProtosVideoStreamingStatusInstance = [[OWSWebRTCProtosVideoStreamingStatus alloc] init]; + } +} ++ (instancetype) defaultInstance { + return defaultOWSWebRTCProtosVideoStreamingStatusInstance; +} +- (instancetype) defaultInstance { + return defaultOWSWebRTCProtosVideoStreamingStatusInstance; +} +- (BOOL) isInitialized { + return YES; +} +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output { + if (self.hasId) { + [output writeUInt64:1 value:self.id]; + } + if (self.hasEnabled) { + [output writeBool:2 value:self.enabled]; + } + [self.unknownFields writeToCodedOutputStream:output]; +} +- (SInt32) serializedSize { + __block SInt32 size_ = memoizedSerializedSize; + if (size_ != -1) { + return size_; + } + + size_ = 0; + if (self.hasId) { + size_ += computeUInt64Size(1, self.id); + } + if (self.hasEnabled) { + size_ += computeBoolSize(2, self.enabled); + } + size_ += self.unknownFields.serializedSize; + memoizedSerializedSize = size_; + return size_; +} ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromData:(NSData*) data { + return (OWSWebRTCProtosVideoStreamingStatus*)[[[OWSWebRTCProtosVideoStreamingStatus builder] mergeFromData:data] build]; +} ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosVideoStreamingStatus*)[[[OWSWebRTCProtosVideoStreamingStatus builder] mergeFromData:data extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromInputStream:(NSInputStream*) input { + return (OWSWebRTCProtosVideoStreamingStatus*)[[[OWSWebRTCProtosVideoStreamingStatus builder] mergeFromInputStream:input] build]; +} ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosVideoStreamingStatus*)[[[OWSWebRTCProtosVideoStreamingStatus builder] mergeFromInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromCodedInputStream:(PBCodedInputStream*) input { + return (OWSWebRTCProtosVideoStreamingStatus*)[[[OWSWebRTCProtosVideoStreamingStatus builder] mergeFromCodedInputStream:input] build]; +} ++ (OWSWebRTCProtosVideoStreamingStatus*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosVideoStreamingStatus*)[[[OWSWebRTCProtosVideoStreamingStatus builder] mergeFromCodedInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosVideoStreamingStatusBuilder*) builder { + return [[OWSWebRTCProtosVideoStreamingStatusBuilder alloc] init]; +} ++ (OWSWebRTCProtosVideoStreamingStatusBuilder*) builderWithPrototype:(OWSWebRTCProtosVideoStreamingStatus*) prototype { + return [[OWSWebRTCProtosVideoStreamingStatus builder] mergeFrom:prototype]; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) builder { + return [OWSWebRTCProtosVideoStreamingStatus builder]; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) toBuilder { + return [OWSWebRTCProtosVideoStreamingStatus builderWithPrototype:self]; +} +- (void) writeDescriptionTo:(NSMutableString*) output withIndent:(NSString*) indent { + if (self.hasId) { + [output appendFormat:@"%@%@: %@\n", indent, @"id", [NSNumber numberWithLongLong:self.id]]; + } + if (self.hasEnabled) { + [output appendFormat:@"%@%@: %@\n", indent, @"enabled", [NSNumber numberWithBool:self.enabled]]; + } + [self.unknownFields writeDescriptionTo:output withIndent:indent]; +} +- (void) storeInDictionary:(NSMutableDictionary *)dictionary { + if (self.hasId) { + [dictionary setObject: [NSNumber numberWithLongLong:self.id] forKey: @"id"]; + } + if (self.hasEnabled) { + [dictionary setObject: [NSNumber numberWithBool:self.enabled] forKey: @"enabled"]; + } + [self.unknownFields storeInDictionary:dictionary]; +} +- (BOOL) isEqual:(id)other { + if (other == self) { + return YES; + } + if (![other isKindOfClass:[OWSWebRTCProtosVideoStreamingStatus class]]) { + return NO; + } + OWSWebRTCProtosVideoStreamingStatus *otherMessage = other; + return + self.hasId == otherMessage.hasId && + (!self.hasId || self.id == otherMessage.id) && + self.hasEnabled == otherMessage.hasEnabled && + (!self.hasEnabled || self.enabled == otherMessage.enabled) && + (self.unknownFields == otherMessage.unknownFields || (self.unknownFields != nil && [self.unknownFields isEqual:otherMessage.unknownFields])); +} +- (NSUInteger) hash { + __block NSUInteger hashCode = 7; + if (self.hasId) { + hashCode = hashCode * 31 + [[NSNumber numberWithLongLong:self.id] hash]; + } + if (self.hasEnabled) { + hashCode = hashCode * 31 + [[NSNumber numberWithBool:self.enabled] hash]; + } + hashCode = hashCode * 31 + [self.unknownFields hash]; + return hashCode; +} +@end + +@interface OWSWebRTCProtosVideoStreamingStatusBuilder() +@property (strong) OWSWebRTCProtosVideoStreamingStatus* resultVideoStreamingStatus; +@end + +@implementation OWSWebRTCProtosVideoStreamingStatusBuilder +@synthesize resultVideoStreamingStatus; +- (instancetype) init { + if ((self = [super init])) { + self.resultVideoStreamingStatus = [[OWSWebRTCProtosVideoStreamingStatus alloc] init]; + } + return self; +} +- (PBGeneratedMessage*) internalGetResult { + return resultVideoStreamingStatus; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) clear { + self.resultVideoStreamingStatus = [[OWSWebRTCProtosVideoStreamingStatus alloc] init]; + return self; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) clone { + return [OWSWebRTCProtosVideoStreamingStatus builderWithPrototype:resultVideoStreamingStatus]; +} +- (OWSWebRTCProtosVideoStreamingStatus*) defaultInstance { + return [OWSWebRTCProtosVideoStreamingStatus defaultInstance]; +} +- (OWSWebRTCProtosVideoStreamingStatus*) build { + [self checkInitialized]; + return [self buildPartial]; +} +- (OWSWebRTCProtosVideoStreamingStatus*) buildPartial { + OWSWebRTCProtosVideoStreamingStatus* returnMe = resultVideoStreamingStatus; + self.resultVideoStreamingStatus = nil; + return returnMe; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) mergeFrom:(OWSWebRTCProtosVideoStreamingStatus*) other { + if (other == [OWSWebRTCProtosVideoStreamingStatus defaultInstance]) { + return self; + } + if (other.hasId) { + [self setId:other.id]; + } + if (other.hasEnabled) { + [self setEnabled:other.enabled]; + } + [self mergeUnknownFields:other.unknownFields]; + return self; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input { + return [self mergeFromCodedInputStream:input extensionRegistry:[PBExtensionRegistry emptyRegistry]]; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + PBUnknownFieldSetBuilder* unknownFields = [PBUnknownFieldSet builderWithUnknownFields:self.unknownFields]; + while (YES) { + SInt32 tag = [input readTag]; + switch (tag) { + case 0: + [self setUnknownFields:[unknownFields build]]; + return self; + default: { + if (![self parseUnknownField:input unknownFields:unknownFields extensionRegistry:extensionRegistry tag:tag]) { + [self setUnknownFields:[unknownFields build]]; + return self; + } + break; + } + case 8: { + [self setId:[input readUInt64]]; + break; + } + case 16: { + [self setEnabled:[input readBool]]; + break; + } + } + } +} +- (BOOL) hasId { + return resultVideoStreamingStatus.hasId; +} +- (UInt64) id { + return resultVideoStreamingStatus.id; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) setId:(UInt64) value { + resultVideoStreamingStatus.hasId = YES; + resultVideoStreamingStatus.id = value; + return self; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) clearId { + resultVideoStreamingStatus.hasId = NO; + resultVideoStreamingStatus.id = 0L; + return self; +} +- (BOOL) hasEnabled { + return resultVideoStreamingStatus.hasEnabled; +} +- (BOOL) enabled { + return resultVideoStreamingStatus.enabled; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) setEnabled:(BOOL) value { + resultVideoStreamingStatus.hasEnabled = YES; + resultVideoStreamingStatus.enabled = value; + return self; +} +- (OWSWebRTCProtosVideoStreamingStatusBuilder*) clearEnabled { + resultVideoStreamingStatus.hasEnabled = NO; + resultVideoStreamingStatus.enabled = NO; + return self; +} +@end + +@interface OWSWebRTCProtosData () +@property (strong) OWSWebRTCProtosConnected* connected; +@property (strong) OWSWebRTCProtosHangup* hangup; +@property (strong) OWSWebRTCProtosVideoStreamingStatus* videoStreamingStatus; +@end + +@implementation OWSWebRTCProtosData + +- (BOOL) hasConnected { + return !!hasConnected_; +} +- (void) setHasConnected:(BOOL) _value_ { + hasConnected_ = !!_value_; +} +@synthesize connected; +- (BOOL) hasHangup { + return !!hasHangup_; +} +- (void) setHasHangup:(BOOL) _value_ { + hasHangup_ = !!_value_; +} +@synthesize hangup; +- (BOOL) hasVideoStreamingStatus { + return !!hasVideoStreamingStatus_; +} +- (void) setHasVideoStreamingStatus:(BOOL) _value_ { + hasVideoStreamingStatus_ = !!_value_; +} +@synthesize videoStreamingStatus; +- (instancetype) init { + if ((self = [super init])) { + self.connected = [OWSWebRTCProtosConnected defaultInstance]; + self.hangup = [OWSWebRTCProtosHangup defaultInstance]; + self.videoStreamingStatus = [OWSWebRTCProtosVideoStreamingStatus defaultInstance]; + } + return self; +} +static OWSWebRTCProtosData* defaultOWSWebRTCProtosDataInstance = nil; ++ (void) initialize { + if (self == [OWSWebRTCProtosData class]) { + defaultOWSWebRTCProtosDataInstance = [[OWSWebRTCProtosData alloc] init]; + } +} ++ (instancetype) defaultInstance { + return defaultOWSWebRTCProtosDataInstance; +} +- (instancetype) defaultInstance { + return defaultOWSWebRTCProtosDataInstance; +} +- (BOOL) isInitialized { + return YES; +} +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output { + if (self.hasConnected) { + [output writeMessage:1 value:self.connected]; + } + if (self.hasHangup) { + [output writeMessage:2 value:self.hangup]; + } + if (self.hasVideoStreamingStatus) { + [output writeMessage:3 value:self.videoStreamingStatus]; + } + [self.unknownFields writeToCodedOutputStream:output]; +} +- (SInt32) serializedSize { + __block SInt32 size_ = memoizedSerializedSize; + if (size_ != -1) { + return size_; + } + + size_ = 0; + if (self.hasConnected) { + size_ += computeMessageSize(1, self.connected); + } + if (self.hasHangup) { + size_ += computeMessageSize(2, self.hangup); + } + if (self.hasVideoStreamingStatus) { + size_ += computeMessageSize(3, self.videoStreamingStatus); + } + size_ += self.unknownFields.serializedSize; + memoizedSerializedSize = size_; + return size_; +} ++ (OWSWebRTCProtosData*) parseFromData:(NSData*) data { + return (OWSWebRTCProtosData*)[[[OWSWebRTCProtosData builder] mergeFromData:data] build]; +} ++ (OWSWebRTCProtosData*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosData*)[[[OWSWebRTCProtosData builder] mergeFromData:data extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosData*) parseFromInputStream:(NSInputStream*) input { + return (OWSWebRTCProtosData*)[[[OWSWebRTCProtosData builder] mergeFromInputStream:input] build]; +} ++ (OWSWebRTCProtosData*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosData*)[[[OWSWebRTCProtosData builder] mergeFromInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosData*) parseFromCodedInputStream:(PBCodedInputStream*) input { + return (OWSWebRTCProtosData*)[[[OWSWebRTCProtosData builder] mergeFromCodedInputStream:input] build]; +} ++ (OWSWebRTCProtosData*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (OWSWebRTCProtosData*)[[[OWSWebRTCProtosData builder] mergeFromCodedInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (OWSWebRTCProtosDataBuilder*) builder { + return [[OWSWebRTCProtosDataBuilder alloc] init]; +} ++ (OWSWebRTCProtosDataBuilder*) builderWithPrototype:(OWSWebRTCProtosData*) prototype { + return [[OWSWebRTCProtosData builder] mergeFrom:prototype]; +} +- (OWSWebRTCProtosDataBuilder*) builder { + return [OWSWebRTCProtosData builder]; +} +- (OWSWebRTCProtosDataBuilder*) toBuilder { + return [OWSWebRTCProtosData builderWithPrototype:self]; +} +- (void) writeDescriptionTo:(NSMutableString*) output withIndent:(NSString*) indent { + if (self.hasConnected) { + [output appendFormat:@"%@%@ {\n", indent, @"connected"]; + [self.connected writeDescriptionTo:output + withIndent:[NSString stringWithFormat:@"%@ ", indent]]; + [output appendFormat:@"%@}\n", indent]; + } + if (self.hasHangup) { + [output appendFormat:@"%@%@ {\n", indent, @"hangup"]; + [self.hangup writeDescriptionTo:output + withIndent:[NSString stringWithFormat:@"%@ ", indent]]; + [output appendFormat:@"%@}\n", indent]; + } + if (self.hasVideoStreamingStatus) { + [output appendFormat:@"%@%@ {\n", indent, @"videoStreamingStatus"]; + [self.videoStreamingStatus writeDescriptionTo:output + withIndent:[NSString stringWithFormat:@"%@ ", indent]]; + [output appendFormat:@"%@}\n", indent]; + } + [self.unknownFields writeDescriptionTo:output withIndent:indent]; +} +- (void) storeInDictionary:(NSMutableDictionary *)dictionary { + if (self.hasConnected) { + NSMutableDictionary *messageDictionary = [NSMutableDictionary dictionary]; + [self.connected storeInDictionary:messageDictionary]; + [dictionary setObject:[NSDictionary dictionaryWithDictionary:messageDictionary] forKey:@"connected"]; + } + if (self.hasHangup) { + NSMutableDictionary *messageDictionary = [NSMutableDictionary dictionary]; + [self.hangup storeInDictionary:messageDictionary]; + [dictionary setObject:[NSDictionary dictionaryWithDictionary:messageDictionary] forKey:@"hangup"]; + } + if (self.hasVideoStreamingStatus) { + NSMutableDictionary *messageDictionary = [NSMutableDictionary dictionary]; + [self.videoStreamingStatus storeInDictionary:messageDictionary]; + [dictionary setObject:[NSDictionary dictionaryWithDictionary:messageDictionary] forKey:@"videoStreamingStatus"]; + } + [self.unknownFields storeInDictionary:dictionary]; +} +- (BOOL) isEqual:(id)other { + if (other == self) { + return YES; + } + if (![other isKindOfClass:[OWSWebRTCProtosData class]]) { + return NO; + } + OWSWebRTCProtosData *otherMessage = other; + return + self.hasConnected == otherMessage.hasConnected && + (!self.hasConnected || [self.connected isEqual:otherMessage.connected]) && + self.hasHangup == otherMessage.hasHangup && + (!self.hasHangup || [self.hangup isEqual:otherMessage.hangup]) && + self.hasVideoStreamingStatus == otherMessage.hasVideoStreamingStatus && + (!self.hasVideoStreamingStatus || [self.videoStreamingStatus isEqual:otherMessage.videoStreamingStatus]) && + (self.unknownFields == otherMessage.unknownFields || (self.unknownFields != nil && [self.unknownFields isEqual:otherMessage.unknownFields])); +} +- (NSUInteger) hash { + __block NSUInteger hashCode = 7; + if (self.hasConnected) { + hashCode = hashCode * 31 + [self.connected hash]; + } + if (self.hasHangup) { + hashCode = hashCode * 31 + [self.hangup hash]; + } + if (self.hasVideoStreamingStatus) { + hashCode = hashCode * 31 + [self.videoStreamingStatus hash]; + } + hashCode = hashCode * 31 + [self.unknownFields hash]; + return hashCode; +} +@end + +@interface OWSWebRTCProtosDataBuilder() +@property (strong) OWSWebRTCProtosData* resultData; +@end + +@implementation OWSWebRTCProtosDataBuilder +@synthesize resultData; +- (instancetype) init { + if ((self = [super init])) { + self.resultData = [[OWSWebRTCProtosData alloc] init]; + } + return self; +} +- (PBGeneratedMessage*) internalGetResult { + return resultData; +} +- (OWSWebRTCProtosDataBuilder*) clear { + self.resultData = [[OWSWebRTCProtosData alloc] init]; + return self; +} +- (OWSWebRTCProtosDataBuilder*) clone { + return [OWSWebRTCProtosData builderWithPrototype:resultData]; +} +- (OWSWebRTCProtosData*) defaultInstance { + return [OWSWebRTCProtosData defaultInstance]; +} +- (OWSWebRTCProtosData*) build { + [self checkInitialized]; + return [self buildPartial]; +} +- (OWSWebRTCProtosData*) buildPartial { + OWSWebRTCProtosData* returnMe = resultData; + self.resultData = nil; + return returnMe; +} +- (OWSWebRTCProtosDataBuilder*) mergeFrom:(OWSWebRTCProtosData*) other { + if (other == [OWSWebRTCProtosData defaultInstance]) { + return self; + } + if (other.hasConnected) { + [self mergeConnected:other.connected]; + } + if (other.hasHangup) { + [self mergeHangup:other.hangup]; + } + if (other.hasVideoStreamingStatus) { + [self mergeVideoStreamingStatus:other.videoStreamingStatus]; + } + [self mergeUnknownFields:other.unknownFields]; + return self; +} +- (OWSWebRTCProtosDataBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input { + return [self mergeFromCodedInputStream:input extensionRegistry:[PBExtensionRegistry emptyRegistry]]; +} +- (OWSWebRTCProtosDataBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + PBUnknownFieldSetBuilder* unknownFields = [PBUnknownFieldSet builderWithUnknownFields:self.unknownFields]; + while (YES) { + SInt32 tag = [input readTag]; + switch (tag) { + case 0: + [self setUnknownFields:[unknownFields build]]; + return self; + default: { + if (![self parseUnknownField:input unknownFields:unknownFields extensionRegistry:extensionRegistry tag:tag]) { + [self setUnknownFields:[unknownFields build]]; + return self; + } + break; + } + case 10: { + OWSWebRTCProtosConnectedBuilder* subBuilder = [OWSWebRTCProtosConnected builder]; + if (self.hasConnected) { + [subBuilder mergeFrom:self.connected]; + } + [input readMessage:subBuilder extensionRegistry:extensionRegistry]; + [self setConnected:[subBuilder buildPartial]]; + break; + } + case 18: { + OWSWebRTCProtosHangupBuilder* subBuilder = [OWSWebRTCProtosHangup builder]; + if (self.hasHangup) { + [subBuilder mergeFrom:self.hangup]; + } + [input readMessage:subBuilder extensionRegistry:extensionRegistry]; + [self setHangup:[subBuilder buildPartial]]; + break; + } + case 26: { + OWSWebRTCProtosVideoStreamingStatusBuilder* subBuilder = [OWSWebRTCProtosVideoStreamingStatus builder]; + if (self.hasVideoStreamingStatus) { + [subBuilder mergeFrom:self.videoStreamingStatus]; + } + [input readMessage:subBuilder extensionRegistry:extensionRegistry]; + [self setVideoStreamingStatus:[subBuilder buildPartial]]; + break; + } + } + } +} +- (BOOL) hasConnected { + return resultData.hasConnected; +} +- (OWSWebRTCProtosConnected*) connected { + return resultData.connected; +} +- (OWSWebRTCProtosDataBuilder*) setConnected:(OWSWebRTCProtosConnected*) value { + resultData.hasConnected = YES; + resultData.connected = value; + return self; +} +- (OWSWebRTCProtosDataBuilder*) setConnectedBuilder:(OWSWebRTCProtosConnectedBuilder*) builderForValue { + return [self setConnected:[builderForValue build]]; +} +- (OWSWebRTCProtosDataBuilder*) mergeConnected:(OWSWebRTCProtosConnected*) value { + if (resultData.hasConnected && + resultData.connected != [OWSWebRTCProtosConnected defaultInstance]) { + resultData.connected = + [[[OWSWebRTCProtosConnected builderWithPrototype:resultData.connected] mergeFrom:value] buildPartial]; + } else { + resultData.connected = value; + } + resultData.hasConnected = YES; + return self; +} +- (OWSWebRTCProtosDataBuilder*) clearConnected { + resultData.hasConnected = NO; + resultData.connected = [OWSWebRTCProtosConnected defaultInstance]; + return self; +} +- (BOOL) hasHangup { + return resultData.hasHangup; +} +- (OWSWebRTCProtosHangup*) hangup { + return resultData.hangup; +} +- (OWSWebRTCProtosDataBuilder*) setHangup:(OWSWebRTCProtosHangup*) value { + resultData.hasHangup = YES; + resultData.hangup = value; + return self; +} +- (OWSWebRTCProtosDataBuilder*) setHangupBuilder:(OWSWebRTCProtosHangupBuilder*) builderForValue { + return [self setHangup:[builderForValue build]]; +} +- (OWSWebRTCProtosDataBuilder*) mergeHangup:(OWSWebRTCProtosHangup*) value { + if (resultData.hasHangup && + resultData.hangup != [OWSWebRTCProtosHangup defaultInstance]) { + resultData.hangup = + [[[OWSWebRTCProtosHangup builderWithPrototype:resultData.hangup] mergeFrom:value] buildPartial]; + } else { + resultData.hangup = value; + } + resultData.hasHangup = YES; + return self; +} +- (OWSWebRTCProtosDataBuilder*) clearHangup { + resultData.hasHangup = NO; + resultData.hangup = [OWSWebRTCProtosHangup defaultInstance]; + return self; +} +- (BOOL) hasVideoStreamingStatus { + return resultData.hasVideoStreamingStatus; +} +- (OWSWebRTCProtosVideoStreamingStatus*) videoStreamingStatus { + return resultData.videoStreamingStatus; +} +- (OWSWebRTCProtosDataBuilder*) setVideoStreamingStatus:(OWSWebRTCProtosVideoStreamingStatus*) value { + resultData.hasVideoStreamingStatus = YES; + resultData.videoStreamingStatus = value; + return self; +} +- (OWSWebRTCProtosDataBuilder*) setVideoStreamingStatusBuilder:(OWSWebRTCProtosVideoStreamingStatusBuilder*) builderForValue { + return [self setVideoStreamingStatus:[builderForValue build]]; +} +- (OWSWebRTCProtosDataBuilder*) mergeVideoStreamingStatus:(OWSWebRTCProtosVideoStreamingStatus*) value { + if (resultData.hasVideoStreamingStatus && + resultData.videoStreamingStatus != [OWSWebRTCProtosVideoStreamingStatus defaultInstance]) { + resultData.videoStreamingStatus = + [[[OWSWebRTCProtosVideoStreamingStatus builderWithPrototype:resultData.videoStreamingStatus] mergeFrom:value] buildPartial]; + } else { + resultData.videoStreamingStatus = value; + } + resultData.hasVideoStreamingStatus = YES; + return self; +} +- (OWSWebRTCProtosDataBuilder*) clearVideoStreamingStatus { + resultData.hasVideoStreamingStatus = NO; + resultData.videoStreamingStatus = [OWSWebRTCProtosVideoStreamingStatus defaultInstance]; + return self; +} +@end + + +// @@protoc_insertion_point(global_scope) diff --git a/Signal/src/call/PeerConnectionClient.swift b/Signal/src/call/PeerConnectionClient.swift new file mode 100644 index 000000000..448879821 --- /dev/null +++ b/Signal/src/call/PeerConnectionClient.swift @@ -0,0 +1,313 @@ +// Created by Michael Kirk on 11/29/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation +import PromiseKit +import WebRTC + +let kAudioTrackType = kRTCMediaStreamTrackKindAudio +let kVideoTrackType = kRTCMediaStreamTrackKindVideo + +class PeerConnectionClient: NSObject, SignalCallAudioManager { + + let TAG = "[PeerConnectionClient]" + enum Identifiers: String { + case mediaStream = "ARDAMS", + videoTrack = "ARDAMSv0", + audioTrack = "ARDAMSa0", + dataChannelSignaling = "signaling" + } + + // Connection + + private let peerConnection: RTCPeerConnection + private let iceServers: [RTCIceServer] + private let connectionConstraints: RTCMediaConstraints + private let configuration: RTCConfiguration + private let factory = RTCPeerConnectionFactory() + + // DataChannel + + // peerConnection expects to be the final owner of dataChannel. Otherwise, a crash when peerConnection deallocs + // `dataChannel` is public because on incoming calls, we don't explicitly create the channel, rather `CallService` + // assigns it when the channel is discovered due to the caller having created it. + public var dataChannel: RTCDataChannel? + + // Audio + + private var audioSender: RTCRtpSender? + private var audioTrack: RTCAudioTrack? + private var audioConstraints: RTCMediaConstraints + + // Video + + private var videoSender: RTCRtpSender? + private var videoTrack: RTCVideoTrack? + private var cameraConstraints: RTCMediaConstraints + + init(iceServers: [RTCIceServer], peerConnectionDelegate: RTCPeerConnectionDelegate) { + self.iceServers = iceServers + + configuration = RTCConfiguration() + configuration.iceServers = iceServers + configuration.bundlePolicy = .maxBundle + configuration.rtcpMuxPolicy = .require + + let connectionConstraintsDict = ["DtlsSrtpKeyAgreement": "true"] + connectionConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: connectionConstraintsDict) + peerConnection = factory.peerConnection(with: configuration, + constraints: connectionConstraints, + delegate: peerConnectionDelegate) + + audioConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints:nil) + cameraConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) + super.init() + + createAudioSender() + createVideoSender() + } + + // MARK: - Media Streams + + public func createSignalingDataChannel(delegate: RTCDataChannelDelegate) { + let dataChannel = peerConnection.dataChannel(forLabel: Identifiers.dataChannelSignaling.rawValue, + configuration: RTCDataChannelConfiguration()) + dataChannel.delegate = delegate + + self.dataChannel = dataChannel + } + + // MARK: Video + + fileprivate func createVideoSender() { + guard !Platform.isSimulator else { + Logger.warn("\(TAG) Refusing to create local video track on simulator.") + return + } + + let videoSource = factory.avFoundationVideoSource(with: cameraConstraints) + let videoTrack = factory.videoTrack(with: videoSource, trackId: Identifiers.videoTrack.rawValue) + self.videoTrack = videoTrack + + // Disable by default until call is connected. + // FIXME - do we require mic permissions at this point? + // if so maybe it would be better to not even add the track until the call is connected + // instead of creating it and disabling it. + videoTrack.isEnabled = false + + // Occasionally seeing this crash on the next line, after a *second* call: +// -[__NSCFNumber length]: unrecognized selector sent to instance 0x1562c610 + // Seems like either videoKind or videoStreamId (both of which are Strings) is being GC'd prematurely. + // Not sure why, but assigned the value to local vars above in hopes of avoiding it. +// let videoKind = kRTCMediaStreamTrackKindVideo + + let videoSender = peerConnection.sender(withKind: kVideoTrackType, streamId: Identifiers.mediaStream.rawValue) + videoSender.track = videoTrack + self.videoSender = videoSender + } + + public func setVideoEnabled(enabled: Bool) { + guard let videoTrack = self.videoTrack else { + let action = enabled ? "enable" : "disable" + Logger.error("\(TAG)) trying to \(action) videoTack which doesn't exist") + return + } + + videoTrack.isEnabled = enabled + } + + // MARK: Audio + + fileprivate func createAudioSender() { + let audioSource = factory.audioSource(with: self.audioConstraints) + + let audioTrack = factory.audioTrack(with: audioSource, trackId: Identifiers.audioTrack.rawValue) + self.audioTrack = audioTrack + + // Disable by default until call is connected. + // FIXME - do we require mic permissions at this point? + // if so maybe it would be better to not even add the track until the call is connected + // instead of creating it and disabling it. + audioTrack.isEnabled = false + + let audioSender = peerConnection.sender(withKind: kAudioTrackType, streamId: Identifiers.mediaStream.rawValue) + audioSender.track = audioTrack + self.audioSender = audioSender + } + + public func setAudioEnabled(enabled: Bool) { + guard let audioTrack = self.audioTrack else { + let action = enabled ? "enable" : "disable" + Logger.error("\(TAG) trying to \(action) audioTrack which doesn't exist.") + return + } + + audioTrack.isEnabled = enabled + } + + // MARK: - Session negotiation + + var defaultOfferConstraints: RTCMediaConstraints { + let mandatoryConstraints = [ + "OfferToReceiveAudio": "true", + "OfferToReceiveVideo" : "true" + ] + return RTCMediaConstraints(mandatoryConstraints:mandatoryConstraints, optionalConstraints:nil) + } + + func createOffer() -> Promise { + return Promise { fulfill, reject in + peerConnection.offer(for: self.defaultOfferConstraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in + guard error == nil else { + reject(error!) + return + } + + guard let sessionDescription = sdp else { + Logger.error("\(self.TAG) No session description was obtained, even though there was no error reported.") + let error = OWSErrorMakeUnableToProcessServerResponseError() + reject(error) + return + } + + fulfill(HardenedRTCSessionDescription(rtcSessionDescription: sessionDescription)) + }) + } + } + + func setLocalSessionDescription(_ sessionDescription: HardenedRTCSessionDescription) -> Promise { + return PromiseKit.wrap { + Logger.verbose("\(self.TAG) setting local session description: \(sessionDescription)") + peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription, completionHandler: $0) + } + } + + func negotiateSessionDescription(remoteDescription: RTCSessionDescription, constraints: RTCMediaConstraints) -> Promise { + return firstly { + return self.setRemoteSessionDescription(remoteDescription) + }.then { + return self.negotiateAnswerSessionDescription(constraints: constraints) + } + } + + func setRemoteSessionDescription(_ sessionDescription: RTCSessionDescription) -> Promise { + return PromiseKit.wrap { + Logger.verbose("\(self.TAG) setting remote description: \(sessionDescription)") + peerConnection.setRemoteDescription(sessionDescription, completionHandler: $0) + } + } + + func negotiateAnswerSessionDescription(constraints: RTCMediaConstraints) -> Promise { + return Promise { fulfill, reject in + Logger.debug("\(self.TAG) negotiating answer session.") + + peerConnection.answer(for: constraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in + guard error == nil else { + reject(error!) + return + } + + guard let sessionDescription = sdp else { + Logger.error("\(self.TAG) unexpected empty session description, even though no error was reported.") + let error = OWSErrorMakeUnableToProcessServerResponseError() + reject(error) + return + } + + let hardenedSessionDescription = HardenedRTCSessionDescription(rtcSessionDescription: sessionDescription) + + self.setLocalSessionDescription(hardenedSessionDescription).then { + fulfill(hardenedSessionDescription) + }.catch { error in + reject(error) + } + }) + } + } + + func addIceCandidate(_ candidate: RTCIceCandidate) { + Logger.debug("\(TAG) adding candidate") + self.peerConnection.add(candidate) + } + + func terminate() { +// Some notes on preventing crashes while disposing of peerConnection for video calls +// from: https://groups.google.com/forum/#!searchin/discuss-webrtc/objc$20crash$20dealloc%7Csort:relevance/discuss-webrtc/7D-vk5yLjn8/rBW2D6EW4GYJ +// The sequence to make it work appears to be +// +// [capturer stop]; // I had to add this as a method to RTCVideoCapturer +// [localRenderer stop]; +// [remoteRenderer stop]; +// [peerConnection close]; + + // audioTrack is a strong property because we need access to it to mute/unmute, but I was seeing it + // become nil when it was only a weak property. So we retain it and manually nil the reference here, because + // we are likely to crash if we retain any peer connection properties when the peerconnection is released + audioTrack = nil + videoTrack = nil + dataChannel = nil + audioSender = nil + videoSender = nil + + peerConnection.close() + } + + // MARK: Data Channel + + func sendDataChannelMessage(data: Data) -> Bool { + guard let dataChannel = self.dataChannel else { + Logger.error("\(TAG) in \(#function) ignoring sending \(data) for nil dataChannel") + return false + } + + let buffer = RTCDataBuffer(data: data, isBinary: false) + return dataChannel.sendData(buffer) + } + + // MARK: CallAudioManager + + internal func configureAudioSession() { + Logger.warn("TODO: \(#function)") + } + + internal func stopAudio() { + Logger.warn("TODO: \(#function)") + } + + internal func startAudio() { + guard let audioSender = self.audioSender else { + Logger.error("\(TAG) ignoring \(#function) because audioSender was nil") + return + } + + Logger.warn("TODO: \(#function)") + } +} + +/** + * Restrict an RTCSessionDescription to more secure parameters + */ +class HardenedRTCSessionDescription { + let rtcSessionDescription: RTCSessionDescription + var sdp: String { return rtcSessionDescription.sdp } + + init(rtcSessionDescription: RTCSessionDescription) { + self.rtcSessionDescription = HardenedRTCSessionDescription.harden(rtcSessionDescription: rtcSessionDescription) + } + + /** + * Set some more secure parameters for the session description + */ + class func harden(rtcSessionDescription: RTCSessionDescription) -> RTCSessionDescription { + var description = rtcSessionDescription.sdp + + // Enforce Constant bit rate. + description = description.replacingOccurrences(of: "(a=fmtp:111 ((?!cbr=).)*)\r?\n", with: "$1;cbr=1\r\n") + + // Strip plaintext audio-level details + // https://tools.ietf.org/html/rfc6464 + description = description.replacingOccurrences(of: ".+urn:ietf:params:rtp-hdrext:ssrc-audio-level.*\r?\n", with: "") + + return RTCSessionDescription.init(type: rtcSessionDescription.type, sdp: description) + } +} diff --git a/Signal/src/call/RecentCallManager.m b/Signal/src/call/RecentCallManager.m index 2ab353c84..25821abda 100644 --- a/Signal/src/call/RecentCallManager.m +++ b/Signal/src/call/RecentCallManager.m @@ -102,7 +102,7 @@ [call saveWithTransaction:transaction]; - NotificationsManager *manager = [TextSecureKitEnv sharedEnv].notificationsManager; + NotificationsManager *manager = [Environment getCurrent].notificationsManager; [manager notifyUserForCall:call inThread:thread]; }]; } diff --git a/Signal/src/call/SignalCall.swift b/Signal/src/call/SignalCall.swift new file mode 100644 index 000000000..ed62c2d01 --- /dev/null +++ b/Signal/src/call/SignalCall.swift @@ -0,0 +1,69 @@ +// Created by Michael Kirk on 12/7/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation + +enum CallState: String { + case idle + case dialing + case answering + case remoteRinging + case localRinging + case connected + case localFailure // terminal + case localHangup // terminal + case remoteHangup // terminal + case remoteBusy // terminal +} + +/** + * Data model for a WebRTC backed voice/video call. + */ +@objc class SignalCall: NSObject { + + let TAG = "[SignalCall]" + + var state: CallState { + didSet { + Logger.debug("\(TAG) state changed: \(oldValue) -> \(state)") + stateDidChange?(state) + } + } + + let signalingId: UInt64 + let remotePhoneNumber: String + let localId: UUID + var hasVideo = false + var error: CallError? + + var stateDidChange: ((_ newState: CallState) -> Void)? + + init(localId: UUID, signalingId: UInt64, state: CallState, remotePhoneNumber: String) { + self.localId = localId + self.signalingId = signalingId + self.state = state + self.remotePhoneNumber = remotePhoneNumber + } + + class func outgoingCall(localId: UUID, remotePhoneNumber: String) -> SignalCall { + return SignalCall(localId: localId, signalingId: UInt64.ows_random(), state: .dialing, remotePhoneNumber: remotePhoneNumber) + } + + class func incomingCall(localId: UUID, remotePhoneNumber: String, signalingId: UInt64) -> SignalCall { + return SignalCall(localId: localId, signalingId: signalingId, state: .answering, remotePhoneNumber: remotePhoneNumber) + } + + // MARK: Equatable + static func == (lhs: SignalCall, rhs: SignalCall) -> Bool { + return lhs.localId == rhs.localId + } + +} + +fileprivate extension UInt64 { + static func ows_random() -> UInt64 { + var random: UInt64 = 0 + arc4random_buf(&random, MemoryLayout.size(ofValue: random)) + return random + } +} diff --git a/Signal/src/call/Speakerbox/CallKitCallManager.swift b/Signal/src/call/Speakerbox/CallKitCallManager.swift new file mode 100644 index 000000000..582cfb5ea --- /dev/null +++ b/Signal/src/call/Speakerbox/CallKitCallManager.swift @@ -0,0 +1,91 @@ +// Created by Michael Kirk on 12/13/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import UIKit +import CallKit + +/** + * Based on SpeakerboxCallManager, from the Apple CallKit Example app. Though, it's responsibilities are mostly mirrored (and delegated from) CallUIAdapter? + * TODO: Would it simplify things to merge this into CallKitCallUIAdaptee? + */ +@available(iOS 10.0, *) +final class CallKitCallManager: NSObject { + + let callController = CXCallController() + + // MARK: Actions + + func startCall(_ call: SignalCall) { + let handle = CXHandle(type: .phoneNumber, value: call.remotePhoneNumber) + let startCallAction = CXStartCallAction(call: call.localId, handle: handle) + + startCallAction.isVideo = call.hasVideo + + let transaction = CXTransaction() + transaction.addAction(startCallAction) + + requestTransaction(transaction) + } + + func end(call: SignalCall) { + let endCallAction = CXEndCallAction(call: call.localId) + let transaction = CXTransaction() + transaction.addAction(endCallAction) + + requestTransaction(transaction) + } + + func setHeld(call: SignalCall, onHold: Bool) { + let setHeldCallAction = CXSetHeldCallAction(call: call.localId, onHold: onHold) + let transaction = CXTransaction() + transaction.addAction(setHeldCallAction) + + requestTransaction(transaction) + } + + private func requestTransaction(_ transaction: CXTransaction) { + callController.request(transaction) { error in + if let error = error { + print("Error requesting transaction: \(error)") + } else { + print("Requested transaction successfully") + } + } + } + + // MARK: Call Management + + private(set) var calls = [SignalCall]() + + func callWithLocalId(_ localId: UUID) -> SignalCall? { + guard let index = calls.index(where: { $0.localId == localId }) else { + return nil + } + return calls[index] + } + + func addCall(_ call: SignalCall) { + calls.append(call) + } + + func removeCall(_ call: SignalCall) { + calls.removeFirst(where: { $0 === call }) + } + + func removeAllCalls() { + calls.removeAll() + } +} + + +fileprivate extension Array { + + mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows { + guard let index = try index(where: predicate) else { + return + } + + remove(at: index) + } + +} diff --git a/Signal/src/call/Speakerbox/CallKitCallUIAdaptee.swift b/Signal/src/call/Speakerbox/CallKitCallUIAdaptee.swift new file mode 100644 index 000000000..7cd78f1f2 --- /dev/null +++ b/Signal/src/call/Speakerbox/CallKitCallUIAdaptee.swift @@ -0,0 +1,58 @@ +// Created by Michael Kirk on 1/3/17. +// Copyright © 2017 Open Whisper Systems. All rights reserved. + +import Foundation + +@available(iOS 10.0, *) +class CallKitCallUIAdaptee: CallUIAdaptee { + + let TAG = "[CallKitCallUIAdaptee]" + let providerDelegate: CallKitProviderDelegate + let callManager: CallKitCallManager + let notificationsAdapter: CallNotificationsAdapter + + init(callService: CallService, notificationsAdapter: CallNotificationsAdapter) { + self.callManager = CallKitCallManager() + self.providerDelegate = CallKitProviderDelegate(callManager: callManager, callService: callService) + self.notificationsAdapter = notificationsAdapter + } + + public func startOutgoingCall(_ call: SignalCall) { + // Add the new outgoing call to the app's list of calls. + // So we can find it in the provider delegate callbacks. + self.callManager.addCall(call) + providerDelegate.callManager.startCall(call) + } + + public func reportIncomingCall(_ call: SignalCall, callerName: String, audioManager: SignalCallAudioManager) { + // FIXME weird to pass the audio manager in here. + // Crux is, the peerconnectionclient is what controls the audio channel. + // But a peerconnectionclient is per call. + // While this providerDelegate is an app singleton. + providerDelegate.audioManager = audioManager + + providerDelegate.reportIncomingCall(call) { error in + if error == nil { + Logger.debug("\(self.TAG) successfully reported incoming call.") + } else { + Logger.error("\(self.TAG) providerDelegate.reportIncomingCall failed with error: \(error)") + } + } + } + + public func reportMissedCall(_ call: SignalCall, callerName: String) { + notificationsAdapter.presentMissedCall(call, callerName: callerName) + } + + func answerCall(_ call: SignalCall) { + showCall(call) + } + + public func declineCall(_ call: SignalCall) { + callManager.end(call: call) + } + + func endCall(_ call: SignalCall) { + callManager.end(call: call) + } +} diff --git a/Signal/src/call/Speakerbox/CallKitProviderDelegate.swift b/Signal/src/call/Speakerbox/CallKitProviderDelegate.swift new file mode 100644 index 000000000..78d36e90c --- /dev/null +++ b/Signal/src/call/Speakerbox/CallKitProviderDelegate.swift @@ -0,0 +1,259 @@ +// Created by Michael Kirk on 12/23/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation +import UIKit +import CallKit +import AVFoundation + +@available(iOS 10.0, *) +final class CallKitProviderDelegate: NSObject, CXProviderDelegate { + + let TAG = "[CallKitProviderDelegate]" + let callManager: CallKitCallManager + let callService: CallService + private let provider: CXProvider + + // FIXME - I might be thinking about this the wrong way. + // It seems like the provider delegate wants to stop/start the audio recording + // process, but the ProviderDelegate is an app singleton + // and the audio recording process is currently controlled (I think) by + // the PeerConnectionClient instance, which is one per call (NOT a singleton). + // It seems like a mess to reconcile this difference in cardinality. But... here we are. + var audioManager: SignalCallAudioManager? + + init(callManager: CallKitCallManager, callService: CallService) { + self.callService = callService + self.callManager = callManager + provider = CXProvider(configuration: type(of: self).providerConfiguration) + + super.init() + + provider.setDelegate(self, queue: nil) + } + + /// The app's provider configuration, representing its CallKit capabilities + static var providerConfiguration: CXProviderConfiguration { + let localizedName = NSLocalizedString("APPLICATION_NAME", comment: "Name of application") + let providerConfiguration = CXProviderConfiguration(localizedName: localizedName) + + providerConfiguration.supportsVideo = true + + providerConfiguration.maximumCallsPerCallGroup = 1 + + providerConfiguration.supportedHandleTypes = [.phoneNumber] + + if let iconMaskImage = UIImage(named: "IconMask") { + providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation(iconMaskImage) + } + + providerConfiguration.ringtoneSound = "r.caf" + + return providerConfiguration + } + + /// Use CXProvider to report the incoming call to the system + func reportIncomingCall(_ call: SignalCall, completion: ((NSError?) -> Void)? = nil) { + // Construct a CXCallUpdate describing the incoming call, including the caller. + let update = CXCallUpdate() + update.remoteHandle = CXHandle(type: .phoneNumber, value: call.remotePhoneNumber) + update.hasVideo = call.hasVideo + + // Report the incoming call to the system + provider.reportNewIncomingCall(with: call.localId, update: update) { error in + /* + Only add incoming call to the app's list of calls if the call was allowed (i.e. there was no error) + since calls may be "denied" for various legitimate reasons. See CXErrorCodeIncomingCallError. + */ + if error == nil { + self.callManager.addCall(call) + } + + completion?(error as? NSError) + } + } + + // MARK: CXProviderDelegate + + func providerDidReset(_ provider: CXProvider) { + Logger.debug("\(TAG) in \(#function)") + + stopAudio() + + /* + End any ongoing calls if the provider resets, and remove them from the app's list of calls, + since they are no longer valid. + */ + // This is a little goofy because CallKit assumes multiple calls (maybe some are held, or group calls?) + // but CallService currently just has one call at a time. + for call in callManager.calls { + callService.handleFailedCall(error: .providerReset) + } + + // Remove all calls from the app's list of calls. + callManager.removeAllCalls() + } + + func provider(_ provider: CXProvider, perform action: CXStartCallAction) { + Logger.debug("\(TAG) in \(#function) CXStartCallAction") + + /* + Configure the audio session, but do not start call audio here, since it must be done once + the audio session has been activated by the system after having its priority elevated. + */ + configureAudioSession() + + // TODO does this work when `action.handle.value` is not in e164 format, e.g. if called via intent? + guard let call = callManager.callWithLocalId(action.callUUID) else { + Logger.error("\(TAG) unable to find call in \(#function)") + return + } + + CallService.signalingQueue.async { + self.callService.handleOutgoingCall(call).then { + action.fulfill() + }.catch { error in + self.callManager.removeCall(call) + action.fail() + } + } + +// TODO FIXME +// /* +// Set callback blocks for significant events in the call's lifecycle, so that the CXProvider may be updated +// to reflect the updated state. +// */ +// call.hasStartedConnectingDidChange = { [weak self] in +// self?.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate) +// } +// call.hasConnectedDidChange = { [weak self] in +// self?.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectDate) +// } + + } + + func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { + Logger.debug("\(TAG) Received \(#function) CXAnswerCallAction") + // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID + guard let call = callManager.callWithLocalId(action.callUUID) else { + action.fail() + return + } + +// Original Speakerbox implementation +// /* +// Configure the audio session, but do not start call audio here, since it must be done once +// the audio session has been activated by the system after having its priority elevated. +// */ +// configureAudioSession() +// +// // Trigger the call to be answered via the underlying network service. +// call.answerSpeakerboxCall() + + // Synchronous to ensure work is done before call is displayed as "answered" + CallService.signalingQueue.sync { + self.callService.handleAnswerCall(call) + } + + // Signal to the system that the action has been successfully performed. + action.fulfill() + } + + func provider(_ provider: CXProvider, perform action: CXEndCallAction) { + Logger.debug("\(TAG) Received \(#function) CXEndCallAction") + guard let call = callManager.callWithLocalId(action.callUUID) else { + action.fail() + return + } + +// Original Speakerbox implementation +// // Stop call audio whenever ending the call. +// stopAudio() +// // Trigger the call to be ended via the underlying network service. +// call.endSpeakerboxCall() + + // Synchronous to ensure call is terminated before call is displayed as "ended" + CallService.signalingQueue.sync { + self.callService.handleLocalHungupCall(call) + } + + // Signal to the system that the action has been successfully performed. + action.fulfill() + + // Remove the ended call from the app's list of calls. + callManager.removeCall(call) + } + + func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) { + Logger.debug("\(TAG) Received \(#function) CXSetHeldCallAction") + guard let call = callManager.callWithLocalId(action.callUUID) else { + action.fail() + return + } + Logger.warn("TODO, set held call: \(call)") + + // TODO FIXME +// // Update the SpeakerboxCall's underlying hold state. +// call.isOnHold = action.isOnHold +// +// // Stop or start audio in response to holding or unholding the call. +// if call.isOnHold { +// stopAudio() +// } else { +// startAudio() +// } + + // Signal to the system that the action has been successfully performed. + action.fulfill() + } + + func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) { + Logger.debug("\(TAG) Timed out \(#function)") + + // React to the action timeout if necessary, such as showing an error UI. + } + + func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { + Logger.debug("\(TAG) Received \(#function)") + + startAudio() + } + + func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { + Logger.debug("\(TAG) Received \(#function)") + + /* + Restart any non-call related audio now that the app's audio session has been + de-activated after having its priority restored to normal. + */ + } + + // MARK: - Audio + + func startAudio() { + guard let audioManager = self.audioManager else { + Logger.error("\(TAG) audioManager was unexpectedly nil while tryign to start audio") + return + } + + audioManager.startAudio() + } + + func stopAudio() { + guard let audioManager = self.audioManager else { + Logger.error("\(TAG) audioManager was unexpectedly nil while tryign to stop audio") + return + } + + audioManager.stopAudio() + } + + func configureAudioSession() { + guard let audioManager = self.audioManager else { + Logger.error("\(TAG) audioManager was unexpectedly nil while trying to: \(#function)") + return + } + + audioManager.configureAudioSession() + } +} diff --git a/Signal/src/call/TurnServerInfo.swift b/Signal/src/call/TurnServerInfo.swift new file mode 100644 index 000000000..594c05d60 --- /dev/null +++ b/Signal/src/call/TurnServerInfo.swift @@ -0,0 +1,32 @@ +// Created by Michael Kirk on 12/18/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation + +struct TurnServerInfo { + + let TAG = "[TurnServerInfo]" + let password: String + let username: String + let urls: [String] + + init?(attributes: [String: AnyObject]) { + if let passwordAttribute = (attributes["password"] as? String) { + password = passwordAttribute + } else { + return nil + } + + if let usernameAttribute = attributes["username"] as? String { + username = usernameAttribute + } else { + return nil + } + + if let urlsAttribute = attributes["urls"] as? [String] { + urls = urlsAttribute + } else { + return nil + } + } +} diff --git a/Signal/src/call/UserInterface/CallUIAdapter.swift b/Signal/src/call/UserInterface/CallUIAdapter.swift new file mode 100644 index 000000000..66a8b89b5 --- /dev/null +++ b/Signal/src/call/UserInterface/CallUIAdapter.swift @@ -0,0 +1,119 @@ +// Created by Michael Kirk on 12/13/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation +import PromiseKit +import CallKit + +protocol CallUIAdaptee { + func startOutgoingCall(_ call: SignalCall) + func reportIncomingCall(_ call: SignalCall, callerName: String, audioManager: SignalCallAudioManager) + func reportMissedCall(_ call: SignalCall, callerName: String) + func answerCall(_ call: SignalCall) + func declineCall(_ call: SignalCall) + func endCall(_ call: SignalCall) +} + +extension CallUIAdaptee { + public func showCall(_ call: SignalCall) { + let callNotificationName = CallService.callServiceActiveCallNotificationName() + NotificationCenter.default.post(name: NSNotification.Name(rawValue: callNotificationName), object: call) + } +} + +/** + * Notify the user of call related activities. + * Driven by either a CallKit or System notifications adaptee + */ +class CallUIAdapter { + + let TAG = "[CallUIAdapter]" + private let adaptee: CallUIAdaptee + private let contactsManager: OWSContactsManager + + required init(callService: CallService, contactsManager: OWSContactsManager, notificationsAdapter: CallNotificationsAdapter) { + self.contactsManager = contactsManager + if Platform.isSimulator { + // Callkit doesn't seem entirely supported in simulator. + // e.g. you can't receive calls in the call screen. + // So we use the non-call kit call UI. + Logger.info("\(TAG) choosing non-callkit adaptee for simulator.") + adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationsAdapter: notificationsAdapter) + } else if #available(iOS 10.0, *) { + Logger.info("\(TAG) choosing callkit adaptee for iOS10+") + adaptee = CallKitCallUIAdaptee(callService: callService, notificationsAdapter: notificationsAdapter) + } else { + Logger.info("\(TAG) choosing non-callkit adaptee for older iOS") + adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationsAdapter: notificationsAdapter) + } + } + + public func reportIncomingCall(_ call: SignalCall, thread: TSContactThread, audioManager: SignalCallAudioManager) { + let callerName = self.contactsManager.displayName(forPhoneIdentifier: call.remotePhoneNumber) + adaptee.reportIncomingCall(call, callerName: callerName, audioManager: audioManager) + } + + public func reportMissedCall(_ call: SignalCall) { + let callerName = self.contactsManager.displayName(forPhoneIdentifier: call.remotePhoneNumber) + adaptee.reportMissedCall(call, callerName: callerName) + } + + public func startOutgoingCall(handle: String) -> SignalCall { + let call = SignalCall.outgoingCall(localId: UUID(), remotePhoneNumber: handle) + adaptee.startOutgoingCall(call) + return call + } + + public func answerCall(_ call: SignalCall) { + adaptee.answerCall(call) + } + + public func declineCall(_ call: SignalCall) { + adaptee.declineCall(call) + } + + public func endCall(_ call: SignalCall) { + adaptee.endCall(call) + } + + public func showCall(_ call: SignalCall) { + adaptee.showCall(call) + } +} + +/** + * FIXME TODO I actually don't yet understand the role of these CallAudioManager methods as + * called in the speakerbox example. Are they redundant with what the RTC setup + * already does for us? + * + * Here's the AVSessionConfig for the ARDRTC Example app, which maybe belongs + * in the coonfigureAudio session. and maybe the adding audio tracks is sufficient for startAudio's implenetation? + * + * + 187 RTCAudioSessionConfiguration *configuration = + 188 [[RTCAudioSessionConfiguration alloc] init]; + 189 configuration.category = AVAudioSessionCategoryAmbient; + 190 configuration.categoryOptions = AVAudioSessionCategoryOptionDuckOthers; + 191 configuration.mode = AVAudioSessionModeDefault; + 192 + 193 RTCAudioSession *session = [RTCAudioSession sharedInstance]; + 194 [session lockForConfiguration]; + 195 BOOL hasSucceeded = NO; + 196 NSError *error = nil; + 197 if (session.isActive) { + 198 hasSucceeded = [session setConfiguration:configuration error:&error]; + 199 } else { + 200 hasSucceeded = [session setConfiguration:configuration + 201 active:YES + 202 error:&error]; + 203 } + 204 if (!hasSucceeded) { + 205 RTCLogError(@"Error setting configuration: %@", error.localizedDescription); + 206 } + 207 [session unlockForConfiguration]; + */ +protocol SignalCallAudioManager { + func startAudio() + func stopAudio() + func configureAudioSession() +} diff --git a/Signal/src/call/UserInterface/OWSCallNotificationsAdaptee.h b/Signal/src/call/UserInterface/OWSCallNotificationsAdaptee.h new file mode 100644 index 000000000..060a58007 --- /dev/null +++ b/Signal/src/call/UserInterface/OWSCallNotificationsAdaptee.h @@ -0,0 +1,16 @@ +// Created by Michael Kirk on 12/28/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +NS_ASSUME_NONNULL_BEGIN + +@class SignalCall; + +@protocol OWSCallNotificationsAdaptee + +- (void)presentIncomingCall:(SignalCall *)call callerName:(NSString *)callerName; + +- (void)presentMissedCall:(SignalCall *)call callerName:(NSString *)callerName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/call/WebRTCCallMessageHandler.swift b/Signal/src/call/WebRTCCallMessageHandler.swift new file mode 100644 index 000000000..61fcda58c --- /dev/null +++ b/Signal/src/call/WebRTCCallMessageHandler.swift @@ -0,0 +1,81 @@ +// Created by Michael Kirk on 12/4/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation + +@objc(OWSWebRTCCallMessageHandler) +class WebRTCCallMessageHandler: NSObject, OWSCallMessageHandler { + + // MARK - Properties + + let TAG = "[WebRTCCallMessageHandler]" + + // MARK: Dependencies + + let accountManager: AccountManager + let callService: CallService + let messageSender: MessageSender + + // MARK: Initializers + + required init(accountManager: AccountManager, callService: CallService, messageSender: MessageSender) { + self.accountManager = accountManager + self.callService = callService + self.messageSender = messageSender + } + + // MARK: - Call Handlers + + public func receivedOffer(_ offer: OWSSignalServiceProtosCallMessageOffer, from callerId: String) { + Logger.verbose("\(TAG) handling offer from caller:\(callerId)") + + let thread = TSContactThread.getOrCreateThread(contactId: callerId) + CallService.signalingQueue.async { + _ = self.callService.handleReceivedOffer(thread: thread, callId: offer.id, sessionDescription: offer.sessionDescription) + } + } + + public func receivedAnswer(_ answer: OWSSignalServiceProtosCallMessageAnswer, from callerId: String) { + Logger.verbose("\(TAG) handling answer from caller:\(callerId)") + + let thread = TSContactThread.getOrCreateThread(contactId: callerId) + CallService.signalingQueue.async { + self.callService.handleReceivedAnswer(thread: thread, callId: answer.id, sessionDescription: answer.sessionDescription) + } + } + + public func receivedIceUpdate(_ iceUpdate: OWSSignalServiceProtosCallMessageIceUpdate, from callerId: String) { + Logger.verbose("\(TAG) handling iceUpdates from caller:\(callerId)") + + let thread = TSContactThread.getOrCreateThread(contactId: callerId) + + // Discrepency between our protobuf's sdpMlineIndex, which is unsigned, + // while the RTC iOS API requires a signed int. + let lineIndex = Int32(iceUpdate.sdpMlineIndex) + + CallService.signalingQueue.async { + self.callService.handleRemoteAddedIceCandidate(thread: thread, callId: iceUpdate.id, sdp: iceUpdate.sdp, lineIndex: lineIndex, mid: iceUpdate.sdpMid) + } + } + + public func receivedHangup(_ hangup: OWSSignalServiceProtosCallMessageHangup, from callerId: String) { + Logger.verbose("\(TAG) handling 'hangup' from caller:\(callerId)") + + let thread = TSContactThread.getOrCreateThread(contactId: callerId) + + CallService.signalingQueue.async { + self.callService.handleRemoteHangup(thread: thread) + } + } + + public func receivedBusy(_ busy: OWSSignalServiceProtosCallMessageBusy, from callerId: String) { + Logger.verbose("\(TAG) handling 'busy' from caller:\(callerId)") + + let thread = TSContactThread.getOrCreateThread(contactId: callerId) + + CallService.signalingQueue.async { + self.callService.handleRemoteBusy(thread: thread) + } + } + +} diff --git a/Signal/src/contact/OWSContactsManager.h b/Signal/src/contact/OWSContactsManager.h index e85397588..c01c6b41d 100644 --- a/Signal/src/contact/OWSContactsManager.h +++ b/Signal/src/contact/OWSContactsManager.h @@ -22,6 +22,8 @@ NS_ASSUME_NONNULL_BEGIN - (nonnull NSArray *)getContactsFromAddressBook:(nonnull ABAddressBookRef)addressBook; - (nullable Contact *)latestContactForPhoneNumber:(nullable PhoneNumber *)phoneNumber; +- (nullable Contact *)contactForPhoneIdentifier:(nullable NSString *)identifier; +- (Contact *)getOrBuildContactForPhoneIdentifier:(NSString *)identifier; - (void)verifyABPermission; diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index 774a75ec1..176bc3adb 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -399,10 +399,15 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in return [self getSignalUsersFromContactsArray:[self allContacts]]; } +- (NSString *)unknownContactName +{ + return NSLocalizedString(@"UNKNOWN_CONTACT_NAME", + @"Displayed if for some reason we can't determine a contacts phone number *or* name"); +} + - (NSString * _Nonnull)displayNameForPhoneIdentifier:(NSString * _Nullable)identifier { if (!identifier) { - return NSLocalizedString(@"UNKNOWN_CONTACT_NAME", - @"Displayed if for some reason we can't determine a contacts phone number *or* name"); + return self.unknownContactName; } Contact *contact = [self contactForPhoneIdentifier:identifier]; @@ -475,6 +480,21 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in return nil; } +- (Contact *)getOrBuildContactForPhoneIdentifier:(NSString *)identifier +{ + Contact *savedContact = [self contactForPhoneIdentifier:identifier]; + if (savedContact) { + return savedContact; + } else { + return [[Contact alloc] initWithContactWithFirstName:self.unknownContactName + andLastName:nil + andUserTextPhoneNumbers:@[ identifier ] + andImage:nil + andContactID:0]; + } +} + + - (UIImage * _Nullable)imageForPhoneIdentifier:(NSString * _Nullable)identifier { Contact *contact = [self contactForPhoneIdentifier:identifier]; diff --git a/Signal/src/environment/Environment.h b/Signal/src/environment/Environment.h index 105bbca64..c1bfb9ba0 100644 --- a/Signal/src/environment/Environment.h +++ b/Signal/src/environment/Environment.h @@ -6,7 +6,7 @@ #import "TSGroupModel.h" #import "TSStorageHeaders.h" -static NSString *const kCallSegue = @"2.0_6.0_Call_Segue"; +static NSString *const kRedphoneCallSegue = @"2.0_6.0_Call_Segue"; /** * @@ -24,6 +24,7 @@ static NSString *const kCallSegue = @"2.0_6.0_Call_Segue"; @"LegacyAndroidInterop_1" #define TESTING_OPTION_USE_DH_FOR_HANDSHAKE @"DhKeyAgreementOnly" +@class UINavigationController; @class RecentCallManager; @class OWSContactsManager; @class PhoneManager; @@ -31,7 +32,11 @@ static NSString *const kCallSegue = @"2.0_6.0_Call_Segue"; @class TSGroupThread; @class ContactsUpdater; @class TSNetworkManager; +@class AccountManager; +@class OWSWebRTCCallMessageHandler; +@class CallService; @class OWSMessageSender; +@class NotificationsManager; @class UINavigationController; @interface Environment : NSObject @@ -67,10 +72,16 @@ static NSString *const kCallSegue = @"2.0_6.0_Call_Segue"; @property (nonatomic, readonly) NSArray *testingAndLegacyOptions; @property (nonatomic, readonly) NSData *zrtpClientId; @property (nonatomic, readonly) NSData *zrtpVersionId; +@property (nonatomic, readonly) AccountManager *accountManager; +@property (nonatomic, readonly) OWSWebRTCCallMessageHandler *callMessageHandler; +@property (nonatomic, readonly) CallService *callService; @property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) ContactsUpdater *contactsUpdater; @property (nonatomic, readonly) TSNetworkManager *networkManager; +@property (nonatomic, readonly) NotificationsManager *notificationsManager; @property (nonatomic, readonly) OWSMessageSender *messageSender; +@property (nonatomic, readonly) PropertyListPreferences *preferences; + @property (nonatomic, readonly) SignalsViewController *signalsViewController; @property (nonatomic, readonly, weak) UINavigationController *signUpFlowNavigationController; diff --git a/Signal/src/environment/Environment.m b/Signal/src/environment/Environment.m index 8e2ea6996..a669be33a 100644 --- a/Signal/src/environment/Environment.m +++ b/Signal/src/environment/Environment.m @@ -6,6 +6,7 @@ #import "KeyAgreementProtocol.h" #import "MessagesViewController.h" #import "RecentCallManager.h" +#import "Signal-Swift.h" #import "SignalKeyingStorage.h" #import "SignalsViewController.h" #import "TSContactThread.h" @@ -18,6 +19,12 @@ static Environment *environment = nil; @implementation Environment +@synthesize accountManager = _accountManager; +@synthesize callMessageHandler = _callMessageHandler; +@synthesize callService = _callService; +@synthesize notificationsManager = _notificationsManager; +@synthesize preferences = _preferences; + + (Environment *)getCurrent { NSAssert((environment != nil), @"Environment is not defined."); return environment; @@ -119,6 +126,45 @@ static Environment *environment = nil; return self; } +- (AccountManager *)accountManager +{ + @synchronized (self) { + if (!_accountManager) { + _accountManager = [[AccountManager alloc] initWithTextSecureAccountManager:[TSAccountManager sharedInstance] + redPhoneAccountManager:[RPAccountManager sharedInstance]]; + } + } + + return _accountManager; +} + +- (OWSWebRTCCallMessageHandler *)callMessageHandler +{ + @synchronized (self) { + if (!_callMessageHandler) { + _callMessageHandler = [[OWSWebRTCCallMessageHandler alloc] initWithAccountManager:self.accountManager + callService:self.callService + messageSender:self.messageSender]; + } + } + + return _callMessageHandler; +} + +- (CallService *)callService +{ + @synchronized (self) { + if (!_callService) { + _callService = [[CallService alloc] initWithAccountManager:self.accountManager + contactsManager:self.contactsManager + messageSender:self.messageSender + notificationsAdapter:[OWSCallNotificationsAdapter new]]; + } + } + + return _callService; +} + + (PhoneManager *)phoneManager { return Environment.getCurrent.phoneManager; } @@ -151,14 +197,37 @@ static Environment *environment = nil; SignalsViewController *vc = [[Environment getCurrent] signalsViewController]; [vc dismissViewControllerAnimated:NO completion:nil]; vc.latestCall = latestCall; - [vc performSegueWithIdentifier:kCallSegue sender:self]; + [vc performSegueWithIdentifier:kRedphoneCallSegue sender:self]; } onThread:NSThread.mainThread untilCancelled:nil]; } -+ (PropertyListPreferences *)preferences { - return [PropertyListPreferences new]; +- (NotificationsManager *)notificationsManager +{ + @synchronized (self) { + if (!_notificationsManager) { + _notificationsManager = [NotificationsManager new]; + } + } + + return _notificationsManager; +} + ++ (PropertyListPreferences *)preferences +{ + return [Environment getCurrent].preferences; +} + +- (PropertyListPreferences *)preferences +{ + @synchronized (self) { + if (!_preferences) { + _preferences = [PropertyListPreferences new]; + } + } + + return _preferences; } - (void)setSignalsViewController:(SignalsViewController *)signalsViewController { diff --git a/Signal/src/environment/NotificationsManager.h b/Signal/src/environment/NotificationsManager.h index 26a14bf4b..96b85e06f 100644 --- a/Signal/src/environment/NotificationsManager.h +++ b/Signal/src/environment/NotificationsManager.h @@ -1,18 +1,23 @@ -// -// NotificationsManager.h -// Signal -// // Created by Frederic Jacobs on 22/12/15. // Copyright © 2015 Open Whisper Systems. All rights reserved. -// -#import +#import "OWSCallNotificationsAdaptee.h" #import -@class TSCall; +NS_ASSUME_NONNULL_BEGIN -@interface NotificationsManager : NSObject +@class TSCall; +@class TSContactThread; +@class OWSContactsManager; +@class SignalCall; +@class PropertyListPreferences; + +@interface NotificationsManager : NSObject + +#pragma mark - RedPhone Call Notifications - (void)notifyUserForCall:(TSCall *)call inThread:(TSThread *)thread; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/environment/NotificationsManager.m b/Signal/src/environment/NotificationsManager.m index de01888bd..627e803ff 100644 --- a/Signal/src/environment/NotificationsManager.m +++ b/Signal/src/environment/NotificationsManager.m @@ -8,8 +8,10 @@ #import "NotificationsManager.h" #import "Environment.h" +#import "OWSContactsManager.h" #import "PropertyListPreferences.h" #import "PushManager.h" +#import "Signal-Swift.h" #import #import #import @@ -20,7 +22,8 @@ @interface NotificationsManager () @property SystemSoundID newMessageSound; -@property (nonatomic, readonly) id contactsManager; +@property NSMutableDictionary *currentNotifications; +@property (nonatomic, readonly) NotificationType notificationPreviewType; @end @@ -29,12 +32,11 @@ - (instancetype)init { self = [super init]; - if (!self) { return self; } - _contactsManager = [TextSecureKitEnv sharedEnv].contactsManager; + _currentNotifications = [NSMutableDictionary new]; NSURL *newMessageURL = [[NSBundle mainBundle] URLForResource:@"NewMessage" withExtension:@"aifc"]; AudioServicesCreateSystemSoundID((__bridge CFURLRef)newMessageURL, &_newMessageSound); @@ -42,6 +44,12 @@ return self; } + +#pragma mark - Redphone Calls + +/** + * Notify user for Redphone Call + */ - (void)notifyUserForCall:(TSCall *)call inThread:(TSThread *)thread { if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) { // Remove previous notification of call and show missed notification. @@ -55,13 +63,19 @@ UILocalNotification *notification = [[UILocalNotification alloc] init]; notification.soundName = @"NewMessage.aifc"; - if ([[Environment preferences] notificationPreviewType] == NotificationNoNameNoPreview) { - notification.alertBody = [NSString stringWithFormat:NSLocalizedString(@"MISSED_CALL", nil)]; - } else { - notification.userInfo = @{Signal_Call_UserInfo_Key : cThread.contactIdentifier}; - notification.category = Signal_CallBack_Category; - notification.alertBody = - [NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_MISSED_CALL", nil), [thread name]]; + switch (self.notificationPreviewType) { + case NotificationNoNameNoPreview: { + notification.alertBody = [NSString stringWithFormat:NSLocalizedString(@"MISSED_CALL", nil)]; + break; + } + case NotificationNamePreview: + case NotificationNameNoPreview: { + notification.userInfo = @{ Signal_Call_UserInfo_Key : cThread.contactIdentifier }; + notification.category = Signal_CallBack_Category; + notification.alertBody = + [NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_MISSED_CALL", nil), [thread name]]; + break; + } } [[PushManager sharedManager] presentNotification:notification]; @@ -69,6 +83,73 @@ } } +#pragma mark - Signal Calls + +/** + * Notify user for incoming WebRTC Call + */ +- (void)presentIncomingCall:(SignalCall *)call callerName:(NSString *)callerName +{ + DDLogDebug(@"%@ incoming call from: %@", self.tag, call.remotePhoneNumber); + + UILocalNotification *notification = [UILocalNotification new]; + notification.category = PushManagerCategoriesIncomingCall; + notification.soundName = @"r.caf"; + NSString *localCallId = call.localId.UUIDString; + notification.userInfo = @{ PushManagerUserInfoKeysLocalCallId : localCallId }; + + NSString *alertMessage; + switch (self.notificationPreviewType) { + case NotificationNoNameNoPreview: { + alertMessage = NSLocalizedString(@"INCOMING_CALL", @"notification body"); + break; + } + case NotificationNameNoPreview: + case NotificationNamePreview: { + alertMessage = + [NSString stringWithFormat:NSLocalizedString(@"INCOMING_CALL_FROM", @"notification body"), callerName]; + break; + } + } + notification.alertBody = [NSString stringWithFormat:@"☎️ %@", alertMessage]; + + [self presentNotification:notification identifier:localCallId]; +} + +/** + * Notify user for missed WebRTC Call + */ +- (void)presentMissedCall:(SignalCall *)call callerName:(NSString *)callerName +{ + UILocalNotification *notification = [UILocalNotification new]; + notification.category = PushManagerCategoriesMissedCall; + NSString *localCallId = call.localId.UUIDString; + notification.userInfo = @{ + PushManagerUserInfoKeysLocalCallId : localCallId, + PushManagerUserInfoKeysCallBackSignalRecipientId : call.remotePhoneNumber + }; + + + NSString *alertMessage; + switch (self.notificationPreviewType) { + case NotificationNoNameNoPreview: { + alertMessage = [NSString stringWithFormat:NSLocalizedString(@"MISSED_CALL", @"notification body")]; + break; + } + case NotificationNameNoPreview: + case NotificationNamePreview: { + alertMessage = + [NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_MISSED_CALL", @"notification body"), callerName]; + break; + } + } + notification.alertBody = [NSString stringWithFormat:@"☎️ %@", alertMessage]; + + [self presentNotification:notification identifier:localCallId]; +} + +#pragma mark - Signal Messages + - (void)notifyUserForErrorMessage:(TSErrorMessage *)message inThread:(TSThread *)thread { NSString *messageDescription = message.description; @@ -80,7 +161,7 @@ NSString *alertBodyString = @""; NSString *authorName = [thread name]; - switch ([[Environment preferences] notificationPreviewType]) { + switch (self.notificationPreviewType) { case NotificationNamePreview: case NotificationNameNoPreview: alertBodyString = [NSString stringWithFormat:@"%@: %@", authorName, messageDescription]; @@ -99,21 +180,25 @@ } } -- (void)notifyUserForIncomingMessage:(TSIncomingMessage *)message from:(NSString *)name inThread:(TSThread *)thread { +- (void)notifyUserForIncomingMessage:(TSIncomingMessage *)message + from:(NSString *)name + inThread:(TSThread *)thread + contactsManager:(id)contactsManager +{ NSString *messageDescription = message.description; if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive && messageDescription) { UILocalNotification *notification = [[UILocalNotification alloc] init]; notification.soundName = @"NewMessage.aifc"; - switch ([[Environment preferences] notificationPreviewType]) { + switch (self.notificationPreviewType) { case NotificationNamePreview: notification.category = Signal_Full_New_Message_Category; notification.userInfo = @{Signal_Thread_UserInfo_Key : thread.uniqueId, Signal_Message_UserInfo_Key : message.uniqueId}; if ([thread isGroupThread]) { - NSString *sender = [self.contactsManager displayNameForPhoneIdentifier:message.authorId]; + NSString *sender = [contactsManager displayNameForPhoneIdentifier:message.authorId]; NSString *threadName = [NSString stringWithFormat:@"\"%@\"", name]; notification.alertBody = [NSString stringWithFormat:NSLocalizedString(@"APN_MESSAGE_IN_GROUP_DETAILED", nil), @@ -139,6 +224,7 @@ notification.alertBody = NSLocalizedString(@"APN_Message", nil); break; default: + DDLogWarn(@"unknown notification preview type: %lu", (unsigned long)self.notificationPreviewType); notification.alertBody = NSLocalizedString(@"APN_Message", nil); break; } @@ -151,4 +237,50 @@ } } +#pragma mark - Util + +- (NotificationType)notificationPreviewType +{ + PropertyListPreferences *prefs = [Environment getCurrent].preferences; + return prefs.notificationPreviewType; +} + +- (void)presentNotification:(UILocalNotification *)notification identifier:(NSString *)identifier +{ + // Replace any existing notification + // e.g. when an "Incoming Call" notification gets replaced with a "Missed Call" notification. + if (self.currentNotifications[identifier]) { + [self cancelNotificationWithIdentifier:identifier]; + } + + [[UIApplication sharedApplication] scheduleLocalNotification:notification]; + DDLogDebug(@"%@ presenting notification with identifier: %@", self.tag, identifier); + + self.currentNotifications[identifier] = notification; +} + +- (void)cancelNotificationWithIdentifier:(NSString *)identifier +{ + UILocalNotification *notification = self.currentNotifications[identifier]; + if (!notification) { + DDLogWarn(@"%@ Couldn't cancel notification because none was found with identifier: %@", self.tag, identifier); + return; + } + [self.currentNotifications removeObjectForKey:identifier]; + + [[UIApplication sharedApplication] cancelLocalNotification:notification]; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + @end diff --git a/Signal/src/environment/PropertyListPreferences.h b/Signal/src/environment/PropertyListPreferences.h index f1e81c630..886380a4e 100644 --- a/Signal/src/environment/PropertyListPreferences.h +++ b/Signal/src/environment/PropertyListPreferences.h @@ -1,5 +1,8 @@ NS_ASSUME_NONNULL_BEGIN +/** + * The users privacy preference for what kind of content to show in lock screen notifications. + */ typedef NS_ENUM(NSUInteger, NotificationType) { NotificationNoNameNoPreview, NotificationNameNoPreview, diff --git a/Signal/src/environment/Release.m b/Signal/src/environment/Release.m index 3c25491ba..9a340a020 100644 --- a/Signal/src/environment/Release.m +++ b/Signal/src/environment/Release.m @@ -1,5 +1,6 @@ #import "Release.h" #import "DiscardingLog.h" +#import "NotificationsManager.h" #import "PhoneManager.h" #import "PhoneNumberUtil.h" #import "RecentCallManager.h" @@ -52,6 +53,7 @@ static unsigned char DH3K_PRIME[] = { storageManager:[TSStorageManager sharedManager] contactsManager:contactsManager contactsUpdater:contactsUpdater]; + return [[Environment alloc] initWithLogging:logging errorNoter:errorNoter serverPort:31337 @@ -83,6 +85,7 @@ static unsigned char DH3K_PRIME[] = { storageManager:[TSStorageManager sharedManager] contactsManager:contactsManager contactsUpdater:contactsUpdater]; + return [[Environment alloc] initWithLogging:logging errorNoter:errorNoter serverPort:31337 diff --git a/Signal/src/network/PushManager.h b/Signal/src/network/PushManager.h index 020fe0d09..0dac0b3fc 100644 --- a/Signal/src/network/PushManager.h +++ b/Signal/src/network/PushManager.h @@ -17,6 +17,13 @@ NS_ASSUME_NONNULL_BEGIN #define Signal_Thread_UserInfo_Key @"Signal_Thread_Id" #define Signal_Message_UserInfo_Key @"Signal_Message_Id" +#define Signal_Full_New_Message_Category @"Signal_Full_New_Message" + +#define Signal_Message_Reply_Identifier @"Signal_New_Message_Reply" +#define Signal_Message_MarkAsRead_Identifier @"Signal_Message_MarkAsRead" + +#pragma mark Redphone Calls (deprecated) + #define Signal_Call_UserInfo_Key @"Signal_Call_Id" #define Signal_Call_Accept_Identifier @"Signal_Call_Accept" @@ -25,11 +32,20 @@ NS_ASSUME_NONNULL_BEGIN #define Signal_CallBack_Identifier @"Signal_CallBack" #define Signal_Call_Category @"Signal_IncomingCall" -#define Signal_Full_New_Message_Category @"Signal_Full_New_Message" + #define Signal_CallBack_Category @"Signal_CallBack" -#define Signal_Message_Reply_Identifier @"Signal_New_Message_Reply" -#define Signal_Message_MarkAsRead_Identifier @"Signal_Message_MarkAsRead" +#pragma mark Signal Calls constants + +FOUNDATION_EXPORT NSString *const PushManagerCategoriesIncomingCall; +FOUNDATION_EXPORT NSString *const PushManagerCategoriesMissedCall; + +FOUNDATION_EXPORT NSString *const PushManagerActionsAcceptCall; +FOUNDATION_EXPORT NSString *const PushManagerActionsDeclineCall; +FOUNDATION_EXPORT NSString *const PushManagerActionsCallBack; + +FOUNDATION_EXPORT NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId; +FOUNDATION_EXPORT 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 41dfabe3a..e0701db89 100644 --- a/Signal/src/network/PushManager.m +++ b/Signal/src/network/PushManager.m @@ -36,6 +36,7 @@ @property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSMessageSender *messageSender; @property (nonatomic, readonly) OWSMessageFetcherJob *messageFetcherJob; +@property (nonatomic, readonly, strong) CallService *callService; @end @@ -56,6 +57,8 @@ notificationTracker:[NotificationTracker notificationTracker] networkManager:[Environment getCurrent].networkManager storageManager:[TSStorageManager sharedManager] + callMessageHandler:[Environment getCurrent].callMessageHandler + callService:[Environment getCurrent].callService contactsUpdater:[Environment getCurrent].contactsUpdater]; } @@ -63,6 +66,8 @@ notificationTracker:(NotificationTracker *)notificationTracker networkManager:(TSNetworkManager *)networkManager storageManager:(TSStorageManager *)storageManager + callMessageHandler:(OWSWebRTCCallMessageHandler *)callMessageHandler + callService:(CallService *)callService contactsUpdater:(ContactsUpdater *)contactsUpdater { self = [super init]; @@ -71,6 +76,9 @@ } _contactsManager = contactsManager; + _callService = callService; + + // DEPRECATED Redphone uses notification tracker. _notificationTracker = notificationTracker; _messageSender = [[OWSMessageSender alloc] initWithNetworkManager:networkManager storageManager:storageManager @@ -79,6 +87,7 @@ TSMessagesManager *messagesManager = [[TSMessagesManager alloc] initWithNetworkManager:networkManager storageManager:storageManager + callMessageHandler:callMessageHandler contactsManager:contactsManager contactsUpdater:contactsUpdater messageSender:_messageSender]; @@ -105,6 +114,7 @@ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { DDLogInfo(@"received: %s", __PRETTY_FUNCTION__); + // DEPRECATED redphone if ([self isRedPhonePush:userInfo]) { ResponderSessionDescriptor *call; if (![self.notificationTracker shouldProcessNotification:userInfo]) { @@ -227,6 +237,7 @@ DDLogInfo(@"received: %s", __PRETTY_FUNCTION__); if ([identifier isEqualToString:Signal_Message_Reply_Identifier]) { + DDLogInfo(@"%@ received reply identifier", self.tag); NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key]; if (threadId) { @@ -253,23 +264,70 @@ }]; } } else if ([identifier isEqualToString:Signal_Call_Accept_Identifier]) { + DDLogInfo(@"%@ received redphone accept action", self.tag); [Environment.phoneManager answerCall]; completionHandler(); } else if ([identifier isEqualToString:Signal_Call_Decline_Identifier]) { + DDLogInfo(@"%@ received redphone decline action", self.tag); [Environment.phoneManager hangupOrDenyCall]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ completionHandler(); }); } else if ([identifier isEqualToString:Signal_CallBack_Identifier]) { + DDLogInfo(@"%@ received redphone callback action", self.tag); NSString *contactId = notification.userInfo[Signal_Call_UserInfo_Key]; PhoneNumber *number = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:contactId]; Contact *contact = [self.contactsManager latestContactForPhoneNumber:number]; [Environment.phoneManager initiateOutgoingCallToContact:contact atRemoteNumber:number]; } else if ([identifier isEqualToString:Signal_Message_MarkAsRead_Identifier]) { [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); + return; + } + + NSUUID *localId = [[NSUUID alloc] initWithUUIDString:localIdString]; + if (!localId) { + DDLogError(@"%@ localIdString failed to parse as UUID.", self.tag); + return; + } + + [self.callService handleAnswerCallWithLocalId:localId]; + } else if ([identifier isEqualToString:PushManagerActionsDeclineCall]) { + DDLogInfo(@"%@ received decline call action", self.tag); + + NSString *localIdString = notification.userInfo[PushManagerUserInfoKeysLocalCallId]; + if (!localIdString) { + DDLogError(@"%@ missing localIdString.", self.tag); + return; + } + + NSUUID *localId = [[NSUUID alloc] initWithUUIDString:localIdString]; + if (!localId) { + DDLogError(@"%@ localIdString failed to parse as UUID.", self.tag); + return; + } + + [self.callService handleDeclineCallWithLocalId:localId]; + } 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.callService handleCallBackWithRecipientId:recipientId]; } else { + DDLogDebug(@"%@ Unhandled action with identifier: %@", self.tag, identifier); + NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key]; [Environment messageThreadId:threadId]; completionHandler(); @@ -406,7 +464,10 @@ return messageCategory; } -- (UIUserNotificationCategory *)userNotificationsCallCategory { +#pragma mark Redphone Calls + +- (UIUserNotificationCategory *)userNotificationsRPCallCategory +{ UIMutableUserNotificationAction *action_accept = [UIMutableUserNotificationAction new]; action_accept.identifier = Signal_Call_Accept_Identifier; action_accept.title = NSLocalizedString(@"ANSWER_CALL_BUTTON_TITLE", @""); @@ -429,7 +490,8 @@ return callCategory; } -- (UIUserNotificationCategory *)userNotificationsCallBackCategory { +- (UIUserNotificationCategory *)userNotificationsRPCallBackCategory +{ UIMutableUserNotificationAction *action_accept = [UIMutableUserNotificationAction new]; action_accept.identifier = Signal_CallBack_Identifier; action_accept.title = NSLocalizedString(@"CALLBACK_BUTTON_TITLE", @""); @@ -445,10 +507,61 @@ return callCategory; } -- (BOOL)needToRegisterForRemoteNotifications { - return self.wantRemoteNotifications && (!UIApplication.sharedApplication.isRegisteredForRemoteNotifications); +#pragma mark - Signal Calls + +NSString *const PushManagerCategoriesIncomingCall = @"PushManagerCategoriesIncomingCall"; +NSString *const PushManagerCategoriesMissedCall = @"PushManagerCategoriesMissedCall"; + +NSString *const PushManagerActionsAcceptCall = @"PushManagerActionsAcceptCall"; +NSString *const PushManagerActionsDeclineCall = @"PushManagerActionsDeclineCall"; +NSString *const PushManagerActionsCallBack = @"PushManagerActionsCallBack"; + +NSString *const PushManagerUserInfoKeysLocalCallId = @"PushManagerUserInfoKeysLocalCallId"; +NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManagerUserInfoKeysCallBackSignalRecipientId"; + +- (UIUserNotificationCategory *)signalIncomingCallCategory +{ + UIMutableUserNotificationAction *acceptAction = [UIMutableUserNotificationAction new]; + acceptAction.identifier = PushManagerActionsAcceptCall; + acceptAction.title = NSLocalizedString(@"ANSWER_CALL_BUTTON_TITLE", @""); + acceptAction.activationMode = UIUserNotificationActivationModeForeground; + acceptAction.destructive = NO; + acceptAction.authenticationRequired = NO; + + UIMutableUserNotificationAction *declineAction = [UIMutableUserNotificationAction new]; + declineAction.identifier = PushManagerActionsDeclineCall; + declineAction.title = NSLocalizedString(@"REJECT_CALL_BUTTON_TITLE", @""); + declineAction.activationMode = UIUserNotificationActivationModeBackground; + declineAction.destructive = NO; + declineAction.authenticationRequired = NO; + + UIMutableUserNotificationCategory *callCategory = [UIMutableUserNotificationCategory new]; + callCategory.identifier = PushManagerCategoriesIncomingCall; + [callCategory setActions:@[ acceptAction, declineAction ] forContext:UIUserNotificationActionContextMinimal]; + [callCategory setActions:@[ acceptAction, declineAction ] forContext:UIUserNotificationActionContextDefault]; + + return callCategory; } +- (UIUserNotificationCategory *)signalMissedCallCategory +{ + UIMutableUserNotificationAction *callBackAction = [UIMutableUserNotificationAction new]; + callBackAction.identifier = PushManagerActionsCallBack; + callBackAction.title = NSLocalizedString(@"CALLBACK_BUTTON_TITLE", @""); + callBackAction.activationMode = UIUserNotificationActivationModeForeground; + callBackAction.destructive = NO; + callBackAction.authenticationRequired = YES; + + UIMutableUserNotificationCategory *callCategory = [UIMutableUserNotificationCategory new]; + callCategory.identifier = PushManagerCategoriesMissedCall; + [callCategory setActions:@[ callBackAction ] forContext:UIUserNotificationActionContextMinimal]; + [callCategory setActions:@[ callBackAction ] forContext:UIUserNotificationActionContextDefault]; + + return callCategory; +} + +#pragma mark Util + - (BOOL)wantRemoteNotifications { #if TARGET_IPHONE_SIMULATOR return NO; @@ -465,9 +578,11 @@ { UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationType)[self allNotificationTypes] - categories:[NSSet setWithObjects:[self userNotificationsCallCategory], + categories:[NSSet setWithObjects:[self userNotificationsRPCallCategory], [self fullNewMessageNotificationCategory], - [self userNotificationsCallBackCategory], + [self userNotificationsRPCallBackCategory], + [self signalIncomingCallCategory], + [self signalMissedCallCategory], nil]]; [UIApplication.sharedApplication registerUserNotificationSettings:settings]; diff --git a/Signal/src/util/Platform.swift b/Signal/src/util/Platform.swift new file mode 100644 index 000000000..ae24556e5 --- /dev/null +++ b/Signal/src/util/Platform.swift @@ -0,0 +1,14 @@ +// Created by Michael Kirk on 12/23/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation + +struct Platform { + static let isSimulator: Bool = { + var isSim = false + #if arch(i386) || arch(x86_64) + isSim = true + #endif + return isSim + }() +} diff --git a/Signal/src/view controllers/AdvancedSettingsTableViewController.m b/Signal/src/view controllers/AdvancedSettingsTableViewController.m index db3814dfd..5ab4a7839 100644 --- a/Signal/src/view controllers/AdvancedSettingsTableViewController.m +++ b/Signal/src/view controllers/AdvancedSettingsTableViewController.m @@ -119,12 +119,10 @@ NS_ASSUME_NONNULL_BEGIN if ([tableView cellForRowAtIndexPath:indexPath] == self.submitLogCell) { [Pastelog submitLogs]; } else if ([tableView cellForRowAtIndexPath:indexPath] == self.registerPushCell) { - OWSAccountManager *accountManager = - [[OWSAccountManager alloc] initWithTextSecureAccountManager:[TSAccountManager sharedInstance] - redPhoneAccountManager:[RPAccountManager sharedInstance]]; - OWSSyncPushTokensJob *syncJob = [[OWSSyncPushTokensJob alloc] initWithPushManager:[PushManager sharedManager] - accountManager:accountManager - preferences:[Environment preferences]]; + OWSSyncPushTokensJob *syncJob = + [[OWSSyncPushTokensJob alloc] initWithPushManager:[PushManager sharedManager] + accountManager:[Environment getCurrent].accountManager + preferences:[Environment preferences]]; syncJob.uploadOnlyIfStale = NO; [syncJob run] .then(^{ diff --git a/Signal/src/view controllers/CallViewController.swift b/Signal/src/view controllers/CallViewController.swift new file mode 100644 index 000000000..92e3d3d33 --- /dev/null +++ b/Signal/src/view controllers/CallViewController.swift @@ -0,0 +1,321 @@ +// Created by Michael Kirk on 11/10/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import Foundation +import WebRTC +import PromiseKit + +@objc class CallAudioService: NSObject { + private let TAG = "[CallAudioService]" + private var vibrateTimer: Timer? + private let audioManager = AppAudioManager.sharedInstance() + + // Mark: Vibration config + private let vibrateRepeatDuration = 1.6 + + // Our ring buzz is a pair of vibrations. + // `pulseDuration` is the small pause between the two vibrations in the pair. + private let pulseDuration = 0.2 + + public var isSpeakerphoneEnabled = false { + didSet { + handleUpdatedSpeakerphone() + } + } + + public func handleState(_ state: CallState) { + switch state { + case .idle: handleIdle() + case .dialing: handleDialing() + case .answering: handleAnswering() + case .remoteRinging: handleRemoteRinging() + case .localRinging: handleLocalRinging() + case .connected: handleConnected() + case .localFailure: handleLocalFailure() + case .localHangup: handleLocalHangup() + case .remoteHangup: handleRemoteHangup() + case .remoteBusy: handleBusy() + } + } + + private func handleIdle() { + Logger.debug("\(TAG) \(#function)") + } + + private func handleDialing() { + Logger.debug("\(TAG) \(#function)") + } + + private func handleAnswering() { + Logger.debug("\(TAG) \(#function)") + stopRinging() + } + + private func handleRemoteRinging() { + Logger.debug("\(TAG) \(#function)") + } + + private func handleLocalRinging() { + Logger.debug("\(TAG) \(#function)") + + audioManager.setAudioEnabled(true) + audioManager.handleInboundRing() + vibrateTimer = Timer.scheduledTimer(timeInterval: vibrateRepeatDuration, target: self, selector: #selector(vibrate), userInfo: nil, repeats: true) + } + + private func handleConnected() { + Logger.debug("\(TAG) \(#function)") + stopRinging() + } + + private func handleLocalFailure() { + Logger.debug("\(TAG) \(#function)") + stopRinging() + } + + private func handleLocalHangup() { + Logger.debug("\(TAG) \(#function)") + stopRinging() + } + + private func handleRemoteHangup() { + Logger.debug("\(TAG) \(#function)") + stopRinging() + } + + private func handleBusy() { + Logger.debug("\(TAG) \(#function)") + stopRinging() + } + + private func handleUpdatedSpeakerphone() { + audioManager.toggleSpeakerPhone(isEnabled: isSpeakerphoneEnabled) + } + + // MARK: Helpers + + private func stopRinging() { + // Disables external speaker used for ringing, unless user enables speakerphone. + audioManager.setDefaultAudioProfile() + audioManager.cancelAllAudio() + + vibrateTimer?.invalidate() + vibrateTimer = nil + } + + public func vibrate() { + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) + DispatchQueue.default.asyncAfter(deadline: DispatchTime.now() + pulseDuration) { + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) + } + } +} + +@objc(OWSCallViewController) +class CallViewController: UIViewController { + + enum CallDirection { + case unspecified, outgoing, incoming + } + + let TAG = "[CallViewController]" + + // Dependencies + let callService: CallService + let callUIAdapter: CallUIAdapter + let contactsManager: OWSContactsManager + let audioService: CallAudioService + + // MARK: Properties + + var peerConnectionClient: PeerConnectionClient? + var callDirection: CallDirection = .unspecified + var thread: TSContactThread! + var call: SignalCall! + + @IBOutlet weak var contactNameLabel: UILabel! + @IBOutlet weak var contactAvatarView: AvatarImageView! + @IBOutlet weak var callStatusLabel: UILabel! + + // MARK: Outgoing or Accepted Call Controls + + @IBOutlet weak var callControls: UIView! + @IBOutlet weak var muteButton: UIButton! + @IBOutlet weak var speakerPhoneButton: UIButton! + + // MARK: Incoming Call Controls + + @IBOutlet weak var incomingCallControls: UIView! + + // MARK: Initializers + + required init?(coder aDecoder: NSCoder) { + contactsManager = Environment.getCurrent().contactsManager + callService = Environment.getCurrent().callService + callUIAdapter = callService.callUIAdapter + audioService = CallAudioService() + super.init(coder: aDecoder) + } + + required init() { + contactsManager = Environment.getCurrent().contactsManager + callService = Environment.getCurrent().callService + callUIAdapter = callService.callUIAdapter + audioService = CallAudioService() + super.init(nibName: nil, bundle: nil) + } + + override func viewDidLoad() { + + guard let thread = self.thread else { + Logger.error("\(TAG) tried to show call call without specifying thread.") + showCallFailed(error: OWSErrorMakeAssertionError()) + return + } + + contactNameLabel.text = contactsManager.displayName(forPhoneIdentifier: thread.contactIdentifier()) + contactAvatarView.image = OWSAvatarBuilder.buildImage(for: thread, contactsManager: contactsManager) + + switch callDirection { + case .unspecified: + Logger.error("\(TAG) must set call direction before call starts.") + showCallFailed(error: OWSErrorMakeAssertionError()) + case .outgoing: + self.call = self.callUIAdapter.startOutgoingCall(handle: thread.contactIdentifier()) + case .incoming: + Logger.error("\(TAG) handling Incoming call") + // No-op, since call service is already set up at this point, the result of which was presenting this viewController. + } + + call.stateDidChange = callStateDidChange + callStateDidChange(call.state) + } + + // objc accessible way to set our swift enum. + func setOutgoingCallDirection() { + callDirection = .outgoing + } + + // objc accessible way to set our swift enum. + func setIncomingCallDirection() { + callDirection = .incoming + } + + func showCallFailed(error: Error) { + // TODO Show something in UI. + Logger.error("\(TAG) call failed with error: \(error)") + } + + func localizedTextForCallState(_ callState: CallState) -> String { + switch callState { + case .idle, .remoteHangup, .localHangup: + return NSLocalizedString("IN_CALL_TERMINATED", comment: "Call setup status label") + case .dialing: + return NSLocalizedString("IN_CALL_CONNECTING", comment: "Call setup status label") + case .remoteRinging, .localRinging: + return NSLocalizedString("IN_CALL_RINGING", comment: "Call setup status label") + case .answering: + return NSLocalizedString("IN_CALL_SECURING", comment: "Call setup status label") + case .connected: + return NSLocalizedString("IN_CALL_TALKING", comment: "Call setup status label") + case .remoteBusy: + return NSLocalizedString("END_CALL_RESPONDER_IS_BUSY", comment: "Call setup status label") + case .localFailure: + return NSLocalizedString("END_CALL_UNCATEGORIZED_FAILURE", comment: "Call setup status label") + } + } + + func updateCallUI(callState: CallState) { + let textForState = localizedTextForCallState(callState) + Logger.info("\(TAG) new call status: \(callState) aka \"\(textForState)\"") + + self.callStatusLabel.text = textForState + + // Show Incoming vs. (Outgoing || Accepted) call controls + callControls.isHidden = callState == .localRinging + incomingCallControls.isHidden = callState != .localRinging + + // Dismiss Handling + switch callState { + case .remoteHangup, .remoteBusy, .localFailure: + Logger.debug("\(TAG) dismissing after delay because new state is \(textForState)") + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + self.dismiss(animated: true) + } + case .localHangup: + Logger.debug("\(TAG) dismissing immediately from local hangup") + self.dismiss(animated: true) + + default: break + } + } + + // MARK: - Actions + + func callStateDidChange(_ newState: CallState) { + DispatchQueue.main.async { + self.updateCallUI(callState: newState) + } + self.audioService.handleState(newState) + } + + /** + * Ends a connected call. Do not confuse with `didPressDeclineCall`. + */ + @IBAction func didPressHangup(sender: UIButton) { + Logger.info("\(TAG) called \(#function)") + if let call = self.call { + callUIAdapter.endCall(call) + } else { + Logger.warn("\(TAG) hung up, but call was unexpectedly nil") + } + + self.dismiss(animated: true) + } + + @IBAction func didPressMute(sender muteButton: UIButton) { + Logger.info("\(TAG) called \(#function)") + muteButton.isSelected = !muteButton.isSelected + CallService.signalingQueue.async { + self.callService.handleToggledMute(isMuted: muteButton.isSelected) + } + } + + @IBAction func didPressSpeakerphone(sender speakerphoneButton: UIButton) { + Logger.info("\(TAG) called \(#function)") + speakerphoneButton.isSelected = !speakerphoneButton.isSelected + audioService.isSpeakerphoneEnabled = speakerphoneButton.isSelected + } + + @IBAction func didPressAnswerCall(sender: UIButton) { + Logger.info("\(TAG) called \(#function)") + + guard let call = self.call else { + Logger.error("\(TAG) call was unexpectedly nil. Terminating call.") + self.callStatusLabel.text = NSLocalizedString("END_CALL_UNCATEGORIZED_FAILURE", comment: "Call setup status label") + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + self.dismiss(animated: true) + } + return + } + + CallService.signalingQueue.async { + self.callService.handleAnswerCall(call) + } + } + + /** + * Denies an incoming not-yet-connected call, Do not confuse with `didPressHangup`. + */ + @IBAction func didPressDeclineCall(sender: UIButton) { + Logger.info("\(TAG) called \(#function)") + + if let call = self.call { + callUIAdapter.declineCall(call) + } else { + Logger.warn("\(TAG) denied call, but call was unexpectedly nil") + } + + self.dismiss(animated: true) + } +} diff --git a/Signal/src/view controllers/CodeVerificationViewController.m b/Signal/src/view controllers/CodeVerificationViewController.m index fc4dc1d33..e9386ec12 100644 --- a/Signal/src/view controllers/CodeVerificationViewController.m +++ b/Signal/src/view controllers/CodeVerificationViewController.m @@ -23,7 +23,7 @@ NSString *const kCompletedRegistrationSegue = @"CompletedRegistration"; @interface CodeVerificationViewController () -@property (nonatomic, strong, readonly) OWSAccountManager *accountManager; +@property (nonatomic, strong, readonly) AccountManager *accountManager; @end @@ -36,8 +36,7 @@ NSString *const kCompletedRegistrationSegue = @"CompletedRegistration"; return self; } - _accountManager = [[OWSAccountManager alloc] initWithTextSecureAccountManager:[TSAccountManager sharedInstance] - redPhoneAccountManager:[RPAccountManager sharedInstance]]; + _accountManager = [Environment getCurrent].accountManager; return self; } @@ -49,8 +48,7 @@ NSString *const kCompletedRegistrationSegue = @"CompletedRegistration"; return self; } - _accountManager = [[OWSAccountManager alloc] initWithTextSecureAccountManager:[TSAccountManager sharedInstance] - redPhoneAccountManager:[RPAccountManager sharedInstance]]; + _accountManager = [Environment getCurrent].accountManager; return self; } diff --git a/Signal/src/view controllers/InCallViewController.m b/Signal/src/view controllers/InCallViewController.m index a8278c42c..19944af34 100644 --- a/Signal/src/view controllers/InCallViewController.m +++ b/Signal/src/view controllers/InCallViewController.m @@ -61,7 +61,7 @@ [super viewWillDisappear:animated]; [self stopRingingAnimation]; [self stopConnectingFlashAnimation]; - [AppAudioManager.sharedInstance cancellAllAudio]; + [AppAudioManager.sharedInstance cancelAllAudio]; [UIDevice.currentDevice setProximityMonitoringEnabled:NO]; } diff --git a/Signal/src/view controllers/MessagesViewController.m b/Signal/src/view controllers/MessagesViewController.m index e8a945cd4..43432bccc 100644 --- a/Signal/src/view controllers/MessagesViewController.m +++ b/Signal/src/view controllers/MessagesViewController.m @@ -64,9 +64,23 @@ #import #import - @import Photos; +@interface Contact (redphoneStubbing) + +@property (nonatomic, readonly) BOOL prefersRedphoneContact; + +@end + +@implementation Contact (redphoneStubbing) + +- (BOOL)prefersRedphoneContact +{ + return NO; +} + +@end + #define kYapDatabaseRangeLength 50 #define kYapDatabaseRangeMaxLength 300 #define kYapDatabaseRangeMinLength 20 @@ -75,9 +89,12 @@ #define JSQ_IMAGE_INSET 5 static NSTimeInterval const kTSMessageSentDateShowTimeInterval = 5 * 60; + +static NSString *const OWSMessagesViewControllerSegueInitiateCall = @"initiateCallSegue"; static NSString *const OWSMessagesViewControllerSegueShowFingerprint = @"fingerprintSegue"; static NSString *const OWSMessagesViewControllerSeguePushConversationSettings = @"OWSMessagesViewControllerSeguePushConversationSettings"; + NSString *const OWSMessagesViewControllerDidAppearNotification = @"OWSMessagesViewControllerDidAppear"; typedef enum : NSUInteger { @@ -646,7 +663,11 @@ typedef enum : NSUInteger { if ([self canCall]) { PhoneNumber *number = [self phoneNumberForThread]; Contact *contact = [self.contactsManager latestContactForPhoneNumber:number]; - [Environment.phoneManager initiateOutgoingCallToContact:contact atRemoteNumber:number]; + if (contact.prefersRedphoneContact) { + [Environment.phoneManager initiateOutgoingCallToContact:contact atRemoteNumber:number]; + } else { + [self performSegueWithIdentifier:OWSMessagesViewControllerSegueInitiateCall sender:self]; + } } else { DDLogWarn(@"Tried to initiate a call but thread is not callable."); } @@ -1615,8 +1636,21 @@ typedef enum : NSUInteger { OWSConversationSettingsTableViewController *controller = (OWSConversationSettingsTableViewController *)segue.destinationViewController; [controller configureWithThread:self.thread]; - } + } else if ([segue.identifier isEqualToString:OWSMessagesViewControllerSegueInitiateCall]) { + if (![segue.destinationViewController isKindOfClass:[OWSCallViewController class]]) { + DDLogError(@"%@ Expected CallViewController but got: %@", self.tag, segue.destinationViewController); + return; + } + OWSCallViewController *callViewController = (OWSCallViewController *)segue.destinationViewController; + + if (![self.thread isKindOfClass:[TSContactThread class]]) { + DDLogError(@"%@ Unexpectedly trying to call in group thread:%@. This isn't supported.", self.thread, self.tag); + return; + } + callViewController.thread = (TSContactThread *)self.thread; + [callViewController setOutgoingCallDirection]; + } } diff --git a/Signal/src/view controllers/SignalsViewController.m b/Signal/src/view controllers/SignalsViewController.m index 911bf7039..8ccab3f3e 100644 --- a/Signal/src/view controllers/SignalsViewController.m +++ b/Signal/src/view controllers/SignalsViewController.m @@ -32,6 +32,8 @@ #define CELL_HEIGHT 72.0f #define HEADER_HEIGHT 44.0f +NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallSegue"; + @interface SignalsViewController () @property (nonatomic, strong) YapDatabaseConnection *editingDbConnection; @@ -41,9 +43,10 @@ @property (nonatomic) long inboxCount; @property (nonatomic, retain) UISegmentedControl *segmentedControl; @property (nonatomic, strong) id previewingContext; +@property (nonatomic, readonly, strong) AccountManager *accountManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) TSMessagesManager *messagesManager; -@property (nonatomic, readonly) OWSMessageSender *messageSender; +@property (nonatomic, readonly, strong) OWSMessageSender *messageSender; @end @@ -56,12 +59,7 @@ return self; } - _contactsManager = [Environment getCurrent].contactsManager; - _messagesManager = [TSMessagesManager sharedManager]; - _messageSender = [[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager - storageManager:[TSStorageManager sharedManager] - contactsManager:_contactsManager - contactsUpdater:[Environment getCurrent].contactsUpdater]; + [self commonInit]; return self; } @@ -73,16 +71,19 @@ return self; } - _contactsManager = [Environment getCurrent].contactsManager; - _messagesManager = [TSMessagesManager sharedManager]; - _messageSender = [[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager - storageManager:[TSStorageManager sharedManager] - contactsManager:_contactsManager - contactsUpdater:[Environment getCurrent].contactsUpdater]; + [self commonInit]; return self; } +- (void)commonInit +{ + _accountManager = [Environment getCurrent].accountManager; + _contactsManager = [Environment getCurrent].contactsManager; + _messagesManager = [TSMessagesManager sharedManager]; + _messageSender = [Environment getCurrent].messageSender; +} + - (void)awakeFromNib { [super awakeFromNib]; @@ -128,6 +129,11 @@ (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable)) { [self registerForPreviewingWithDelegate:self sourceView:self.tableView]; } + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleActiveCallNotifciation:) + name:[CallService callServiceActiveCallNotificationName] + object:nil]; } - (UIViewController *)previewingContext:(id)previewingContext @@ -148,6 +154,21 @@ } } +- (void)handleActiveCallNotifciation:(NSNotification *)notification +{ + if (![notification.object isKindOfClass:[SignalCall class]]) { + DDLogError(@"%@ expected presentCall observer to be notified with a SignalCall, but found %@", + self.tag, + notification.object); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + SignalCall *call = (SignalCall *)notification.object; + [self performSegueWithIdentifier:SignalsViewControllerSegueShowIncomingCall sender:call]; + }); +} + - (void)previewingContext:(id)previewingContext commitViewController:(UIViewController *)viewControllerToCommit { MessagesViewController *vc = (MessagesViewController *)viewControllerToCommit; @@ -224,13 +245,9 @@ - (void)ensureNotificationsUpToDate { - OWSAccountManager *accountManager = - [[OWSAccountManager alloc] initWithTextSecureAccountManager:[TSAccountManager sharedInstance] - redPhoneAccountManager:[RPAccountManager sharedInstance]]; - OWSSyncPushTokensJob *syncPushTokensJob = [[OWSSyncPushTokensJob alloc] initWithPushManager:[PushManager sharedManager] - accountManager:accountManager + accountManager:self.accountManager preferences:[Environment preferences]]; [syncPushTokensJob run]; } @@ -432,10 +449,27 @@ #pragma mark - Navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - if ([segue.identifier isEqualToString:kCallSegue]) { + if ([segue.identifier isEqualToString:kRedphoneCallSegue]) { InCallViewController *vc = [segue destinationViewController]; [vc configureWithLatestCall:_latestCall]; _latestCall = nil; + } else if ([segue.identifier isEqualToString:SignalsViewControllerSegueShowIncomingCall]) { + DDLogDebug(@"%@ preparing for incoming call segue", self.tag); + if (![segue.destinationViewController isKindOfClass:[OWSCallViewController class]]) { + DDLogError(@"%@ Received unexpected destination view controller: %@", self.tag, segue.destinationViewController); + return; + } + OWSCallViewController *callViewController = (OWSCallViewController *)segue.destinationViewController; + [callViewController setIncomingCallDirection]; + + if (![sender isKindOfClass:[SignalCall class]]) { + DDLogError(@"%@ expecting call segueu to be sent by a SignalCall, but found: %@", self.tag, sender); + return; + } + SignalCall *call = (SignalCall *)sender; + TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:call.remotePhoneNumber]; + callViewController.thread = thread; + callViewController.call = call; } } diff --git a/Signal/src/views/AvatarImageView.swift b/Signal/src/views/AvatarImageView.swift new file mode 100644 index 000000000..0687e7b06 --- /dev/null +++ b/Signal/src/views/AvatarImageView.swift @@ -0,0 +1,14 @@ +// Created by Michael Kirk on 12/11/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +import UIKit + +@IBDesignable +class AvatarImageView: UIImageView { + + override func layoutSubviews() { + self.layer.masksToBounds = true + self.layer.cornerRadius = self.frame.size.width / 2 + } + +} diff --git a/Signal/translations/TRANSLATIONS.md b/Signal/translations/TRANSLATIONS.md index 1568574e8..200651f58 100644 --- a/Signal/translations/TRANSLATIONS.md +++ b/Signal/translations/TRANSLATIONS.md @@ -5,7 +5,7 @@ upload our source language (US English) to Transifex, where our translators can submit their translations. Before the app is released, we pull their latest work into the code base. -### Fetch +## Fetch You should always fetch the latest translations before pushing new source translations, as any newly updated source strings will blow away @@ -21,11 +21,14 @@ This imposes some limits on what localizations we include. For example, we don't want to include languages until a substantial portion of the app has been translated. -### Managing Source Strings +## Source Strings -There is currently no automated way to extract our localized strings. -Instead developers keep our [source strings file](translations/en.lproj/Localized.strings) -up to date manually as we change strings in the code base. +Run `bin/auto-genstrings` to extract the latest translatable strings and +comments from our local files (Signal-iOS and SignalServiceKit). The +script assumes Signal-iOS and SignalServiceKit have the same parent +directory. e.g. `~/src/Signal-iOS` and `~/src/SignalServiceKit` + +### Writing Good Translatable Strings Be sure to add a comment to provide context to the translators. For example: @@ -35,11 +38,13 @@ example: /* Button label to archive the current conversation */ ARCHIVE_ACTION="Archive" -Why comment? For example, in English these are the same, but in (e.g.) -Finnish the noun/verb are distinct. +Be sure to include comments in your translations, and enforce that other +contributors do as well. Why comment? For example, in English these are +the same, but in Finnish the noun/verb are distinct. /* Tab button label which takes you to view all your archived conversations */ ARCHIVE_HEADER="Arkisto" + /* Button label to archive the current conversation */ ARCHIVE_ACTION="Arkistoi" diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index fcd61510a..4953e7348 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -40,6 +40,9 @@ /* No comment provided by engineer. */ "APN_MESSAGE_IN_GROUP_DETAILED" = "%@ in group %@: %@"; +/* Name of application */ +"APPLICATION_NAME" = "Signal"; + /* Pressing this button moves a thread from the inbox to the archive */ "ARCHIVE_ACTION" = "Archive"; @@ -221,13 +224,13 @@ "END_CALL_REPLACED_BY_NEXT" = "You Made Another Call."; /* No comment provided by engineer. */ -"END_CALL_RESPONDER_IS_BUSY" = "Busy..."; +"END_CALL_RESPONDER_IS_BUSY" = "Busy."; /* No comment provided by engineer. */ "END_CALL_STALE_SESSION" = "Missed the Call."; /* No comment provided by engineer. */ -"END_CALL_UNCATEGORIZED_FAILURE" = "Client Failed!"; +"END_CALL_UNCATEGORIZED_FAILURE" = "Call Failed."; /* Generic notice when message failed to send. */ "ERROR_DESCRIPTION_CLIENT_SENDING_FAILURE" = "Failed to send message."; @@ -241,6 +244,9 @@ /* Generic server error */ "ERROR_DESCRIPTION_SERVER_FAILURE" = "Server Error. Please try again later."; +/* Worst case generic error message */ +"ERROR_DESCRIPTION_UNKNOWN_ERROR" = "An unkown error occurred."; + /* Error message when attempting to send message */ "ERROR_DESCRIPTION_UNREGISTERED_RECIPIENT" = "Recipient is no longer a Signal user."; @@ -353,7 +359,7 @@ "IN_CALL_TALKING" = "Secured. Active."; /* Call setup status label */ -"IN_CALL_TERMINATED" = "Done."; +"IN_CALL_TERMINATED" = "Call Ended."; /* No comment provided by engineer. */ "INCOMING_CALL" = "Incoming call"; diff --git a/protobuf/Makefile b/protobuf/Makefile new file mode 100644 index 000000000..6431433bd --- /dev/null +++ b/protobuf/Makefile @@ -0,0 +1,15 @@ +# Assumes you've installed protobuf-objc +# see: https://github.com/alexeyxo/protobuf-objc + +PROTOC=protoc \ + --plugin=/usr/local/bin/proto-gen-objc \ + --proto_path="${HOME}/src/WhisperSystems/protobuf-objc/src/compiler/" \ + --proto_path="${HOME}/src/WhisperSystems/protobuf-objc/src/compiler/google/protobuf/" \ + --proto_path='./' + +all: webrtc_data_proto + +webrtc_data_proto: OWSWebRTCDataProtos.proto + $(PROTOC) --objc_out=../Signal/src/call/ \ + OWSWebRTCDataProtos.proto + diff --git a/protobuf/OWSWebRtcDataProtos.proto b/protobuf/OWSWebRtcDataProtos.proto new file mode 100644 index 000000000..e057fbc25 --- /dev/null +++ b/protobuf/OWSWebRtcDataProtos.proto @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2014-2016 Open Whisper Systems + * + * Licensed according to the LICENSE file in this repository. + */ + +package signal; + +option java_package = "org.thoughtcrime.securesms.webrtc"; +option java_outer_classname = "WebRtcDataProtos"; + +// These options require the objc protobuf tools and may need to be commented +// out if using them for a different platform. +import "objectivec-descriptor.proto"; +option (google.protobuf.objectivec_file_options).class_prefix = "OWSWebRTCProtos"; + +message Connected +{ + optional uint64 id = 1; +} + +message Hangup +{ + optional uint64 id = 1; +} + +message VideoStreamingStatus +{ + optional uint64 id = 1; + optional bool enabled = 2; +} + +message Data +{ + + optional Connected connected = 1; + optional Hangup hangup = 2; + optional VideoStreamingStatus videoStreamingStatus = 3; +}