Skip to content

Commit

Permalink
Don't use CallKit for video calls received while the app is active
Browse files Browse the repository at this point in the history
  • Loading branch information
Imperiopolis committed Aug 17, 2020
1 parent dc543ce commit 7137cf7
Show file tree
Hide file tree
Showing 17 changed files with 261 additions and 107 deletions.
12 changes: 8 additions & 4 deletions Signal.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,6 @@
34C2EEB42270D1CE00BCA1D0 /* StickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEB32270D1CE00BCA1D0 /* StickerView.swift */; };
34C2EEB62270FF7C00BCA1D0 /* LinearHorizontalLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEB52270FF7B00BCA1D0 /* LinearHorizontalLayout.swift */; };
34C2EEB92272244600BCA1D0 /* OWSMessageStickerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEB72272244500BCA1D0 /* OWSMessageStickerView.m */; };
34C3C78D20409F320000134C /* Opening.m4r in Resources */ = {isa = PBXBuildFile; fileRef = 34C3C78C20409F320000134C /* Opening.m4r */; };
34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 34C3C78E2040A4F70000134C /* sonarping.mp3 */; };
34C3C7922040B0DD0000134C /* OWSAudioPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
34C3C7932040B0DD0000134C /* OWSAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C3C7912040B0DC0000134C /* OWSAudioPlayer.m */; };
Expand Down Expand Up @@ -653,6 +652,8 @@
885AC56723AB42430077705E /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885AC56623AB42430077705E /* NotificationService.swift */; };
885C35502370DFD50004BA35 /* OWSSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885C354F2370DFD50004BA35 /* OWSSyncManager.swift */; };
8861DED02445349C00BB4145 /* LinkingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8861DECF2445349B00BB4145 /* LinkingTextView.swift */; };
886CB07824E77E5B00753909 /* silence.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 886CB07724E77E5B00753909 /* silence.aiff */; };
886CB07C24E78F2200753909 /* Reflection.m4r in Resources */ = {isa = PBXBuildFile; fileRef = 886CB07B24E78F2200753909 /* Reflection.m4r */; };
887889A52476E999001B5FCF /* OWSPinConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887889A42476E999001B5FCF /* OWSPinConfirmationViewController.swift */; };
887889B1247F2E72001B5FCF /* Emoji+Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887889AF247F2E72001B5FCF /* Emoji+Category.swift */; };
887889B2247F2E72001B5FCF /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887889B0247F2E72001B5FCF /* Emoji.swift */; };
Expand Down Expand Up @@ -1234,7 +1235,6 @@
34C2EEB52270FF7B00BCA1D0 /* LinearHorizontalLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinearHorizontalLayout.swift; sourceTree = "<group>"; };
34C2EEB72272244500BCA1D0 /* OWSMessageStickerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageStickerView.m; sourceTree = "<group>"; };
34C2EEB82272244500BCA1D0 /* OWSMessageStickerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageStickerView.h; sourceTree = "<group>"; };
34C3C78C20409F320000134C /* Opening.m4r */ = {isa = PBXFileReference; lastKnownFileType = file; path = Opening.m4r; sourceTree = "<group>"; };
34C3C78E2040A4F70000134C /* sonarping.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = sonarping.mp3; path = Signal/AudioFiles/sonarping.mp3; sourceTree = SOURCE_ROOT; };
34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAudioPlayer.h; sourceTree = "<group>"; };
34C3C7912040B0DC0000134C /* OWSAudioPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAudioPlayer.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1618,6 +1618,8 @@
885C354F2370DFD50004BA35 /* OWSSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSyncManager.swift; sourceTree = "<group>"; };
885DDD212362695B0003AC9C /* ActionSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = "<group>"; };
8861DECF2445349B00BB4145 /* LinkingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkingTextView.swift; sourceTree = "<group>"; };
886CB07724E77E5B00753909 /* silence.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = silence.aiff; sourceTree = "<group>"; };
886CB07B24E78F2200753909 /* Reflection.m4r */ = {isa = PBXFileReference; lastKnownFileType = file; path = Reflection.m4r; sourceTree = "<group>"; };
886F08A6234958970085C27F /* InputAccessoryViewPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputAccessoryViewPlaceholder.swift; sourceTree = "<group>"; };
8872409323E12DF500824D62 /* NotificationServiceExtension-AppStore.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "NotificationServiceExtension-AppStore.entitlements"; sourceTree = "<group>"; };
887889952474E199001B5FCF /* ga */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ga; path = translations/ga.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1963,6 +1965,7 @@
isa = PBXGroup;
children = (
45A2F004204473A3002E978A /* NewMessage.aifc */,
886CB07724E77E5B00753909 /* silence.aiff */,
34661FB720C1C0D60056EDD6 /* message_sent.aiff */,
34CF0783203E6B77005C4D61 /* busy_tone_ansi.caf */,
34CF0786203E6B78005C4D61 /* end_call_tone_cept.caf */,
Expand Down Expand Up @@ -2613,7 +2616,7 @@
34C3C78B20409F320000134C /* ringtoneSounds */ = {
isa = PBXGroup;
children = (
34C3C78C20409F320000134C /* Opening.m4r */,
886CB07B24E78F2200753909 /* Reflection.m4r */,
);
name = ringtoneSounds;
path = Signal/AudioFiles/ringtoneSounds;
Expand Down Expand Up @@ -4013,11 +4016,11 @@
34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */,
45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */,
887CD4872473587300FDD265 /* transfer.json in Resources */,
34C3C78D20409F320000134C /* Opening.m4r in Resources */,
4C0CF6FA2386295400C9F818 /* tap_to_focus.json in Resources */,
B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */,
34CF0787203E6B78005C4D61 /* busy_tone_ansi.caf in Resources */,
4C9D349C2369F11F006A4307 /* notificationPermission0.png in Resources */,
886CB07824E77E5B00753909 /* silence.aiff in Resources */,
34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */,
45A2F005204473A3002E978A /* NewMessage.aifc in Resources */,
45B74A882044AAB600CD42F8 /* aurora.aifc in Resources */,
Expand All @@ -4032,6 +4035,7 @@
88A9419724099F85000E9700 /* attachment_light.json in Resources */,
45B74A812044AAB600CD42F8 /* chord-quiet.aifc in Resources */,
45B74A832044AAB600CD42F8 /* circles.aifc in Resources */,
886CB07C24E78F2200753909 /* Reflection.m4r in Resources */,
88A9419524099F7E000E9700 /* attachment_dark.json in Resources */,
45B74A892044AAB600CD42F8 /* circles-quiet.aifc in Resources */,
4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */,
Expand Down
Binary file removed Signal/AudioFiles/ringtoneSounds/Opening.m4r
Binary file not shown.
Binary file added Signal/AudioFiles/ringtoneSounds/Reflection.m4r
Binary file not shown.
Binary file added Signal/Sounds/silence.aiff
Binary file not shown.
68 changes: 58 additions & 10 deletions Signal/src/call/CallAudioService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ protocol CallAudioServiceDelegate: class {
@objc class CallAudioService: NSObject, CallObserver {

private var vibrateTimer: Timer?
private var muteStatusTimer: Timer?
private let audioPlayer = AVAudioPlayer()
private let handleRinging: Bool
var handleRinging = false
weak var delegate: CallAudioServiceDelegate? {
willSet {
assert(newValue == nil || delegate == nil)
Expand All @@ -40,9 +41,7 @@ protocol CallAudioServiceDelegate: class {

// MARK: - Initializers

init(handleRinging: Bool) {
self.handleRinging = handleRinging

override init() {
super.init()

// We cannot assert singleton here, because this class gets rebuilt when the user changes relevant call settings
Expand Down Expand Up @@ -129,6 +128,26 @@ protocol CallAudioServiceDelegate: class {
}
}

private static let muteSoundId = OWSSounds.systemSoundID(for: .silence, quiet: false)
private static var isMuteSwitchEnabled = AtomicBool(false)
private static func updateMuteSwitchStatus(completion: @escaping (_ statusDidChange: Bool) -> Void) {
// In order to detect if the mute switch is enabled, we play a silent sound.
// If playback finishes immediately, the switch is on. This is super hack-y,
// but the only non-private API available to do this somewhat reliably.
let startTime = CACurrentMediaTime()
AudioServicesPlaySystemSoundWithCompletion(muteSoundId) {
let elapsedTime = CACurrentMediaTime() - startTime
let currentValue = isMuteSwitchEnabled.get()
let newValue = elapsedTime < 0.1
if currentValue != newValue {
isMuteSwitchEnabled.set(newValue)
DispatchQueue.main.async { completion(true) }
} else {
DispatchQueue.main.async { completion(false) }
}
}
}

private func ensureProperAudioSession(call: SignalCall?) {
AssertIsOnMainThread()

Expand All @@ -148,9 +167,14 @@ protocol CallAudioServiceDelegate: class {
let options: AVAudioSession.CategoryOptions = call.audioSource?.isBuiltInEarPiece == true ? [] : [.allowBluetooth]

if call.state == .localRinging {
// SoloAmbient plays through speaker, but respects silent switch
setAudioSession(category: .soloAmbient,
mode: .default)
// SoloAmbient plays through speaker and respects silent switch,
// but does not playback in the background. Playback plays through
// speaker and plays back in the background, but does not respect
// the mute switch. Ideally, there would be a category that allowed
// background playback AND respecting the mute switch, but because
// there is not we have to manually switch between categories when
// we detect the mute switch changed.
setAudioSession(category: Self.isMuteSwitchEnabled.get() ? .soloAmbient : .playback, mode: .default)
} else if call.hasLocalVideo {
// Because ModeVideoChat affects gain, we don't want to apply it until the call is connected.
// otherwise sounds like ringing will be extra loud for video vs. speakerphone
Expand Down Expand Up @@ -365,11 +389,31 @@ protocol CallAudioServiceDelegate: class {
return
}

vibrateTimer = WeakTimer.scheduledTimer(timeInterval: vibrateRepeatDuration, target: self, userInfo: nil, repeats: true) {[weak self] _ in
vibrateTimer?.invalidate()
vibrateTimer = .scheduledTimer(withTimeInterval: vibrateRepeatDuration, repeats: true) { [weak self] _ in
self?.ringVibration()
}
vibrateTimer?.fire()
play(sound: .defaultiOSIncomingRingtone)

// Before playing the ring tone, check if the mute switch is enabled.
// If it is not enabled, we want to play with a mode that lets us
// ring in the background.
Self.updateMuteSwitchStatus { [weak self] stateDidChange in
guard !call.isEnded else { return }
if stateDidChange { self?.ensureProperAudioSession(call: call) }
self?.play(sound: .defaultiOSIncomingRingtone)
}

// Check regularly if the mute switch status has changed. If it has,
// we need to update our audio session category to ensure ringtone
// playback.
muteStatusTimer?.invalidate()
muteStatusTimer = .scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] _ in
Self.updateMuteSwitchStatus { stateDidChange in
guard !call.isEnded else { return }
guard stateDidChange else { return }
self?.ensureProperAudioSession(call: call)
}
}
}

private func stopAnyRingingVibration() {
Expand All @@ -382,6 +426,10 @@ protocol CallAudioServiceDelegate: class {
// Stop vibrating
vibrateTimer?.invalidate()
vibrateTimer = nil

// Stop polling mute switch status
muteStatusTimer?.invalidate()
muteStatusTimer = nil
}

// public so it can be called by timer via selector
Expand Down
35 changes: 14 additions & 21 deletions Signal/src/call/CallService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ extension SignalCall: CallManagerCallReference { }
// Create a callRecord for outgoing calls immediately.
let callRecord = TSCall(
callType: .outgoingIncomplete,
offerType: call.offerMediaType.recentOfferType,
offerType: call.offerMediaType,
thread: call.thread,
sentAtTimestamp: call.sentAtTimestamp
)
Expand All @@ -216,7 +216,7 @@ extension SignalCall: CallManagerCallReference { }
let localDeviceId = TSAccountManager.sharedInstance().storedDeviceId()

do {
try callManager.placeCall(call: call, callMediaType: call.offerMediaType.callMediaType, localDevice: localDeviceId)
try callManager.placeCall(call: call, callMediaType: call.offerMediaType.asCallMediaType, localDevice: localDeviceId)
} catch {
self.handleFailedCall(failedCall: call, error: error)
}
Expand All @@ -242,7 +242,7 @@ extension SignalCall: CallManagerCallReference { }

let callRecord = TSCall(
callType: .incomingIncomplete,
offerType: call.offerMediaType.recentOfferType,
offerType: call.offerMediaType,
thread: call.thread,
sentAtTimestamp: call.sentAtTimestamp
)
Expand Down Expand Up @@ -301,7 +301,7 @@ extension SignalCall: CallManagerCallReference { }
} else if call.state == .localRinging {
let callRecord = TSCall(
callType: .incomingDeclined,
offerType: call.offerMediaType.recentOfferType,
offerType: call.offerMediaType,
thread: call.thread,
sentAtTimestamp: call.sentAtTimestamp
)
Expand Down Expand Up @@ -407,7 +407,7 @@ extension SignalCall: CallManagerCallReference { }
Logger.warn("user is not onboarded, skipping call.")
let callRecord = TSCall(
callType: .incomingMissed,
offerType: newCall.offerMediaType.recentOfferType,
offerType: newCall.offerMediaType,
thread: thread,
sentAtTimestamp: sentAtTimestamp
)
Expand Down Expand Up @@ -440,7 +440,7 @@ extension SignalCall: CallManagerCallReference { }

let callRecord = TSCall(
callType: .incomingMissedBecauseOfChangedIdentity,
offerType: newCall.offerMediaType.recentOfferType,
offerType: newCall.offerMediaType,
thread: thread,
sentAtTimestamp: sentAtTimestamp
)
Expand Down Expand Up @@ -475,7 +475,7 @@ extension SignalCall: CallManagerCallReference { }
// or the caller can try again.
let callRecord = TSCall(
callType: .incomingMissed,
offerType: newCall.offerMediaType.recentOfferType,
offerType: newCall.offerMediaType,
thread: thread,
sentAtTimestamp: sentAtTimestamp
)
Expand Down Expand Up @@ -833,7 +833,7 @@ extension SignalCall: CallManagerCallReference { }
assert(call.direction == .incoming)
let callRecord = TSCall(
callType: .incomingMissed,
offerType: call.offerMediaType.recentOfferType,
offerType: call.offerMediaType,
thread: call.thread,
sentAtTimestamp: call.sentAtTimestamp
)
Expand Down Expand Up @@ -1096,7 +1096,7 @@ extension SignalCall: CallManagerCallReference { }
} else {
callRecord = TSCall(
callType: .incomingMissed,
offerType: call.offerMediaType.recentOfferType,
offerType: call.offerMediaType,
thread: call.thread,
sentAtTimestamp: call.sentAtTimestamp
)
Expand Down Expand Up @@ -1136,7 +1136,7 @@ extension SignalCall: CallManagerCallReference { }
} else {
let callRecord = TSCall(
callType: .incomingAnsweredElsewhere,
offerType: call.offerMediaType.recentOfferType,
offerType: call.offerMediaType,
thread: call.thread,
sentAtTimestamp: call.sentAtTimestamp
)
Expand All @@ -1161,7 +1161,7 @@ extension SignalCall: CallManagerCallReference { }
} else {
let callRecord = TSCall(
callType: .incomingDeclinedElsewhere,
offerType: call.offerMediaType.recentOfferType,
offerType: call.offerMediaType,
thread: call.thread,
sentAtTimestamp: call.sentAtTimestamp
)
Expand All @@ -1186,7 +1186,7 @@ extension SignalCall: CallManagerCallReference { }
} else {
let callRecord = TSCall(
callType: .incomingBusyElsewhere,
offerType: call.offerMediaType.recentOfferType,
offerType: call.offerMediaType,
thread: call.thread,
sentAtTimestamp: call.sentAtTimestamp
)
Expand Down Expand Up @@ -1682,18 +1682,11 @@ extension NSNumber {
}
}

extension CallOfferMediaType {
var callMediaType: CallMediaType {
extension TSRecentCallOfferType {
var asCallMediaType: CallMediaType {
switch self {
case .audio: return .audioCall
case .video: return .videoCall
}
}

var recentOfferType: TSRecentCallOfferType {
switch self {
case .audio: return .audio
case .video: return .video
}
}
}
8 changes: 5 additions & 3 deletions Signal/src/call/SignalCall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public enum CallState: String {
case busyElsewhere // terminal
}

public enum CallOfferMediaType {
case audio, video
public enum CallAdapterType {
case `default`, nonCallKit
}

public enum CallDirection {
Expand Down Expand Up @@ -75,6 +75,8 @@ public class SignalCall: NSObject, SignalCallNotificationInfo {
}
}

var callAdapterType: CallAdapterType?

let videoCaptureController = VideoCaptureController()

weak var localCaptureSession: AVCaptureSession? {
Expand Down Expand Up @@ -182,7 +184,7 @@ public class SignalCall: NSObject, SignalCallNotificationInfo {
}
}

public var offerMediaType: CallOfferMediaType = .audio
public var offerMediaType: TSRecentCallOfferType = .audio

public var isMuted = false {
didSet {
Expand Down
Loading

0 comments on commit 7137cf7

Please sign in to comment.