diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 51cb1bc6d..67a1bc8de 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -902,8 +902,22 @@ extension ConversationVC: // For call info messages show the "call missed" modal guard cellViewModel.variant != .infoCall else { - let callMissedTipsModal: CallMissedTipsModal = CallMissedTipsModal(caller: cellViewModel.authorName) - present(callMissedTipsModal, animated: true, completion: nil) + // If the failure was due to the mic permission being denied then we want to show the permission modal, + // otherwise we want to show the call missed tips modal + guard + let infoMessageData: Data = (cellViewModel.rawBody ?? "").data(using: .utf8), + let messageInfo: CallMessage.MessageInfo = try? JSONDecoder().decode( + CallMessage.MessageInfo.self, + from: infoMessageData + ), + messageInfo.state == .permissionDeniedMicrophone + else { + let callMissedTipsModal: CallMissedTipsModal = CallMissedTipsModal(caller: cellViewModel.authorName) + present(callMissedTipsModal, animated: true, completion: nil) + return + } + + Permissions.requestMicrophonePermissionIfNeeded(presentingViewController: self) return } diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index d6b2176b8..7476d2f9d 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import AVFAudio import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit @@ -147,14 +148,15 @@ final class CallMessageCell: MessageCell { switch messageInfo.state { case .outgoing: return UIImage(named: "CallOutgoing")?.withRenderingMode(.alwaysTemplate) case .incoming: return UIImage(named: "CallIncoming")?.withRenderingMode(.alwaysTemplate) - case .missed, .permissionDenied: return UIImage(named: "CallMissed")?.withRenderingMode(.alwaysTemplate) + case .missed, .permissionDenied, .permissionDeniedMicrophone: + return UIImage(named: "CallMissed")?.withRenderingMode(.alwaysTemplate) default: return nil } }() iconImageView.themeTintColor = { switch messageInfo.state { case .outgoing, .incoming: return .textPrimary - case .missed, .permissionDenied: return .danger + case .missed, .permissionDenied, .permissionDeniedMicrophone: return .danger default: return nil } }() @@ -162,8 +164,13 @@ final class CallMessageCell: MessageCell { iconImageViewHeightConstraint.constant = (iconImageView.image != nil ? CallMessageCell.iconSize : 0) let shouldShowInfoIcon: Bool = ( - messageInfo.state == .permissionDenied && - !Storage.shared[.areCallsEnabled] + ( + messageInfo.state == .permissionDenied && + !Storage.shared[.areCallsEnabled] + ) || ( + messageInfo.state == .permissionDeniedMicrophone && + AVAudioSession.sharedInstance().recordPermission != .granted + ) ) infoImageViewWidthConstraint.constant = (shouldShowInfoIcon ? CallMessageCell.iconSize : 0) infoImageViewHeightConstraint.constant = (shouldShowInfoIcon ? CallMessageCell.iconSize : 0) @@ -217,7 +224,15 @@ final class CallMessageCell: MessageCell { else { return } // Should only be tappable if the info icon is visible - guard messageInfo.state == .permissionDenied && !Storage.shared[.areCallsEnabled] else { return } + guard + ( + messageInfo.state == .permissionDenied && + !Storage.shared[.areCallsEnabled] + ) || ( + messageInfo.state == .permissionDeniedMicrophone && + AVAudioSession.sharedInstance().recordPermission != .granted + ) + else { return } self.delegate?.handleItemTapped(cellViewModel, cell: self, cellLocation: gestureRecognizer.location(in: self)) } diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 8e7ed5c31..3e6fec49a 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -244,7 +244,10 @@ public class NotificationPresenter: NotificationsProtocol { else { return } // Only notify missed calls - guard messageInfo.state == .missed || messageInfo.state == .permissionDenied else { return } + switch messageInfo.state { + case .missed, .permissionDenied, .permissionDeniedMicrophone: break + default: return + } let category = AppNotificationCategory.errorMessage let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] @@ -264,6 +267,11 @@ public class NotificationPresenter: NotificationsProtocol { format: "modal_call_missed_tips_explanation".localized(), senderName ) + case .permissionDeniedMicrophone: + return String( + format: "call_missed".localized(), + senderName + ) case .missed: return String( format: "call_missed".localized(), diff --git a/Session/Utilities/Permissions.swift b/Session/Utilities/Permissions.swift index 4e711d628..955728877 100644 --- a/Session/Utilities/Permissions.swift +++ b/Session/Utilities/Permissions.swift @@ -3,6 +3,7 @@ import UIKit import Photos import PhotosUI +import AVFAudio import SessionUIKit import SessionUtilitiesKit import SessionMessagingKit diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index 7a2cb9534..cb7900d33 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -226,6 +226,7 @@ public extension CallMessage { case outgoing case missed case permissionDenied + case permissionDeniedMicrophone case unknown } @@ -253,7 +254,7 @@ public extension CallMessage { threadContactDisplayName ) - case .missed, .permissionDenied: + case .missed, .permissionDenied, .permissionDeniedMicrophone: return String( format: "call_missed".localized(), threadContactDisplayName diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 798276db3..127ea94ac 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import AVFAudio import GRDB import WebRTC import SessionUtilitiesKit @@ -75,8 +76,11 @@ extension MessageReceiver { return } - guard db[.areCallsEnabled] else { - if let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(db, for: message, state: .permissionDenied, using: dependencies) { + let hasMicrophonePermission: Bool = (AVAudioSession.sharedInstance().recordPermission == .granted) + guard db[.areCallsEnabled] && hasMicrophonePermission else { + let state: CallMessage.MessageInfo.State = (db[.areCallsEnabled] ? .permissionDeniedMicrophone : .permissionDenied) + + if let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(db, for: message, state: state, using: dependencies) { let thread: SessionThread = try SessionThread .fetchOrCreate(db, id: sender, variant: .contact, shouldBeVisible: nil) diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index 27b0db43c..c53fa4bb3 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -146,7 +146,10 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { else { return } // Only notify missed calls - guard messageInfo.state == .missed || messageInfo.state == .permissionDenied else { return } + switch messageInfo.state { + case .missed, .permissionDenied, .permissionDeniedMicrophone: break + default: return + } let userInfo: [String: Any] = [ NotificationServiceExtension.isFromRemoteKey: true, @@ -174,6 +177,12 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { senderName ) } + else if messageInfo.state == .permissionDeniedMicrophone { + notificationContent.body = String( + format: "call_missed".localized(), + senderName + ) + } addNotifcationRequest( identifier: UUID().uuidString, diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 4eb4b578e..3fb59a4cf 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import AVFAudio import Combine import GRDB import CallKit @@ -158,14 +159,15 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension throw NotificationError.ignorableMessage } - switch (db[.areCallsEnabled], isCallOngoing) { + let hasMicrophonePermission: Bool = (AVAudioSession.sharedInstance().recordPermission == .granted) + switch ((db[.areCallsEnabled] && hasMicrophonePermission), isCallOngoing) { case (false, _): if let sender: String = callMessage.sender, let interaction: Interaction = try MessageReceiver.insertCallInfoMessage( db, for: callMessage, - state: .permissionDenied, + state: (db[.areCallsEnabled] ? .permissionDeniedMicrophone : .permissionDenied), using: dependencies ) {