CallKitManager.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "CallKitManager.h"
  6. #import <CallKit/CXError.h>
  7. #import "CallConstants.h"
  8. #import "NCAudioController.h"
  9. #import "NCAPIController.h"
  10. #import "NCAppBranding.h"
  11. #import "NCDatabaseManager.h"
  12. #import "NCNotificationController.h"
  13. #import "NCRoomsManager.h"
  14. #import "NCSettingsController.h"
  15. #import "NCUserInterfaceController.h"
  16. #import "NextcloudTalk-Swift.h"
  17. NSString * const CallKitManagerDidAnswerCallNotification = @"CallKitManagerDidAnswerCallNotification";
  18. NSString * const CallKitManagerDidEndCallNotification = @"CallKitManagerDidEndCallNotification";
  19. NSString * const CallKitManagerDidStartCallNotification = @"CallKitManagerDidStartCallNotification";
  20. NSString * const CallKitManagerDidChangeAudioMuteNotification = @"CallKitManagerDidChangeAudioMuteNotification";
  21. NSString * const CallKitManagerWantsToUpgradeToVideoCallNotification = @"CallKitManagerWantsToUpgradeToVideoCall";
  22. NSString * const CallKitManagerDidFailRequestingCallTransactionNotification = @"CallKitManagerDidFailRequestingCallTransaction";
  23. NSTimeInterval const kCallKitManagerMaxRingingTimeSeconds = 45.0;
  24. NSTimeInterval const kCallKitManagerCheckCallStateEverySeconds = 5.0;
  25. @interface CallKitManager () <CXProviderDelegate>
  26. @property (nonatomic, strong) CXProvider *provider;
  27. @property (nonatomic, strong) CXCallController *callController;
  28. @property (nonatomic, strong) NSMutableDictionary *hangUpTimers; // uuid -> hangUpTimer
  29. @property (nonatomic, strong) NSMutableDictionary *callStateTimers; // uuid -> callStateTimer
  30. @property (nonatomic, assign) BOOL startCallRetried;
  31. @end
  32. @implementation CallKitCall
  33. @end
  34. @implementation CallKitManager
  35. + (CallKitManager *)sharedInstance
  36. {
  37. static CallKitManager *sharedInstance = nil;
  38. static dispatch_once_t onceToken;
  39. dispatch_once(&onceToken, ^{
  40. sharedInstance = [[CallKitManager alloc] init];
  41. [sharedInstance provider];
  42. });
  43. return sharedInstance;
  44. }
  45. - (id)init
  46. {
  47. self = [super init];
  48. if (self) {
  49. self.calls = [[NSMutableDictionary alloc] init];
  50. self.hangUpTimers = [[NSMutableDictionary alloc] init];
  51. self.callStateTimers = [[NSMutableDictionary alloc] init];
  52. }
  53. return self;
  54. }
  55. + (BOOL)isCallKitAvailable
  56. {
  57. if ([NCUtils isiOSAppOnMac]) {
  58. // There's currently no support for CallKit when running on MacOS.
  59. // If this is enabled on MacOS, there's no audio, because we fail to retrieve
  60. // the streams from CallKit. Tested with MacOS 12 & 13.
  61. return NO;
  62. }
  63. // CallKit should be deactivated in China as requested by Apple
  64. return ![NSLocale.currentLocale.countryCode isEqual: @"CN"];
  65. }
  66. #pragma mark - Getters
  67. - (CXProvider *)provider
  68. {
  69. if (!_provider) {
  70. _provider = [[CXProvider alloc] initWithConfiguration:[self defaultProviderConfiguration]];
  71. [_provider setDelegate:self queue:nil];
  72. }
  73. return _provider;
  74. }
  75. - (CXCallController *)callController
  76. {
  77. if (!_callController) {
  78. _callController = [[CXCallController alloc] init];
  79. }
  80. return _callController;
  81. }
  82. #pragma mark - Utils
  83. - (CXCallUpdate *)defaultCallUpdate
  84. {
  85. CXCallUpdate *update = [[CXCallUpdate alloc] init];
  86. update.supportsHolding = NO;
  87. update.supportsGrouping = NO;
  88. update.supportsUngrouping = NO;
  89. update.supportsDTMF = NO;
  90. update.hasVideo = NO;
  91. return update;
  92. }
  93. - (CXProviderConfiguration *)defaultProviderConfiguration
  94. {
  95. CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] init];
  96. configuration.supportsVideo = YES;
  97. configuration.maximumCallGroups = 1;
  98. configuration.maximumCallsPerCallGroup = 1;
  99. configuration.includesCallsInRecents = [NCUserDefaults includeCallsInRecents];
  100. configuration.supportedHandleTypes = [NSSet setWithObjects:@(CXHandleTypePhoneNumber), @(CXHandleTypeEmailAddress), @(CXHandleTypeGeneric), nil];
  101. configuration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:@"app-logo-callkit"]);
  102. return configuration;
  103. }
  104. - (CallKitCall *)callForToken:(NSString *)token
  105. {
  106. for (CallKitCall *call in [_calls allValues]) {
  107. if ([call.token isEqualToString:token]) {
  108. return call;
  109. }
  110. }
  111. return nil;;
  112. }
  113. #pragma mark - Actions
  114. - (void)setDefaultProviderConfiguration
  115. {
  116. if (_provider) {
  117. [_provider setConfiguration:[self defaultProviderConfiguration]];
  118. }
  119. }
  120. - (void)reportIncomingCall:(NSString *)token withDisplayName:(NSString *)displayName forAccountId:(NSString *)accountId
  121. {
  122. NSString *protectedDataAvailable = @"available";
  123. if (!UIApplication.sharedApplication.isProtectedDataAvailable) {
  124. protectedDataAvailable = @"unavailable";
  125. }
  126. [NCUtils log:[NSString stringWithFormat:@"Report incoming call for token %@ for account %@. Protected data is %@", token, accountId, protectedDataAvailable]];
  127. BOOL ongoingCalls = _calls.count > 0;
  128. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  129. // If the app is not active (e.g. in background) and there is an open chat
  130. BOOL isAppActive = [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive;
  131. ChatViewController *chatViewController = [[NCRoomsManager sharedInstance] chatViewController];
  132. if (!isAppActive && chatViewController) {
  133. // Leave the chat so it doesn't try to join the chat conversation when the app becomes active.
  134. [chatViewController leaveChat];
  135. [[NCUserInterfaceController sharedInstance] presentConversationsList];
  136. }
  137. // If the incoming call is from a different account
  138. if (![activeAccount.accountId isEqualToString:accountId]) {
  139. // If there is an ongoing call then show a local notification
  140. if (ongoingCalls) {
  141. [self reportAndCancelIncomingCall:token withDisplayName:displayName forAccountId:accountId];
  142. return;
  143. // Change accounts if there are no ongoing calls
  144. } else {
  145. [[NCSettingsController sharedInstance] setActiveAccountWithAccountId:accountId];
  146. }
  147. }
  148. CXCallUpdate *update = [self defaultCallUpdate];
  149. update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:token];
  150. update.localizedCallerName = displayName;
  151. NSUUID *callUUID = [NSUUID new];
  152. CallKitCall *call = [[CallKitCall alloc] init];
  153. call.uuid = callUUID;
  154. call.token = token;
  155. call.displayName = displayName;
  156. call.accountId = accountId;
  157. call.update = update;
  158. call.reportedWhileInCall = ongoingCalls;
  159. call.isRinging = YES;
  160. __weak CallKitManager *weakSelf = self;
  161. [self.provider reportNewIncomingCallWithUUID:callUUID update:update completion:^(NSError * _Nullable error) {
  162. if (!error) {
  163. // Add call to calls array
  164. [weakSelf.calls setObject:call forKey:callUUID];
  165. // Add hangUpTimer to timers array
  166. NSTimer *hangUpTimer = [NSTimer scheduledTimerWithTimeInterval:kCallKitManagerMaxRingingTimeSeconds target:self selector:@selector(endCallWithMissedCallNotification:) userInfo:call repeats:NO];
  167. [weakSelf.hangUpTimers setObject:hangUpTimer forKey:callUUID];
  168. // Add callStateTimer to timers array
  169. NSTimer *callStateTimer = [NSTimer scheduledTimerWithTimeInterval:kCallKitManagerCheckCallStateEverySeconds target:self selector:@selector(checkCallStateForCall:) userInfo:call repeats:NO];
  170. [weakSelf.callStateTimers setObject:callStateTimer forKey:callUUID];
  171. // Get call info from server
  172. [weakSelf getCallInfoForCall:call];
  173. } else {
  174. NSLog(@"Provider could not present incoming call view.");
  175. }
  176. }];
  177. }
  178. - (void)reportAndCancelIncomingCall:(NSString *)token withDisplayName:(NSString *)displayName forAccountId:(NSString *)accountId
  179. {
  180. CXCallUpdate *update = [self defaultCallUpdate];
  181. NSUUID *callUUID = [NSUUID new];
  182. CallKitCall *call = [[CallKitCall alloc] init];
  183. call.uuid = callUUID;
  184. call.token = token;
  185. call.accountId = accountId;
  186. call.update = update;
  187. __weak CallKitManager *weakSelf = self;
  188. [self.provider reportNewIncomingCallWithUUID:callUUID update:update completion:^(NSError * _Nullable error) {
  189. if (!error) {
  190. [weakSelf.calls setObject:call forKey:callUUID];
  191. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:call.token forKey:@"roomToken"];
  192. [userInfo setValue:@(kNCLocalNotificationTypeCancelledCall) forKey:@"localNotificationType"];
  193. [userInfo setObject:call.accountId forKey:@"accountId"];
  194. [[NCNotificationController sharedInstance] showLocalNotification:kNCLocalNotificationTypeCancelledCall withUserInfo:userInfo];
  195. [weakSelf endCallWithUUID:callUUID];
  196. } else {
  197. NSLog(@"Provider could not present incoming call view.");
  198. }
  199. }];
  200. }
  201. - (void)reportIncomingCallForNonCallKitDevicesWithPushNotification:(NCPushNotification *)pushNotification
  202. {
  203. CXCallUpdate *update = [self defaultCallUpdate];
  204. NSUUID *callUUID = [NSUUID new];
  205. CallKitCall *call = [[CallKitCall alloc] init];
  206. call.uuid = callUUID;
  207. call.token = pushNotification.roomToken;
  208. call.accountId = pushNotification.accountId;
  209. call.update = update;
  210. __weak CallKitManager *weakSelf = self;
  211. [self.provider reportNewIncomingCallWithUUID:callUUID update:update completion:^(NSError * _Nullable error) {
  212. if (!error) {
  213. [weakSelf.calls setObject:call forKey:callUUID];
  214. [[NCNotificationController sharedInstance] showLocalNotificationForIncomingCallWithPushNotificaion:pushNotification];
  215. [weakSelf endCallWithUUID:callUUID];
  216. } else {
  217. NSLog(@"Provider could not present incoming call view.");
  218. }
  219. }];
  220. }
  221. - (void)reportIncomingCallForOldAccount
  222. {
  223. CXCallUpdate *update = [self defaultCallUpdate];
  224. update.localizedCallerName = NSLocalizedString(@"Old account", @"Will be used as the caller name when a VoIP notification can't be decrypted");
  225. NSUUID *callUUID = [NSUUID new];
  226. CallKitCall *call = [[CallKitCall alloc] init];
  227. call.uuid = callUUID;
  228. call.update = update;
  229. __weak CallKitManager *weakSelf = self;
  230. [self.provider reportNewIncomingCallWithUUID:callUUID update:update completion:^(NSError * _Nullable error) {
  231. if (!error) {
  232. [weakSelf.calls setObject:call forKey:callUUID];
  233. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:@(kNCLocalNotificationTypeCallFromOldAccount) forKey:@"localNotificationType"];
  234. [[NCNotificationController sharedInstance] showLocalNotification:kNCLocalNotificationTypeCallFromOldAccount withUserInfo:userInfo];
  235. [weakSelf endCallWithUUID:callUUID];
  236. } else {
  237. NSLog(@"Provider could not present incoming call view.");
  238. }
  239. }];
  240. }
  241. - (void)getCallInfoForCall:(CallKitCall *)call
  242. {
  243. NCRoom *room = [[NCDatabaseManager sharedInstance] roomWithToken:call.token forAccountId:call.accountId];
  244. if (room) {
  245. [self updateCall:call withDisplayName:room.displayName];
  246. }
  247. TalkAccount *account = [[NCDatabaseManager sharedInstance] talkAccountForAccountId:call.accountId];
  248. [[NCAPIController sharedInstance] getRoomForAccount:account withToken:call.token completionBlock:^(NSDictionary *roomDict, NSError *error) {
  249. if (!error) {
  250. NCRoom *room = [NCRoom roomWithDictionary:roomDict andAccountId:call.accountId];
  251. [self updateCall:call withDisplayName:room.displayName];
  252. if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityCallFlags forAccountId:call.accountId]) {
  253. NSInteger callFlag = [[roomDict objectForKey:@"callFlag"] integerValue];
  254. if (callFlag == CallFlagDisconnected) {
  255. [self presentMissedCallNotificationForCall:call];
  256. [self endCallWithUUID:call.uuid];
  257. } else if ((callFlag & CallFlagWithVideo) != 0) {
  258. [self updateCall:call hasVideo:YES];
  259. }
  260. }
  261. }
  262. }];
  263. }
  264. - (void)updateCall:(CallKitCall *)call withDisplayName:(NSString *)displayName
  265. {
  266. call.displayName = displayName;
  267. call.update.localizedCallerName = displayName;
  268. [self.provider reportCallWithUUID:call.uuid updated:call.update];
  269. }
  270. - (void)updateCall:(CallKitCall *)call hasVideo:(BOOL)hasVideo
  271. {
  272. call.update.hasVideo = hasVideo;
  273. [self.provider reportCallWithUUID:call.uuid updated:call.update];
  274. }
  275. - (void)stopHangUpTimerForCallUUID:(NSUUID *)uuid
  276. {
  277. NSTimer *hangUpTimer = [_hangUpTimers objectForKey:uuid];
  278. if (hangUpTimer) {
  279. [hangUpTimer invalidate];
  280. [_hangUpTimers removeObjectForKey:uuid];
  281. }
  282. }
  283. - (void)stopCallStateTimerForCallUUID:(NSUUID *)uuid
  284. {
  285. NSTimer *callStateTimer = [_callStateTimers objectForKey:uuid];
  286. if (callStateTimer) {
  287. [callStateTimer invalidate];
  288. [_callStateTimers removeObjectForKey:uuid];
  289. }
  290. }
  291. - (void)endCallWithMissedCallNotification:(NSTimer*)timer
  292. {
  293. CallKitCall *call = [timer userInfo];
  294. [self presentMissedCallNotificationForCall:call];
  295. [self endCallWithUUID:call.uuid];
  296. }
  297. - (void)presentMissedCallNotificationForCall:(CallKitCall *)call
  298. {
  299. if (call) {
  300. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:call.token forKey:@"roomToken"];
  301. [userInfo setValue:call.displayName forKey:@"displayName"];
  302. [userInfo setValue:@(kNCLocalNotificationTypeMissedCall) forKey:@"localNotificationType"];
  303. [userInfo setObject:call.accountId forKey:@"accountId"];
  304. [[NCNotificationController sharedInstance] showLocalNotification:kNCLocalNotificationTypeMissedCall withUserInfo:userInfo];
  305. }
  306. }
  307. - (void)checkCallStateForCall:(NSTimer *)timer
  308. {
  309. CallKitCall *call = [timer userInfo];
  310. if (!call) {
  311. return;
  312. }
  313. __weak CallKitManager *weakSelf = self;
  314. TalkAccount *account = [[NCDatabaseManager sharedInstance] talkAccountForAccountId:call.accountId];
  315. [[NCAPIController sharedInstance] getPeersForCall:call.token forAccount:account withCompletionBlock:^(NSMutableArray *peers, NSError *error, NSInteger statusCode) {
  316. // Make sure call is still ringing at this point to avoid a race-condition between answering the call on this device and the API callback
  317. if (!call.isRinging) {
  318. return;
  319. }
  320. if (statusCode == 404) {
  321. // The conversation was not found for this participant
  322. // Mostlikely the conversation was removed while an incoming call was ongoing
  323. [self endCallWithUUID:call.uuid];
  324. return;
  325. }
  326. if (!error && peers.count == 0) {
  327. // No one is in the call, we can hang up and show missed call notification
  328. [self presentMissedCallNotificationForCall:call];
  329. [self endCallWithUUID:call.uuid];
  330. return;
  331. }
  332. NSInteger callAPIVersion = [[NCAPIController sharedInstance] callAPIVersionForAccount:account];
  333. for (NSMutableDictionary *user in peers) {
  334. NSString *userId = [user objectForKey:@"userId"];
  335. BOOL isUserActorType = YES;
  336. if (callAPIVersion >= APIv3) {
  337. userId = [user objectForKey:@"actorId"];
  338. isUserActorType = [[user objectForKey:@"actorType"] isEqualToString:@"users"];
  339. }
  340. if ([account.userId isEqualToString:userId] && isUserActorType) {
  341. // Account is already in a call (answered the call on a different device) -> no need to keep ringing
  342. [self endCallWithUUID:call.uuid];
  343. return;
  344. }
  345. }
  346. // Reschedule next check
  347. NSTimer *callStateTimer = [NSTimer scheduledTimerWithTimeInterval:kCallKitManagerCheckCallStateEverySeconds target:self selector:@selector(checkCallStateForCall:) userInfo:call repeats:NO];
  348. [weakSelf.callStateTimers setObject:callStateTimer forKey:call.uuid];
  349. }];
  350. }
  351. - (void)startCall:(NSString *)token withVideoEnabled:(BOOL)videoEnabled andDisplayName:(NSString *)displayName asInitiator:(BOOL)initiator silently:(BOOL)silently recordingConsent:(BOOL)recordingConsent withAccountId:(NSString *)accountId
  352. {
  353. if (![CallKitManager isCallKitAvailable]) {
  354. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:token forKey:@"roomToken"];
  355. [userInfo setValue:@(videoEnabled) forKey:@"isVideoEnabled"];
  356. [userInfo setValue:@(initiator) forKey:@"initiator"];
  357. [userInfo setValue:@(silently) forKey:@"silentCall"];
  358. [userInfo setValue:@(recordingConsent) forKey:@"recordingConsent"];
  359. [[NSNotificationCenter defaultCenter] postNotificationName:CallKitManagerDidStartCallNotification
  360. object:self
  361. userInfo:userInfo];
  362. return;
  363. }
  364. // Start a new call
  365. if (_calls.count == 0) {
  366. CXCallUpdate *update = [self defaultCallUpdate];
  367. CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:token];
  368. update.remoteHandle = handle;
  369. update.localizedCallerName = displayName;
  370. update.hasVideo = videoEnabled;
  371. NSUUID *callUUID = [NSUUID new];
  372. CallKitCall *call = [[CallKitCall alloc] init];
  373. call.uuid = callUUID;
  374. call.token = token;
  375. call.displayName = displayName;
  376. call.accountId = accountId;
  377. call.update = update;
  378. call.initiator = initiator;
  379. call.silentCall = silently;
  380. call.recordingConsent = recordingConsent;
  381. CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:callUUID handle:handle];
  382. startCallAction.video = videoEnabled;
  383. startCallAction.contactIdentifier = displayName;
  384. CXTransaction *transaction = [[CXTransaction alloc] init];
  385. [transaction addAction:startCallAction];
  386. __weak CallKitManager *weakSelf = self;
  387. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
  388. [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
  389. if (!error) {
  390. self->_startCallRetried = NO;
  391. [weakSelf.calls setObject:call forKey:callUUID];
  392. } else {
  393. if (self->_startCallRetried) {
  394. NSLog(@"%@", error.localizedDescription);
  395. self->_startCallRetried = NO;
  396. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:token forKey:@"roomToken"];
  397. [[NSNotificationCenter defaultCenter] postNotificationName:CallKitManagerDidFailRequestingCallTransactionNotification
  398. object:self
  399. userInfo:userInfo];
  400. } else {
  401. self->_startCallRetried = YES;
  402. [self startCall:token withVideoEnabled:videoEnabled andDisplayName:displayName asInitiator:initiator silently:silently recordingConsent:recordingConsent withAccountId:accountId];
  403. }
  404. }
  405. }];
  406. });
  407. // Send notification for video call upgrade.
  408. // Since we send the token in the notification, it will only ask
  409. // for an upgrade if there is an ongoing (audioOnly) call in that room.
  410. } else if (videoEnabled) {
  411. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:token forKey:@"roomToken"];
  412. [[NSNotificationCenter defaultCenter] postNotificationName:CallKitManagerWantsToUpgradeToVideoCallNotification
  413. object:self
  414. userInfo:userInfo];
  415. }
  416. }
  417. - (void)presentRecordingConsentRequiredNotificationForCall:(CallKitCall *)call
  418. {
  419. if (call) {
  420. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:call.token forKey:@"roomToken"];
  421. [userInfo setValue:call.displayName forKey:@"displayName"];
  422. [userInfo setValue:@(kNCLocalNotificationTypeRecordingConsentRequired) forKey:@"localNotificationType"];
  423. [userInfo setObject:call.accountId forKey:@"accountId"];
  424. [[NCNotificationController sharedInstance] showLocalNotification:kNCLocalNotificationTypeRecordingConsentRequired withUserInfo:userInfo];
  425. }
  426. }
  427. - (void)endCall:(NSString *)token withStatusCode:(NSInteger)statusCode
  428. {
  429. [NCUtils log:[NSString stringWithFormat:@"End call for token %@ with statusCode %ld", token, statusCode]];
  430. CallKitCall *call = [self callForToken:token];
  431. if (call) {
  432. // Check if recording consent is required
  433. if (statusCode == 400) {
  434. [self presentRecordingConsentRequiredNotificationForCall:call];
  435. }
  436. [self endCallWithUUID:call.uuid];
  437. }
  438. }
  439. - (void)endCallWithUUID:(NSUUID *)uuid
  440. {
  441. CallKitCall *call = [_calls objectForKey:uuid];
  442. if (call) {
  443. CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:call.uuid];
  444. CXTransaction *transaction = [[CXTransaction alloc] init];
  445. [transaction addAction:endCallAction];
  446. [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
  447. if (error) {
  448. NSLog(@"%@", error.localizedDescription);
  449. }
  450. }];
  451. }
  452. }
  453. - (void)changeAudioMuted:(BOOL)muted forCall:(NSString *)token
  454. {
  455. CallKitCall *call = [self callForToken:token];
  456. if (call) {
  457. CXSetMutedCallAction *muteAction = [[CXSetMutedCallAction alloc] initWithCallUUID:call.uuid muted:muted];
  458. CXTransaction *transaction = [[CXTransaction alloc] init];
  459. [transaction addAction:muteAction];
  460. [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
  461. if (error) {
  462. NSLog(@"%@", error.localizedDescription);
  463. }
  464. }];
  465. }
  466. }
  467. - (void)switchCallFrom:(NSString *)from toCall:(NSString *)to
  468. {
  469. CallKitCall *call = [self callForToken:from];
  470. if (call) {
  471. call.token = to;
  472. }
  473. }
  474. #pragma mark - CXProviderDelegate
  475. - (void)providerDidReset:(CXProvider *)provider
  476. {
  477. NSLog(@"Provider:didReset");
  478. }
  479. - (void)provider:(CXProvider *)provider performStartCallAction:(nonnull CXStartCallAction *)action
  480. {
  481. CallKitCall *call = [_calls objectForKey:action.callUUID];
  482. if (call) {
  483. // Seems to be needed to display the call name correctly
  484. [_provider reportCallWithUUID:call.uuid updated:call.update];
  485. // Report outgoing call
  486. [provider reportOutgoingCallWithUUID:action.callUUID connectedAtDate:[NSDate new]];
  487. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:action.handle.value forKey:@"roomToken"];
  488. [userInfo setValue:@(action.isVideo) forKey:@"isVideoEnabled"];
  489. [userInfo setValue:@(call.initiator) forKey:@"initiator"];
  490. [userInfo setValue:@(call.silentCall) forKey:@"silentCall"];
  491. [userInfo setValue:@(call.recordingConsent) forKey:@"recordingConsent"];
  492. [[NSNotificationCenter defaultCenter] postNotificationName:CallKitManagerDidStartCallNotification
  493. object:self
  494. userInfo:userInfo];
  495. }
  496. [action fulfill];
  497. }
  498. - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action
  499. {
  500. CallKitCall *call = [_calls objectForKey:action.callUUID];
  501. if (call) {
  502. [NCUtils log:[NSString stringWithFormat:@"CallKit provider answer call action for token %@", call.token]];
  503. call.isRinging = NO;
  504. [self stopCallStateTimerForCallUUID:call.uuid];
  505. [self stopHangUpTimerForCallUUID:call.uuid];
  506. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:call.token forKey:@"roomToken"];
  507. [userInfo setValue:@(call.update.hasVideo) forKey:@"hasVideo"];
  508. [userInfo setValue:@(call.reportedWhileInCall) forKey:@"waitForCallEnd"];
  509. [[NSNotificationCenter defaultCenter] postNotificationName:CallKitManagerDidAnswerCallNotification
  510. object:self
  511. userInfo:userInfo];
  512. }
  513. [action fulfill];
  514. }
  515. - (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action
  516. {
  517. CallKitCall *call = [_calls objectForKey:action.callUUID];
  518. if (call) {
  519. [NCUtils log:[NSString stringWithFormat:@"CallKit provider end call action for token %@", call.token]];
  520. call.isRinging = NO;
  521. [self stopCallStateTimerForCallUUID:call.uuid];
  522. [self stopHangUpTimerForCallUUID:call.uuid];
  523. NSString *leaveCallToken = [call.token copy];
  524. [_calls removeObjectForKey:action.callUUID];
  525. if (leaveCallToken) {
  526. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:leaveCallToken forKey:@"roomToken"];
  527. [[NSNotificationCenter defaultCenter] postNotificationName:CallKitManagerDidEndCallNotification
  528. object:self
  529. userInfo:userInfo];
  530. }
  531. }
  532. [action fulfill];
  533. }
  534. - (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action
  535. {
  536. CallKitCall *call = [_calls objectForKey:action.callUUID];
  537. if (call) {
  538. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:call.token forKey:@"roomToken"];
  539. [userInfo setValue:@(action.isMuted) forKey:@"isMuted"];
  540. [[NSNotificationCenter defaultCenter] postNotificationName:CallKitManagerDidChangeAudioMuteNotification
  541. object:self
  542. userInfo:userInfo];
  543. }
  544. [action fulfill];
  545. }
  546. - (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession
  547. {
  548. NSLog(@"Provider:didActivateAudioSession - %@", audioSession);
  549. [[WebRTCCommon shared] dispatch:^{
  550. [[NCAudioController sharedInstance] providerDidActivateAudioSession:audioSession];
  551. }];
  552. }
  553. - (void)provider:(CXProvider *)provider didDeactivateAudioSession:(nonnull AVAudioSession *)audioSession
  554. {
  555. NSLog(@"Provider:didDeactivateAudioSession - %@", audioSession);
  556. [[WebRTCCommon shared] dispatch:^{
  557. [[NCAudioController sharedInstance] providerDidDeactivateAudioSession:audioSession];
  558. }];
  559. }
  560. @end