From df99ce0055c786c960debaf5a479b60e0d73ded5 Mon Sep 17 00:00:00 2001 From: Sasha Weiss Date: Thu, 18 Apr 2024 15:33:08 -0700 Subject: [PATCH] Post a Calls Tab notification when marking unended group calls as ended --- Signal/Calls/WebRTCCallMessageHandler.swift | 4 - .../DebugUI/DebugUICallsTab.swift | 2 +- SignalNSE/NSECallMessageHandler.swift | 4 - .../InteractionStore+CallRecord.swift | 34 ++++++ .../Calls/Group/OWSGroupCallMessage.h | 2 - .../Calls/Group/OWSGroupCallMessage.m | 9 -- SignalServiceKit/Calls/GroupCallManager.swift | 100 +++++++++++++----- 7 files changed, 111 insertions(+), 44 deletions(-) diff --git a/Signal/Calls/WebRTCCallMessageHandler.swift b/Signal/Calls/WebRTCCallMessageHandler.swift index 05cb0a7ab3..2f9e7bfca5 100644 --- a/Signal/Calls/WebRTCCallMessageHandler.swift +++ b/Signal/Calls/WebRTCCallMessageHandler.swift @@ -175,10 +175,6 @@ class WebRTCCallMessageHandler: CallMessageHandler { for groupThread: TSGroupThread, serverReceivedTimestamp: UInt64 ) async { - GroupCallPeekLogger.shared.info( - "Received group call update message for thread \(groupThread.uniqueId), eraId \(String(describing: updateMessage.eraID))" - ) - await groupCallManager.peekGroupCallAndUpdateThread( groupThread, peekTrigger: .receivedGroupUpdateMessage( diff --git a/Signal/src/ViewControllers/DebugUI/DebugUICallsTab.swift b/Signal/src/ViewControllers/DebugUI/DebugUICallsTab.swift index 5ec2283c0b..9655b5262d 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUICallsTab.swift +++ b/Signal/src/ViewControllers/DebugUI/DebugUICallsTab.swift @@ -338,7 +338,7 @@ class DebugUICallsTab: DebugUIPage { thread: groupThread, sentAtTimestamp: nowTimestamp ) - callInteraction.update(withHasEnded: true, transaction: tx) + callInteraction.hasEnded = true callInteraction.anyInsert(transaction: tx) let callRecord = CallRecord( diff --git a/SignalNSE/NSECallMessageHandler.swift b/SignalNSE/NSECallMessageHandler.swift index 17048af9bb..466e7e5a90 100644 --- a/SignalNSE/NSECallMessageHandler.swift +++ b/SignalNSE/NSECallMessageHandler.swift @@ -194,10 +194,6 @@ class NSECallMessageHandler: CallMessageHandler { for groupThread: TSGroupThread, serverReceivedTimestamp: UInt64 ) async { - GroupCallPeekLogger.shared.info( - "Received group call update message for thread \(groupThread.uniqueId), eraId \(String(describing: updateMessage.eraID))" - ) - await groupCallManager.peekGroupCallAndUpdateThread( groupThread, peekTrigger: .receivedGroupUpdateMessage( diff --git a/SignalServiceKit/Calls/CallRecord/InteractionStore+CallRecord.swift b/SignalServiceKit/Calls/CallRecord/InteractionStore+CallRecord.swift index 4a74f451c9..c1627a368f 100644 --- a/SignalServiceKit/Calls/CallRecord/InteractionStore+CallRecord.swift +++ b/SignalServiceKit/Calls/CallRecord/InteractionStore+CallRecord.swift @@ -87,6 +87,40 @@ public extension InteractionStore { groupCallInteraction.joinedMemberUuids = joinedMemberAcis.map { $0.serviceIdUppercaseString } } + postUpdatedNotification( + callId: callId, + groupThreadRowId: groupThreadRowId, + notificationScheduler: notificationScheduler, + tx: tx + ) + } + + func markGroupCallInteractionAsEnded( + groupCallInteraction: OWSGroupCallMessage, + callId: UInt64, + groupThreadRowId: Int64, + notificationScheduler: Scheduler, + tx: DBWriteTransaction + ) { + updateInteraction(groupCallInteraction, tx: tx) { groupCallInteraction in + groupCallInteraction.hasEnded = true + groupCallInteraction.joinedMemberUuids = [] + } + + postUpdatedNotification( + callId: callId, + groupThreadRowId: groupThreadRowId, + notificationScheduler: notificationScheduler, + tx: tx + ) + } + + private func postUpdatedNotification( + callId: UInt64, + groupThreadRowId: Int64, + notificationScheduler: Scheduler, + tx: DBWriteTransaction + ) { tx.addAsyncCompletion(on: notificationScheduler) { NotificationCenter.default.post(GroupCallInteractionUpdatedNotification( callId: callId, diff --git a/SignalServiceKit/Calls/Group/OWSGroupCallMessage.h b/SignalServiceKit/Calls/Group/OWSGroupCallMessage.h index 6ef944ba33..9a8d778200 100644 --- a/SignalServiceKit/Calls/Group/OWSGroupCallMessage.h +++ b/SignalServiceKit/Calls/Group/OWSGroupCallMessage.h @@ -93,8 +93,6 @@ NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:receivedAtTimestamp - (NSString *)systemTextWithTransaction:(SDSAnyReadTransaction *)transaction; -- (void)updateWithHasEnded:(BOOL)hasEnded transaction:(SDSAnyWriteTransaction *)transaction; - @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/Calls/Group/OWSGroupCallMessage.m b/SignalServiceKit/Calls/Group/OWSGroupCallMessage.m index 3b43e5e64e..f70d3cc5c3 100644 --- a/SignalServiceKit/Calls/Group/OWSGroupCallMessage.m +++ b/SignalServiceKit/Calls/Group/OWSGroupCallMessage.m @@ -198,15 +198,6 @@ NS_ASSUME_NONNULL_BEGIN } } -- (void)updateWithHasEnded:(BOOL)hasEnded transaction:(SDSAnyWriteTransaction *)transaction -{ - [self anyUpdateGroupCallMessageWithTransaction:transaction - block:^(OWSGroupCallMessage *message) { - message.hasEnded = hasEnded; - message.joinedMemberUuids = @[]; - }]; -} - #pragma mark - Private - (NSString *)participantNameForAddress:(SignalServiceAddress *)address transaction:(SDSAnyReadTransaction *)transaction diff --git a/SignalServiceKit/Calls/GroupCallManager.swift b/SignalServiceKit/Calls/GroupCallManager.swift index d440b5eb75..c68a5ee480 100644 --- a/SignalServiceKit/Calls/GroupCallManager.swift +++ b/SignalServiceKit/Calls/GroupCallManager.swift @@ -18,7 +18,7 @@ public class CurrentCallNoOpThreadProvider: CurrentCallThreadProvider { /// Fetches & updates group call state. public class GroupCallManager { /// The triggers that may kick off a group call peek. - public enum PeekTrigger { + public enum PeekTrigger: CustomStringConvertible { /// We received a group update message, and are peeking in response. case receivedGroupUpdateMessage(eraId: String?, messageTimestamp: UInt64) @@ -33,6 +33,16 @@ public class GroupCallManager { return timestamp } } + + public var description: String { + switch self { + case .receivedGroupUpdateMessage(let eraId, _): + let callId = eraId.map { CallId(eraId: $0) } + return "GroupCallUpdateMessage, callId: \(callId?.description ?? "(null)")" + case .localEvent(let timestamp): + return "LocalEvent" + } + } } private var callRecordStore: CallRecordStore { DependenciesBridge.shared.callRecordStore } @@ -60,14 +70,18 @@ public class GroupCallManager { _ thread: TSGroupThread, peekTrigger: PeekTrigger ) async { - // If the currentCall is for the provided thread, we don't need to perform an explicit - // peek. Connected calls will receive automatic updates from RingRTC + logger.info("Peek requested for thread \(thread.uniqueId) with trigger: \(peekTrigger)") + + // If the currentCall is for the provided thread, we don't need to + // perform an explicit peek. Connected calls will receive automatic + // updates from RingRTC. guard currentCallThreadProvider.currentCallThread != thread else { - GroupCallPeekLogger.shared.info("Ignoring peek request for the current call") + logger.info("Ignoring peek request for the current call.") return } guard thread.isLocalUserFullMember else { + logger.warn("Ignoring peek request for non-member thread!") return } @@ -114,7 +128,7 @@ public class GroupCallManager { }() if shouldUpdateCallModels { - self.logger.info("Applying group call PeekInfo for thread: \(thread.uniqueId) eraId: \(info.eraId ?? "(null)")") + self.logger.info("Applying group call PeekInfo for thread: \(thread.uniqueId), callId: \(info.callId?.description ?? "(null)")") await self.databaseStorage.awaitableWrite { tx in self.updateGroupCallModelsForPeek( @@ -125,7 +139,7 @@ public class GroupCallManager { ) } } else { - self.logger.info("Ignoring group call PeekInfo for thread: \(thread.uniqueId) stale eraId: \(info.eraId ?? "(null)")") + self.logger.info("Ignoring group call PeekInfo for thread: \(thread.uniqueId), stale callId: \(info.callId?.description ?? "(null)")") } } catch { if error.isNetworkFailureOrTimeout { @@ -148,8 +162,7 @@ public class GroupCallManager { triggerEventTimestamp: UInt64, tx: SDSAnyWriteTransaction ) { - let currentEraId: String? = peekInfo.eraId - let currentCallId: UInt64? = currentEraId.map { callIdFromEra($0) } + let currentCallId: CallId? = peekInfo.callId // Clean up any unended group calls that don't match the currently // in-progress call. @@ -160,7 +173,6 @@ public class GroupCallManager { ) guard - let currentEraId, let currentCallId, let creatorAci = peekInfo.creator.map({ Aci(fromUUID: $0) }), let groupThreadRowId = groupThread.sqliteRowId @@ -186,7 +198,7 @@ public class GroupCallManager { // "current" call ID. If so, we should reuse/update it and its // interaction. switch self.callRecordStore.fetch( - callId: currentCallId, + callId: currentCallId.rawValue, threadRowId: groupThreadRowId, tx: tx.asV2Write ) { @@ -212,13 +224,13 @@ public class GroupCallManager { case .found(let interactionToUpdate): let wasOldMessageEmpty = interactionToUpdate.joinedMemberUuids?.count == 0 && !interactionToUpdate.hasEnded - logger.info("Updating group call interaction for thread \(groupThread.uniqueId), eraId \(currentEraId). Joined member count: \(joinedMemberAcis.count)") + logger.info("Updating group call interaction for thread \(groupThread.uniqueId), callId \(currentCallId). Joined member count: \(joinedMemberAcis.count)") self.interactionStore.updateGroupCallInteractionAcis( groupCallInteraction: interactionToUpdate, joinedMemberAcis: joinedMemberAcis, creatorAci: creatorAci, - callId: currentCallId, + callId: currentCallId.rawValue, groupThreadRowId: groupThreadRowId, notificationScheduler: self.schedulers.main, tx: tx.asV2Write @@ -259,7 +271,7 @@ public class GroupCallManager { } private func createModelsForNewGroupCall( - callId: UInt64, + callId: CallId, joinedMemberAcis: [Aci], creatorAci: Aci?, triggerEventTimestamp: UInt64, @@ -277,7 +289,7 @@ public class GroupCallManager { logger.info("Creating record for group call discovered via peek.") _ = groupCallRecordManager.createGroupCallRecordForPeek( - callId: callId, + callId: callId.rawValue, groupCallInteraction: newGroupCallInteraction, groupCallInteractionRowId: interactionRowId, groupThread: groupThread, @@ -298,18 +310,18 @@ public class GroupCallManager { /// The interaction representing the in-progress call for the given group /// (matching the given call ID), if any. private func cleanUpUnendedCallMessagesAsNecessary( - currentCallId: UInt64?, + currentCallId: CallId?, groupThread: TSGroupThread, tx: SDSAnyWriteTransaction ) -> OWSGroupCallMessage? { enum CallIdProvider { - case legacyEraId(callId: UInt64) + case legacyEraId(eraId: String) case callRecord(callRecord: CallRecord) - var callId: UInt64 { + var callId: CallId { switch self { - case .legacyEraId(let callId): return callId - case .callRecord(let callRecord): return callRecord.callId + case .legacyEraId(let eraId): return CallId(eraId: eraId) + case .callRecord(let callRecord): return CallId(callRecord.callId) } } } @@ -325,7 +337,7 @@ public class GroupCallManager { if let legacyCallInteractionEraId = groupCallInteraction.eraId { return ( groupCallInteraction, - .legacyEraId(callId: callIdFromEra(legacyCallInteractionEraId)) + .legacyEraId(eraId: legacyCallInteractionEraId) ) } else if let callRowId = groupCallInteraction.sqliteRowId, @@ -347,11 +359,22 @@ public class GroupCallManager { // Any call in our database that hasn't ended yet that doesn't match the // current call ID must have ended by definition. We do that update now. for (unendedCallInteraction, callIdProvider) in unendedCalls { - guard callIdProvider.callId != currentCallId else { + guard + callIdProvider.callId != currentCallId, + let groupThreadRowId = groupThread.sqliteRowId + else { continue } - unendedCallInteraction.update(withHasEnded: true, transaction: tx) + logger.info("Marking unended group call interaction as ended for thread \(groupThread.uniqueId), callId \(callIdProvider.callId).") + + interactionStore.markGroupCallInteractionAsEnded( + groupCallInteraction: unendedCallInteraction, + callId: callIdProvider.callId.rawValue, + groupThreadRowId: groupThreadRowId, + notificationScheduler: schedulers.main, + tx: tx.asV2Write + ) } guard let currentCallId else { @@ -385,7 +408,7 @@ public class GroupCallManager { return } - let callId = callIdFromEra(eraId) + let callId = CallId(eraId: eraId) guard let groupThreadRowId = groupThread.sqliteRowId else { owsFailDebug("Missing SQLite row ID for group thread!") @@ -393,7 +416,7 @@ public class GroupCallManager { } switch self.callRecordStore.fetch( - callId: callId, + callId: callId.rawValue, threadRowId: groupThreadRowId, tx: tx.asV2Read ) { @@ -457,3 +480,32 @@ public class GroupCallManager { ) } } + +// MARK: - + +/// A wrapper around UInt64 call IDs that pre-redacts them the same way the hex +/// redaction rule would otherwise. +private struct CallId: CustomStringConvertible, Equatable { + private static let unredactedLength: Int = 3 + + let rawValue: UInt64 + + init(_ rawValue: UInt64) { + self.rawValue = rawValue + } + + init(eraId: String) { + self.rawValue = callIdFromEra(eraId) + } + + var description: String { + let redactedCallId = "\(rawValue)".suffix(Self.unredactedLength) + return "…\(redactedCallId)" + } +} + +private extension PeekInfo { + var callId: CallId? { + return eraId.map { CallId(eraId: $0) } + } +}