// // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import SignalCoreKit /// Represents a call event that occurred on this device that we want to /// communicate to our linked devices. @objc(OutgoingCallEvent) class OutgoingCallEvent: NSObject, NSCoding { typealias ConversationId = CallSyncMessageConversationId enum CallType: UInt { case audio case video case group } enum EventDirection: UInt { case incoming case outgoing } enum EventType: UInt { case accepted case notAccepted case deleted } let timestamp: UInt64 let conversationId: ConversationId let callId: UInt64 let callType: CallType let eventDirection: EventDirection let eventType: EventType init( timestamp: UInt64, conversationId: ConversationId, callId: UInt64, callType: CallType, eventDirection: EventDirection, eventType: EventType ) { self.timestamp = timestamp self.conversationId = conversationId self.callId = callId self.callType = callType self.eventDirection = eventDirection self.eventType = eventType } // MARK: NSCoding private enum Keys { static let timestamp = "timestamp" static let conversationId = "peerUuid" static let callId = "callId" static let callType = "type" static let eventDirection = "direction" static let eventType = "event" } func encode(with coder: NSCoder) { coder.encode(NSNumber(value: timestamp), forKey: Keys.timestamp) coder.encode(conversationId.asData as NSData, forKey: Keys.conversationId) coder.encode(NSNumber(value: callId), forKey: Keys.callId) coder.encode(NSNumber(value: callType.rawValue), forKey: Keys.callType) coder.encode(NSNumber(value: eventDirection.rawValue), forKey: Keys.eventDirection) coder.encode(NSNumber(value: eventType.rawValue), forKey: Keys.eventType) } required init?(coder: NSCoder) { guard let timestamp = coder.decodeObject(of: NSNumber.self, forKey: Keys.timestamp) as? UInt64, let conversationId: ConversationId = coder.decodeObject( of: NSData.self, forKey: Keys.conversationId ).flatMap({ .from(data: $0 as Data) }), let callId = coder.decodeObject(of: NSNumber.self, forKey: Keys.callId) as? UInt64, let callTypeRaw = coder.decodeObject(of: NSNumber.self, forKey: Keys.callType) as? UInt, let callType = CallType(rawValue: callTypeRaw), let eventDirectionRaw = coder.decodeObject(of: NSNumber.self, forKey: Keys.eventDirection) as? UInt, let eventDirection = EventDirection(rawValue: eventDirectionRaw), let eventTypeRaw = coder.decodeObject(of: NSNumber.self, forKey: Keys.eventType) as? UInt, let eventType = EventType(rawValue: eventTypeRaw) else { owsFailDebug("Missing or unrecognized fields!") return nil } self.timestamp = timestamp self.conversationId = conversationId self.callId = callId self.callType = callType self.eventDirection = eventDirection self.eventType = eventType } } /// Represents a sync message containing a "call event". /// /// Indicates to linked devices that a call has changed state. For example, that /// we accepted a ringing call on this device. /// /// - SeeAlso ``IncomingCallEventSyncMessageManager`` @objc(OutgoingCallEventSyncMessage) public class OutgoingCallEventSyncMessage: OWSOutgoingSyncMessage { /// The call event. /// /// The ObjC name must remain as-is for compatibility with legacy data /// archived using Mantle. When this model was originally written (in ObjC), /// the property was named `event` - therefore, Mantle will have used that /// name as a key when doing its reflection-based archiving. /// /// - Note /// Nullability here is intentional, since Mantle will set this property via /// its reflection-based `init(coder:)` when we call `super.init(coder:)`. @objc(event) private(set) var callEvent: OutgoingCallEvent! init( thread: TSThread, event: OutgoingCallEvent, tx: SDSAnyReadTransaction ) { self.callEvent = event super.init(thread: thread, transaction: tx) } required public init?(coder: NSCoder) { super.init(coder: coder) } required public init(dictionary dictionaryValue: [String: Any]!) throws { try super.init(dictionary: dictionaryValue) } override public var isUrgent: Bool { false } override public func syncMessageBuilder(transaction: SDSAnyReadTransaction) -> SSKProtoSyncMessageBuilder? { let callEventBuilder = SSKProtoSyncMessageCallEvent.builder() callEventBuilder.setCallID(callEvent.callId) callEventBuilder.setType(callEvent.callType.protoValue) callEventBuilder.setDirection(callEvent.eventDirection.protoValue) callEventBuilder.setEvent(callEvent.eventType.protoValue) callEventBuilder.setTimestamp(callEvent.timestamp) callEventBuilder.setConversationID(callEvent.conversationId.asData) let builder = SSKProtoSyncMessage.builder() builder.setCallEvent(callEventBuilder.buildInfallibly()) return builder } } fileprivate extension OutgoingCallEvent.CallType { var protoValue: SSKProtoSyncMessageCallEventType { switch self { case .audio: return .audioCall case .video: return .videoCall case .group: return .groupCall } } } fileprivate extension OutgoingCallEvent.EventDirection { var protoValue: SSKProtoSyncMessageCallEventDirection { switch self { case .incoming: return .incoming case .outgoing: return .outgoing } } } fileprivate extension OutgoingCallEvent.EventType { var protoValue: SSKProtoSyncMessageCallEventEvent { switch self { case .accepted: return .accepted case .notAccepted: return .notAccepted case .deleted: return .deleted } } }