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
This commit is contained in:
Michael Kirk 2016-11-12 12:22:29 -05:00 committed by Matthew Chen
parent f01c5a1985
commit 647b2b37e9
53 changed files with 5244 additions and 163 deletions

30
MAINTAINING.md Normal file
View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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 = "<group>"; };
450873C61D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSIncomingMessageCollectionViewCell.m; sourceTree = "<group>"; };
450873C91D9D86F4006B54F2 /* OWSExpirableMessageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSExpirableMessageView.h; sourceTree = "<group>"; };
4509E7991DD653700025A59F /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = Carthage/Build/iOS/WebRTC.framework; sourceTree = "<group>"; };
4509E79B1DD6545B0025A59F /* CallViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallViewController.swift; sourceTree = "<group>"; };
450DF2041E0D74AC003D14BE /* Platform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Platform.swift; sourceTree = "<group>"; };
450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = UserNotificationsAdaptee.swift; path = UserInterface/Notifications/UserNotificationsAdaptee.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
4516E3FD1DD2193B00DC4206 /* OWS101ExistingUsersBlockOnIdentityChange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWS101ExistingUsersBlockOnIdentityChange.h; path = Migrations/OWS101ExistingUsersBlockOnIdentityChange.h; sourceTree = "<group>"; };
4516E3FE1DD2193B00DC4206 /* OWS101ExistingUsersBlockOnIdentityChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWS101ExistingUsersBlockOnIdentityChange.m; path = Migrations/OWS101ExistingUsersBlockOnIdentityChange.m; sourceTree = "<group>"; };
451764261DE939F300EDB8B9 /* ContactsPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsPicker.swift; sourceTree = "<group>"; };
451764281DE939FD00EDB8B9 /* ContactCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactCell.xib; sourceTree = "<group>"; };
451764291DE939FD00EDB8B9 /* ContactCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = "<group>"; };
451A13B01E13DED2000A50FD /* CallNotificationsAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallNotificationsAdapter.swift; path = ../UserInterface/Notifications/CallNotificationsAdapter.swift; sourceTree = "<group>"; };
451DE9F11DC1585F00810E42 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/iOS/PromiseKit.framework; sourceTree = "<group>"; };
451DE9FC1DC1A28200810E42 /* SyncPushTokensJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SyncPushTokensJob.swift; path = Models/SyncPushTokensJob.swift; sourceTree = "<group>"; };
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 = "<group>"; };
453D28B81D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessagesBubblesSizeCalculator.h; sourceTree = "<group>"; };
453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessagesBubblesSizeCalculator.m; sourceTree = "<group>"; };
45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataChannelMessage.swift; sourceTree = "<group>"; };
454B35071D08EED80026D658 /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = translations/mk.lproj/Localizable.strings; sourceTree = "<group>"; };
45514DE11DDFA183003EFF90 /* InviteFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InviteFlow.swift; sourceTree = "<group>"; };
45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAvatarBuilder.h; sourceTree = "<group>"; };
@ -593,6 +626,8 @@
45666F7A1D9C0533008FE134 /* OWSDatabaseMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDatabaseMigration.m; path = Migrations/OWSDatabaseMigration.m; sourceTree = "<group>"; };
45666F7C1D9C0814008FE134 /* OWSDatabaseMigrationRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSDatabaseMigrationRunner.h; path = Migrations/OWSDatabaseMigrationRunner.h; sourceTree = "<group>"; };
45666F7D1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDatabaseMigrationRunner.m; path = Migrations/OWSDatabaseMigrationRunner.m; sourceTree = "<group>"; };
4574A5D51DD6704700C6B692 /* CallService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallService.swift; sourceTree = "<group>"; };
45794E851E00620000066731 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallUIAdapter.swift; path = UserInterface/CallUIAdapter.swift; sourceTree = "<group>"; };
45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSearcher.h; sourceTree = "<group>"; };
45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcher.m; sourceTree = "<group>"; };
45843D211D223BA10013E85A /* OWSContactsSearcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcherTest.m; sourceTree = "<group>"; };
@ -600,6 +635,9 @@
45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactAvatarBuilder.m; sourceTree = "<group>"; };
4589670F1DC117CC00E9DD21 /* SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SignalTests-Bridging-Header.h"; sourceTree = "<group>"; };
458967101DC117CC00E9DD21 /* AccountManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AccountManagerTest.swift; path = Models/AccountManagerTest.swift; sourceTree = "<group>"; };
458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerConnectionClient.swift; sourceTree = "<group>"; };
458DE9D71DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSWebRTCDataProtos.pb.h; sourceTree = "<group>"; };
458DE9D81DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSWebRTCDataProtos.pb.m; sourceTree = "<group>"; };
458E382F1D6682450094BD24 /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = "<group>"; };
458E38301D6682450094BD24 /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = "<group>"; };
458E38321D66873D0094BD24 /* OWSLinkDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkDeviceViewController.h; sourceTree = "<group>"; };
@ -612,6 +650,7 @@
4597E94E1D8313C100040CDE /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = translations/sq.lproj/Localizable.strings; sourceTree = "<group>"; };
4597E94F1D8313CB00040CDE /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = translations/bg.lproj/Localizable.strings; sourceTree = "<group>"; };
459C3F0C1C9B3A1B003ACF51 /* TSMessageAdapterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSMessageAdapterTest.m; path = "view controllers/Signals/TSMessageAdapters/TSMessageAdapterTest.m"; sourceTree = "<group>"; };
45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = "<group>"; };
45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = "<group>"; };
45B201751DAECBFE00C461E0 /* HighlightableLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightableLabel.swift; sourceTree = "<group>"; };
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 = "<group>"; };
45C681C21D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDisplayedMessageCollectionViewCell.m; sourceTree = "<group>"; };
45C681C31D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OWSDisplayedMessageCollectionViewCell.xib; sourceTree = "<group>"; };
45C9DEB71DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebRTCCallMessageHandler.swift; sourceTree = "<group>"; };
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 = "<group>"; };
45CD81EE1DC030E7004C9430 /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
@ -636,12 +676,20 @@
45E1F3A41DEF20A100852CF1 /* NoSignalContactsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoSignalContactsView.swift; sourceTree = "<group>"; };
45E282DE1D08E67800ADD4C8 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = translations/gl.lproj/Localizable.strings; sourceTree = "<group>"; };
45E282DF1D08E6CC00ADD4C8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = translations/id.lproj/Localizable.strings; sourceTree = "<group>"; };
45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSCallNotificationsAdaptee.h; path = UserInterface/OWSCallNotificationsAdaptee.h; sourceTree = "<group>"; };
45E2E91F1E153B3D00457AA0 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Strings.swift; path = UserInterface/Strings.swift; sourceTree = "<group>"; };
45EB32CD1D7465C900735B2E /* OWSLinkedDevicesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkedDevicesTableViewController.h; sourceTree = "<group>"; };
45EB32CE1D7465C900735B2E /* OWSLinkedDevicesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkedDevicesTableViewController.m; sourceTree = "<group>"; };
45F2B1921D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOutgoingMessageCollectionViewCell.h; sourceTree = "<group>"; };
45F2B1931D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOutgoingMessageCollectionViewCell.m; sourceTree = "<group>"; };
45F2B1951D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OWSIncomingMessageCollectionViewCell.xib; sourceTree = "<group>"; };
45F2B1961D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OWSOutgoingMessageCollectionViewCell.xib; sourceTree = "<group>"; };
45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarImageView.swift; sourceTree = "<group>"; };
45F659721E1BD99C00444429 /* CallKitCallUIAdaptee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitCallUIAdaptee.swift; sourceTree = "<group>"; };
45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonCallKitCallUIAdaptee.swift; sourceTree = "<group>"; };
45FBC5951DF8575700E9B410 /* CallKitProviderDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitProviderDelegate.swift; sourceTree = "<group>"; };
45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitCallManager.swift; sourceTree = "<group>"; };
45FBC5D01DF8592E00E9B410 /* SignalCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalCall.swift; sourceTree = "<group>"; };
4CE0E3751B95453C007210CF /* TSAnimatedAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSAnimatedAdapter.h; sourceTree = "<group>"; };
4CE0E3761B954546007210CF /* TSAnimatedAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSAnimatedAdapter.m; sourceTree = "<group>"; };
701231B318ECAA4500D456C4 /* EvpMessageDigest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EvpMessageDigest.h; sourceTree = "<group>"; };
@ -886,7 +934,7 @@
B60C16641988999D00E97A6C /* VersionMigrations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VersionMigrations.m; sourceTree = "<group>"; };
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 = "<group>"; };
B6258B321C29E2E60014138E /* NotificationsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationsManager.m; sourceTree = "<group>"; };
B6258B321C29E2E60014138E /* NotificationsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = NotificationsManager.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
B625CD551ABB589C00E8B23C /* NewMessage.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = NewMessage.aifc; sourceTree = "<group>"; };
B62D53F51A23CCAD009AAF82 /* TSMessageAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSMessageAdapter.h; sourceTree = "<group>"; };
B62D53F61A23CCAD009AAF82 /* TSMessageAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSMessageAdapter.m; sourceTree = "<group>"; };
@ -1017,8 +1065,8 @@
B6B1013A196D213F007E3930 /* SignalKeyingStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalKeyingStorage.h; sourceTree = "<group>"; };
B6B1013B196D213F007E3930 /* SignalKeyingStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalKeyingStorage.m; sourceTree = "<group>"; };
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 = "<group>"; };
B6B9ECFB198B31BA00C620D3 /* PushManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PushManager.m; sourceTree = "<group>"; };
B6B9ECFA198B31BA00C620D3 /* PushManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PushManager.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
B6B9ECFB198B31BA00C620D3 /* PushManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PushManager.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
B6BADBE51B88D1AC0086A80D /* LockInteractionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LockInteractionController.h; sourceTree = "<group>"; };
B6BADBE61B88D1AC0086A80D /* LockInteractionController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LockInteractionController.m; sourceTree = "<group>"; };
B6BC3D0C1AA544B100C2907F /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = translations/da.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -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 = "<group>";
};
450DF2071E0DD29E003D14BE /* Notifications */ = {
isa = PBXGroup;
children = (
450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */,
);
name = Notifications;
sourceTree = "<group>";
};
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 = "<group>";
};
45666F731D9BFDB9008FE134 /* Migrations */ = {
isa = PBXGroup;
children = (
@ -1234,6 +1314,18 @@
name = Migrations;
sourceTree = "<group>";
};
45794E841E0061CF00066731 /* UserInterface */ = {
isa = PBXGroup;
children = (
45FBC57A1DF8575700E9B410 /* CallKit */,
45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */,
451A13B01E13DED2000A50FD /* CallNotificationsAdapter.swift */,
45794E851E00620000066731 /* CallUIAdapter.swift */,
45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */,
);
name = UserInterface;
sourceTree = "<group>";
};
457F3AC01D14A0F700C51351 /* Models */ = {
isa = PBXGroup;
children = (
@ -1305,6 +1397,28 @@
name = Jobs;
sourceTree = "<group>";
};
45F659741E1BDA4300444429 /* Redphone */ = {
isa = PBXGroup;
children = (
76EB03FF18170B33006006FC /* RecentCall.h */,
76EB040018170B33006006FC /* RecentCall.m */,
76EB040118170B33006006FC /* RecentCallManager.h */,
76EB040218170B33006006FC /* RecentCallManager.m */,
);
name = Redphone;
sourceTree = "<group>";
};
45FBC57A1DF8575700E9B410 /* CallKit */ = {
isa = PBXGroup;
children = (
45FBC5951DF8575700E9B410 /* CallKitProviderDelegate.swift */,
45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */,
45F659721E1BD99C00444429 /* CallKitCallUIAdaptee.swift */,
);
name = CallKit;
path = Speakerbox;
sourceTree = "<group>";
};
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 = "<group>";
@ -1796,6 +1911,7 @@
45666F551D9B2827008FE134 /* OWSScrubbingLogFormatter.m */,
45CD81F01DC03A22004C9430 /* OWSLogger.h */,
45CD81F11DC03A22004C9430 /* OWSLogger.m */,
450DF2041E0D74AC003D14BE /* Platform.swift */,
);
path = util;
sourceTree = "<group>";
@ -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 = (

View File

@ -28,7 +28,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A17C67DE5BCCF5991DAF479D127CE09E"
BlueprintIdentifier = "67EEE5C28C29FF38DA320F2E9F2B8666"
BuildableName = "libSignalServiceKit.a"
BlueprintName = "SignalServiceKit"
ReferencedContainer = "container:Pods/Pods.xcodeproj">

5
Signal/.swiftlint.yml Normal file
View File

@ -0,0 +1,5 @@
line_length: 200
disabled_rules:
- file_length
- todo

View File

@ -24,6 +24,8 @@
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/TSAccountManager.h>
@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.
*/

View File

@ -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<Void> {
return Promise { fulfill, reject in
self.textSecureAccountManager.verifyAccount(withCode:verificationCode,
success:fulfill,
failure:reject)
}
}
private func fetchRedPhoneToken() -> Promise<String> {
return Promise { fulfill, reject in
self.textSecureAccountManager.obtainRPRegistrationToken(success:fulfill,
failure:reject)
}
}
private func registerForRedPhone(tsToken: String) -> Promise<Void> {
return Promise { fulfill, reject in
self.redPhoneAccountManager.register(withTsToken:tsToken,
success:fulfill,
failure:reject)
}
}
// MARK: Push Tokens
func updatePushTokens(pushToken: String, voipToken: String) -> Promise<Void> {
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<Void> {
// MARK: Turn Server
func getTurnServerInfo() -> Promise<TurnServerInfo> {
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<String> {
return Promise { fulfill, reject in
self.textSecureAccountManager.obtainRPRegistrationToken(success:fulfill,
failure:reject)
}
}
private func registerForRedPhone(tsToken: String) -> Promise<Void> {
return Promise { fulfill, reject in
self.redPhoneAccountManager.register(withTsToken:tsToken,
success:fulfill,
failure:reject)
}
}
}

View File

@ -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(); }
}
}

View File

@ -3,10 +3,14 @@
//
#import <Foundation/Foundation.h>
#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 <SignalServiceKit/NSData+Base64.h>
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSAcknowledgeMessageDeliveryRequest.h>
#import <SignalServiceKit/OWSCallAnswerMessage.h>
#import <SignalServiceKit/OWSCallBusyMessage.h>
#import <SignalServiceKit/OWSCallHangupMessage.h>
#import <SignalServiceKit/OWSCallIceUpdateMessage.h>
#import <SignalServiceKit/OWSCallMessageHandler.h>
#import <SignalServiceKit/OWSCallOfferMessage.h>
#import <SignalServiceKit/OWSEndSessionMessage.h>
#import <SignalServiceKit/OWSError.h>
#import <SignalServiceKit/OWSGetMessagesRequest.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSOutgoingCallMessage.h>
#import <SignalServiceKit/OWSSignalService.h>
#import <SignalServiceKit/OWSTurnServerInfoRequest.h>
#import <SignalServiceKit/SignalRecipient.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSCall.h>
#import <SignalServiceKit/TSContactThread.h>
#import <SignalServiceKit/TSErrorMessage.h>
#import <SignalServiceKit/TSInfoMessage.h>

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11542" systemVersion="15G1108" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="tuk-0x-yCb">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="15G1212" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="tuk-0x-yCb">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11524"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
@ -92,6 +92,7 @@
<outlet property="emptyBoxLabel" destination="Srx-i1-WhD" id="wap-un-Cz5"/>
<outlet property="tableView" destination="PaA-ol-uQT" id="nQU-tR-wbL"/>
<segue destination="Duq-aU-MmN" kind="modal" identifier="2.0_6.0_Call_Segue" modalPresentationStyle="fullScreen" modalTransitionStyle="crossDissolve" id="gHJ-y4-zWg"/>
<segue destination="Tyf-mN-gzf" kind="modal" identifier="ShowIncomingCallSegue" id="G2B-Fr-Ezs"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dE8-zB-mtF" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -121,12 +122,237 @@
<connections>
<segue destination="4oU-Rv-yJi" kind="push" identifier="OWSMessagesViewControllerSeguePushConversationSettings" id="vOd-aS-6Wx"/>
<segue destination="urv-62-RsD" kind="modal" identifier="fingerprintSegue" id="tfr-ZV-qWs"/>
<segue destination="Tyf-mN-gzf" kind="modal" identifier="initiateCallSegue" modalTransitionStyle="crossDissolve" id="I6y-pT-nEd"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="yXZ-iE-5va" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2287" y="-1516"/>
</scene>
<!--Current Call-->
<scene sceneID="Xck-Ph-UlV">
<objects>
<viewController title="Current Call" definesPresentationContext="YES" modalTransitionStyle="crossDissolve" modalPresentationStyle="currentContext" id="Tyf-mN-gzf" customClass="OWSCallViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="0w2-kv-AfM"/>
<viewControllerLayoutGuide type="bottom" id="d8M-LU-Hfa"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="fjB-Ns-OQs">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uej-BZ-jAb">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="Kr3-nb-TPK">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<blurEffect style="dark"/>
</visualEffectView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bgz-d3-b1N" userLabel="Contact Container">
<rect key="frame" x="28" y="20" width="319" height="334.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="calling mobile ..." lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XWw-3X-PNN">
<rect key="frame" x="0.0" y="71" width="319" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="mYD-Hx-NRM"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="19"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="id3-xi-PFz" customClass="AvatarImageView" customModule="Signal" customModuleProvider="target">
<rect key="frame" x="82.5" y="129" width="155.5" height="155.5"/>
<constraints>
<constraint firstAttribute="width" secondItem="id3-xi-PFz" secondAttribute="height" multiplier="1:1" id="Tfc-PI-hRy"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Emma Goldman" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UbL-8Z-oK1">
<rect key="frame" x="0.0" y="32" width="319" height="39"/>
<constraints>
<constraint firstAttribute="height" constant="39" id="Gvs-2Y-TYi"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="light" pointSize="32"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="XWw-3X-PNN" firstAttribute="top" secondItem="UbL-8Z-oK1" secondAttribute="bottom" id="5RT-FQ-bI8"/>
<constraint firstItem="id3-xi-PFz" firstAttribute="centerX" secondItem="bgz-d3-b1N" secondAttribute="centerX" id="7it-02-4fC"/>
<constraint firstItem="XWw-3X-PNN" firstAttribute="leading" secondItem="bgz-d3-b1N" secondAttribute="leading" id="EgA-RW-icW"/>
<constraint firstItem="UbL-8Z-oK1" firstAttribute="leading" secondItem="bgz-d3-b1N" secondAttribute="leading" id="G32-rI-2ce"/>
<constraint firstAttribute="bottom" secondItem="id3-xi-PFz" secondAttribute="bottom" constant="50" id="VnJ-m2-aUj"/>
<constraint firstAttribute="trailing" secondItem="XWw-3X-PNN" secondAttribute="trailing" id="aDA-Aj-66D"/>
<constraint firstItem="UbL-8Z-oK1" firstAttribute="top" secondItem="bgz-d3-b1N" secondAttribute="top" constant="32" id="pRt-uC-KIw"/>
<constraint firstItem="id3-xi-PFz" firstAttribute="top" secondItem="XWw-3X-PNN" secondAttribute="bottom" constant="30" id="qVE-XG-OAR"/>
<constraint firstAttribute="trailing" secondItem="UbL-8Z-oK1" secondAttribute="trailing" id="xmD-aB-zJj"/>
</constraints>
</view>
<view opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" translatesAutoresizingMaskIntoConstraints="NO" id="6aP-bG-6qH" userLabel="Call Controls">
<rect key="frame" x="0.0" y="354.5" width="375" height="200"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Dfe-Kk-RoU" userLabel="Top Row">
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NES-Ce-PcK" userLabel="Mute">
<rect key="frame" x="39" y="0.0" width="80" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="IQT-LY-p9i"/>
<constraint firstAttribute="width" constant="80" id="ZIe-VD-8Mc"/>
</constraints>
<state key="normal" image="mute-inactive">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="mute-active"/>
<connections>
<action selector="didPressMuteWithSender:" destination="Tyf-mN-gzf" eventType="touchUpInside" id="ko9-U2-m5M"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Bb2-w8-mPU" userLabel="Speaker">
<rect key="frame" x="255" y="0.0" width="80" height="80"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="en7-kS-BRE"/>
<constraint firstAttribute="height" constant="80" id="ra6-Jk-V6g"/>
</constraints>
<state key="normal" image="speaker-inactive">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="speaker-active"/>
<connections>
<action selector="didPressSpeakerphoneWithSender:" destination="Tyf-mN-gzf" eventType="touchUpInside" id="VbQ-qg-gmn"/>
</connections>
</button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VBi-XB-xTO" userLabel="Message">
<rect key="frame" x="147" y="0.0" width="80" height="80"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="Bn4-cs-jmd"/>
<constraint firstAttribute="height" constant="80" id="bvL-na-JU4"/>
</constraints>
<state key="normal" image="logoSignal">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="mute-active"/>
</button>
</subviews>
<constraints>
<constraint firstItem="Bb2-w8-mPU" firstAttribute="centerY" secondItem="Dfe-Kk-RoU" secondAttribute="centerY" id="8m5-B0-bmL"/>
<constraint firstItem="NES-Ce-PcK" firstAttribute="centerY" secondItem="Dfe-Kk-RoU" secondAttribute="centerY" id="LaE-2R-kF3"/>
<constraint firstAttribute="height" constant="80" id="R3K-0P-4g0"/>
<constraint firstItem="Bb2-w8-mPU" firstAttribute="leading" secondItem="VBi-XB-xTO" secondAttribute="trailing" constant="28" id="e1y-c2-cnF"/>
<constraint firstItem="VBi-XB-xTO" firstAttribute="centerX" secondItem="Dfe-Kk-RoU" secondAttribute="centerX" id="kMb-az-s1i"/>
<constraint firstItem="VBi-XB-xTO" firstAttribute="leading" secondItem="NES-Ce-PcK" secondAttribute="trailing" constant="28" id="rQ0-Ca-K3s"/>
<constraint firstItem="VBi-XB-xTO" firstAttribute="centerY" secondItem="Dfe-Kk-RoU" secondAttribute="centerY" id="z7C-hz-PCx"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HOb-Yz-o3O" userLabel="Bottom Row">
<rect key="frame" x="0.0" y="120" width="375" height="80"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iSB-Wx-bHp" userLabel="Hang Up">
<rect key="frame" x="147.5" y="0.0" width="80" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="Wj8-Pk-fTC"/>
<constraint firstAttribute="width" secondItem="iSB-Wx-bHp" secondAttribute="height" multiplier="1:1" id="m67-U4-0tb"/>
</constraints>
<state key="normal" image="endcall.png"/>
<connections>
<action selector="didPressHangupWithSender:" destination="Tyf-mN-gzf" eventType="touchUpInside" id="bG7-oV-BIA"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="iSB-Wx-bHp" firstAttribute="centerY" secondItem="HOb-Yz-o3O" secondAttribute="centerY" id="UIW-TY-NOh"/>
<constraint firstItem="iSB-Wx-bHp" firstAttribute="centerX" secondItem="HOb-Yz-o3O" secondAttribute="centerX" id="WnN-Bn-Ohu"/>
<constraint firstAttribute="height" constant="80" id="YyR-vw-WDS"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="Dfe-Kk-RoU" firstAttribute="leading" secondItem="6aP-bG-6qH" secondAttribute="leading" id="34q-ot-2uZ"/>
<constraint firstAttribute="trailing" secondItem="HOb-Yz-o3O" secondAttribute="trailing" id="AO4-C5-Sca"/>
<constraint firstAttribute="bottom" secondItem="HOb-Yz-o3O" secondAttribute="bottom" id="FFb-Oi-psa"/>
<constraint firstItem="HOb-Yz-o3O" firstAttribute="leading" secondItem="6aP-bG-6qH" secondAttribute="leading" id="HgC-9f-j7X"/>
<constraint firstAttribute="trailing" secondItem="Dfe-Kk-RoU" secondAttribute="trailing" id="Nis-dZ-voT"/>
<constraint firstItem="Dfe-Kk-RoU" firstAttribute="top" secondItem="6aP-bG-6qH" secondAttribute="top" id="tJF-wT-zni"/>
<constraint firstItem="HOb-Yz-o3O" firstAttribute="top" secondItem="Dfe-Kk-RoU" secondAttribute="bottom" constant="40" id="uux-HY-Dp1"/>
</constraints>
</view>
<view opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" translatesAutoresizingMaskIntoConstraints="NO" id="5E5-dq-23I" userLabel="Incoming Call Controls">
<rect key="frame" x="0.0" y="571" width="375" height="96"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qtF-I8-OlZ" userLabel="Bottom Row">
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tqx-VV-3f1" userLabel="Hang Up">
<rect key="frame" x="39.5" y="0.0" width="80" height="80"/>
<constraints>
<constraint firstAttribute="width" secondItem="tqx-VV-3f1" secondAttribute="height" multiplier="1:1" id="LSQ-AP-Ojt"/>
<constraint firstAttribute="height" constant="80" id="g3g-jD-jeK"/>
</constraints>
<state key="normal" image="endcall.png"/>
<connections>
<action selector="didPressDeclineCallWithSender:" destination="Tyf-mN-gzf" eventType="touchUpInside" id="9BJ-eS-7AG"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="TUk-K2-YSx" userLabel="Answer">
<rect key="frame" x="255.5" y="0.0" width="80" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="1rO-db-thZ"/>
<constraint firstAttribute="width" secondItem="TUk-K2-YSx" secondAttribute="height" multiplier="1:1" id="d6N-G5-flo"/>
</constraints>
<state key="normal" image="call.png"/>
<connections>
<action selector="didPressAnswerCallWithSender:" destination="Tyf-mN-gzf" eventType="touchUpInside" id="Nsy-gL-24V"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="TUk-K2-YSx" firstAttribute="centerY" secondItem="qtF-I8-OlZ" secondAttribute="centerY" id="8of-qZ-3g6"/>
<constraint firstItem="TUk-K2-YSx" firstAttribute="centerX" secondItem="qtF-I8-OlZ" secondAttribute="centerX" constant="108" id="BwS-B7-UFj"/>
<constraint firstAttribute="height" constant="80" id="a38-ig-Vdx"/>
<constraint firstItem="tqx-VV-3f1" firstAttribute="centerX" secondItem="qtF-I8-OlZ" secondAttribute="centerX" constant="-108" id="a7f-sa-u5E"/>
<constraint firstItem="tqx-VV-3f1" firstAttribute="centerY" secondItem="qtF-I8-OlZ" secondAttribute="centerY" id="hEK-8j-k8P"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="qtF-I8-OlZ" firstAttribute="top" secondItem="5E5-dq-23I" secondAttribute="top" id="0rH-Ib-QGg"/>
<constraint firstAttribute="bottom" secondItem="qtF-I8-OlZ" secondAttribute="bottom" constant="16" id="m7r-9Q-dbe"/>
<constraint firstItem="qtF-I8-OlZ" firstAttribute="leading" secondItem="5E5-dq-23I" secondAttribute="leading" id="uOS-TT-KF0"/>
<constraint firstAttribute="trailing" secondItem="qtF-I8-OlZ" secondAttribute="trailing" id="uja-WP-bxH"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="uej-BZ-jAb" secondAttribute="trailing" id="6mk-1V-qHZ"/>
<constraint firstItem="6aP-bG-6qH" firstAttribute="top" secondItem="bgz-d3-b1N" secondAttribute="bottom" id="714-6d-zm0"/>
<constraint firstItem="uej-BZ-jAb" firstAttribute="leading" secondItem="fjB-Ns-OQs" secondAttribute="leading" id="ClP-il-Yf3"/>
<constraint firstItem="bgz-d3-b1N" firstAttribute="top" secondItem="0w2-kv-AfM" secondAttribute="bottom" id="GIA-x8-Ugw"/>
<constraint firstItem="d8M-LU-Hfa" firstAttribute="top" relation="greaterThanOrEqual" secondItem="6aP-bG-6qH" secondAttribute="bottom" id="M1W-xS-Rly"/>
<constraint firstItem="d8M-LU-Hfa" firstAttribute="top" secondItem="5E5-dq-23I" secondAttribute="bottom" id="TX0-VF-EdJ"/>
<constraint firstItem="d8M-LU-Hfa" firstAttribute="top" secondItem="uej-BZ-jAb" secondAttribute="bottom" id="UMq-wM-QrB"/>
<constraint firstItem="bgz-d3-b1N" firstAttribute="height" secondItem="fjB-Ns-OQs" secondAttribute="height" multiplier="1:2" constant="1" id="YGm-Ij-xfM"/>
<constraint firstAttribute="trailing" secondItem="6aP-bG-6qH" secondAttribute="trailing" id="dw7-6I-6ja"/>
<constraint firstItem="5E5-dq-23I" firstAttribute="leading" secondItem="fjB-Ns-OQs" secondAttribute="leading" id="ig5-hm-72O"/>
<constraint firstItem="uej-BZ-jAb" firstAttribute="top" secondItem="0w2-kv-AfM" secondAttribute="bottom" constant="-20" id="qV1-mQ-MCP"/>
<constraint firstItem="6aP-bG-6qH" firstAttribute="leading" secondItem="fjB-Ns-OQs" secondAttribute="leading" id="rD8-nc-45P"/>
<constraint firstAttribute="trailing" secondItem="5E5-dq-23I" secondAttribute="trailing" id="rTC-1c-nvO"/>
<constraint firstAttribute="trailingMargin" secondItem="bgz-d3-b1N" secondAttribute="trailing" constant="12" id="tGv-Vo-kls"/>
<constraint firstItem="bgz-d3-b1N" firstAttribute="leading" secondItem="fjB-Ns-OQs" secondAttribute="leadingMargin" constant="12" id="uCE-8S-LvK"/>
</constraints>
</view>
<connections>
<outlet property="callControls" destination="6aP-bG-6qH" id="out-ek-gWQ"/>
<outlet property="callStatusLabel" destination="XWw-3X-PNN" id="GIf-J0-uGc"/>
<outlet property="contactAvatarView" destination="id3-xi-PFz" id="CUV-hJ-Qcp"/>
<outlet property="contactNameLabel" destination="UbL-8Z-oK1" id="h9V-l9-JVF"/>
<outlet property="incomingCallControls" destination="5E5-dq-23I" id="fWz-1n-pjI"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="5mi-rT-gg5" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1790" y="-2348"/>
</scene>
<!--Fingerprint View Controller-->
<scene sceneID="ldP-mt-Vsq">
<objects>
@ -323,7 +549,7 @@
</connections>
</barButtonItem>
</objects>
<point key="canvasLocation" x="-1087" y="-2423"/>
<point key="canvasLocation" x="-860" y="-1285"/>
</scene>
<!--Conversation Settings-->
<scene sceneID="Flt-X5-Amc">
@ -601,7 +827,7 @@
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Ftx-dN-loa" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1567.5" y="-1539.5"/>
<point key="canvasLocation" x="-1393" y="-1482"/>
</scene>
<!--Show Group Members View Controller-->
<scene sceneID="VBt-Ax-0G9">
@ -921,19 +1147,19 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Signal on Chrome" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="57o-uV-YOg">
<rect key="frame" x="17" y="8" width="136" height="20"/>
<rect key="frame" x="17" y="8" width="136" height="19.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Linked: Jun 12, 2016" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uL2-wj-OZr">
<rect key="frame" x="17" y="28" width="124" height="18"/>
<rect key="frame" x="17" y="27.5" width="124" height="18"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Last Seen: Aug 25, 2016" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Kek-MK-oLy">
<rect key="frame" x="17" y="46" width="148" height="17"/>
<rect key="frame" x="17" y="45.5" width="148" height="17.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -1072,7 +1298,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Network Status" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uNq-FV-lwt">
<rect key="frame" x="15" y="-1" width="200" height="45"/>
<rect key="frame" x="15" y="-0.5" width="200" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="nOw-0c-lAd"/>
<constraint firstAttribute="width" constant="200" id="q6L-Sa-lrA"/>
@ -1082,7 +1308,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connected" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tg3-dQ-odw">
<rect key="frame" x="260" y="-1" width="100" height="45"/>
<rect key="frame" x="260" y="-0.5" width="100" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="Lw2-Fv-sOC"/>
<constraint firstAttribute="height" constant="44" id="uvH-QZ-iUw"/>
@ -1293,28 +1519,28 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6B0-ZZ-d6K" userLabel="Instructions">
<rect key="frame" x="16" y="344" width="343" height="279"/>
<rect key="frame" x="16" y="343.5" width="343" height="279.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Oqf-fj-8Va" userLabel="Spacer">
<rect key="frame" x="0.0" y="0.0" width="343" height="64"/>
<rect key="frame" x="0.0" y="0.0" width="343" height="64.5"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="16" id="Xq5-fV-fUr"/>
</constraints>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_devices_ios" translatesAutoresizingMaskIntoConstraints="NO" id="zl7-KG-ma4">
<rect key="frame" x="86" y="64" width="171" height="114"/>
<rect key="frame" x="86" y="64.5" width="171" height="114"/>
<color key="tintColor" red="0.67450980392156867" green="0.67450980392156867" blue="0.67450980392156867" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" secondItem="zl7-KG-ma4" secondAttribute="height" multiplier="3:2" id="RKt-hc-iWB"/>
</constraints>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GMf-cM-kQN" userLabel="Spacer">
<rect key="frame" x="0.0" y="215" width="343" height="64"/>
<rect key="frame" x="0.0" y="215.5" width="343" height="64"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Scan the QR code displayed on the device to link." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="3D8-yn-TJ8">
<rect key="frame" x="0.0" y="194" width="343" height="21"/>
<rect key="frame" x="0.0" y="194.5" width="343" height="21"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -1340,7 +1566,7 @@
</constraints>
</view>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eF5-us-VJe">
<rect key="frame" x="0.0" y="64" width="375" height="280"/>
<rect key="frame" x="0.0" y="64" width="375" height="279.5"/>
<color key="backgroundColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<segue destination="xDh-Mk-Yo9" kind="embed" identifier="embedDeviceQRScanner" id="mve-0t-D0g"/>
@ -1623,7 +1849,7 @@
<viewControllerLayoutGuide type="bottom" id="s4y-gf-WU5"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="aYO-nF-lxB">
<rect key="frame" x="0.0" y="0.0" width="375" height="280"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="279.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
@ -1662,6 +1888,7 @@
<image name="endcall.png" width="100" height="100"/>
<image name="ic_devices_ios" width="180" height="119"/>
<image name="ic_lock_outline" width="32" height="32"/>
<image name="logoSignal" width="138" height="139"/>
<image name="mute-active" width="80" height="80"/>
<image name="mute-inactive" width="80" height="80"/>
<image name="settings" width="44" height="44"/>
@ -1678,5 +1905,6 @@
<inferredMetricsTieBreakers>
<segue reference="wgA-Oo-kKq"/>
<segue reference="E8S-Yc-X7E"/>
<segue reference="G2B-Fr-Ezs"/>
</inferredMetricsTieBreakers>
</document>

View File

@ -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)
}
}

View File

@ -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<UNNotificationCategory> {
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)
}
}

View File

@ -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}}")
}
}

View File

@ -14,6 +14,8 @@
@import AVFoundation;
NS_ASSUME_NONNULL_BEGIN
@interface AppAudioManager : NSObject <SoundPlayerDelegate>
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

View File

@ -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

View File

@ -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<Void>?
// 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<Void> {
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<HardenedRTCSessionDescription> 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<Void> 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<HardenedRTCSessionDescription> 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<Void>.pending()
let timeout: Promise<Void> = 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 <iOS10, since syntax is different and it's just a development convenience.
}
}
private func getIceServers() -> 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<Void> {
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"
}
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}
}

View File

@ -0,0 +1,305 @@
// Generated by the protocol buffer compiler. DO NOT EDIT!
#import <ProtocolBuffers/ProtocolBuffers.h>
// @@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<GeneratedMessageProtocol> {
@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<GeneratedMessageProtocol> {
@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<GeneratedMessageProtocol> {
@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<GeneratedMessageProtocol> {
@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)

File diff suppressed because it is too large Load Diff

View File

@ -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<HardenedRTCSessionDescription> {
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<Void> {
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<HardenedRTCSessionDescription> {
return firstly {
return self.setRemoteSessionDescription(remoteDescription)
}.then {
return self.negotiateAnswerSessionDescription(constraints: constraints)
}
}
func setRemoteSessionDescription(_ sessionDescription: RTCSessionDescription) -> Promise<Void> {
return PromiseKit.wrap {
Logger.verbose("\(self.TAG) setting remote description: \(sessionDescription)")
peerConnection.setRemoteDescription(sessionDescription, completionHandler: $0)
}
}
func negotiateAnswerSessionDescription(constraints: RTCMediaConstraints) -> Promise<HardenedRTCSessionDescription> {
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)
}
}

View File

@ -102,7 +102,7 @@
[call saveWithTransaction:transaction];
NotificationsManager *manager = [TextSecureKitEnv sharedEnv].notificationsManager;
NotificationsManager *manager = [Environment getCurrent].notificationsManager;
[manager notifyUserForCall:call inThread:thread];
}];
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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
}
}
}

View File

@ -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()
}

View File

@ -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 <NSObject>
- (void)presentIncomingCall:(SignalCall *)call callerName:(NSString *)callerName;
- (void)presentMissedCall:(SignalCall *)call callerName:(NSString *)callerName;
@end
NS_ASSUME_NONNULL_END

View File

@ -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)
}
}
}

View File

@ -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;

View File

@ -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];

View File

@ -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;

View File

@ -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 {

View File

@ -1,18 +1,23 @@
//
// NotificationsManager.h
// Signal
//
// Created by Frederic Jacobs on 22/12/15.
// Copyright © 2015 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "OWSCallNotificationsAdaptee.h"
#import <SignalServiceKit/NotificationsProtocol.h>
@class TSCall;
NS_ASSUME_NONNULL_BEGIN
@interface NotificationsManager : NSObject <NotificationsProtocol>
@class TSCall;
@class TSContactThread;
@class OWSContactsManager;
@class SignalCall;
@class PropertyListPreferences;
@interface NotificationsManager : NSObject <NotificationsProtocol, OWSCallNotificationsAdaptee>
#pragma mark - RedPhone Call Notifications
- (void)notifyUserForCall:(TSCall *)call inThread:(TSThread *)thread;
@end
NS_ASSUME_NONNULL_END

View File

@ -8,8 +8,10 @@
#import "NotificationsManager.h"
#import "Environment.h"
#import "OWSContactsManager.h"
#import "PropertyListPreferences.h"
#import "PushManager.h"
#import "Signal-Swift.h"
#import <AudioToolbox/AudioServices.h>
#import <SignalServiceKit/TSCall.h>
#import <SignalServiceKit/TSContactThread.h>
@ -20,7 +22,8 @@
@interface NotificationsManager ()
@property SystemSoundID newMessageSound;
@property (nonatomic, readonly) id<ContactsManagerProtocol> contactsManager;
@property NSMutableDictionary<NSString *, UILocalNotification *> *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<ContactsManagerProtocol>)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

View File

@ -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,

View File

@ -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

View File

@ -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);

View File

@ -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];

View File

@ -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
}()
}

View File

@ -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(^{

View File

@ -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)
}
}

View File

@ -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;
}

View File

@ -61,7 +61,7 @@
[super viewWillDisappear:animated];
[self stopRingingAnimation];
[self stopConnectingFlashAnimation];
[AppAudioManager.sharedInstance cancellAllAudio];
[AppAudioManager.sharedInstance cancelAllAudio];
[UIDevice.currentDevice setProximityMonitoringEnabled:NO];
}

View File

@ -64,9 +64,23 @@
#import <SignalServiceKit/TSNetworkManager.h>
#import <YapDatabase/YapDatabaseView.h>
@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];
}
}

View File

@ -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<UIViewControllerPreviewing>)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<UIViewControllerPreviewing>)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;
}
}

View File

@ -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
}
}

View File

@ -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"

View File

@ -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";

15
protobuf/Makefile Normal file
View File

@ -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

View File

@ -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;
}