You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CallKit.m 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. //
  2. // Based on RNCallKit
  3. //
  4. // Original license:
  5. //
  6. // Copyright (c) 2016, Ian Yu-Hsun Lin
  7. //
  8. // Permission to use, copy, modify, and/or distribute this software for any
  9. // purpose with or without fee is hereby granted, provided that the above
  10. // copyright notice and this permission notice appear in all copies.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  13. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  14. // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  15. // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  16. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  17. // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  18. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  19. //
  20. #import <AVFoundation/AVFoundation.h>
  21. #import <CallKit/CallKit.h>
  22. #import <Foundation/Foundation.h>
  23. #import <UIKit/UIKit.h>
  24. #import <React/RCTBridge.h>
  25. #import <React/RCTConvert.h>
  26. #import <React/RCTEventDispatcher.h>
  27. #import <React/RCTEventEmitter.h>
  28. #import <React/RCTUtils.h>
  29. // The events emitted/supported by RNCallKit:
  30. static NSString * const RNCallKitPerformAnswerCallAction
  31. = @"performAnswerCallAction";
  32. static NSString * const RNCallKitPerformEndCallAction
  33. = @"performEndCallAction";
  34. static NSString * const RNCallKitPerformSetMutedCallAction
  35. = @"performSetMutedCallAction";
  36. static NSString * const RNCallKitProviderDidReset
  37. = @"providerDidReset";
  38. @interface RNCallKit : RCTEventEmitter <CXProviderDelegate>
  39. @end
  40. @implementation RNCallKit
  41. {
  42. CXCallController *_callController;
  43. CXProvider *_provider;
  44. }
  45. RCT_EXTERN void RCTRegisterModule(Class);
  46. + (void)load {
  47. // Make the react-native module RNCallKit available (to JS) only if CallKit
  48. // is available on the executing operating sytem. For example, CallKit is
  49. // not available on iOS 9.
  50. if ([CXCallController class]) {
  51. RCTRegisterModule(self);
  52. }
  53. }
  54. + (NSString *)moduleName {
  55. return @"RNCallKit";
  56. }
  57. - (NSArray<NSString *> *)supportedEvents {
  58. return @[
  59. RNCallKitPerformAnswerCallAction,
  60. RNCallKitPerformEndCallAction,
  61. RNCallKitPerformSetMutedCallAction,
  62. RNCallKitProviderDidReset
  63. ];
  64. }
  65. // Display the incoming call to the user
  66. RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)callUUID
  67. handle:(NSString *)handle
  68. hasVideo:(BOOL)hasVideo
  69. resolve:(RCTPromiseResolveBlock)resolve
  70. reject:(RCTPromiseRejectBlock)reject) {
  71. #ifdef DEBUG
  72. NSLog(@"[RNCallKit][displayIncomingCall] callUUID = %@", callUUID);
  73. #endif
  74. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  75. CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
  76. callUpdate.remoteHandle
  77. = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
  78. callUpdate.supportsDTMF = NO;
  79. callUpdate.supportsHolding = NO;
  80. callUpdate.supportsGrouping = NO;
  81. callUpdate.supportsUngrouping = NO;
  82. callUpdate.hasVideo = hasVideo;
  83. [self.provider reportNewIncomingCallWithUUID:callUUID_
  84. update:callUpdate
  85. completion:^(NSError * _Nullable error) {
  86. if (error) {
  87. reject(nil, @"Error reporting new incoming call", error);
  88. } else {
  89. resolve(nil);
  90. }
  91. }];
  92. }
  93. // End call
  94. RCT_EXPORT_METHOD(endCall:(NSString *)callUUID
  95. resolve:(RCTPromiseResolveBlock)resolve
  96. reject:(RCTPromiseRejectBlock)reject) {
  97. #ifdef DEBUG
  98. NSLog(@"[RNCallKit][endCall] callUUID = %@", callUUID);
  99. #endif
  100. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  101. CXEndCallAction *action
  102. = [[CXEndCallAction alloc] initWithCallUUID:callUUID_];
  103. [self requestTransaction:[[CXTransaction alloc] initWithAction:action]
  104. resolve:resolve
  105. reject:reject];
  106. }
  107. // Mute / unmute (audio)
  108. RCT_EXPORT_METHOD(setMuted:(NSString *)callUUID
  109. muted:(BOOL)muted
  110. resolve:(RCTPromiseResolveBlock)resolve
  111. reject:(RCTPromiseRejectBlock)reject) {
  112. #ifdef DEBUG
  113. NSLog(@"[RNCallKit][setMuted] callUUID = %@", callUUID);
  114. #endif
  115. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  116. CXSetMutedCallAction *action
  117. = [[CXSetMutedCallAction alloc] initWithCallUUID:callUUID_ muted:muted];
  118. [self requestTransaction:[[CXTransaction alloc] initWithAction:action]
  119. resolve:resolve
  120. reject:reject];
  121. }
  122. RCT_EXPORT_METHOD(setProviderConfiguration:(NSDictionary *)dictionary) {
  123. #ifdef DEBUG
  124. NSLog(
  125. @"[RNCallKit][setProviderConfiguration:] dictionary = %@",
  126. dictionary);
  127. #endif
  128. CXProviderConfiguration *configuration
  129. = [self providerConfigurationFromDictionary:dictionary];
  130. if (_provider) {
  131. _provider.configuration = configuration;
  132. } else {
  133. _provider = [[CXProvider alloc] initWithConfiguration:configuration];
  134. [_provider setDelegate:self queue:nil];
  135. }
  136. }
  137. // Start outgoing call
  138. RCT_EXPORT_METHOD(startCall:(NSString *)callUUID
  139. handle:(NSString *)handle
  140. video:(BOOL)video
  141. resolve:(RCTPromiseResolveBlock)resolve
  142. reject:(RCTPromiseRejectBlock)reject) {
  143. #ifdef DEBUG
  144. NSLog(@"[RNCallKit][startCall] callUUID = %@", callUUID);
  145. #endif
  146. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  147. CXHandle *handle_
  148. = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
  149. CXStartCallAction *action
  150. = [[CXStartCallAction alloc] initWithCallUUID:callUUID_
  151. handle:handle_];
  152. action.video = video;
  153. CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
  154. [self requestTransaction:transaction resolve:resolve reject:reject];
  155. }
  156. // Indicate call failed
  157. RCT_EXPORT_METHOD(reportCallFailed:(NSString *)callUUID
  158. resolve:(RCTPromiseResolveBlock)resolve
  159. reject:(RCTPromiseRejectBlock)reject) {
  160. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  161. [self.provider reportCallWithUUID:callUUID_
  162. endedAtDate:[NSDate date]
  163. reason:CXCallEndedReasonFailed];
  164. resolve(nil);
  165. }
  166. // Indicate outgoing call connected.
  167. RCT_EXPORT_METHOD(reportConnectedOutgoingCall:(NSString *)callUUID
  168. resolve:(RCTPromiseResolveBlock)resolve
  169. reject:(RCTPromiseRejectBlock)reject) {
  170. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  171. [self.provider reportOutgoingCallWithUUID:callUUID_
  172. connectedAtDate:[NSDate date]];
  173. resolve(nil);
  174. }
  175. // Update call in case we have a display name or video capability changes.
  176. RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
  177. options:(NSDictionary *)options
  178. resolve:(RCTPromiseResolveBlock)resolve
  179. reject:(RCTPromiseRejectBlock)reject) {
  180. #ifdef DEBUG
  181. NSLog(
  182. @"[RNCallKit][updateCall] callUUID = %@ options = %@",
  183. callUUID,
  184. options);
  185. #endif
  186. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  187. CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
  188. if (options[@"displayName"]) {
  189. callUpdate.localizedCallerName = options[@"displayName"];
  190. }
  191. if (options[@"hasVideo"]) {
  192. callUpdate.hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
  193. }
  194. [self.provider reportCallWithUUID:callUUID_ updated:callUpdate];
  195. resolve(nil);
  196. }
  197. #pragma mark - Helper methods
  198. - (CXCallController *)callController {
  199. if (!_callController) {
  200. _callController = [[CXCallController alloc] init];
  201. }
  202. return _callController;
  203. }
  204. - (CXProvider *)provider {
  205. if (!_provider) {
  206. [self setProviderConfiguration:nil];
  207. }
  208. return _provider;
  209. }
  210. - (CXProviderConfiguration *)providerConfigurationFromDictionary:(NSDictionary* )dictionary {
  211. #ifdef DEBUG
  212. NSLog(@"[RNCallKit][providerConfigurationFromDictionary:]");
  213. #endif
  214. if (!dictionary) {
  215. dictionary = @{};
  216. }
  217. // localizedName
  218. NSString *localizedName = dictionary[@"localizedName"];
  219. if (!localizedName) {
  220. localizedName
  221. = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"];
  222. }
  223. CXProviderConfiguration *providerConfiguration
  224. = [[CXProviderConfiguration alloc] initWithLocalizedName:localizedName];
  225. // iconTemplateImageData
  226. NSString *iconTemplateImageName = dictionary[@"iconTemplateImageName"];
  227. if (iconTemplateImageName) {
  228. UIImage *iconTemplateImage
  229. = [UIImage imageNamed:iconTemplateImageName
  230. inBundle:[NSBundle bundleForClass:self.class]
  231. compatibleWithTraitCollection:nil];
  232. if (iconTemplateImage) {
  233. providerConfiguration.iconTemplateImageData
  234. = UIImagePNGRepresentation(iconTemplateImage);
  235. }
  236. }
  237. providerConfiguration.maximumCallGroups = 1;
  238. providerConfiguration.maximumCallsPerCallGroup = 1;
  239. providerConfiguration.ringtoneSound = dictionary[@"ringtoneSound"];
  240. providerConfiguration.supportedHandleTypes
  241. = [NSSet setWithObjects:@(CXHandleTypeGeneric), nil];
  242. providerConfiguration.supportsVideo = YES;
  243. return providerConfiguration;
  244. }
  245. - (void)requestTransaction:(CXTransaction *)transaction
  246. resolve:(RCTPromiseResolveBlock)resolve
  247. reject:(RCTPromiseRejectBlock)reject {
  248. #ifdef DEBUG
  249. NSLog(@"[RNCallKit][requestTransaction] transaction = %@", transaction);
  250. #endif
  251. [self.callController requestTransaction:transaction
  252. completion:^(NSError * _Nullable error) {
  253. if (error) {
  254. NSLog(
  255. @"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)",
  256. transaction.actions,
  257. error);
  258. reject(nil, @"Error processing CallKit transaction", error);
  259. } else {
  260. resolve(nil);
  261. }
  262. }];
  263. }
  264. #pragma mark - CXProviderDelegate
  265. // Called when the provider has been reset. We should terminate all calls.
  266. - (void)providerDidReset:(CXProvider *)provider {
  267. #ifdef DEBUG
  268. NSLog(@"[RNCallKit][CXProviderDelegate][providerDidReset:]");
  269. #endif
  270. [self sendEventWithName:RNCallKitProviderDidReset body:nil];
  271. }
  272. // Answering incoming call
  273. - (void) provider:(CXProvider *)provider
  274. performAnswerCallAction:(CXAnswerCallAction *)action {
  275. #ifdef DEBUG
  276. NSLog(@"[RNCallKit][CXProviderDelegate][provider:performAnswerCallAction:]");
  277. #endif
  278. [self sendEventWithName:RNCallKitPerformAnswerCallAction
  279. body:@{ @"callUUID": action.callUUID.UUIDString }];
  280. [action fulfill];
  281. }
  282. // Call ended, user request
  283. - (void) provider:(CXProvider *)provider
  284. performEndCallAction:(CXEndCallAction *)action {
  285. #ifdef DEBUG
  286. NSLog(@"[RNCallKit][CXProviderDelegate][provider:performEndCallAction:]");
  287. #endif
  288. [self sendEventWithName:RNCallKitPerformEndCallAction
  289. body:@{ @"callUUID": action.callUUID.UUIDString }];
  290. [action fulfill];
  291. }
  292. // Handle audio mute from CallKit view
  293. - (void) provider:(CXProvider *)provider
  294. performSetMutedCallAction:(CXSetMutedCallAction *)action {
  295. #ifdef DEBUG
  296. NSLog(@"[RNCallKit][CXProviderDelegate][provider:performSetMutedCallAction:]");
  297. #endif
  298. [self sendEventWithName:RNCallKitPerformSetMutedCallAction
  299. body:@{
  300. @"callUUID": action.callUUID.UUIDString,
  301. @"muted": @(action.muted)
  302. }];
  303. [action fulfill];
  304. }
  305. // Starting outgoing call
  306. - (void) provider:(CXProvider *)provider
  307. performStartCallAction:(CXStartCallAction *)action {
  308. #ifdef DEBUG
  309. NSLog(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction:]");
  310. #endif
  311. [action fulfill];
  312. // Update call info.
  313. NSUUID *callUUID = action.callUUID;
  314. CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
  315. callUpdate.remoteHandle = action.handle;
  316. callUpdate.supportsDTMF = NO;
  317. callUpdate.supportsHolding = NO;
  318. callUpdate.supportsGrouping = NO;
  319. callUpdate.supportsUngrouping = NO;
  320. callUpdate.hasVideo = action.isVideo;
  321. [provider reportCallWithUUID:callUUID updated:callUpdate];
  322. // Notify the system about the outgoing call.
  323. [provider reportOutgoingCallWithUUID:callUUID
  324. startedConnectingAtDate:[NSDate date]];
  325. }
  326. // The following just help with debugging:
  327. #ifdef DEBUG
  328. - (void) provider:(CXProvider *)provider
  329. didActivateAudioSession:(AVAudioSession *)audioSession {
  330. NSLog(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession:]");
  331. }
  332. - (void) provider:(CXProvider *)provider
  333. didDeactivateAudioSession:(AVAudioSession *)audioSession {
  334. NSLog(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession:]");
  335. }
  336. - (void) provider:(CXProvider *)provider
  337. timedOutPerformingAction:(CXAction *)action {
  338. NSLog(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction:]");
  339. }
  340. #endif
  341. @end