mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Conference updates
This commit is contained in:
parent
e6fc58d797
commit
f9191aba6b
@ -926,13 +926,17 @@ private final class NotificationServiceHandler {
|
||||
var localContactId: String?
|
||||
}
|
||||
|
||||
struct ConferenceCallData {
|
||||
struct GroupCallData {
|
||||
var id: Int64
|
||||
var updates: String
|
||||
var fromId: PeerId
|
||||
var fromTitle: String
|
||||
var isVideo: Bool
|
||||
var messageId: Int32
|
||||
var accountId: Int64
|
||||
}
|
||||
|
||||
var callData: CallData?
|
||||
var conferenceCallData: ConferenceCallData?
|
||||
var groupCallData: GroupCallData?
|
||||
|
||||
if let messageIdString = payloadJson["msg_id"] as? String {
|
||||
messageId = Int32(messageIdString)
|
||||
@ -959,20 +963,24 @@ private final class NotificationServiceHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if let locKey = payloadJson["loc-key"] as? String, (locKey == "CONF_CALL_REQUEST" || locKey == "CONF_CALL_MISSED"), let callIdString = payloadJson["call_id"] as? String {
|
||||
if let callId = Int64(callIdString) {
|
||||
if let updates = payloadJson["updates"] as? String {
|
||||
var updateString = updates
|
||||
updateString = updateString.replacingOccurrences(of: "-", with: "+")
|
||||
updateString = updateString.replacingOccurrences(of: "_", with: "/")
|
||||
while updateString.count % 4 != 0 {
|
||||
updateString.append("=")
|
||||
}
|
||||
if let updateData = Data(base64Encoded: updateString) {
|
||||
if let callUpdate = AccountStateManager.extractIncomingCallUpdate(data: updateData) {
|
||||
let _ = callUpdate
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
if let locKey = payloadJson["loc-key"] as? String, locKey == "CONF_CALL_REQUEST" {
|
||||
}
|
||||
#endif
|
||||
|
||||
if let peerId, let locKey = payloadJson["loc-key"] as? String, (locKey == "CONF_CALL_REQUEST" || locKey == "CONF_VIDEOCALL_REQUEST"), let callIdString = payloadJson["call_id"] as? String, let messageIdString = payloadJson["msg_id"] as? String {
|
||||
if let callId = Int64(callIdString), let messageId = Int32(messageIdString) {
|
||||
if let fromTitle = payloadJson["call_conference_from"] as? String {
|
||||
let isVideo = locKey == "CONF_VIDEOCALL_REQUEST"
|
||||
|
||||
groupCallData = GroupCallData(
|
||||
id: callId,
|
||||
fromId: peerId,
|
||||
fromTitle: fromTitle,
|
||||
isVideo: isVideo,
|
||||
messageId: messageId,
|
||||
accountId: recordId.int64
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if let callIdString = payloadJson["call_id"] as? String, let callAccessHashString = payloadJson["call_ah"] as? String, let peerId = peerId, let updates = payloadJson["updates"] as? String {
|
||||
@ -1011,12 +1019,15 @@ private final class NotificationServiceHandler {
|
||||
case readMessage(MessageId)
|
||||
case readStories(peerId: PeerId, maxId: Int32)
|
||||
case call(CallData)
|
||||
case groupCall(GroupCallData)
|
||||
}
|
||||
|
||||
var action: Action?
|
||||
|
||||
if let callData = callData {
|
||||
action = .call(callData)
|
||||
} else if let groupCallData {
|
||||
action = .groupCall(groupCallData)
|
||||
} else if let locKey = payloadJson["loc-key"] as? String {
|
||||
switch locKey {
|
||||
case "SESSION_REVOKE":
|
||||
@ -1265,6 +1276,50 @@ private final class NotificationServiceHandler {
|
||||
content.body = "Incoming Call"
|
||||
}
|
||||
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
}
|
||||
})
|
||||
}
|
||||
case let .groupCall(groupCallData):
|
||||
if let stateManager = strongSelf.stateManager {
|
||||
let content = NotificationContent(isLockedMessage: nil)
|
||||
updateCurrentContent(content)
|
||||
|
||||
let _ = (stateManager.postbox.transaction { transaction -> TelegramUser? in
|
||||
return transaction.getPeer(groupCallData.fromId) as? TelegramUser
|
||||
}).start(next: { fromPeer in
|
||||
var voipPayload: [AnyHashable: Any] = [
|
||||
"group_call_id": "\(groupCallData.id)",
|
||||
"msg_id": "\(groupCallData.messageId)",
|
||||
"video": "0",
|
||||
"from_id": "\(groupCallData.fromId.id._internalGetInt64Value())",
|
||||
"from_title": groupCallData.fromTitle,
|
||||
"accountId": "\(groupCallData.accountId)"
|
||||
]
|
||||
if let phoneNumber = fromPeer?.phone {
|
||||
voipPayload["phoneNumber"] = phoneNumber
|
||||
}
|
||||
|
||||
if #available(iOS 14.5, *), voiceCallSettings.enableSystemIntegration {
|
||||
Logger.shared.log("NotificationService \(episode)", "Will report voip notification")
|
||||
let content = NotificationContent(isLockedMessage: nil)
|
||||
updateCurrentContent(content)
|
||||
|
||||
CXProvider.reportNewIncomingVoIPPushPayload(voipPayload, completion: { error in
|
||||
Logger.shared.log("NotificationService \(episode)", "Did report voip notification, error: \(String(describing: error))")
|
||||
|
||||
completed()
|
||||
})
|
||||
} else {
|
||||
var content = NotificationContent(isLockedMessage: nil)
|
||||
if let peer = fromPeer {
|
||||
content.title = peer.debugDisplayTitle
|
||||
content.body = incomingCallMessage
|
||||
} else {
|
||||
content.body = "Incoming Call"
|
||||
}
|
||||
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
}
|
||||
|
@ -82,13 +82,15 @@ public struct PresentationCallState: Equatable {
|
||||
public var remoteVideoState: RemoteVideoState
|
||||
public var remoteAudioState: RemoteAudioState
|
||||
public var remoteBatteryLevel: RemoteBatteryLevel
|
||||
public var supportsConferenceCalls: Bool
|
||||
|
||||
public init(state: State, videoState: VideoState, remoteVideoState: RemoteVideoState, remoteAudioState: RemoteAudioState, remoteBatteryLevel: RemoteBatteryLevel) {
|
||||
public init(state: State, videoState: VideoState, remoteVideoState: RemoteVideoState, remoteAudioState: RemoteAudioState, remoteBatteryLevel: RemoteBatteryLevel, supportsConferenceCalls: Bool) {
|
||||
self.state = state
|
||||
self.videoState = videoState
|
||||
self.remoteVideoState = remoteVideoState
|
||||
self.remoteAudioState = remoteAudioState
|
||||
self.remoteBatteryLevel = remoteBatteryLevel
|
||||
self.supportsConferenceCalls = supportsConferenceCalls
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,11 +167,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
self.conferenceAddParticipant?()
|
||||
}
|
||||
|
||||
var isConferencePossible = true
|
||||
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_enable_conference"] as? Double {
|
||||
isConferencePossible = value != 0.0
|
||||
}
|
||||
|
||||
self.callScreenState = PrivateCallScreen.State(
|
||||
strings: presentationData.strings,
|
||||
lifecycleState: .connecting,
|
||||
@ -185,7 +180,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
remoteVideo: nil,
|
||||
isRemoteBatteryLow: false,
|
||||
isEnergySavingEnabled: !self.sharedContext.energyUsageSettings.fullTranslucency,
|
||||
isConferencePossible: isConferencePossible
|
||||
isConferencePossible: false
|
||||
)
|
||||
|
||||
self.isMicrophoneMutedDisposable = (call.isMuted
|
||||
@ -549,6 +544,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
callScreenState.isRemoteAudioMuted = false
|
||||
}
|
||||
|
||||
callScreenState.isConferencePossible = callState.supportsConferenceCalls
|
||||
|
||||
if self.callScreenState != callScreenState {
|
||||
self.callScreenState = callScreenState
|
||||
self.update(transition: .animated(duration: 0.35, curve: .spring))
|
||||
|
@ -275,6 +275,8 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
private var callWasActive = false
|
||||
private var shouldPresentCallRating = false
|
||||
|
||||
private var supportsConferenceCalls: Bool = false
|
||||
|
||||
private var previousVideoState: PresentationCallState.VideoState?
|
||||
private var previousRemoteVideoState: PresentationCallState.RemoteVideoState?
|
||||
private var previousRemoteAudioState: PresentationCallState.RemoteAudioState?
|
||||
@ -444,9 +446,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
self.isVideo = startWithVideo
|
||||
if self.isVideo {
|
||||
self.videoCapturer = OngoingCallVideoCapturer()
|
||||
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .active(isScreencast: self.isScreencastActive, endpointId: ""), remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
|
||||
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .active(isScreencast: self.isScreencastActive, endpointId: ""), remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal, supportsConferenceCalls: self.supportsConferenceCalls))
|
||||
} else {
|
||||
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .inactive : .notAvailable, remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
|
||||
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .inactive : .notAvailable, remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal, supportsConferenceCalls: self.supportsConferenceCalls))
|
||||
}
|
||||
|
||||
self.serializedData = serializedData
|
||||
@ -457,11 +459,25 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
|
||||
var didReceiveAudioOutputs = false
|
||||
|
||||
if let incomingConferenceSource = incomingConferenceSource {
|
||||
self.sessionStateDisposable = (context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Messages.Message(id: incomingConferenceSource)
|
||||
if let incomingConferenceSource {
|
||||
let isRinging = context.account.callSessionManager.ringingStates()
|
||||
|> map { ringingStates -> Bool in
|
||||
for ringingState in ringingStates {
|
||||
if ringingState.id == internalId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> take(1)
|
||||
self.sessionStateDisposable = (combineLatest(queue: .mainQueue(),
|
||||
isRinging,
|
||||
context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Messages.Message(id: incomingConferenceSource)
|
||||
)
|
||||
)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] message in
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] isRinging, message in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -485,6 +501,8 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
} else {
|
||||
state = .terminated(id: nil, reason: .ended(.hungUp), options: CallTerminationOptions())
|
||||
}
|
||||
} else if isRinging {
|
||||
state = .ringing
|
||||
} else {
|
||||
state = .terminated(id: nil, reason: .ended(.hungUp), options: CallTerminationOptions())
|
||||
}
|
||||
@ -817,6 +835,10 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
}
|
||||
|
||||
if case let .active(_, _, _, _, _, _, _, _, supportsConferenceCallsValue) = sessionState.state {
|
||||
self.supportsConferenceCalls = supportsConferenceCallsValue
|
||||
}
|
||||
|
||||
let mappedVideoState: PresentationCallState.VideoState
|
||||
let mappedRemoteVideoState: PresentationCallState.RemoteVideoState
|
||||
let mappedRemoteAudioState: PresentationCallState.RemoteAudioState
|
||||
@ -883,7 +905,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
|
||||
switch sessionState.state {
|
||||
case .ringing:
|
||||
presentationState = PresentationCallState(state: .ringing, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
presentationState = PresentationCallState(state: .ringing, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
if previous == nil || previousControl == nil {
|
||||
if !self.reportedIncomingCall, let stableId = sessionState.stableId {
|
||||
self.reportedIncomingCall = true
|
||||
@ -922,17 +944,17 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
case .accepting:
|
||||
self.callWasActive = true
|
||||
presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
case let .dropping(reason):
|
||||
if case .ended(.switchedToConference) = reason {
|
||||
} else {
|
||||
presentationState = PresentationCallState(state: .terminating(reason), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
presentationState = PresentationCallState(state: .terminating(reason), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
}
|
||||
case let .terminated(id, reason, options):
|
||||
presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
case let .requesting(ringing):
|
||||
presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
case .active(_, _, _, _, _, _, _, _), .switchedToConference:
|
||||
presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
case .active(_, _, _, _, _, _, _, _, _), .switchedToConference:
|
||||
self.callWasActive = true
|
||||
|
||||
var isConference = false
|
||||
@ -940,12 +962,12 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
isConference = true
|
||||
}
|
||||
|
||||
if let callContextState = callContextState, !isConference, case let .active(_, _, keyVisualHash, _, _, _, _, _) = sessionState.state {
|
||||
if let callContextState = callContextState, !isConference, case let .active(_, _, keyVisualHash, _, _, _, _, _, _) = sessionState.state {
|
||||
switch callContextState.state {
|
||||
case .initializing:
|
||||
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
case .failed:
|
||||
presentationState = PresentationCallState(state: .terminating(.error(.disconnected)), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
presentationState = PresentationCallState(state: .terminating(.error(.disconnected)), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
case .connected:
|
||||
let timestamp: Double
|
||||
@ -955,7 +977,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
timestamp = CFAbsoluteTimeGetCurrent()
|
||||
self.activeTimestamp = timestamp
|
||||
}
|
||||
presentationState = PresentationCallState(state: .active(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
presentationState = PresentationCallState(state: .active(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
case .reconnecting:
|
||||
let timestamp: Double
|
||||
if let activeTimestamp = self.activeTimestamp {
|
||||
@ -964,10 +986,10 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
timestamp = CFAbsoluteTimeGetCurrent()
|
||||
self.activeTimestamp = timestamp
|
||||
}
|
||||
presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
}
|
||||
} else if !isConference, case let .active(_, _, keyVisualHash, _, _, _, _, _) = sessionState.state {
|
||||
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
} else if !isConference, case let .active(_, _, keyVisualHash, _, _, _, _, _, _) = sessionState.state {
|
||||
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel, supportsConferenceCalls: self.supportsConferenceCalls)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1025,7 +1047,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
subscribedToScheduled: false,
|
||||
isStream: false
|
||||
), conferenceCallData),
|
||||
internalId: CallSessionInternalId(),
|
||||
internalId: self.internalId,
|
||||
peerId: nil,
|
||||
isChannel: false,
|
||||
invite: nil,
|
||||
@ -1155,7 +1177,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
if let _ = audioSessionControl {
|
||||
self.audioSessionShouldBeActive.set(true)
|
||||
}
|
||||
case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P):
|
||||
case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P, _):
|
||||
self.audioSessionShouldBeActive.set(true)
|
||||
|
||||
if conferenceCallData != nil {
|
||||
|
@ -1146,7 +1146,7 @@ public final class AccountStateManager {
|
||||
strongSelf.reportMessageDeliveryDisposable.add(_internal_reportMessageDelivery(postbox: strongSelf.postbox, network: strongSelf.network, messageIds: Array(events.reportMessageDelivery), fromPushNotification: false).start())
|
||||
}
|
||||
if !events.addedConferenceInvitationMessagesIds.isEmpty {
|
||||
strongSelf.callSessionManager?.addConferenceInvitationMessages(ids: events.addedConferenceInvitationMessagesIds)
|
||||
strongSelf.callSessionManager?.addConferenceInvitationMessages(ids: events.addedConferenceInvitationMessagesIds.map { ($0, nil) })
|
||||
}
|
||||
if !events.isContactUpdates.isEmpty {
|
||||
strongSelf.addIsContactUpdates(events.isContactUpdates)
|
||||
|
@ -85,7 +85,7 @@ enum CallSessionInternalState {
|
||||
case requesting(a: Data, disposable: Disposable)
|
||||
case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?)
|
||||
case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, disposable: Disposable)
|
||||
case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool)
|
||||
case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, supportsConferenceCalls: Bool)
|
||||
case switchedToConference(slug: String)
|
||||
case dropping(reason: CallSessionTerminationReason, disposable: Disposable)
|
||||
case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool)
|
||||
@ -104,7 +104,7 @@ enum CallSessionInternalState {
|
||||
return id
|
||||
case let .confirming(id, _, _, _, _, _):
|
||||
return id
|
||||
case let .active(id, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return id
|
||||
case .switchedToConference:
|
||||
return nil
|
||||
@ -120,19 +120,36 @@ public typealias CallSessionInternalId = UUID
|
||||
typealias CallSessionStableId = Int64
|
||||
|
||||
private final class StableIncomingUUIDs {
|
||||
private enum Key: Hashable {
|
||||
case global(callId: Int64)
|
||||
case group(peerId: Int64, messageId: Int32)
|
||||
}
|
||||
|
||||
static let shared = Atomic<StableIncomingUUIDs>(value: StableIncomingUUIDs())
|
||||
|
||||
private var dict: [Int64: UUID] = [:]
|
||||
private var dict: [Key: UUID] = [:]
|
||||
|
||||
private init() {
|
||||
}
|
||||
|
||||
func get(id: Int64) -> UUID {
|
||||
if let value = self.dict[id] {
|
||||
let key = Key.global(callId: id)
|
||||
if let value = self.dict[key] {
|
||||
return value
|
||||
} else {
|
||||
let value = UUID()
|
||||
self.dict[id] = value
|
||||
self.dict[key] = value
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func get(peerId: Int64, messageId: Int32) -> UUID {
|
||||
let key = Key.group(peerId: peerId, messageId: messageId)
|
||||
if let value = self.dict[key] {
|
||||
return value
|
||||
} else {
|
||||
let value = UUID()
|
||||
self.dict[key] = value
|
||||
return value
|
||||
}
|
||||
}
|
||||
@ -173,7 +190,7 @@ public enum CallSessionState {
|
||||
case ringing
|
||||
case accepting
|
||||
case requesting(ringing: Bool)
|
||||
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool)
|
||||
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, supportsConferenceCalls: Bool)
|
||||
case switchedToConference(slug: String)
|
||||
case dropping(reason: CallSessionTerminationReason)
|
||||
case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions)
|
||||
@ -190,8 +207,8 @@ public enum CallSessionState {
|
||||
self = .requesting(ringing: true)
|
||||
case let .requested(_, _, _, _, _, remoteConfirmationTimestamp):
|
||||
self = .requesting(ringing: remoteConfirmationTimestamp != nil)
|
||||
case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P):
|
||||
self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P)
|
||||
case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, supportsConferenceCalls):
|
||||
self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, supportsConferenceCalls: supportsConferenceCalls)
|
||||
case let .dropping(reason, _):
|
||||
self = .dropping(reason: reason)
|
||||
case let .terminated(id, accessHash, reason, reportRating, sendDebugLogs):
|
||||
@ -379,6 +396,16 @@ private final class CallSessionContext {
|
||||
}
|
||||
}
|
||||
|
||||
public struct IncomingConferenceTermporaryExternalInfo {
|
||||
public var callId: Int64
|
||||
public var isVideo: Bool
|
||||
|
||||
public init(callId: Int64, isVideo: Bool) {
|
||||
self.callId = callId
|
||||
self.isVideo = isVideo
|
||||
}
|
||||
}
|
||||
|
||||
private final class IncomingConferenceInvitationContext {
|
||||
enum State: Equatable {
|
||||
case pending
|
||||
@ -393,19 +420,52 @@ private final class IncomingConferenceInvitationContext {
|
||||
|
||||
private(set) var state: State = .pending
|
||||
|
||||
init(queue: Queue, postbox: Postbox, messageId: MessageId, updated: @escaping () -> Void) {
|
||||
init(queue: Queue, postbox: Postbox, internalId: CallSessionInternalId, messageId: MessageId, externalInfo: IncomingConferenceTermporaryExternalInfo?, updated: @escaping () -> Void) {
|
||||
self.queue = queue
|
||||
|
||||
self.internalId = CallSessionInternalId()
|
||||
self.internalId = internalId
|
||||
|
||||
let key = PostboxViewKey.messages(Set([messageId]))
|
||||
self.disposable = (postbox.combinedView(keys: [key])
|
||||
|> map { view -> Message? in
|
||||
guard let view = view.views[key] as? MessagesView else {
|
||||
return nil
|
||||
}
|
||||
return view.messages[messageId]
|
||||
|
||||
if let externalInfo {
|
||||
self.state = .ringing(
|
||||
callId: externalInfo.callId,
|
||||
isVideo: externalInfo.isVideo,
|
||||
otherParticipants: []
|
||||
)
|
||||
}
|
||||
|
||||
let waitSignal: Signal<Void, NoError>
|
||||
if externalInfo != nil {
|
||||
waitSignal = postbox.combinedView(keys: [key])
|
||||
|> mapToSignal { view -> Signal<Void, NoError> in
|
||||
guard let view = view.views[key] as? MessagesView else {
|
||||
return .never()
|
||||
}
|
||||
if view.messages[messageId] == nil {
|
||||
return .never()
|
||||
}
|
||||
return .single(Void())
|
||||
}
|
||||
|> take(1)
|
||||
|> timeout(5.0, queue: self.queue, alternate: deferred {
|
||||
Logger.shared.log("CallSessionManagerContext", "IncomingConferenceInvitationContext timeout for message \(messageId)")
|
||||
return Signal<Void, NoError>.single(Void())
|
||||
})
|
||||
} else {
|
||||
waitSignal = .single(Void())
|
||||
}
|
||||
|
||||
self.disposable = (waitSignal |> map { _ -> Message? in return nil }
|
||||
|> then(
|
||||
postbox.combinedView(keys: [key])
|
||||
|> map { view -> Message? in
|
||||
guard let view = view.views[key] as? MessagesView else {
|
||||
return nil
|
||||
}
|
||||
return view.messages[messageId]
|
||||
}
|
||||
)
|
||||
|> deliverOn(self.queue)).startStrict(next: { [weak self] message in
|
||||
guard let self = self else {
|
||||
return
|
||||
@ -782,7 +842,7 @@ private final class CallSessionManagerContext {
|
||||
case let .accepting(id, accessHash, _, _, disposable):
|
||||
dropData = (id, accessHash, .abort)
|
||||
disposable.dispose()
|
||||
case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _, _):
|
||||
let duration = max(0, Int32(CFAbsoluteTimeGetCurrent()) - beginTimestamp)
|
||||
let internalReason: DropCallSessionReason
|
||||
switch reason {
|
||||
@ -884,7 +944,7 @@ private final class CallSessionManagerContext {
|
||||
var dropData: (CallSessionStableId, Int64)?
|
||||
let isVideo = context.type == .video
|
||||
switch context.state {
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _):
|
||||
dropData = (id, accessHash)
|
||||
default:
|
||||
break
|
||||
@ -934,9 +994,9 @@ private final class CallSessionManagerContext {
|
||||
case let .waiting(config):
|
||||
context.state = .awaitingConfirmation(id: id, accessHash: accessHash, gAHash: gAHash, b: b, config: config)
|
||||
strongSelf.contextUpdated(internalId: internalId)
|
||||
case let .call(config, gA, timestamp, connections, maxLayer, version, customParameters, allowsP2P):
|
||||
case let .call(config, gA, timestamp, connections, maxLayer, version, customParameters, allowsP2P, supportsConferenceCalls):
|
||||
if let (key, keyId, keyVisualHash) = strongSelf.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gA) {
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P)
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, supportsConferenceCalls: supportsConferenceCalls)
|
||||
strongSelf.contextUpdated(internalId: internalId)
|
||||
} else {
|
||||
strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -957,7 +1017,7 @@ private final class CallSessionManagerContext {
|
||||
func sendSignalingData(internalId: CallSessionInternalId, data: Data) {
|
||||
if let context = self.contexts[internalId] {
|
||||
switch context.state {
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _):
|
||||
context.signalingDisposables.add(self.network.request(Api.functions.phone.sendSignalingData(peer: .inputPhoneCall(id: id, accessHash: accessHash), data: Buffer(data: data))).start())
|
||||
default:
|
||||
break
|
||||
@ -973,7 +1033,7 @@ private final class CallSessionManagerContext {
|
||||
|
||||
var idAndAccessHash: (id: Int64, accessHash: Int64)?
|
||||
switch context.state {
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _):
|
||||
idAndAccessHash = (id, accessHash)
|
||||
default:
|
||||
break
|
||||
@ -1097,7 +1157,7 @@ private final class CallSessionManagerContext {
|
||||
disposable.dispose()
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _):
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .awaitingConfirmation(id, accessHash, _, _, _):
|
||||
@ -1127,13 +1187,14 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
case let .phoneCall(flags, id, _, _, _, _, gAOrB, keyFingerprint, callProtocol, connections, startDate, customParameters):
|
||||
let allowsP2P = (flags & (1 << 5)) != 0
|
||||
let supportsConferenceCalls = (flags & (1 << 8)) != 0
|
||||
if let internalId = self.contextIdByStableId[id] {
|
||||
if let context = self.contexts[internalId] {
|
||||
switch context.state {
|
||||
case .accepting, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference:
|
||||
break
|
||||
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P):
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P)
|
||||
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, supportsConferenceCalls):
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, supportsConferenceCalls: supportsConferenceCalls)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .awaitingConfirmation(_, accessHash, gAHash, b, config):
|
||||
if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) {
|
||||
@ -1152,7 +1213,7 @@ private final class CallSessionManagerContext {
|
||||
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
|
||||
context.isVideoPossible = isVideoPossible
|
||||
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P)
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, supportsConferenceCalls: supportsConferenceCalls)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
} else {
|
||||
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -1179,7 +1240,7 @@ private final class CallSessionManagerContext {
|
||||
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
|
||||
context.isVideoPossible = isVideoPossible
|
||||
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P)
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, supportsConferenceCalls: supportsConferenceCalls)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
} else {
|
||||
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -1256,10 +1317,12 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
}
|
||||
|
||||
func addConferenceInvitationMessages(ids: [MessageId]) {
|
||||
for id in ids {
|
||||
func addConferenceInvitationMessages(ids: [(id: MessageId, externalInfo: IncomingConferenceTermporaryExternalInfo?)]) {
|
||||
var updateRingingStates = false
|
||||
for (id, externalInfo) in ids {
|
||||
if self.incomingConferenceInvitationContexts[id] == nil {
|
||||
let context = IncomingConferenceInvitationContext(queue: self.queue, postbox: self.postbox, messageId: id, updated: { [weak self] in
|
||||
Logger.shared.log("CallSessionManagerContext", "Adding incoming conference invitation context for message \(id)")
|
||||
let context = IncomingConferenceInvitationContext(queue: self.queue, postbox: self.postbox, internalId: CallSessionManager.getStableIncomingUUID(peerId: id.peerId.id._internalGetInt64Value(), messageId: id.id), messageId: id, externalInfo: externalInfo, updated: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -1275,8 +1338,14 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
})
|
||||
self.incomingConferenceInvitationContexts[id] = context
|
||||
updateRingingStates = true
|
||||
} else {
|
||||
Logger.shared.log("CallSessionManagerContext", "Conference invitation context for message \(id) already exists")
|
||||
}
|
||||
}
|
||||
if updateRingingStates {
|
||||
self.ringingStatesUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
private func makeSessionEncryptionKey(config: SecretChatEncryptionConfig, gAHash: Data, b: Data, gA: Data) -> (key: Data, keyId: Int64, keyVisualHash: Data)? {
|
||||
@ -1350,6 +1419,12 @@ public final class CallSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static func getStableIncomingUUID(peerId: Int64, messageId: Int32) -> UUID {
|
||||
return StableIncomingUUIDs.shared.with { impl in
|
||||
return impl.get(peerId: peerId, messageId: messageId)
|
||||
}
|
||||
}
|
||||
|
||||
private let queue = Queue()
|
||||
private var contextRef: Unmanaged<CallSessionManagerContext>?
|
||||
|
||||
@ -1388,7 +1463,7 @@ public final class CallSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
func addConferenceInvitationMessages(ids: [MessageId]) {
|
||||
public func addConferenceInvitationMessages(ids: [(id: MessageId, externalInfo: IncomingConferenceTermporaryExternalInfo?)]) {
|
||||
self.withContext { context in
|
||||
context.addConferenceInvitationMessages(ids: ids)
|
||||
}
|
||||
@ -1500,7 +1575,7 @@ public final class CallSessionManager {
|
||||
|
||||
private enum AcceptedCall {
|
||||
case waiting(config: SecretChatEncryptionConfig)
|
||||
case call(config: SecretChatEncryptionConfig, gA: Data, timestamp: Int32, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool)
|
||||
case call(config: SecretChatEncryptionConfig, gA: Data, timestamp: Int32, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, supportsConferenceCalls: Bool)
|
||||
}
|
||||
|
||||
private enum AcceptCallResult {
|
||||
@ -1553,7 +1628,7 @@ private func acceptCallSession(accountPeerId: PeerId, postbox: Postbox, network:
|
||||
customParametersValue = data
|
||||
}
|
||||
|
||||
return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: (flags & (1 << 5)) != 0))
|
||||
return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: (flags & (1 << 5)) != 0, supportsConferenceCalls: (flags & (1 << 8)) != 0))
|
||||
} else {
|
||||
return .failed
|
||||
}
|
||||
|
@ -2058,12 +2058,6 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
})
|
||||
}
|
||||
|
||||
/*func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
|
||||
if (application.applicationState == .inactive) {
|
||||
Logger.shared.log("App \(self.episodeId)", "tap local notification \(String(describing: notification.userInfo)), applicationState \(application.applicationState)")
|
||||
}
|
||||
}*/
|
||||
|
||||
public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
|
||||
if #available(iOS 9.0, *) {
|
||||
if case PKPushType.voIP = type {
|
||||
@ -2156,102 +2150,180 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
return
|
||||
}
|
||||
|
||||
guard var updateString = payloadJson["updates"] as? String else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "updates is nil")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
updateString = updateString.replacingOccurrences(of: "-", with: "+")
|
||||
updateString = updateString.replacingOccurrences(of: "_", with: "/")
|
||||
while updateString.count % 4 != 0 {
|
||||
updateString.append("=")
|
||||
}
|
||||
guard let updateData = Data(base64Encoded: updateString) else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't decode updateData")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
guard let callUpdate = AccountStateManager.extractIncomingCallUpdate(data: updateData) else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't extract call update")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
guard let callKitIntegration = CallKitIntegration.shared else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "CallKitIntegration is not available")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
let phoneNumber = payloadJson["phoneNumber"] as? String
|
||||
|
||||
callKitIntegration.reportIncomingCall(
|
||||
uuid: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId),
|
||||
stableId: callUpdate.callId,
|
||||
handle: "\(callUpdate.peer.id.id._internalGetInt64Value())",
|
||||
phoneNumber: phoneNumber.flatMap(formatPhoneNumber),
|
||||
isVideo: callUpdate.isVideo,
|
||||
displayTitle: callUpdate.peer.debugDisplayTitle,
|
||||
completion: { error in
|
||||
if let error = error {
|
||||
if error.domain == "com.apple.CallKit.error.incomingcall" && (error.code == -3 || error.code == 3) {
|
||||
Logger.shared.log("PresentationCall", "reportIncomingCall device in DND mode")
|
||||
} else {
|
||||
Logger.shared.log("PresentationCall", "reportIncomingCall error \(error)")
|
||||
/*Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.callSessionManager.drop(internalId: strongSelf.internalId, reason: .hangUp, debugLog: .single(nil))
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
if let fromIdString = payloadJson["from_id"] as? String, let fromId = Int64(fromIdString), let groupCallIdString = payloadJson["group_call_id"] as? String, let groupCallId = Int64(groupCallIdString), let messageIdString = payloadJson["msg_id"] as? String, let messageId = Int32(messageIdString), let isVideoString = payloadJson["video"] as? String, let isVideo = Int32(isVideoString), let fromTitle = payloadJson["from_title"] as? String {
|
||||
guard let callKitIntegration = CallKitIntegration.shared else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "CallKitIntegration is not available")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
)
|
||||
|
||||
let _ = (self.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
||||
let _ = (sharedApplicationContext.sharedContext.activeAccountContexts
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { activeAccounts in
|
||||
var processed = false
|
||||
for (_, context, _) in activeAccounts.accounts {
|
||||
if context.account.id == accountId {
|
||||
context.account.stateManager.processIncomingCallUpdate(data: updateData, completion: { _ in
|
||||
})
|
||||
let fromPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(fromId))
|
||||
let messageId = MessageId(peerId: fromPeerId, namespace: Namespaces.Message.Cloud, id: messageId)
|
||||
|
||||
//callUpdate.callId
|
||||
let disposable = MetaDisposable()
|
||||
self.watchedCallsDisposables.add(disposable)
|
||||
let internalId = CallSessionManager.getStableIncomingUUID(peerId: fromPeerId.id._internalGetInt64Value(), messageId: messageId.id)
|
||||
|
||||
disposable.set((context.account.callSessionManager.callState(internalId: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId))
|
||||
|> deliverOnMainQueue).start(next: { state in
|
||||
switch state.state {
|
||||
case .terminated:
|
||||
callKitIntegration.dropCall(uuid: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}))
|
||||
//TODO:localize
|
||||
let displayTitle: "\(fromTitle)"
|
||||
|
||||
processed = true
|
||||
|
||||
break
|
||||
callKitIntegration.reportIncomingCall(
|
||||
uuid: internalId,
|
||||
stableId: groupCallId,
|
||||
handle: "\(fromPeerId.id._internalGetInt64Value())",
|
||||
phoneNumber: phoneNumber.flatMap(formatPhoneNumber),
|
||||
isVideo: isVideo != 0,
|
||||
displayTitle: displayTitle,
|
||||
completion: { error in
|
||||
if let error = error {
|
||||
if error.domain == "com.apple.CallKit.error.incomingcall" && (error.code == -3 || error.code == 3) {
|
||||
Logger.shared.log("PresentationCall", "reportIncomingCall device in DND mode")
|
||||
} else {
|
||||
Logger.shared.log("PresentationCall", "reportIncomingCall error \(error)")
|
||||
/*Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.callSessionManager.drop(internalId: strongSelf.internalId, reason: .hangUp, debugLog: .single(nil))
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if !processed {
|
||||
callKitIntegration.dropCall(uuid: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId))
|
||||
let _ = (self.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
||||
let _ = (sharedApplicationContext.sharedContext.activeAccountContexts
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { activeAccounts in
|
||||
var processed = false
|
||||
for (_, context, _) in activeAccounts.accounts {
|
||||
if context.account.id == accountId {
|
||||
context.account.callSessionManager.addConferenceInvitationMessages(ids: [(messageId, IncomingConferenceTermporaryExternalInfo(callId: groupCallId, isVideo: isVideo != 0))])
|
||||
|
||||
/*disposable.set((context.account.callSessionManager.callState(internalId: internalId)
|
||||
|> deliverOnMainQueue).start(next: { state in
|
||||
switch state.state {
|
||||
case .terminated:
|
||||
callKitIntegration.dropCall(uuid: internalId)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}))*/
|
||||
|
||||
processed = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !processed {
|
||||
callKitIntegration.dropCall(uuid: internalId)
|
||||
}
|
||||
})
|
||||
|
||||
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 2.0)
|
||||
|
||||
if case PKPushType.voIP = type {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "pushRegistry payload: \(payload.dictionaryPayload)")
|
||||
sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload)
|
||||
}
|
||||
})
|
||||
|
||||
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 2.0)
|
||||
|
||||
if case PKPushType.voIP = type {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "pushRegistry payload: \(payload.dictionaryPayload)")
|
||||
sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload)
|
||||
} else {
|
||||
guard var updateString = payloadJson["updates"] as? String else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "updates is nil")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
updateString = updateString.replacingOccurrences(of: "-", with: "+")
|
||||
updateString = updateString.replacingOccurrences(of: "_", with: "/")
|
||||
while updateString.count % 4 != 0 {
|
||||
updateString.append("=")
|
||||
}
|
||||
guard let updateData = Data(base64Encoded: updateString) else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't decode updateData")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
guard let callUpdate = AccountStateManager.extractIncomingCallUpdate(data: updateData) else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't extract call update")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
guard let callKitIntegration = CallKitIntegration.shared else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "CallKitIntegration is not available")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
callKitIntegration.reportIncomingCall(
|
||||
uuid: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId),
|
||||
stableId: callUpdate.callId,
|
||||
handle: "\(callUpdate.peer.id.id._internalGetInt64Value())",
|
||||
phoneNumber: phoneNumber.flatMap(formatPhoneNumber),
|
||||
isVideo: callUpdate.isVideo,
|
||||
displayTitle: callUpdate.peer.debugDisplayTitle,
|
||||
completion: { error in
|
||||
if let error = error {
|
||||
if error.domain == "com.apple.CallKit.error.incomingcall" && (error.code == -3 || error.code == 3) {
|
||||
Logger.shared.log("PresentationCall", "reportIncomingCall device in DND mode")
|
||||
} else {
|
||||
Logger.shared.log("PresentationCall", "reportIncomingCall error \(error)")
|
||||
/*Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.callSessionManager.drop(internalId: strongSelf.internalId, reason: .hangUp, debugLog: .single(nil))
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let _ = (self.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
||||
let _ = (sharedApplicationContext.sharedContext.activeAccountContexts
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { activeAccounts in
|
||||
var processed = false
|
||||
for (_, context, _) in activeAccounts.accounts {
|
||||
if context.account.id == accountId {
|
||||
context.account.stateManager.processIncomingCallUpdate(data: updateData, completion: { _ in
|
||||
})
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
self.watchedCallsDisposables.add(disposable)
|
||||
|
||||
disposable.set((context.account.callSessionManager.callState(internalId: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId))
|
||||
|> deliverOnMainQueue).start(next: { state in
|
||||
switch state.state {
|
||||
case .terminated:
|
||||
callKitIntegration.dropCall(uuid: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}))
|
||||
|
||||
processed = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !processed {
|
||||
callKitIntegration.dropCall(uuid: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId))
|
||||
}
|
||||
})
|
||||
|
||||
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 2.0)
|
||||
|
||||
if case PKPushType.voIP = type {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "pushRegistry payload: \(payload.dictionaryPayload)")
|
||||
sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "Invoking completion handler")
|
||||
|
||||
|
@ -155,6 +155,12 @@ public final class SharedWakeupManager {
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
let keepUpdatesForCalls = combineLatest(queue: .mainQueue(), hasActiveCalls, hasActiveGroupCalls)
|
||||
|> map { hasActiveCalls, hasActiveGroupCalls -> Bool in
|
||||
return hasActiveCalls || hasActiveGroupCalls
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
let isPlayingBackgroundActiveCall = combineLatest(queue: .mainQueue(), hasActiveCalls, hasActiveGroupCalls, hasActiveAudioSession)
|
||||
|> map { hasActiveCalls, hasActiveGroupCalls, hasActiveAudioSession -> Bool in
|
||||
return (hasActiveCalls || hasActiveGroupCalls) && hasActiveAudioSession
|
||||
@ -181,9 +187,9 @@ public final class SharedWakeupManager {
|
||||
|
||||
let userInterfaceInUse = accountUserInterfaceInUse(account.id)
|
||||
|
||||
return combineLatest(queue: .mainQueue(), account.importantTasksRunning, notificationManager?.isPollingState(accountId: account.id) ?? .single(false), hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, hasWatchTasks, userInterfaceInUse)
|
||||
|> map { importantTasksRunning, isPollingState, hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, hasWatchTasks, userInterfaceInUse -> (Account, Bool, AccountTasks) in
|
||||
return (account, primary?.id == account.id, AccountTasks(stateSynchronization: isPollingState, importantTasks: importantTasksRunning, backgroundLocation: hasActiveLiveLocationPolling, backgroundDownloads: false, backgroundAudio: hasActiveAudio, activeCalls: hasActiveCalls, watchTasks: hasWatchTasks, userInterfaceInUse: userInterfaceInUse))
|
||||
return combineLatest(queue: .mainQueue(), account.importantTasksRunning, notificationManager?.isPollingState(accountId: account.id) ?? .single(false), hasActiveAudio, keepUpdatesForCalls, hasActiveLiveLocationPolling, hasWatchTasks, userInterfaceInUse)
|
||||
|> map { importantTasksRunning, isPollingState, hasActiveAudio, keepUpdatesForCalls, hasActiveLiveLocationPolling, hasWatchTasks, userInterfaceInUse -> (Account, Bool, AccountTasks) in
|
||||
return (account, primary?.id == account.id, AccountTasks(stateSynchronization: isPollingState, importantTasks: importantTasksRunning, backgroundLocation: hasActiveLiveLocationPolling, backgroundDownloads: false, backgroundAudio: hasActiveAudio, activeCalls: keepUpdatesForCalls, watchTasks: hasWatchTasks, userInterfaceInUse: userInterfaceInUse))
|
||||
}
|
||||
}
|
||||
return combineLatest(signals)
|
||||
|
Loading…
x
Reference in New Issue
Block a user