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:
parent
f01c5a1985
commit
647b2b37e9
|
@ -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)
|
4
Podfile
4
Podfile
|
@ -4,8 +4,8 @@ source 'https://github.com/CocoaPods/Specs.git'
|
|||
target 'Signal' do
|
||||
pod 'SocketRocket', :git => 'https://github.com/facebook/SocketRocket.git'
|
||||
pod 'AxolotlKit', git: 'https://github.com/WhisperSystems/SignalProtocolKit.git'
|
||||
pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git', branch: 'master'
|
||||
#pod 'SignalServiceKit', path: '../SignalServiceKit'
|
||||
#pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git'
|
||||
pod 'SignalServiceKit', path: '../SignalServiceKit'
|
||||
pod 'OpenSSL'
|
||||
pod 'PastelogKit', '~> 1.3'
|
||||
pod 'FFCircularProgressView', '~> 0.5'
|
||||
|
|
14
Podfile.lock
14
Podfile.lock
|
@ -32,7 +32,7 @@ PODS:
|
|||
- JSQMessagesViewController (7.3.4):
|
||||
- JSQSystemSoundPlayer (~> 2.0.1)
|
||||
- JSQSystemSoundPlayer (2.0.1)
|
||||
- libPhoneNumber-iOS (0.9.2)
|
||||
- libPhoneNumber-iOS (0.9.1)
|
||||
- Mantle (2.1.0):
|
||||
- Mantle/extobjc (= 2.1.0)
|
||||
- Mantle/extobjc (2.1.0)
|
||||
|
@ -121,7 +121,7 @@ DEPENDENCIES:
|
|||
- OpenSSL
|
||||
- PastelogKit (~> 1.3)
|
||||
- SCWaveformView (~> 1.0)
|
||||
- SignalServiceKit (from `https://github.com/WhisperSystems/SignalServiceKit.git`, branch `master`)
|
||||
- SignalServiceKit (from `../SignalServiceKit`)
|
||||
- SocketRocket (from `https://github.com/facebook/SocketRocket.git`)
|
||||
- ZXingObjC
|
||||
|
||||
|
@ -129,8 +129,7 @@ EXTERNAL SOURCES:
|
|||
AxolotlKit:
|
||||
:git: https://github.com/WhisperSystems/SignalProtocolKit.git
|
||||
SignalServiceKit:
|
||||
:branch: master
|
||||
:git: https://github.com/WhisperSystems/SignalServiceKit.git
|
||||
:path: "../SignalServiceKit"
|
||||
SocketRocket:
|
||||
:git: https://github.com/facebook/SocketRocket.git
|
||||
|
||||
|
@ -138,9 +137,6 @@ CHECKOUT OPTIONS:
|
|||
AxolotlKit:
|
||||
:commit: 714f5ebe199ecc999b33c6f97a4bb57e2db90e75
|
||||
:git: https://github.com/WhisperSystems/SignalProtocolKit.git
|
||||
SignalServiceKit:
|
||||
:commit: 7b7b338075f3b4615755fcc1f1fb8a8c67b0bd0a
|
||||
:git: https://github.com/WhisperSystems/SignalServiceKit.git
|
||||
SocketRocket:
|
||||
:commit: 41b57bb2fc292a814f758441a05243eb38457027
|
||||
:git: https://github.com/facebook/SocketRocket.git
|
||||
|
@ -154,7 +150,7 @@ SPEC CHECKSUMS:
|
|||
HKDFKit: c058305d6f64b84f28c50bd7aa89574625bcb62a
|
||||
JSQMessagesViewController: 39fed975e3c9f8eba7292071e29eeb541d105e66
|
||||
JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d
|
||||
libPhoneNumber-iOS: a8bffdec18c37728360f6771fe021302f1e0b497
|
||||
libPhoneNumber-iOS: 81ad1e6bfcf46e668636333269afbfe60399a55b
|
||||
Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b
|
||||
OpenSSL: 246ffb948e9d56466727fd318134af35f5aa764e
|
||||
PastelogKit: 7b475be4cf577713506a943dd940bcc0499c8bca
|
||||
|
@ -170,6 +166,6 @@ SPEC CHECKSUMS:
|
|||
YapDatabase: b1e43555a34a5298e23a045be96817a5ef0da58f
|
||||
ZXingObjC: bf15b3814f7a105b6d99f47da2333c93a063650a
|
||||
|
||||
PODFILE CHECKSUM: cb24c78080551874a45d1a20de4a1bef7427b41f
|
||||
PODFILE CHECKSUM: 05b247199157a4742765e0ea45fe671dbf48b58e
|
||||
|
||||
COCOAPODS: 1.0.1
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "A17C67DE5BCCF5991DAF479D127CE09E"
|
||||
BlueprintIdentifier = "67EEE5C28C29FF38DA320F2E9F2B8666"
|
||||
BuildableName = "libSignalServiceKit.a"
|
||||
BlueprintName = "SignalServiceKit"
|
||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
line_length: 200
|
||||
disabled_rules:
|
||||
- file_length
|
||||
- todo
|
||||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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}}")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -102,7 +102,7 @@
|
|||
|
||||
[call saveWithTransaction:transaction];
|
||||
|
||||
NotificationsManager *manager = [TextSecureKitEnv sharedEnv].notificationsManager;
|
||||
NotificationsManager *manager = [Environment getCurrent].notificationsManager;
|
||||
[manager notifyUserForCall:call inThread:thread];
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
}()
|
||||
}
|
|
@ -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(^{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
[super viewWillDisappear:animated];
|
||||
[self stopRingingAnimation];
|
||||
[self stopConnectingFlashAnimation];
|
||||
[AppAudioManager.sharedInstance cancellAllAudio];
|
||||
[AppAudioManager.sharedInstance cancelAllAudio];
|
||||
[UIDevice.currentDevice setProximityMonitoringEnabled:NO];
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue