Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2025-04-06 23:05:33 +04:00
commit 4830be32b3
8 changed files with 405 additions and 176 deletions

View File

@ -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)
@ -958,21 +962,25 @@ private final class NotificationServiceHandler {
peerId = PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(encryptionIdValue))
}
}
#if DEBUG
if let locKey = payloadJson["loc-key"] as? String, locKey == "CONF_CALL_REQUEST" {
}
#endif
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 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()
}

View File

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

View File

@ -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
@ -548,6 +543,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
case .active:
callScreenState.isRemoteAudioMuted = false
}
callScreenState.isConferencePossible = callState.supportsConferenceCalls
if self.callScreenState != callScreenState {
self.callScreenState = callScreenState

View File

@ -274,6 +274,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?
@ -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())
}
@ -816,6 +834,10 @@ public final class PresentationCallImpl: PresentationCall {
}
}
}
if case let .active(_, _, _, _, _, _, _, _, supportsConferenceCallsValue) = sessionState.state {
self.supportsConferenceCalls = supportsConferenceCallsValue
}
let mappedVideoState: PresentationCallState.VideoState
let mappedRemoteVideoState: PresentationCallState.RemoteVideoState
@ -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 {

View File

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

View File

@ -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)? {
@ -1349,6 +1418,12 @@ public final class CallSessionManager {
return impl.get(id: stableId)
}
}
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
}

View File

@ -2057,12 +2057,6 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
sharedApplicationContext.notificationManager.addNotification(userInfo)
})
}
/*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, *) {
@ -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))
}
}*/
}
}
}
)
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
})
//callUpdate.callId
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 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 fromPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(fromId))
let messageId = MessageId(peerId: fromPeerId, namespace: Namespaces.Message.Cloud, id: messageId)
let internalId = CallSessionManager.getStableIncomingUUID(peerId: fromPeerId.id._internalGetInt64Value(), messageId: messageId.id)
//TODO:localize
let displayTitle: "\(fromTitle)"
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))
}
}*/
}
}
}
)
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)
}
})
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)
}
})
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")

View File

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