diff --git a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift index bca3f90d24..1b261aee5d 100644 --- a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift +++ b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift @@ -28,8 +28,28 @@ public final class CallKitIntegration { var audioSessionActive: Signal { return self.audioSessionActivePromise.get() } + + private static let sharedInstance: CallKitIntegration? = CallKitIntegration() + public static var shared: CallKitIntegration? { + return self.sharedInstance + } + + func setup( + startCall: @escaping (AccountContext, UUID, String, Bool) -> Signal, + answerCall: @escaping (UUID) -> Void, + endCall: @escaping (UUID) -> Signal, + setCallMuted: @escaping (UUID, Bool) -> Void, + audioSessionActivationChanged: @escaping (Bool) -> Void + ) { + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + if sharedProviderDelegate == nil { + sharedProviderDelegate = CallKitProviderDelegate() + } + (sharedProviderDelegate as? CallKitProviderDelegate)?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged) + } + } - init?(startCall: @escaping (AccountContext, UUID, String, Bool) -> Signal, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) { + private init?() { if !CallKitIntegration.isAvailable { return nil } @@ -39,10 +59,6 @@ public final class CallKitIntegration { #else if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - if sharedProviderDelegate == nil { - sharedProviderDelegate = CallKitProviderDelegate() - } - (sharedProviderDelegate as? CallKitProviderDelegate)?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged) } else { return nil } @@ -68,9 +84,9 @@ public final class CallKitIntegration { } } - func reportIncomingCall(uuid: UUID, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) { + public func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - (sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, handle: handle, isVideo: isVideo, displayTitle: displayTitle, completion: completion) + (sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, isVideo: isVideo, displayTitle: displayTitle, completion: completion) } } @@ -101,6 +117,8 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { private let callController = CXCallController() private var currentStartCallAccount: (UUID, AccountContext)? + + private var alreadyReportedIncomingCalls = Set() private var startCall: ((AccountContext, UUID, String, Bool) -> Signal)? private var answerCall: ((UUID) -> Void)? @@ -163,7 +181,9 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { } func answerCall(uuid: UUID) { - + let answerCallAction = CXAnswerCallAction(call: uuid) + let transaction = CXTransaction(action: answerCallAction) + self.requestTransaction(transaction) } func startCall(context: AccountContext, peerId: PeerId, isVideo: Bool, displayTitle: String) { @@ -189,7 +209,13 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { }) } - func reportIncomingCall(uuid: UUID, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) { + func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) { + if self.alreadyReportedIncomingCalls.contains(uuid) { + completion?(nil) + return + } + self.alreadyReportedIncomingCalls.insert(uuid) + let update = CXCallUpdate() update.remoteHandle = CXHandle(type: .generic, value: handle) update.localizedCallerName = displayTitle diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index 8f34551fc4..56cfa62c67 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -608,27 +608,34 @@ public final class PresentationCallImpl: PresentationCall { case .ringing: presentationState = PresentationCallState(state: .ringing, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) if previous == nil || previousControl == nil { - if !self.reportedIncomingCall { + if !self.reportedIncomingCall, let stableId = sessionState.stableId { self.reportedIncomingCall = true - self.callKitIntegration?.reportIncomingCall(uuid: self.internalId, handle: "\(self.peerId.id)", isVideo: sessionState.type == .video, displayTitle: self.peer?.debugDisplayTitle ?? "Unknown", completion: { [weak self] 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") - Queue.mainQueue().async { - /*if let strongSelf = self { - strongSelf.callSessionManager.drop(internalId: strongSelf.internalId, reason: .busy, debugLog: .single(nil)) - }*/ - } - } 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)) + self.callKitIntegration?.reportIncomingCall( + uuid: self.internalId, + stableId: stableId, + handle: "\(self.peerId.id)", + isVideo: sessionState.type == .video, + displayTitle: self.peer?.debugDisplayTitle ?? "Unknown", + completion: { [weak self] 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") + Queue.mainQueue().async { + /*if let strongSelf = self { + strongSelf.callSessionManager.drop(internalId: strongSelf.internalId, reason: .busy, debugLog: .single(nil)) + }*/ + } + } 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)) + } } } } } - }) + ) } } case .accepting: diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 07c4eb7ba1..da802b9552 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -113,7 +113,14 @@ public final class PresentationCallManagerImpl: PresentationCallManager { return OngoingCallContext.versions(includeExperimental: includeExperimental, includeReference: includeReference) } - public init(accountManager: AccountManager, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[AccountContext], NoError>) { + public init( + accountManager: AccountManager, + getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), + isMediaPlaying: @escaping () -> Bool, + resumeMediaPlayback: @escaping () -> Void, + audioSession: ManagedAudioSession, + activeAccounts: Signal<[AccountContext], NoError> + ) { self.getDeviceAccessData = getDeviceAccessData self.accountManager = accountManager self.audioSession = audioSession @@ -127,7 +134,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager { var setCallMutedImpl: ((UUID, Bool) -> Void)? var audioSessionActivationChangedImpl: ((Bool) -> Void)? - self.callKitIntegration = CallKitIntegration(startCall: { context, uuid, handle, isVideo in + self.callKitIntegration = CallKitIntegration.shared + self.callKitIntegration?.setup(startCall: { context, uuid, handle, isVideo in if let startCallImpl = startCallImpl { return startCallImpl(context, uuid, handle, isVideo) } else { diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 19d4c84d1a..159651cf71 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -675,6 +675,11 @@ public struct AccountRunningImportantTasks: OptionSet { public struct MasterNotificationKey: Codable { public let id: Data public let data: Data + + public init(id: Data, data: Data) { + self.id = id + self.data = data + } } public func masterNotificationsKey(account: Account, ignoreDisabled: Bool) -> Signal { @@ -810,7 +815,8 @@ public func accountBackupData(postbox: Postbox) -> Signal { deinit { assert(self.queue.isCurrent()) } + + fileprivate func transactionSync(ignoreDisabled: Bool, _ f: (AccountManagerModifier) -> T) -> T { + self.valueBox.begin() + + let transaction = AccountManagerModifier(getRecords: { + return self.currentAtomicState.records.map { $0.1 } + }, updateRecord: { id, update in + let current = self.currentAtomicState.records[id] + let updated = update(current) + if updated != current { + if let updated = updated { + self.currentAtomicState.records[id] = updated + } else { + self.currentAtomicState.records.removeValue(forKey: id) + } + self.currentAtomicStateUpdated = true + self.currentRecordOperations.append(.set(id: id, record: updated)) + } + }, getCurrent: { + if let id = self.currentAtomicState.currentRecordId, let record = self.currentAtomicState.records[id] { + return (record.id, record.attributes) + } else { + return nil + } + }, setCurrentId: { id in + self.currentAtomicState.currentRecordId = id + self.currentMetadataOperations.append(.updateCurrentAccountId(id)) + self.currentAtomicStateUpdated = true + }, getCurrentAuth: { + if let record = self.currentAtomicState.currentAuthRecord { + return record + } else { + return nil + } + }, createAuth: { attributes in + let record = AuthAccountRecord(id: generateAccountRecordId(), attributes: attributes) + self.currentAtomicState.currentAuthRecord = record + self.currentAtomicStateUpdated = true + self.currentMetadataOperations.append(.updateCurrentAuthAccountRecord(record)) + return record + }, removeAuth: { + self.currentAtomicState.currentAuthRecord = nil + self.currentMetadataOperations.append(.updateCurrentAuthAccountRecord(nil)) + self.currentAtomicStateUpdated = true + }, createRecord: { attributes in + let id = generateAccountRecordId() + let record = AccountRecord(id: id, attributes: attributes, temporarySessionId: nil) + self.currentAtomicState.records[id] = record + self.currentRecordOperations.append(.set(id: id, record: record)) + self.currentAtomicStateUpdated = true + return id + }, getSharedData: { key in + return self.sharedDataTable.get(key: key) + }, updateSharedData: { key, f in + let updated = f(self.sharedDataTable.get(key: key)) + self.sharedDataTable.set(key: key, value: updated, updatedKeys: &self.currentUpdatedSharedDataKeys) + }, getAccessChallengeData: { + return self.legacyMetadataTable.getAccessChallengeData() + }, setAccessChallengeData: { data in + self.currentUpdatedAccessChallengeData = data + self.currentAtomicStateUpdated = true + self.legacyMetadataTable.setAccessChallengeData(data) + self.currentAtomicState.accessChallengeData = data + }, getVersion: { + return self.legacyMetadataTable.getVersion() + }, setVersion: { version in + self.legacyMetadataTable.setVersion(version) + }, getNotice: { key in + self.noticeTable.get(key: key) + }, setNotice: { key, value in + self.noticeTable.set(key: key, value: value) + self.currentUpdatedNoticeEntryKeys.insert(key) + }, clearNotices: { + self.noticeTable.clear() + }) + + let result = f(transaction) + + self.beforeCommit() + + self.valueBox.commit() + + return result + } fileprivate func transaction(ignoreDisabled: Bool, _ f: @escaping (AccountManagerModifier) -> T) -> Signal { return Signal { subscriber in self.queue.justDispatch { - self.valueBox.begin() - - let transaction = AccountManagerModifier(getRecords: { - return self.currentAtomicState.records.map { $0.1 } - }, updateRecord: { id, update in - let current = self.currentAtomicState.records[id] - let updated = update(current) - if updated != current { - if let updated = updated { - self.currentAtomicState.records[id] = updated - } else { - self.currentAtomicState.records.removeValue(forKey: id) - } - self.currentAtomicStateUpdated = true - self.currentRecordOperations.append(.set(id: id, record: updated)) - } - }, getCurrent: { - if let id = self.currentAtomicState.currentRecordId, let record = self.currentAtomicState.records[id] { - return (record.id, record.attributes) - } else { - return nil - } - }, setCurrentId: { id in - self.currentAtomicState.currentRecordId = id - self.currentMetadataOperations.append(.updateCurrentAccountId(id)) - self.currentAtomicStateUpdated = true - }, getCurrentAuth: { - if let record = self.currentAtomicState.currentAuthRecord { - return record - } else { - return nil - } - }, createAuth: { attributes in - let record = AuthAccountRecord(id: generateAccountRecordId(), attributes: attributes) - self.currentAtomicState.currentAuthRecord = record - self.currentAtomicStateUpdated = true - self.currentMetadataOperations.append(.updateCurrentAuthAccountRecord(record)) - return record - }, removeAuth: { - self.currentAtomicState.currentAuthRecord = nil - self.currentMetadataOperations.append(.updateCurrentAuthAccountRecord(nil)) - self.currentAtomicStateUpdated = true - }, createRecord: { attributes in - let id = generateAccountRecordId() - let record = AccountRecord(id: id, attributes: attributes, temporarySessionId: nil) - self.currentAtomicState.records[id] = record - self.currentRecordOperations.append(.set(id: id, record: record)) - self.currentAtomicStateUpdated = true - return id - }, getSharedData: { key in - return self.sharedDataTable.get(key: key) - }, updateSharedData: { key, f in - let updated = f(self.sharedDataTable.get(key: key)) - self.sharedDataTable.set(key: key, value: updated, updatedKeys: &self.currentUpdatedSharedDataKeys) - }, getAccessChallengeData: { - return self.legacyMetadataTable.getAccessChallengeData() - }, setAccessChallengeData: { data in - self.currentUpdatedAccessChallengeData = data - self.currentAtomicStateUpdated = true - self.legacyMetadataTable.setAccessChallengeData(data) - self.currentAtomicState.accessChallengeData = data - }, getVersion: { - return self.legacyMetadataTable.getVersion() - }, setVersion: { version in - self.legacyMetadataTable.setVersion(version) - }, getNotice: { key in - self.noticeTable.get(key: key) - }, setNotice: { key, value in - self.noticeTable.set(key: key, value: value) - self.currentUpdatedNoticeEntryKeys.insert(key) - }, clearNotices: { - self.noticeTable.clear() - }) - - let result = f(transaction) - - self.beforeCommit() - - self.valueBox.commit() - //self.valueBox.checkpoint() + let result = self.transactionSync(ignoreDisabled: ignoreDisabled, f) subscriber.putNext(result) subscriber.putCompletion() @@ -293,6 +298,13 @@ final class AccountManagerImpl { }) |> switchToLatest } + + fileprivate func _internalAccountRecordsSync() -> AccountRecordsView { + let mutableView = MutableAccountRecordsView(getRecords: { + return self.currentAtomicState.records.map { $0.1 } + }, currentId: self.currentAtomicState.currentRecordId, currentAuth: self.currentAtomicState.currentAuthRecord) + return AccountRecordsView(mutableView) + } fileprivate func sharedData(keys: Set) -> Signal, NoError> { return self.transaction(ignoreDisabled: false, { transaction -> Signal, NoError> in @@ -517,6 +529,14 @@ public final class AccountManager { return disposable } } + + public func _internalAccountRecordsSync() -> AccountRecordsView { + var result: AccountRecordsView? + self.impl.syncWith { impl in + result = impl._internalAccountRecordsSync() + } + return result! + } public func sharedData(keys: Set) -> Signal, NoError> { return Signal { subscriber in diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index c7a584d693..1f27657478 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -1170,6 +1170,74 @@ public final class AccountStateManager { func notifyDeletedMessages(messageIds: [MessageId]) { self.deletedMessagesPipe.putNext(messageIds.map { .messageId($0) }) } + + public final class IncomingCallUpdate { + public let callId: Int64 + public let callAccessHash: Int64 + public let timestamp: Int32 + public let peer: EnginePeer + + init( + callId: Int64, + callAccessHash: Int64, + timestamp: Int32, + peer: EnginePeer + ) { + self.callId = callId + self.callAccessHash = callAccessHash + self.timestamp = timestamp + self.peer = peer + } + } + + public static func extractIncomingCallUpdate(data: Data) -> IncomingCallUpdate? { + var rawData = data + let reader = BufferReader(Buffer(data: data)) + if let signature = reader.readInt32(), signature == 0x3072cfa1 { + if let compressedData = parseBytes(reader) { + if let decompressedData = MTGzip.decompress(compressedData.makeData()) { + rawData = decompressedData + } + } + } + + guard let updates = Api.parse(Buffer(data: rawData)) as? Api.Updates else { + return nil + } + switch updates { + case let .updates(updates, users, _, _, _): + var peers: [Peer] = [] + for user in users { + peers.append(TelegramUser(user: user)) + } + + for update in updates { + switch update { + case let .updatePhoneCall(phoneCall): + switch phoneCall { + case let .phoneCallRequested(_, id, accessHash, date, adminId, _, _, _): + guard let peer = peers.first(where: { $0.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)) }) else { + return nil + } + return IncomingCallUpdate( + callId: id, + callAccessHash: accessHash, + timestamp: date, + peer: EnginePeer(peer) + ) + default: + break + } + default: + break + } + } + + return nil + default: + return nil + } + } public func processIncomingCallUpdate(data: Data, completion: @escaping ((CallSessionRingingState, CallSession)?) -> Void) { var rawData = data diff --git a/submodules/TelegramCore/Sources/State/CallSessionManager.swift b/submodules/TelegramCore/Sources/State/CallSessionManager.swift index 52638a6c16..16cd09f66f 100644 --- a/submodules/TelegramCore/Sources/State/CallSessionManager.swift +++ b/submodules/TelegramCore/Sources/State/CallSessionManager.swift @@ -63,11 +63,53 @@ enum CallSessionInternalState { case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowsP2P: Bool) case dropping(reason: CallSessionTerminationReason, disposable: Disposable) case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool) + + var stableId: Int64? { + switch self { + case let .ringing(id, _, _, _, _): + return id + case let .accepting(id, _, _, _, _): + return id + case let .awaitingConfirmation(id, _, _, _, _): + return id + case .requesting: + return nil + case let .requested(id, _, _, _, _, _): + return id + case let .confirming(id, _, _, _, _, _): + return id + case let .active(id, _, _, _, _, _, _, _, _, _): + return id + case .dropping: + return nil + case let .terminated(id, _, _, _, _): + return id + } + } } public typealias CallSessionInternalId = UUID typealias CallSessionStableId = Int64 +private final class StableIncomingUUIDs { + static let shared = Atomic(value: StableIncomingUUIDs()) + + private var dict: [Int64: UUID] = [:] + + private init() { + } + + func get(id: Int64) -> UUID { + if let value = self.dict[id] { + return value + } else { + let value = UUID() + self.dict[id] = value + return value + } + } +} + public struct CallSessionRingingState: Equatable { public let id: CallSessionInternalId public let peerId: PeerId @@ -147,10 +189,27 @@ public struct CallSession { } public let id: CallSessionInternalId + public let stableId: Int64? public let isOutgoing: Bool public let type: CallType public let state: CallSessionState public let isVideoPossible: Bool + + init( + id: CallSessionInternalId, + stableId: Int64?, + isOutgoing: Bool, + type: CallType, + state: CallSessionState, + isVideoPossible: Bool + ) { + self.id = id + self.stableId = stableId + self.isOutgoing = isOutgoing + self.type = type + self.state = state + self.isVideoPossible = isVideoPossible + } } public enum CallSessionConnection: Equatable { @@ -384,7 +443,7 @@ private final class CallSessionManagerContext { let index = context.subscribers.add { next in subscriber.putNext(next) } - subscriber.putNext(CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context), isVideoPossible: context.isVideoPossible)) + subscriber.putNext(CallSession(id: internalId, stableId: context.state.stableId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context), isVideoPossible: context.isVideoPossible)) disposable.set(ActionDisposable { queue.async { if let strongSelf = self, let context = strongSelf.contexts[internalId] { @@ -447,7 +506,7 @@ private final class CallSessionManagerContext { private func contextUpdated(internalId: CallSessionInternalId) { if let context = self.contexts[internalId] { - let session = CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context), isVideoPossible: context.isVideoPossible) + let session = CallSession(id: internalId, stableId: context.state.stableId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context), isVideoPossible: context.isVideoPossible) for subscriber in context.subscribers.copyItems() { subscriber(session) } @@ -469,7 +528,7 @@ private final class CallSessionManagerContext { isVideoPossible = true //#endif - let internalId = CallSessionInternalId() + let internalId = CallSessionManager.getStableIncomingUUID(stableId: stableId) let context = CallSessionContext(peerId: peerId, isOutgoing: false, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions)) self.contexts[internalId] = context let queue = self.queue @@ -835,7 +894,7 @@ private final class CallSessionManagerContext { } } if let context = self.contexts[internalId] { - let callSession = CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context), isVideoPossible: context.isVideoPossible) + let callSession = CallSession(id: internalId, stableId: id, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context), isVideoPossible: context.isVideoPossible) if let resultRingingStateValue = resultRingingStateValue { resultRingingState = (resultRingingStateValue, callSession) } @@ -948,6 +1007,12 @@ public enum CallRequestError { } public final class CallSessionManager { + public static func getStableIncomingUUID(stableId: Int64) -> UUID { + return StableIncomingUUIDs.shared.with { impl in + return impl.get(id: stableId) + } + } + private let queue = Queue() private var contextRef: Unmanaged? diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountBackupDataAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountBackupDataAttribute.swift index 5190d8f308..5aafb2432a 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountBackupDataAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountBackupDataAttribute.swift @@ -7,19 +7,22 @@ public struct AccountBackupData: Codable, Equatable { public var masterDatacenterKey: Data public var masterDatacenterKeyId: Int64 public var notificationEncryptionKeyId: Data? + public var notificationEncryptionKey: Data? public init( masterDatacenterId: Int32, peerId: Int64, masterDatacenterKey: Data, masterDatacenterKeyId: Int64, - notificationEncryptionKeyId: Data? + notificationEncryptionKeyId: Data?, + notificationEncryptionKey: Data? ) { self.masterDatacenterId = masterDatacenterId self.peerId = peerId self.masterDatacenterKey = masterDatacenterKey self.masterDatacenterKeyId = masterDatacenterKeyId self.notificationEncryptionKeyId = notificationEncryptionKeyId + self.notificationEncryptionKey = notificationEncryptionKey } } diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index e63a26350a..8144b38a5f 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -175,6 +175,35 @@ final class SharedApplicationContext { } } +private struct AccountManagerState { + struct NotificationKey { + var accountId: AccountRecordId + var id: Data + var key: Data + } + + var notificationKeys: [NotificationKey] +} + +private func extractAccountManagerState(records: AccountRecordsView) -> AccountManagerState { + return AccountManagerState( + notificationKeys: records.records.compactMap { record -> AccountManagerState.NotificationKey? in + for attribute in record.attributes { + if case let .backupData(backupData) = attribute { + if let notificationEncryptionKeyId = backupData.data?.notificationEncryptionKeyId, let notificationEncryptionKey = backupData.data?.notificationEncryptionKey { + return AccountManagerState.NotificationKey( + accountId: record.id, + id: notificationEncryptionKeyId, + key: notificationEncryptionKey + ) + } + } + } + return nil + } + ) +} + @objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate { @objc var window: UIWindow? var nativeWindow: (UIWindow & WindowHost)? @@ -192,6 +221,9 @@ final class SharedApplicationContext { private let sharedContextPromise = Promise() private let watchCommunicationManagerPromise = Promise() + + private var accountManager: AccountManager? + private var accountManagerState: AccountManagerState? private var contextValue: AuthorizedApplicationContext? private let context = Promise() @@ -486,8 +518,6 @@ final class SharedApplicationContext { UNUserNotificationCenter.current().delegate = self } - telegramUIDeclareEncodables() - GlobalExperimentalSettings.isAppStoreBuild = buildConfig.isAppStoreBuild GlobalExperimentalSettings.enableFeed = false @@ -495,8 +525,6 @@ final class SharedApplicationContext { self.hasActiveAudioSession.set(MediaManagerImpl.globalAudioSession.isActive()) - initializeAccountManagement() - let applicationBindings = TelegramApplicationBindings(isMainApp: true, appBundleId: baseAppBundleId, containerPath: appGroupUrl.path, appSpecificScheme: buildConfig.appSpecificUrlScheme, openUrl: { url in var parsedUrl = URL(string: url) if let parsed = parsedUrl { @@ -676,47 +704,29 @@ final class SharedApplicationContext { UIDevice.current.setValue(value, forKey: "orientation") UINavigationController.attemptRotationToDeviceOrientation() }) - - let accountManagerSignal = Signal, NoError> { subscriber in - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true) - return (upgradedAccounts(accountManager: accountManager, rootPath: rootPath, encryptionParameters: encryptionParameters) - |> deliverOnMainQueue).start(next: { progress in - if self.dataImportSplash == nil { - self.dataImportSplash = makeLegacyDataImportSplash(theme: nil, strings: nil) - self.dataImportSplash?.serviceAction = { - self.debugPressed() - } - self.mainWindow.coveringView = self.dataImportSplash - } - self.dataImportSplash?.progress = (.generic, progress) - }, completed: { - if let dataImportSplash = self.dataImportSplash { - self.dataImportSplash = nil - if self.mainWindow.coveringView === dataImportSplash { - self.mainWindow.coveringView = nil - } - } - subscriber.putNext(accountManager) - subscriber.putCompletion() - }) + + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true) + self.accountManager = accountManager + + telegramUIDeclareEncodables() + initializeAccountManagement() + + self.accountManagerState = extractAccountManagerState(records: accountManager._internalAccountRecordsSync()) + let _ = (accountManager.accountRecords() + |> deliverOnMainQueue).start(next: { view in + self.accountManagerState = extractAccountManagerState(records: view) + }) + + var systemUserInterfaceStyle: WindowUserInterfaceStyle = .light + if #available(iOS 13.0, *) { + if let traitCollection = window.rootViewController?.traitCollection { + systemUserInterfaceStyle = WindowUserInterfaceStyle(style: traitCollection.userInterfaceStyle) + } } - let sharedContextSignal = accountManagerSignal - |> deliverOnMainQueue - |> take(1) - |> deliverOnMainQueue - |> take(1) - |> mapToSignal { accountManager -> Signal<(AccountManager, InitialPresentationDataAndSettings), NoError> in - var systemUserInterfaceStyle: WindowUserInterfaceStyle = .light - if #available(iOS 13.0, *) { - if let traitCollection = window.rootViewController?.traitCollection { - systemUserInterfaceStyle = WindowUserInterfaceStyle(style: traitCollection.userInterfaceStyle) - } - } - return currentPresentationDataAndSettings(accountManager: accountManager, systemUserInterfaceStyle: systemUserInterfaceStyle) - |> map { initialPresentationDataAndSettings -> (AccountManager, InitialPresentationDataAndSettings) in - return (accountManager, initialPresentationDataAndSettings) - } + let sharedContextSignal = currentPresentationDataAndSettings(accountManager: accountManager, systemUserInterfaceStyle: systemUserInterfaceStyle) + |> map { initialPresentationDataAndSettings -> (AccountManager, InitialPresentationDataAndSettings) in + return (accountManager, initialPresentationDataAndSettings) } |> deliverOnMainQueue |> mapToSignal { accountManager, initialPresentationDataAndSettings -> Signal<(SharedApplicationContext, LoggingSettings), NoError> in @@ -1417,7 +1427,7 @@ final class SharedApplicationContext { } public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { - /*guard var encryptedPayload = payload.dictionaryPayload["p"] as? String else { + guard var encryptedPayload = payload.dictionaryPayload["p"] as? String else { return } encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+") @@ -1425,11 +1435,81 @@ final class SharedApplicationContext { while encryptedPayload.count % 4 != 0 { encryptedPayload.append("=") } - guard let data = Data(base64Encoded: encryptedPayload) else { + guard let payloadData = Data(base64Encoded: encryptedPayload) else { + return + } + guard let keyId = notificationPayloadKeyId(data: payloadData) else { return } - let semaphore = DispatchSemaphore(value: 0) + guard let accountManagerState = self.accountManagerState else { + return + } + + var maybeAccountId: AccountRecordId? + var maybeNotificationKey: MasterNotificationKey? + + for key in accountManagerState.notificationKeys { + if key.id == keyId { + maybeAccountId = key.accountId + maybeNotificationKey = MasterNotificationKey(id: key.id, data: key.key) + break + } + } + + guard let accountId = maybeAccountId, let notificationKey = maybeNotificationKey else { + return + } + guard let decryptedPayload = decryptedNotificationPayload(key: notificationKey, data: payloadData) else { + return + } + guard let payloadJson = try? JSONSerialization.jsonObject(with: decryptedPayload, options: []) as? [String: Any] else { + return + } + guard var updateString = payloadJson["updates"] as? String else { + 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 { + return + } + guard let callUpdate = AccountStateManager.extractIncomingCallUpdate(data: updateData) else { + return + } + guard let callKitIntegration = CallKitIntegration.shared else { + return + } + + callKitIntegration.reportIncomingCall( + uuid: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId), + stableId: callUpdate.callId, + handle: "\(callUpdate.peer.id.id)", + isVideo: false, + 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 _ = accountId + + /*let semaphore = DispatchSemaphore(value: 0) var accountAndDecryptedPayload: (Account, Data)? var sharedApplicationContextValue: SharedApplicationContext? @@ -1486,6 +1566,7 @@ final class SharedApplicationContext { } } }*/ + let _ = (self.sharedContextPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { sharedApplicationContext in diff --git a/submodules/TelegramUI/Sources/UpgradedAccounts.swift b/submodules/TelegramUI/Sources/UpgradedAccounts.swift index 756300e25d..8b13789179 100644 --- a/submodules/TelegramUI/Sources/UpgradedAccounts.swift +++ b/submodules/TelegramUI/Sources/UpgradedAccounts.swift @@ -1,121 +1 @@ -import Foundation -import UIKit -import TelegramCore -import Postbox -import SwiftSignalKit -import TelegramUIPreferences -import MediaResources -private enum LegacyPreferencesKeyValues: Int32 { - case cacheStorageSettings = 1 - case localizationSettings = 2 - case proxySettings = 5 - - var key: ValueBoxKey { - let key = ValueBoxKey(length: 4) - key.setInt32(0, value: self.rawValue) - return key - } -} - -private enum UpgradedSharedDataKeyValues: Int32 { - case cacheStorageSettings = 2 - case localizationSettings = 3 - case proxySettings = 4 - - var key: ValueBoxKey { - let key = ValueBoxKey(length: 4) - key.setInt32(0, value: self.rawValue) - return key - } -} - -private enum LegacyApplicationSpecificPreferencesKeyValues: Int32 { - case inAppNotificationSettings = 0 - case presentationPasscodeSettings = 1 - case automaticMediaDownloadSettings = 2 - case generatedMediaStoreSettings = 3 - case voiceCallSettings = 4 - case presentationThemeSettings = 5 - case instantPagePresentationSettings = 6 - case callListSettings = 7 - case experimentalSettings = 8 - case musicPlaybackSettings = 9 - case mediaInputSettings = 10 - case experimentalUISettings = 11 - case contactSynchronizationSettings = 12 - case stickerSettings = 13 - case watchPresetSettings = 14 - case webSearchSettings = 15 - case voipDerivedState = 16 - - var key: ValueBoxKey { - return applicationSpecificPreferencesKey(self.rawValue) - } -} - -private enum UpgradedApplicationSpecificSharedDataKeyValues: Int32 { - case inAppNotificationSettings = 0 - case presentationPasscodeSettings = 1 - case automaticMediaDownloadSettings = 2 - case generatedMediaStoreSettings = 3 - case voiceCallSettings = 4 - case presentationThemeSettings = 5 - case instantPagePresentationSettings = 6 - case callListSettings = 7 - case experimentalSettings = 8 - case musicPlaybackSettings = 9 - case mediaInputSettings = 10 - case experimentalUISettings = 11 - case stickerSettings = 12 - case watchPresetSettings = 13 - case webSearchSettings = 14 - case contactSynchronizationSettings = 15 - - var key: ValueBoxKey { - return applicationSpecificSharedDataKey(self.rawValue) - } -} - -private let preferencesKeyMapping: [LegacyPreferencesKeyValues: UpgradedSharedDataKeyValues] = [ - .cacheStorageSettings: .cacheStorageSettings, - .localizationSettings: .localizationSettings, - .proxySettings: .proxySettings -] - -private let applicationSpecificPreferencesKeyMapping: [LegacyApplicationSpecificPreferencesKeyValues: UpgradedApplicationSpecificSharedDataKeyValues] = [ - .inAppNotificationSettings: .inAppNotificationSettings, - .presentationPasscodeSettings: .presentationPasscodeSettings, - .automaticMediaDownloadSettings: .automaticMediaDownloadSettings, - .generatedMediaStoreSettings: .generatedMediaStoreSettings, - .voiceCallSettings: .voiceCallSettings, - .presentationThemeSettings: .presentationThemeSettings, - .instantPagePresentationSettings: .instantPagePresentationSettings, - .callListSettings: .callListSettings, - .experimentalSettings: .experimentalSettings, - .musicPlaybackSettings: .musicPlaybackSettings, - .mediaInputSettings: .mediaInputSettings, - .experimentalUISettings: .experimentalUISettings, - .stickerSettings: .stickerSettings, - .watchPresetSettings: .watchPresetSettings, - .webSearchSettings: .webSearchSettings, - .contactSynchronizationSettings: .contactSynchronizationSettings -] - -private func upgradedSharedDataValue(_ value: PreferencesEntry?) -> PreferencesEntry? { - return value -} - -public func upgradedAccounts(accountManager: AccountManager, rootPath: String, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { - return accountManager.transaction { transaction -> (Int32?, AccountRecordId?) in - return (transaction.getVersion(), transaction.getCurrent()?.0) - } - |> mapToSignal { version, currentId -> Signal in - return accountManager.transaction { transaction -> Void in - transaction.setVersion(4) - } - |> ignoreValues - |> mapToSignal { _ -> Signal in - } - } -}