diff --git a/README.md b/README.md index 35ed1d30..c7f8482c 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ RNCallKeep.setup(options).then(accepted => {}); - `ringtoneSound`: string (optional) If provided, it will be played when incoming calls received; the system will use the default ringtone if this is not provided - `includesCallsInRecents`: boolean (optional) - If provided, calls will be shown in the recent calls when true and not when false (ios 11 and above) + If provided, calls will be shown in the recent calls when true and not when false (ios 11 and above) (Default: true) - `maximumCallGroups`: string (optional) If provided, the maximum number of call groups supported by this application (Default: 3) - `maximumCallsPerCallGroup`: string (optional) @@ -187,13 +187,33 @@ RNCallKeep.isCallActive(uuid); - `uuid`: string - The `uuid` used for `startCall` or `displayIncomingCall` + +### getCalls + +_This feature is available only on IOS._ + +Returns a Promise. The result will be an array with all current calls and their states. + +```js +RNCallKeep.getCalls(); + +response: +[{ + callUUID: "E26B14F7-2CDF-48D0-9925-532199AE7C48" + hasConnected: true + hasEnded: false + onHold: false + outgoing: false +}] +``` + ### displayIncomingCall Display system UI for incoming calls -````js +```js RNCallKeep.displayIncomingCall(uuid, handle, localizedCallerName); -```` +``` - `uuid`: string - An `uuid` that should be stored and re-used for `stopCall`. @@ -217,7 +237,6 @@ RNCallKeep.displayIncomingCall(uuid, handle, localizedCallerName); - `android`: object (currently no-op) ### answerIncomingCall -_This feature is available only on Android._ Use this to tell the sdk a user answered a call from the app UI. @@ -798,7 +817,7 @@ In some case your application can be unreachable : - when the user kill the application - when it's in background since a long time (eg: after ~5mn the os will kill all connections). -To be able to wake up your application to display the incoming call, you can use [https://github.com/ianlin/react-native-voip-push-notification](react-native-voip-push-notification) on iOS or BackgroundMessaging from [react-native-firebase](https://rnfirebase.io/messaging/usage#receiving-messages)-(Optional)(Android-only)-Listen-for-FCM-messages-in-the-background). +To be able to wake up your application to display the incoming call, you can use [https://github.com/react-native-webrtc/react-native-voip-push-notification](react-native-voip-push-notification) on iOS or BackgroundMessaging from [react-native-firebase](https://rnfirebase.io/messaging/usage#receiving-messages)-(Optional)(Android-only)-Listen-for-FCM-messages-in-the-background). You have to send a push to your application, like with Firebase for Android and with a library supporting PushKit pushes for iOS. diff --git a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java index aa83b532..10dadb4b 100644 --- a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java +++ b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java @@ -33,7 +33,7 @@ import android.os.Handler; import android.speech.tts.Voice; import androidx.annotation.Nullable; -import android.support.v4.app.NotificationCompat; +import androidx.core.app.NotificationCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.telecom.CallAudioState; import android.telecom.Connection; diff --git a/example/yarn.lock b/example/yarn.lock index 368032db..0c5b1c74 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -2565,9 +2565,9 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== inline-style-prefixer@^5.0.3: version "5.1.0" diff --git a/index.d.ts b/index.d.ts index 3c6cd6a4..b0e6006d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -23,6 +23,7 @@ declare module 'react-native-callkeep' { maximumCallGroups?: string, maximumCallsPerCallGroup?: string, ringtoneSound?: string, + includesCallsInRecents?: boolean }, android: { alertTitle: string, @@ -40,12 +41,23 @@ declare module 'react-native-callkeep' { export type DidDisplayIncomingCallPayload = string | undefined; export type DidPerformSetMutedCallActionPayload = boolean; + export const CONSTANTS: { + END_CALL_REASONS: { + FAILED: 1, + REMOTE_ENDED: 2, + UNANSWERED: 3, + ANSWERED_ELSEWHERE: 4, + DECLINED_ELSEWHERE: 5 | 2, + MISSED: 2 | 6 + } + }; + export default class RNCallKeep { static addEventListener(type: Events, handler: (args: any) => void): void static removeEventListener(type: Events): void - static setup(options: IOptions): Promise + static setup(options: IOptions): Promise static hasDefaultPhoneAccount(): boolean @@ -103,8 +115,13 @@ declare module 'react-native-callkeep' { static setReachable(): void + /** + * @description isCallActive method is available only on iOS. + */ static isCallActive(uuid: string): Promise + static getCalls(): Promise + /** * @description supportConnectionService method is available only on Android. */ diff --git a/index.js b/index.js index 6986d51f..49874d40 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ import { NativeModules, Platform, Alert } from 'react-native'; -import { listeners, emit } from './actions' +import { listeners, emit } from './actions'; const RNCallKeepModule = NativeModules.RNCallKeep; const isIOS = Platform.OS === 'ios'; @@ -13,13 +13,13 @@ const CONSTANTS = { UNANSWERED: 3, ANSWERED_ELSEWHERE: 4, DECLINED_ELSEWHERE: isIOS ? 5 : 2, // make declined elsewhere link to "Remote ended" on android because that's kinda true - MISSED: isIOS ? 2 : 6 } + MISSED: isIOS ? 2 : 6, + }, }; export { CONSTANTS }; class RNCallKeep { - constructor() { this._callkeepEventHandlers = new Map(); } @@ -55,7 +55,6 @@ class RNCallKeep { RNCallKeepModule.registerPhoneAccount(); }; - registerAndroidEvents = () => { if (isIOS) { return; @@ -71,7 +70,14 @@ class RNCallKeep { return; }; - displayIncomingCall = (uuid, handle, localizedCallerName = '', handleType = 'number', hasVideo = false, options = null) => { + displayIncomingCall = ( + uuid, + handle, + localizedCallerName = '', + handleType = 'number', + hasVideo = false, + options = null + ) => { if (!isIOS) { RNCallKeepModule.displayIncomingCall(uuid, handle, localizedCallerName); return; @@ -83,16 +89,24 @@ class RNCallKeep { let supportsGrouping = !!(options?.ios?.supportsGrouping ?? true); let supportsUngrouping = !!(options?.ios?.supportsUngrouping ?? true); - RNCallKeepModule.displayIncomingCall(uuid, handle, handleType, hasVideo, localizedCallerName, supportsHolding, supportsDTMF, supportsGrouping, supportsUngrouping); + RNCallKeepModule.displayIncomingCall( + uuid, + handle, + handleType, + hasVideo, + localizedCallerName, + supportsHolding, + supportsDTMF, + supportsGrouping, + supportsUngrouping + ); }; answerIncomingCall = (uuid) => { - if (!isIOS) { - RNCallKeepModule.answerIncomingCall(uuid); - } + RNCallKeepModule.answerIncomingCall(uuid); }; - startCall = (uuid, handle, contactIdentifier, handleType = 'number', hasVideo = false ) => { + startCall = (uuid, handle, contactIdentifier, handleType = 'number', hasVideo = false) => { if (!isIOS) { RNCallKeepModule.startCall(uuid, handle, contactIdentifier); return; @@ -107,7 +121,7 @@ class RNCallKeep { } return RNCallKeepModule.checkPhoneAccountEnabled(); - } + }; isConnectionServiceAvailable = async () => { if (isIOS) { @@ -115,7 +129,7 @@ class RNCallKeep { } return RNCallKeepModule.isConnectionServiceAvailable(); - } + }; reportConnectingOutgoingCallWithUUID = (uuid) => { //only available on iOS @@ -145,7 +159,13 @@ class RNCallKeep { } }; - isCallActive = async(uuid) => await RNCallKeepModule.isCallActive(uuid); + isCallActive = async (uuid) => await RNCallKeepModule.isCallActive(uuid); + + getCalls = () => { + if (isIOS) { + return RNCallKeepModule.getCalls(); + } + }; endCall = (uuid) => RNCallKeepModule.endCall(uuid); @@ -153,11 +173,9 @@ class RNCallKeep { supportConnectionService = () => supportConnectionService; - hasPhoneAccount = async () => - isIOS ? true : await RNCallKeepModule.hasPhoneAccount(); + hasPhoneAccount = async () => (isIOS ? true : await RNCallKeepModule.hasPhoneAccount()); - hasOutgoingCall = async () => - isIOS ? null : await RNCallKeepModule.hasOutgoingCall(); + hasOutgoingCall = async () => (isIOS ? null : await RNCallKeepModule.hasOutgoingCall()); setMutedCall = (uuid, shouldMute) => { RNCallKeepModule.setMutedCall(uuid, shouldMute); @@ -174,14 +192,10 @@ class RNCallKeep { toggleAudioRouteSpeaker = (uuid, useSpeaker) => isIOS ? null : RNCallKeepModule.toggleAudioRouteSpeaker(uuid, useSpeaker); checkIfBusy = () => - isIOS - ? RNCallKeepModule.checkIfBusy() - : Promise.reject('RNCallKeep.checkIfBusy was called from unsupported OS'); + isIOS ? RNCallKeepModule.checkIfBusy() : Promise.reject('RNCallKeep.checkIfBusy was called from unsupported OS'); checkSpeaker = () => - isIOS - ? RNCallKeepModule.checkSpeaker() - : Promise.reject('RNCallKeep.checkSpeaker was called from unsupported OS'); + isIOS ? RNCallKeepModule.checkSpeaker() : Promise.reject('RNCallKeep.checkSpeaker was called from unsupported OS'); setAvailable = (state) => { if (isIOS) { @@ -228,7 +242,7 @@ class RNCallKeep { if (options && options.ios) { iosOptions = { ...options.ios, - } + }; } RNCallKeepModule.updateDisplay(uuid, displayName, handle, iosOptions); }; @@ -246,16 +260,17 @@ class RNCallKeep { : Promise.reject('RNCallKeep.reportUpdatedCall was called from unsupported OS'); }; - _setupIOS = async (options) => new Promise((resolve, reject) => { - if (!options.appName) { - reject('RNCallKeep.setup: option "appName" is required'); - } - if (typeof options.appName !== 'string') { - reject('RNCallKeep.setup: option "appName" should be of type "string"'); - } + _setupIOS = async (options) => + new Promise((resolve, reject) => { + if (!options.appName) { + reject('RNCallKeep.setup: option "appName" is required'); + } + if (typeof options.appName !== 'string') { + reject('RNCallKeep.setup: option "appName" should be of type "string"'); + } - resolve(RNCallKeepModule.setup(options)); - }); + resolve(RNCallKeepModule.setup(options)); + }); _setupAndroid = async (options) => { RNCallKeepModule.setup(options); @@ -280,27 +295,26 @@ class RNCallKeep { } }; - _alert = async (options, condition) => new Promise((resolve, reject) => { - if (!condition) { - return resolve(false); - } + _alert = async (options, condition) => + new Promise((resolve, reject) => { + if (!condition) { + return resolve(false); + } - Alert.alert( - options.alertTitle, - options.alertDescription, - [ - { - text: options.cancelButton, - onPress: reject, - style: 'cancel', - }, - { text: options.okButton, - onPress: () => resolve(true) - }, - ], - { cancelable: true }, - ); - }); + Alert.alert( + options.alertTitle, + options.alertDescription, + [ + { + text: options.cancelButton, + onPress: reject, + style: 'cancel', + }, + { text: options.okButton, onPress: () => resolve(true) }, + ], + { cancelable: true } + ); + }); backToForeground() { if (isIOS) { @@ -309,7 +323,6 @@ class RNCallKeep { NativeModules.RNCallKeep.backToForeground(); } - } export default new RNCallKeep(); diff --git a/ios/RNCallKeep/RNCallKeep.m b/ios/RNCallKeep/RNCallKeep.m index 04af4039..86b64882 100644 --- a/ios/RNCallKeep/RNCallKeep.m +++ b/ios/RNCallKeep/RNCallKeep.m @@ -220,6 +220,19 @@ + (void)initCallKitProvider { [self requestTransaction:transaction]; } +RCT_EXPORT_METHOD(answerIncomingCall:(NSString *)uuidString) +{ +#ifdef DEBUG + NSLog(@"[RNCallKeep][answerIncomingCall] uuidString = %@", uuidString); +#endif + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXAnswerCallAction *answerCallAction = [[CXAnswerCallAction alloc] initWithCallUUID:uuid]; + CXTransaction *transaction = [[CXTransaction alloc] init]; + [transaction addAction:answerCallAction]; + + [self requestTransaction:transaction]; +} + RCT_EXPORT_METHOD(endCall:(NSString *)uuidString) { #ifdef DEBUG @@ -335,12 +348,28 @@ + (void)initCallKitProvider { [self requestTransaction:transaction]; } -RCT_EXPORT_METHOD(isCallActive:(NSString *)uuidString) +RCT_EXPORT_METHOD(isCallActive:(NSString *)uuidString + isCallActiveResolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { #ifdef DEBUG NSLog(@"[RNCallKeep][isCallActive] uuid = %@", uuidString); #endif - [RNCallKeep isCallActive: uuidString]; + BOOL isActive = [RNCallKeep isCallActive: uuidString]; + if (isActive) { + resolve(@YES); + } else { + resolve(@NO); + } +} + +RCT_EXPORT_METHOD(getCalls:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ +#ifdef DEBUG + NSLog(@"[RNCallKeep][getCalls]"); +#endif + resolve([RNCallKeep getCalls]); } - (void)requestTransaction:(CXTransaction *)transaction @@ -381,13 +410,34 @@ + (BOOL)isCallActive:(NSString *)uuidString for(CXCall *call in callObserver.calls){ NSLog(@"[RNCallKeep] isCallActive %@ %d ?", call.UUID, [call.UUID isEqual:uuid]); - if([call.UUID isEqual:[[NSUUID alloc] initWithUUIDString:uuidString]] && !call.hasConnected){ - return true; + if([call.UUID isEqual:[[NSUUID alloc] initWithUUIDString:uuidString]]){ + return call.hasConnected; } } return false; } ++ (NSMutableArray *) getCalls +{ +#ifdef DEBUG + NSLog(@"[RNCallKeep][getCalls]"); +#endif + CXCallObserver *callObserver = [[CXCallObserver alloc] init]; + NSMutableArray *currentCalls = [NSMutableArray array]; + for(CXCall *call in callObserver.calls){ + NSString *uuidString = [call.UUID UUIDString]; + NSDictionary *requestedCall= @{ + @"callUUID": uuidString, + @"outgoing": call.outgoing? @YES : @NO, + @"onHold": call.onHold? @YES : @NO, + @"hasConnected": call.hasConnected ? @YES : @NO, + @"hasEnded": call.hasEnded ? @YES : @NO + }; + [currentCalls addObject:requestedCall]; + } + return currentCalls; +} + + (void)endCallWithUUID:(NSString *)uuidString reason:(int)reason {