diff --git a/README.md b/README.md index f6520a56..be61547f 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,11 @@ For more information about **ConnectionService** on Android, please see [Android - [Demo](#Demo) - [Installation](#Installation) - [Usage](#Usage) + - [Expo](#Usage-with-Expo) - [Constants](#Constants) - [Android Self Managed](#Android-Self-Managed-Mode) - [API](#Api) - - [Example](##Example) + - [Example](#Example) - [PushKit](#PushKit) - [Android 11](#Android-11) - [Debug](#Debug) @@ -129,6 +130,11 @@ Alternative on iOS you can perform setup in `AppDelegate.m`. Doing this allows c - `displayCallReachabilityTimeout`: number in ms (optional) If provided, starts a timeout that checks if the application is reachable and ends the call if not (Default: null) You'll have to call `setReachable()` as soon as your Javascript application is started. + - `audioSession`: object + - `categoryOptions`: AudioSessionCategoryOption|number (optional) + If provided, it will override the default AVAudioSession setCategory options. + - `mode`: AudioSessionMode|string (optional) + If provided, it will override the default AVAudioSession setMode mode. - `android`: object - `alertTitle`: string (required) When asking for _phone account_ permission, we need to provider a title for the `Alert` to ask the user for it @@ -155,6 +161,10 @@ Alternative on iOS you can perform setup in `AppDelegate.m`. Doing this allows c You can alternatively just call `setSettings()` with the same option as `setup()` to define only your settings. +# Usage with Expo + +To use this library with Expo, you will need to create a development build. Expo Go does not support custom native modules. For information on how to create and run a development build, visit: [Create a development build - Expo Documentation](https://docs.expo.dev/develop/development-builds/create-a-build/). You can use and test this library with a development build installed on your physical device (iOS and Android). + # Constants To make passing the right integer into methods easier, there are constants that are exported from the module. @@ -209,6 +219,7 @@ Self Managed calling apps are an advanced topic, and there are many steps involv | [setForegroundServiceSettings()](#setForegroundServiceSettings) | `Promise` | ❌ | ✅ | | [canMakeMultipleCalls()](#canMakeMultipleCalls) | `Promise` | ❌ | ✅ | | [setCurrentCallActive()](#setCurrentCallActive) | `Promise` | ❌ | ✅ | +| [checkIsInManagedCall()](#setAvailable) | `Promise` | ❌ | ✅ | | [isCallActive()](#isCallActive) | `Promise` | ✅ | ❌ | | [getCalls()](#getCalls) | `Promise` | ✅ | ❌ | | [displayIncomingCall()](#displayIncomingCall) | `Promise` | ✅ | ✅ | @@ -306,6 +317,16 @@ RNCallKeep.setCurrentCallActive(uuid); - `uuid`: string - The `uuid` used for `startCall` or `displayIncomingCall` +### checkIsInManagedCall +_This feature is available only on Android._ + +Returns true if there is an active native call + +```js +RNCallKeep.checkIsInManagedCall(); +``` + + ### isCallActive _This feature is available only on IOS._ @@ -726,11 +747,12 @@ RNCallKeep.registerAndroidEvents(); | [didPerformSetMutedCallAction](#didPerformSetMutedCallAction) | ✅ | ✅ | | [didToggleHoldCallAction](#didToggleHoldCallAction) | ✅ | ✅ | | [didPerformDTMFAction](#didPerformDTMFAction) | ✅ | ✅ | -| [didLoadWithEvents](#didLoadWithEvents) | ✅ | ✅ | +| [didLoadWithEvents](#didLoadWithEvents) | ✅ | ❌ | | [showIncomingCallUi](#showIncomingCallUi) | ❌ | ✅ | | [silenceIncomingCall](#silenceIncomingCall) | ❌ | ✅ | | [checkReachability](#checkReachability) | ❌ | ✅ | | [didChangeAudioRoute](#didChangeAudioRoute) | ✅ | ✅ | +| [onHasActiveCall](#onHasActiveCall) | ❌ | ✅ | ### didReceiveStartCallAction @@ -754,7 +776,7 @@ RNCallKeep.addEventListener('didReceiveStartCallAction', ({ handle, callUUID, na - `name` (string) - Name of the callee -### - answerCall +### answerCall User answer the incoming call @@ -767,7 +789,7 @@ RNCallKeep.addEventListener('answerCall', ({ callUUID }) => { - `callUUID` (string) - The UUID of the call that is to be answered. -### - endCall +### endCall User finish the call. @@ -780,7 +802,7 @@ RNCallKeep.addEventListener('endCall', ({ callUUID }) => { - `callUUID` (string) - The UUID of the call that is to be ended. -### - didActivateAudioSession +### didActivateAudioSession The `AudioSession` has been activated by **RNCallKeep**. @@ -791,7 +813,7 @@ RNCallKeep.addEventListener('didActivateAudioSession', () => { }); ``` -### - didDisplayIncomingCall +### didDisplayIncomingCall Callback for `RNCallKeep.displayIncomingCall` @@ -821,7 +843,7 @@ RNCallKeep.addEventListener('didDisplayIncomingCall', ({ error, callUUID, handle - `payload` (object) - VOIP push payload. -### - didPerformSetMutedCallAction +### didPerformSetMutedCallAction A call was muted by the system or the user: @@ -835,7 +857,7 @@ RNCallKeep.addEventListener('didPerformSetMutedCallAction', ({ muted, callUUID } - `callUUID` (string) - The UUID of the call. -### - didToggleHoldCallAction +### didToggleHoldCallAction A call was held or unheld by the current user @@ -845,10 +867,13 @@ RNCallKeep.addEventListener('didToggleHoldCallAction', ({ hold, callUUID }) => { }); ``` -### - didChangeAudioRoute +- `hold` (boolean) +- `callUUID` (string) + - The UUID of the call. + +### didChangeAudioRoute Triggered when the audio route has been changed. -⚠️ Will send `Speaker` on iOS but `SPEAKER` on Android. ```js RNCallKeep.addEventListener('didChangeAudioRoute', ({ output }) => { @@ -856,11 +881,12 @@ RNCallKeep.addEventListener('didChangeAudioRoute', ({ output }) => { }); ``` -- `hold` (boolean) -- `callUUID` (string) - - The UUID of the call. +- `output` (string) ⚠️ Will send `Speaker` on iOS but `SPEAKER` on Android. +- `reason` (number, iOS only) See case's in https://developer.apple.com/documentation/avfaudio/avaudiosession/routechangereason +- `handle` (string, Android only) Phone number of the incoming caller +- `callUUID` (string, Android only) The UUID of the call -### - didPerformDTMFAction +### didPerformDTMFAction Used type a number on his dialer @@ -875,7 +901,7 @@ RNCallKeep.addEventListener('didPerformDTMFAction', ({ digits, callUUID }) => { - `callUUID` (string) - The UUID of the call. -### - didLoadWithEvents +### didLoadWithEvents iOS only. @@ -902,7 +928,7 @@ RNCallKeep.addEventListener('didLoadWithEvents', (events) => { - `data`: object Object with data passed together with specific event so it can be handled in the same way like original event, for example `({ callUUID })` for `answerCall` event if `name` is `RNCallKeepPerformAnswerCallAction` -### - showIncomingCallUi +### showIncomingCallUi _Android only. Self Managed only._ @@ -923,7 +949,7 @@ The following values will match those initially passed to `displayIncomingCall` - `name` (string) - Caller Name. -### - silenceIncomingCall +### silenceIncomingCall _Android only. Self Managed only._ @@ -944,7 +970,7 @@ The following values will match those initially passed to `silenceIncomingCall` - `name` (string) - Caller Name. -### - createIncomingConnectionFailed +### createIncomingConnectionFailed _Android only. Self Managed only._ @@ -965,7 +991,7 @@ The following values will match those initially passed to `silenceIncomingCall` - `name` (string) - Caller Name. -### - checkReachability +### checkReachability _Android only._ @@ -979,6 +1005,19 @@ RNCallKeep.addEventListener('checkReachability', () => { ``` +### onHasActiveCall + +_Android only._ + +A listener that tells the JS side if a native call has been answered while there was an active self-managed call + +```js +RNCallKeep.addEventListener('onHasActiveCall', () => { + // eg: End active app call if native call is answered +}); + +``` + ## Example A full example is available in the [example](https://github.com/react-native-webrtc/react-native-callkeep/tree/master/example) folder. @@ -1104,6 +1143,7 @@ class RNCallKeepExample extends React.Component { } ``` + ## Receiving a call when the application is not reachable. In some case your application can be unreachable : diff --git a/actions.js b/actions.js index 4e936360..fbbe4ca2 100644 --- a/actions.js +++ b/actions.js @@ -19,6 +19,7 @@ const RNCallKeepShowIncomingCallUi = 'RNCallKeepShowIncomingCallUi'; const RNCallKeepOnSilenceIncomingCall = 'RNCallKeepOnSilenceIncomingCall'; const RNCallKeepOnIncomingConnectionFailed = 'RNCallKeepOnIncomingConnectionFailed'; const RNCallKeepDidChangeAudioRoute = 'RNCallKeepDidChangeAudioRoute'; +const RNCallKeepHasActiveCall = 'RNCallKeepHasActiveCall'; const isIOS = Platform.OS === 'ios'; const didReceiveStartCallAction = handler => { @@ -60,6 +61,9 @@ const didDisplayIncomingCall = handler => eventEmitter.addListener(RNCallKeepDid const didPerformSetMutedCallAction = handler => eventEmitter.addListener(RNCallKeepDidPerformSetMutedCallAction, (data) => handler(data)); +const onHasActiveCall = handler => + eventEmitter.addListener(RNCallKeepHasActiveCall, handler); + const didToggleHoldCallAction = handler => eventEmitter.addListener(RNCallKeepDidToggleHoldAction, handler); @@ -103,4 +107,5 @@ export const listeners = { silenceIncomingCall, createIncomingConnectionFailed, didChangeAudioRoute, + onHasActiveCall }; diff --git a/android/src/main/java/io/wazo/callkeep/Constants.java b/android/src/main/java/io/wazo/callkeep/Constants.java index 923d2ec9..5a8f7c6e 100644 --- a/android/src/main/java/io/wazo/callkeep/Constants.java +++ b/android/src/main/java/io/wazo/callkeep/Constants.java @@ -23,6 +23,7 @@ public class Constants { public static final String EXTRA_CALL_UUID = "EXTRA_CALL_UUID"; public static final String EXTRA_CALLER_NAME = "EXTRA_CALLER_NAME"; public static final String EXTRA_HAS_VIDEO = "EXTRA_HAS_VIDEO"; + public static final String EXTRA_PAYLOAD = "EXTRA_PAYLOAD"; // Can't use telecom.EXTRA_DISABLE_ADD_CALL ... public static final String EXTRA_DISABLE_ADD_CALL = "android.telecom.extra.DISABLE_ADD_CALL"; diff --git a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java index c92dd984..025480ac 100644 --- a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java +++ b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java @@ -16,7 +16,7 @@ */ package io.wazo.callkeep; - +import com.facebook.react.bridge.LifecycleEventListener; import android.Manifest; import android.app.Activity; import android.content.BroadcastReceiver; @@ -35,6 +35,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Looper; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -48,6 +49,8 @@ import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.TelephonyManager; +import android.telephony.TelephonyCallback; +import android.telephony.PhoneStateListener; import android.util.Log; import com.facebook.react.bridge.Arguments; @@ -83,6 +86,7 @@ import static io.wazo.callkeep.Constants.EXTRA_CALL_UUID; import static io.wazo.callkeep.Constants.EXTRA_CALL_NUMBER; import static io.wazo.callkeep.Constants.EXTRA_HAS_VIDEO; +import static io.wazo.callkeep.Constants.EXTRA_PAYLOAD; import static io.wazo.callkeep.Constants.ACTION_END_CALL; import static io.wazo.callkeep.Constants.ACTION_ANSWER_CALL; import static io.wazo.callkeep.Constants.ACTION_MUTE_CALL; @@ -100,7 +104,7 @@ import static io.wazo.callkeep.Constants.ACTION_DID_CHANGE_AUDIO_ROUTE; // @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionServiceActivity.java -public class RNCallKeepModule extends ReactContextBaseJavaModule { +public class RNCallKeepModule extends ReactContextBaseJavaModule implements LifecycleEventListener { public static final int REQUEST_READ_PHONE_STATE = 1337; public static final int REQUEST_REGISTER_CALL_PROVIDER = 394859; @@ -116,6 +120,8 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule { private static final String TAG = "RNCallKeep"; private static TelecomManager telecomManager; + private LegacyCallStateListener legacyCallStateListener; + private CallStateListener callStateListener; private static TelephonyManager telephonyManager; private static Promise hasPhoneAccountPromise; private ReactApplicationContext reactContext; @@ -125,11 +131,14 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule { private static WritableMap _settings; private WritableNativeArray delayedEvents; private boolean hasListeners = false; + private boolean hasActiveCall = false; public static RNCallKeepModule getInstance(ReactApplicationContext reactContext, boolean realContext) { if (instance == null) { Log.d(TAG, "[RNCallKeepModule] getInstance : " + (reactContext == null ? "null" : "ok")); instance = new RNCallKeepModule(reactContext); + instance.registerReceiver(); + instance.fetchStoredSettings(reactContext); } if (realContext) { instance.setContext(reactContext); @@ -147,12 +156,12 @@ public static WritableMap getSettings(@Nullable Context context) { private RNCallKeepModule(ReactApplicationContext reactContext) { super(reactContext); + // This line for listening to the Activity Lifecycle Events so we can end the calls onDestroy + reactContext.addLifecycleEventListener(this); Log.d(TAG, "[RNCallKeepModule] constructor"); this.reactContext = reactContext; delayedEvents = new WritableNativeArray(); - this.registerReceiver(); - this.fetchStoredSettings(reactContext); } private boolean isSelfManaged() { @@ -216,6 +225,125 @@ public void initializeTelecomManager() { telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); } + + + /** + * Monitors and logs phone call activities, and shows the phone state + */ + private class LegacyCallStateListener extends PhoneStateListener { + + @Override + public void onCallStateChanged(int state, String incomingNumber) { + switch (state) { + case TelephonyManager.CALL_STATE_RINGING: + // Incoming call is ringing (not used for outgoing call). + break; + case TelephonyManager.CALL_STATE_OFFHOOK: + // Phone call is active -- off the hook. + // Check if there is active call in native + boolean isInManagedCall = RNCallKeepModule.this.checkIsInManagedCall(); + + // Only let the JS side know if there is active app call & active native call + if(RNCallKeepModule.this.hasActiveCall && isInManagedCall){ + WritableMap args = Arguments.createMap(); + RNCallKeepModule.this.sendEventToJS("RNCallKeepHasActiveCall",args); + }else if(VoiceConnectionService.currentConnections.size() > 0){ + // Will enter here for the first time to mark the app has active call + RNCallKeepModule.this.hasActiveCall = true; + } + break; + case TelephonyManager.CALL_STATE_IDLE: + // Phone is idle before and after phone call. + // If running on version older than 19 (KitKat), + // restart activity when phone call ends. + break; + default: + break; + } + } + } + + private class CallStateListener extends TelephonyCallback implements TelephonyCallback.CallStateListener { + + @Override + public void onCallStateChanged(int state) { + switch (state) { + case TelephonyManager.CALL_STATE_RINGING: + // Incoming call is ringing (not used for outgoing call). + break; + case TelephonyManager.CALL_STATE_OFFHOOK: + // Phone call is active -- off the hook. + + // Check if there is active call in native + boolean isInManagedCall = RNCallKeepModule.this.checkIsInManagedCall(); + + // Only let the JS side know if there is active app call & active native call + if(RNCallKeepModule.this.hasActiveCall && isInManagedCall){ + WritableMap args = Arguments.createMap(); + RNCallKeepModule.this.sendEventToJS("RNCallKeepHasActiveCall",args); + }else if(VoiceConnectionService.currentConnections.size() > 0){ + // Will enter here for the first time to mark the app has active call + RNCallKeepModule.this.hasActiveCall = true; + } + break; + case TelephonyManager.CALL_STATE_IDLE: + // Phone is idle before and after phone call. + // If running on version older than 19 (KitKat), + // restart activity when phone call ends. + break; + default: + break; + } + } + } + + public void stopListenToNativeCallsState() { + Log.d(TAG, "[RNCallKeepModule] stopListenToNativeCallsState"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && callStateListener !=null){ + telephonyManager.unregisterTelephonyCallback(callStateListener); + } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && legacyCallStateListener != null){ + telephonyManager.listen(legacyCallStateListener, PhoneStateListener.LISTEN_NONE); + Looper.myLooper().quit(); + } + } + + public void listenToNativeCallsState() { + Log.d(TAG, "[RNCallKeepModule] listenToNativeCallsState"); + Context context = this.getAppContext(); + int permissionCheck = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE); + + if (permissionCheck == PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + callStateListener = new CallStateListener(); + telephonyManager.registerTelephonyCallback(context.getMainExecutor(),callStateListener); + } else { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + legacyCallStateListener = new LegacyCallStateListener(); + telephonyManager.listen(legacyCallStateListener, PhoneStateListener.LISTEN_CALL_STATE); + Looper.loop(); + } + } + } + + public boolean checkIsInManagedCall() { + Context context = this.getAppContext(); + int permissionCheck = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE); + + if (permissionCheck == PackageManager.PERMISSION_GRANTED) { + return telecomManager.isInManagedCall(); + } + return false; + } + + @ReactMethod + public void checkIsInManagedCall(Promise promise) { + boolean isInManagedCall = this.checkIsInManagedCall(); + promise.resolve(isInManagedCall); + } + @ReactMethod public void setSettings(ReadableMap options) { Log.d(TAG, "[RNCallKeepModule] setSettings : " + options); @@ -308,17 +436,21 @@ public void unregisterEvents() { @ReactMethod public void displayIncomingCall(String uuid, String number, String callerName) { - this.displayIncomingCall(uuid, number, callerName, false); + this.displayIncomingCall(uuid, number, callerName, false, null); } @ReactMethod public void displayIncomingCall(String uuid, String number, String callerName, boolean hasVideo) { + this.displayIncomingCall(uuid, number, callerName, hasVideo, null); + } + + public void displayIncomingCall(String uuid, String number, String callerName, boolean hasVideo, @Nullable Bundle payload) { if (!isConnectionServiceAvailable() || !hasPhoneAccount()) { Log.w(TAG, "[RNCallKeepModule] displayIncomingCall ignored due to no ConnectionService or no phone account"); return; } - Log.d(TAG, "[RNCallKeepModule] displayIncomingCall, uuid: " + uuid + ", number: " + number + ", callerName: " + callerName + ", hasVideo: " + hasVideo); + Log.d(TAG, "[RNCallKeepModule] displayIncomingCall, uuid: " + uuid + ", number: " + number + ", callerName: " + callerName + ", hasVideo: " + hasVideo + ", payload: " + payload); Bundle extras = new Bundle(); Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); @@ -327,7 +459,10 @@ public void displayIncomingCall(String uuid, String number, String callerName, b extras.putString(EXTRA_CALLER_NAME, callerName); extras.putString(EXTRA_CALL_UUID, uuid); extras.putString(EXTRA_HAS_VIDEO, String.valueOf(hasVideo)); - + if (payload != null) { + extras.putBundle(EXTRA_PAYLOAD, payload); + } + this.listenToNativeCallsState(); telecomManager.addNewIncomingCall(handle, extras); } @@ -350,12 +485,16 @@ public void answerIncomingCall(String uuid) { @ReactMethod public void startCall(String uuid, String number, String callerName) { - this.startCall(uuid, number, callerName, false); + this.startCall(uuid, number, callerName, false, null); } @ReactMethod public void startCall(String uuid, String number, String callerName, boolean hasVideo) { - Log.d(TAG, "[RNCallKeepModule] startCall called, uuid: " + uuid + ", number: " + number + ", callerName: " + callerName); + this.startCall(uuid, number, callerName, hasVideo, null); + } + + public void startCall(String uuid, String number, String callerName, boolean hasVideo, @Nullable Bundle payload) { + Log.d(TAG, "[RNCallKeepModule] startCall called, uuid: " + uuid + ", number: " + number + ", callerName: " + callerName + ", payload: " + payload); if (!isConnectionServiceAvailable() || !hasPhoneAccount() || !hasPermissions() || number == null) { Log.w(TAG, "[RNCallKeepModule] startCall ignored: " + isConnectionServiceAvailable() + ", " + hasPhoneAccount() + ", " + hasPermissions() + ", " + number); @@ -370,12 +509,15 @@ public void startCall(String uuid, String number, String callerName, boolean has callExtras.putString(EXTRA_CALL_UUID, uuid); callExtras.putString(EXTRA_CALL_NUMBER, number); callExtras.putString(EXTRA_HAS_VIDEO, String.valueOf(hasVideo)); + if (payload != null) { + callExtras.putBundle(EXTRA_PAYLOAD, payload); + } extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle); extras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras); Log.d(TAG, "[RNCallKeepModule] startCall, uuid: " + uuid); - + this.listenToNativeCallsState(); telecomManager.placeCall(uri, extras); } @@ -392,8 +534,12 @@ public void endCall(String uuid) { Log.w(TAG, "[RNCallKeepModule] endCall ignored because no connection found, uuid: " + uuid); return; } + Context context = this.getAppContext(); + AudioManager audioManager = (AudioManager) context.getSystemService(context.AUDIO_SERVICE); + audioManager.setMode(0); conn.onDisconnect(); - + this.stopListenToNativeCallsState(); + this.hasActiveCall = false; Log.d(TAG, "[RNCallKeepModule] endCall executed, uuid: " + uuid); } @@ -411,7 +557,8 @@ public void endAllCalls() { Connection connectionToEnd = connectionEntry.getValue(); connectionToEnd.onDisconnect(); } - + this.stopListenToNativeCallsState(); + this.hasActiveCall = false; Log.d(TAG, "[RNCallKeepModule] endAllCalls executed"); } @@ -447,7 +594,7 @@ public void checkPhoneAccountPermission(ReadableArray optionalPermissions, Promi allPermissionaw.pushString(allPermission); } - getReactApplicationContext() + this.reactContext .getNativeModule(PermissionsModule.class) .requestMultiplePermissions(allPermissionaw, new Promise() { @Override @@ -577,8 +724,41 @@ public void reportEndCallWithUUID(String uuid, int reason) { return; } conn.reportDisconnect(reason); + + this.stopListenToNativeCallsState(); } + @Override + public void onHostResume() { + + } + + @Override + public void onHostPause() { + + } + + @Override + public void onHostDestroy() { + // When activity destroyed end all calls + Log.d(TAG, "[RNCallKeepModule] onHostDestroy called"); + if (!isConnectionServiceAvailable() || !hasPhoneAccount()) { + Log.w(TAG, "[RNCallKeepModule] onHostDestroy ignored due to no ConnectionService or no phone account"); + return; + } + + ArrayList> connections = + new ArrayList>(VoiceConnectionService.currentConnections.entrySet()); + for (Map.Entry connectionEntry : connections) { + Connection connectionToEnd = connectionEntry.getValue(); + connectionToEnd.onDisconnect(); + } + this.stopListenToNativeCallsState(); + Log.d(TAG, "[RNCallKeepModule] onHostDestroy executed"); + // This line will kill the android process after ending all calls + android.os.Process.killProcess(android.os.Process.myPid()); + } + @ReactMethod public void rejectCall(String uuid) { Log.d(TAG, "[RNCallKeepModule] rejectCall, uuid: " + uuid); @@ -592,7 +772,7 @@ public void rejectCall(String uuid) { Log.w(TAG, "[RNCallKeepModule] rejectCall ignored because no connection found, uuid: " + uuid); return; } - + this.stopListenToNativeCallsState(); conn.onReject(); } @@ -838,7 +1018,7 @@ public void openPhoneAccounts() { return; } - if (Build.MANUFACTURER.equalsIgnoreCase("Samsung")) { + if (Build.MANUFACTURER.equalsIgnoreCase("Samsung") || Build.MANUFACTURER.equalsIgnoreCase("OnePlus")) { Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); intent.setComponent(new ComponentName("com.android.server.telecom", @@ -968,7 +1148,7 @@ private void registerPhoneAccount(Context appContext) { telecomManager.registerPhoneAccount(account); } - private void sendEventToJS(String eventName, @Nullable WritableMap params) { + public void sendEventToJS(String eventName, @Nullable WritableMap params) { boolean isBoundToJS = this.reactContext.hasActiveCatalystInstance(); Log.v(TAG, "[RNCallKeepModule] sendEventToJS, eventName: " + eventName + ", bound: " + isBoundToJS + ", hasListeners: " + hasListeners + " args : " + (params != null ? params.toString() : "null")); @@ -1021,8 +1201,9 @@ private boolean hasPhoneAccount() { telecomManager.getPhoneAccount(handle).isEnabled(); } - private void registerReceiver() { + protected void registerReceiver() { if (!isReceiverRegistered) { + isReceiverRegistered = true; voiceBroadcastReceiver = new VoiceBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_END_CALL); @@ -1042,9 +1223,11 @@ private void registerReceiver() { if (this.reactContext != null) { LocalBroadcastManager.getInstance(this.reactContext).registerReceiver(voiceBroadcastReceiver, intentFilter); - isReceiverRegistered = true; + VoiceConnectionService.startObserving(); + } else { + isReceiverRegistered = false; } } } @@ -1072,12 +1255,12 @@ private WritableMap storeSettings(ReadableMap options) { return MapUtils.readableToWritableMap(options); } - private static void fetchStoredSettings(@Nullable Context fromContext) { - Context context = fromContext != null ? fromContext : instance.getAppContext(); - if (instance == null && context == null) { + protected static void fetchStoredSettings(@Nullable Context fromContext) { + if (instance == null && fromContext == null) { Log.w(TAG, "[RNCallKeepModule][fetchStoredSettings] no instance nor fromContext."); return; } + Context context = fromContext != null ? fromContext : instance.getAppContext(); _settings = new WritableNativeMap(); if (context == null) { Log.w(TAG, "[RNCallKeepModule][fetchStoredSettings] no react context found."); diff --git a/android/src/main/java/io/wazo/callkeep/VoiceConnection.java b/android/src/main/java/io/wazo/callkeep/VoiceConnection.java index 5625ee44..1ea76fba 100644 --- a/android/src/main/java/io/wazo/callkeep/VoiceConnection.java +++ b/android/src/main/java/io/wazo/callkeep/VoiceConnection.java @@ -271,6 +271,11 @@ public void onStateChanged(int state) { @Override public void onSilence() { + // onSilence was added on API level 29 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return; + } + super.onSilence(); sendCallRequestToActivity(ACTION_ON_SILENCE_INCOMING_CALL, handle); diff --git a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java index 47200be5..5d586923 100644 --- a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java +++ b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java @@ -23,6 +23,8 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Activity; import android.content.res.Resources; import android.content.Intent; import android.content.Context; @@ -82,6 +84,7 @@ public class VoiceConnectionService extends ConnectionService { private static ConnectionRequest currentConnectionRequest; private static PhoneAccountHandle phoneAccountHandle; private static String TAG = "RNCallKeep"; + private static int NOTIFICATION_ID = -4567; // Delay events sent to RNCallKeepModule when there is no listener available private static List delayedEvents = new ArrayList(); @@ -215,17 +218,23 @@ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManage @Override public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { VoiceConnectionService.hasOutgoingCall = true; - String uuid = UUID.randomUUID().toString(); - Log.d(TAG, "[VoiceConnectionService] onCreateOutgoingConnection, uuid:" + uuid); + Bundle extras = request.getExtras(); + String callUUID = extras.getString(EXTRA_CALL_UUID); + + if(callUUID == null || callUUID == ""){ + callUUID = UUID.randomUUID().toString(); + } + + Log.d(TAG, "[VoiceConnectionService] onCreateOutgoingConnection, uuid:" + callUUID); if (!isInitialized && !isReachable) { - this.notReachableCallUuid = uuid; + this.notReachableCallUuid = callUUID; this.currentConnectionRequest = request; this.checkReachability(); } - return this.makeOutgoingCall(request, uuid, false); + return this.makeOutgoingCall(request, callUUID, false); } private Connection makeOutgoingCall(ConnectionRequest request, String uuid, Boolean forceWakeUp) { @@ -269,7 +278,7 @@ private Connection makeOutgoingCall(ConnectionRequest request, String uuid, Bool // ‍️Weirdly on some Samsung phones (A50, S9...) using `setInitialized` will not display the native UI ... // when making a call from the native Phone application. The call will still be displayed correctly without it. if (!Build.MANUFACTURER.equalsIgnoreCase("Samsung")) { - Log.d(TAG, "[VoiceConnectionService] onCreateOutgoingConnection: initializing connection on Samsung device"); + Log.d(TAG, "[VoiceConnectionService] onCreateOutgoingConnection: initializing connection on non-Samsung device"); outgoingCallConnection.setInitialized(); } @@ -291,7 +300,7 @@ private void startForegroundService() { Log.d(TAG, "[VoiceConnectionService] startForegroundService"); ReadableMap foregroundSettings = getForegroundSettings(null); - if (foregroundSettings == null || !foregroundSettings.hasKey("channelId")) { + if (!this.isForegroundServiceConfigured()) { Log.w(TAG, "[VoiceConnectionService] Not creating foregroundService because not configured"); return; } @@ -310,6 +319,18 @@ private void startForegroundService() { .setPriority(NotificationManager.IMPORTANCE_MIN) .setCategory(Notification.CATEGORY_SERVICE); + Activity currentActivity = RNCallKeepModule.instance.getCurrentReactActivity(); + if (currentActivity != null) { + Intent notificationIntent = new Intent(this, currentActivity.getClass()); + notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + + final int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT; + + PendingIntent pendingIntent = PendingIntent.getActivity(this, NOTIFICATION_ID, notificationIntent, flag); + + notificationBuilder.setContentIntent(pendingIntent); + } + if (foregroundSettings.hasKey("notificationIcon")) { Context context = this.getApplicationContext(); Resources res = context.getResources(); @@ -320,18 +341,39 @@ private void startForegroundService() { Log.d(TAG, "[VoiceConnectionService] Starting foreground service"); Notification notification = notificationBuilder.build(); - startForeground(FOREGROUND_SERVICE_TYPE_MICROPHONE, notification); + + try { + startForeground(FOREGROUND_SERVICE_TYPE_MICROPHONE, notification); + } catch (Exception e) { + Log.w(TAG, "[VoiceConnectionService] Can't start foreground service : " + e.toString()); + } } private void stopForegroundService() { Log.d(TAG, "[VoiceConnectionService] stopForegroundService"); ReadableMap foregroundSettings = getForegroundSettings(null); - if (foregroundSettings == null || !foregroundSettings.hasKey("channelId")) { - Log.d(TAG, "[VoiceConnectionService] Discarding stop foreground service, no service configured"); + if (!this.isForegroundServiceConfigured()) { + Log.w(TAG, "[VoiceConnectionService] Not creating foregroundService because not configured"); return; } - stopForeground(FOREGROUND_SERVICE_TYPE_MICROPHONE); + + try { + stopForeground(FOREGROUND_SERVICE_TYPE_MICROPHONE); + } catch (Exception e) { + Log.w(TAG, "[VoiceConnectionService] can't stop foreground service :" + e.toString()); + } + } + + private boolean isForegroundServiceConfigured() { + ReadableMap foregroundSettings = getForegroundSettings(null); + try { + return foregroundSettings != null && foregroundSettings.hasKey("channelId"); + } catch (Exception e) { + // Fix ArrayIndexOutOfBoundsException thrown by ReadableNativeMap.hasKey + Log.w(TAG, "[VoiceConnectionService] Not creating foregroundService due to configuration retrieval error" + e.toString()); + return false; + } } private void wakeUpApplication(String uuid, String number, String displayName) { @@ -340,18 +382,22 @@ private void wakeUpApplication(String uuid, String number, String displayName) { // Avoid to call wake up the app again in wakeUpAfterReachabilityTimeout. this.currentConnectionRequest = null; - Intent headlessIntent = new Intent( - this.getApplicationContext(), - RNCallKeepBackgroundMessagingService.class - ); - headlessIntent.putExtra("callUUID", uuid); - headlessIntent.putExtra("name", displayName); - headlessIntent.putExtra("handle", number); - - ComponentName name = this.getApplicationContext().startService(headlessIntent); - if (name != null) { - Log.d(TAG, "[VoiceConnectionService] wakeUpApplication, acquiring lock for application:" + name); - HeadlessJsTaskService.acquireWakeLockNow(this.getApplicationContext()); + try { + Intent headlessIntent = new Intent( + this.getApplicationContext(), + RNCallKeepBackgroundMessagingService.class + ); + headlessIntent.putExtra("callUUID", uuid); + headlessIntent.putExtra("name", displayName); + headlessIntent.putExtra("handle", number); + + ComponentName name = this.getApplicationContext().startService(headlessIntent); + if (name != null) { + Log.d(TAG, "[VoiceConnectionService] wakeUpApplication, acquiring lock for application:" + name); + HeadlessJsTaskService.acquireWakeLockNow(this.getApplicationContext()); + } + } catch (Exception e) { + Log.w(TAG, "[VoiceConnectionService] wakeUpApplication, error" + e.toString()); } } diff --git a/docs/android-installation.md b/docs/android-installation.md index 23bf42e8..fcbb8e37 100644 --- a/docs/android-installation.md +++ b/docs/android-installation.md @@ -65,6 +65,10 @@ public class MainActivity extends ReactActivity { + +// Use this to target android >= 14 + + // ... diff --git a/docs/ios-installation.md b/docs/ios-installation.md index 9849fe90..14622475 100644 --- a/docs/ios-installation.md +++ b/docs/ios-installation.md @@ -96,7 +96,7 @@ Add it before the `@end` tag. ```diff + - (BOOL)application:(UIApplication *)application + continueUserActivity:(NSUserActivity *)userActivity -+ restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler ++ restorationHandler:(void(^)(NSArray> * __nullable restorableObjects))restorationHandler + { + return [RNCallKeep application:application + continueUserActivity:userActivity diff --git a/example/App.js b/example/App.js index 82feed7c..c96213db 100644 --- a/example/App.js +++ b/example/App.js @@ -77,7 +77,7 @@ export default function App() { const { [callUUID]: __, ...updatedHeldCalls } = heldCalls; setCalls(updated); - setCalls(updatedHeldCalls); + setHeldCalls(updatedHeldCalls); }; const setCallHeld = (callUUID, held) => { diff --git a/example/package.json b/example/package.json index cdfefad9..a24d1b5f 100644 --- a/example/package.json +++ b/example/package.json @@ -14,7 +14,7 @@ "react-native-callkeep": "3.0.7", "react-native-device-info": "^2.3.2", "react-native-gesture-handler": "~1.3.0", - "react-native-reanimated": "~1.1.0", + "react-native-reanimated": "~2.10.0", "react-native-unimodules": "~0.5.2", "react-native-web": "^0.11.4", "uuid": "^3.3.2" diff --git a/example/yarn.lock b/example/yarn.lock index 03768dc8..f1401464 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -16,6 +16,13 @@ dependencies: "@babel/highlight" "^7.14.5" +"@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + "@babel/core@^7.0.0", "@babel/core@^7.1.0": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30" @@ -45,6 +52,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.6.tgz#9e481a3fe9ca6261c972645ae3904ec0f9b34a1d" + integrity sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA== + dependencies: + "@babel/types" "^7.19.4" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + "@babel/generator@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.5.tgz#873a7f936a3c89491b43536d12245b626664e3cf" @@ -70,6 +86,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" @@ -107,6 +130,19 @@ "@babel/helper-replace-supers" "^7.15.0" "@babel/helper-split-export-declaration" "^7.14.5" +"@babel/helper-create-class-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" + integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-create-class-features-plugin@^7.4.4", "@babel/helper-create-class-features-plugin@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.5.tgz#401f302c8ddbc0edd36f7c6b2887d8fa1122e5a4" @@ -128,6 +164,11 @@ "@babel/types" "^7.5.5" lodash "^4.17.13" +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + "@babel/helper-explode-assignable-expression@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" @@ -154,6 +195,14 @@ "@babel/template" "^7.14.5" "@babel/types" "^7.14.5" +"@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + "@babel/helper-get-function-arity@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" @@ -175,6 +224,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-hoist-variables@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz#0298b5f25c8c09c53102d52ac4a98f773eb2850a" @@ -189,6 +245,13 @@ dependencies: "@babel/types" "^7.15.0" +"@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + "@babel/helper-member-expression-to-functions@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz#1fb5b8ec4453a93c439ee9fe3aeea4a84b76b590" @@ -229,6 +292,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-plugin-utils@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" @@ -239,6 +309,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== +"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + "@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351" @@ -267,6 +342,17 @@ "@babel/traverse" "^7.15.0" "@babel/types" "^7.15.0" +"@babel/helper-replace-supers@^7.18.9": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" + integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" + "@babel/helper-replace-supers@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz#f84ce43df031222d2bad068d2626cb5799c34bc2" @@ -292,6 +378,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-split-export-declaration@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" @@ -299,11 +392,26 @@ dependencies: "@babel/types" "^7.4.4" +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": version "7.14.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + "@babel/helper-wrap-function@^7.1.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa" @@ -341,6 +449,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.0.0", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b" @@ -351,6 +468,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862" integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA== +"@babel/parser@^7.18.10", "@babel/parser@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.6.tgz#b923430cb94f58a7eae8facbffa9efd19130e7f8" + integrity sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA== + "@babel/plugin-external-helpers@^7.0.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-external-helpers/-/plugin-external-helpers-7.2.0.tgz#7f4cb7dee651cd380d2034847d914288467a6be4" @@ -554,6 +676,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/plugin-syntax-typescript@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" + integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-typescript@^7.2.0": version "7.3.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz#a7cc3f66119a9f7ebe2de5383cce193473d65991" @@ -731,12 +860,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-object-assign@^7.0.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.2.0.tgz#6fdeea42be17040f119e38e23ea0f49f31968bde" - integrity sha512-nmE55cZBPFgUktbF2OuoZgPRadfxosLOpSgzEPYotKSls9J4pEPcembi8r78RU37Rph6UApCpNmsQA4QMWK9Ng== +"@babel/plugin-transform-object-assign@^7.0.0", "@babel/plugin-transform-object-assign@^7.16.7": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.18.6.tgz#7830b4b6f83e1374a5afb9f6111bcfaea872cdd2" + integrity sha512-mQisZ3JfqWh2gVXvfqYCAAyRs6+7oev+myBsTwW5RnPhYXOTuCEw2oe3YgxlXMViXUS53lG8koulI7mJ+8JE+A== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.5.5": version "7.5.5" @@ -863,6 +992,15 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-typescript" "^7.2.0" +"@babel/plugin-transform-typescript@^7.18.6": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.3.tgz#4f1db1e0fe278b42ddbc19ec2f6cd2f8262e35d6" + integrity sha512-z6fnuK9ve9u/0X0rRvI9MY0xg+DOUaABDYOe+/SQTxtlptaBB/V9JIUxJn6xp3lMBeb9qe8xSFmHU35oZDXD+w== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-typescript" "^7.18.6" + "@babel/plugin-transform-typescript@^7.5.0": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.15.0.tgz#553f230b9d5385018716586fc48db10dd228eb7e" @@ -937,6 +1075,15 @@ js-levenshtein "^1.1.3" semver "^5.5.0" +"@babel/preset-typescript@^7.16.7": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" + integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-typescript" "^7.18.6" + "@babel/register@^7.0.0": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.5.5.tgz#40fe0d474c8c8587b28d6ae18a03eddad3dac3c1" @@ -974,6 +1121,15 @@ "@babel/parser" "^7.14.5" "@babel/types" "^7.14.5" +"@babel/template@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + "@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.5.tgz#f664f8f368ed32988cd648da9f72d5ca70f165bb" @@ -1004,6 +1160,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.19.1": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.6.tgz#7b4c865611df6d99cb131eec2e8ac71656a490dc" + integrity sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.6" + "@babel/types" "^7.19.4" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a" @@ -1021,6 +1193,15 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" +"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1134,6 +1315,38 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@react-native-community/cli-debugger-ui@^4.13.1": version "4.13.1" resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-4.13.1.tgz#07de6d4dab80ec49231de1f1fbf658b4ad39b32c" @@ -1259,10 +1472,10 @@ resolved "https://registry.yarnpkg.com/@types/fbemitter/-/fbemitter-2.0.32.tgz#8ed204da0f54e9c8eaec31b1eec91e25132d082c" integrity sha1-jtIE2g9U6cjq7DGx7skeJRMtCCw= -"@types/invariant@^2.2.29": - version "2.2.30" - resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.30.tgz#20efa342807606ada5483731a8137cb1561e5fe9" - integrity sha512-98fB+yo7imSD2F7PF7GIpELNgtLNgo5wjivu0W5V4jx+KVVJxo6p/qN4zdzSTBWy4/sN3pPyXwnhRSD28QX+ag== +"@types/invariant@^2.2.29", "@types/invariant@^2.2.35": + version "2.2.35" + resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be" + integrity sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg== "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.3" @@ -1665,9 +1878,9 @@ babel-preset-fbjs@^3.3.0: babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.1.2, base64-js@^1.5.1: version "1.5.1" @@ -1687,10 +1900,10 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -big-integer@^1.6.7: - version "1.6.44" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.44.tgz#4ee9ae5f5839fc11ade338fea216b4513454a539" - integrity sha512-7MzElZPTyJ2fNvBkPxtFQ2fWIkVmuzw41+BZHSzpEq3ymB2MfeKp1+yXl/tS75xCx+WnyV+yb0kp+K1C3UNwmQ== +big-integer@1.6.x: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== bindings@^1.5.0: version "1.5.0" @@ -1704,19 +1917,19 @@ blueimp-md5@^2.10.0: resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.11.0.tgz#eff55d30fe3daddd7e801072e2c4483e5fcfc87c" integrity sha512-xvA4mdnIevstCvNKTRLMOKi7L76U/X/CTs9Yz+PLWmWAC/7SuYi5Xv2J7bAhJnE2+LcLv+x4+0vusvKgM9LnZQ== -bplist-creator@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.0.7.tgz#37df1536092824b87c42f957b01344117372ae45" - integrity sha1-N98VNgkoJLh8QvlXsBNEEXNyrkU= +bplist-creator@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.1.0.tgz#018a2d1b587f769e379ef5519103730f8963ba1e" + integrity sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg== dependencies: - stream-buffers "~2.2.0" + stream-buffers "2.2.x" -bplist-parser@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.1.1.tgz#d60d5dcc20cba6dc7e1f299b35d3e1f95dafbae6" - integrity sha1-1g1dzCDLptx+HymbNdPh+V2vuuY= +bplist-parser@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.1.tgz#e1c90b2ca2a9f9474cc72f6862bbf3fee8341fd1" + integrity sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA== dependencies: - big-integer "^1.6.7" + big-integer "1.6.x" brace-expansion@^1.1.7: version "1.1.11" @@ -1808,6 +2021,14 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -2026,7 +2247,7 @@ compression@^1.7.1: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== concat-stream@^1.6.0: version "1.6.2" @@ -2171,9 +2392,9 @@ decamelize@^1.2.0: integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== deep-assign@^3.0.0: version "3.0.0" @@ -2767,6 +2988,15 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -2836,6 +3066,11 @@ has-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -2867,6 +3102,13 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hermes-engine@~0.4.0: version "0.4.3" resolved "https://registry.yarnpkg.com/hermes-engine/-/hermes-engine-0.4.3.tgz#1754932f989daddd149172600f01e69cb8f27298" @@ -3374,6 +3616,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" @@ -3895,9 +4142,9 @@ min-document@^2.19.0: dom-walk "^0.1.0" minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" @@ -4069,6 +4316,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + object-keys@^1.0.11, object-keys@^1.0.12: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -4289,7 +4541,7 @@ pkg-up@^2.0.0: dependencies: find-up "^2.1.0" -plist@^3.0.1: +plist@^3.0.1, plist@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.5.tgz#2cbeb52d10e3cdccccf0c11a63a85d830970a987" integrity sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA== @@ -4391,9 +4643,11 @@ pump@^3.0.0: once "^1.3.1" qs@^6.5.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" querystringify@^2.1.1: version "2.2.0" @@ -4467,10 +4721,18 @@ react-native-gesture-handler@~1.3.0: invariant "^2.2.2" prop-types "^15.5.10" -react-native-reanimated@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-1.1.0.tgz#ba6864055ec3a206cdd5209a293fe653ce276206" - integrity sha512-UGDVNfvuIkMqYUx6aytSzihuzv6sWubn0MQi8dRcw7BjgezhjJnVnJ/NSOcpL3cO+Ld7lFcRX6GKcskwkHdPkw== +react-native-reanimated@~2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.10.0.tgz#ed53be66bbb553b5b5e93e93ef4217c87b8c73db" + integrity sha512-jKm3xz5nX7ABtHzzuuLmawP0pFWP77lXNdIC6AWOceBs23OHUaJ29p4prxr/7Sb588GwTbkPsYkDqVFaE3ezNQ== + dependencies: + "@babel/plugin-transform-object-assign" "^7.16.7" + "@babel/preset-typescript" "^7.16.7" + "@types/invariant" "^2.2.35" + invariant "^2.2.4" + lodash.isequal "^4.5.0" + setimmediate "^1.0.5" + string-hash-64 "^1.0.3" react-native-unimodules@~0.5.2: version "0.5.2" @@ -4819,14 +5081,14 @@ scheduler@^0.13.3, scheduler@^0.13.6: object-assign "^4.1.1" semver@^5.1.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" - integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@^6.1.1, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== send@0.17.1: version "0.17.1" @@ -4909,19 +5171,28 @@ shell-quote@1.6.1, shell-quote@^1.6.1: array-reduce "~0.0.0" jsonify "~0.0.0" +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= simple-plist@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.0.0.tgz#bed3085633b22f371e111f45d159a1ccf94b81eb" - integrity sha512-043L2rO80LVF7zfZ+fqhsEkoJFvW8o59rt/l4ctx1TJWoTx7/jkiS1R5TatD15Z1oYnuLJytzE7gcnnBuIPL2g== + version "1.3.1" + resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017" + integrity sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw== dependencies: - bplist-creator "0.0.7" - bplist-parser "0.1.1" - plist "^3.0.1" + bplist-creator "0.1.0" + bplist-parser "0.3.1" + plist "^3.0.5" slash@^2.0.0: version "2.0.0" @@ -5055,11 +5326,16 @@ static-extend@^0.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -stream-buffers@~2.2.0: +stream-buffers@2.2.x: version "2.2.0" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" integrity sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ= +string-hash-64@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322" + integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw== + string-width@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -5244,9 +5520,9 @@ typedarray@^0.0.6: integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= ua-parser-js@^0.7.18, ua-parser-js@^0.7.19: - version "0.7.28" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" - integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== + version "0.7.33" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" + integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== uglify-es@^3.1.9: version "3.3.9" diff --git a/index.d.ts b/index.d.ts index 224720d7..25b43da9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,21 +1,62 @@ declare module 'react-native-callkeep' { - export type Events = - 'didReceiveStartCallAction' | - 'answerCall' | - 'endCall' | - 'didActivateAudioSession' | - 'didDeactivateAudioSession' | - 'didDisplayIncomingCall' | - 'didToggleHoldCallAction' | - 'didPerformDTMFAction' | - 'didResetProvider' | - 'checkReachability' | - 'didPerformSetMutedCallAction' | - 'didChangeAudioRoute' | - 'didLoadWithEvents' | - 'showIncomingCallUi' | - 'silenceIncomingCall' | - 'createIncomingConnectionFailed'; + export type NativeEvents = { + didReceiveStartCallAction: 'RNCallKeepDidReceiveStartCallAction'; + answerCall: 'RNCallKeepPerformAnswerCallAction'; + endCall: 'RNCallKeepPerformEndCallAction'; + didActivateAudioSession: 'RNCallKeepDidActivateAudioSession'; + didDeactivateAudioSession: 'RNCallKeepDidDeactivateAudioSession'; + didDisplayIncomingCall: 'RNCallKeepDidDisplayIncomingCall'; + didPerformSetMutedCallAction: 'RNCallKeepDidPerformSetMutedCallAction'; + didToggleHoldCallAction: 'RNCallKeepDidToggleHoldAction'; + didChangeAudioRoute: 'RNCallKeepDidChangeAudioRoute'; + didPerformDTMFAction: 'RNCallKeepDidPerformDTMFAction'; + showIncomingCallUi: 'RNCallKeepShowIncomingCallUi'; + silenceIncomingCall: 'RNCallKeepOnSilenceIncomingCall'; + createIncomingConnectionFailed: 'RNCallKeepOnIncomingConnectionFailed'; + checkReachability: 'RNCallKeepCheckReachability'; + didResetProvider: 'RNCallKeepProviderReset'; + didLoadWithEvents: 'RNCallKeepDidLoadWithEvents'; + onHasActiveCall : 'onHasActiveCall'; + } + + export type InitialEvents = Array<{ + [Event in Events]: { name: NativeEvents[Event], data: EventsPayload[Event] } + }[Events]> + + export type Events = keyof NativeEvents; + export type EventsPayload = { + didReceiveStartCallAction: { handle: string, callUUID?: string, name?: string }; + answerCall: { callUUID: string }; + endCall: { callUUID: string }; + didActivateAudioSession: undefined; + didDeactivateAudioSession: undefined; + didDisplayIncomingCall: { + error?: string, + errorCode?: 'Unentitled' | 'CallUUIDAlreadyExists' | 'FilteredByDoNotDisturb' | 'FilteredByBlockList' | 'Unknown', + callUUID: string, + handle: string, + localizedCallerName: string, + hasVideo: '1' | '0', + fromPushKit: '1' | '0', + payload: object, + }; + didPerformSetMutedCallAction: { muted: boolean, callUUID: string }; + didToggleHoldCallAction: { hold: boolean, callUUID: string }; + didChangeAudioRoute: { + output: string, + reason?: number, + handle?: string, + callUUID?: string, + }; + didPerformDTMFAction: { digits: string, callUUID: string }; + showIncomingCallUi: { handle: string, callUUID: string, name: string }; + silenceIncomingCall: { handle: string, callUUID: string, name: string }; + createIncomingConnectionFailed: { handle: string, callUUID: string, name: string }; + checkReachability: undefined; + didResetProvider: undefined; + didLoadWithEvents: InitialEvents; + onHasActiveCall : undefined; + } type HandleType = 'generic' | 'number' | 'email'; @@ -25,6 +66,29 @@ declare module 'react-native-callkeep' { selected?: boolean } + export enum AudioSessionCategoryOption { + mixWithOthers = 0x1, + duckOthers = 0x2, + interruptSpokenAudioAndMixWithOthers = 0x11, + allowBluetooth = 0x4, + allowBluetoothA2DP = 0x20, + allowAirPlay = 0x40, + defaultToSpeaker = 0x8, + overrideMutedMicrophoneInterruption = 0x80, + } + + export enum AudioSessionMode { + default = 'AVAudioSessionModeDefault', + gameChat = 'AVAudioSessionModeGameChat', + measurement = 'AVAudioSessionModeMeasurement', + moviePlayback = 'AVAudioSessionModeMoviePlayback', + spokenAudio = 'AVAudioSessionModeSpokenAudio', + videoChat = 'AVAudioSessionModeVideoChat', + videoRecording = 'AVAudioSessionModeVideoRecording', + voiceChat = 'AVAudioSessionModeVoiceChat', + voicePrompt = 'AVAudioSessionModeVoicePrompt', + } + interface IOptions { ios: { appName: string, @@ -34,6 +98,10 @@ declare module 'react-native-callkeep' { maximumCallsPerCallGroup?: string, ringtoneSound?: string, includesCallsInRecents?: boolean + audioSession?: { + categoryOptions?: AudioSessionCategoryOption | number, + mode?: AudioSessionMode | string, + } }, android: { alertTitle: string, @@ -52,12 +120,6 @@ declare module 'react-native-callkeep' { } } - export type DidReceiveStartCallActionPayload = { handle: string }; - export type AnswerCallPayload = { callUUID: string }; - export type EndCallPayload = AnswerCallPayload; - export type DidDisplayIncomingCallPayload = string | undefined; - export type DidPerformSetMutedCallActionPayload = boolean; - export const CONSTANTS: { END_CALL_REASONS: { FAILED: 1, @@ -69,12 +131,19 @@ declare module 'react-native-callkeep' { } }; + export class EventListener { + remove(): void + } + export default class RNCallKeep { - static getInitialEvents(): Promise> + static getInitialEvents(): Promise static clearInitialEvents(): void - static addEventListener(type: Events, handler: (args: any) => void): void + static addEventListener( + type: Event, + handler: (args: EventsPayload[Event]) => void, + ): EventListener static removeEventListener(type: Events): void @@ -138,18 +207,24 @@ declare module 'react-native-callkeep' { static setReachable(): void - static setSettings(settings: Object): void; + static setSettings(settings: IOptions): void; /** * @description isCallActive method is available only on iOS. */ static isCallActive(uuid: string): Promise - static getCalls(): Promise + static getCalls(): Promise<{ + callUUID: string, + hasConnected: boolean, + hasEnded: boolean, + onHold: boolean, + outgoing: boolean + }[] | void> static getAudioRoutes(): Promise - static setAudioRoute: (uuid:string, inputName: string) => Promise + static setAudioRoute: (uuid: string, inputName: string) => Promise /** * @description supportConnectionService method is available only on Android. @@ -193,12 +268,17 @@ declare module 'react-native-callkeep' { */ static setAvailable(active: boolean): void - static setForegroundServiceSettings(settings: Object): void + static setForegroundServiceSettings(settings: NonNullable): void static canMakeMultipleCalls(allow: boolean): void static setCurrentCallActive(callUUID: string): void static backToForeground(): void + + /** + * @descriptions Android Only, Check if there is active native call + */ + static checkIsInManagedCall(): Promise } } diff --git a/index.js b/index.js index 96a31647..a011f677 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,29 @@ const RNCallKeepModule = NativeModules.RNCallKeep; const isIOS = Platform.OS === 'ios'; const supportConnectionService = !isIOS && Platform.Version >= 23; +const AudioSessionCategoryOption = { + mixWithOthers: 0x1, + duckOthers: 0x2, + interruptSpokenAudioAndMixWithOthers: 0x11, + allowBluetooth: 0x4, + allowBluetoothA2DP: 0x20, + allowAirPlay: 0x40, + defaultToSpeaker: 0x8, + overrideMutedMicrophoneInterruption: 0x80, +} + +const AudioSessionMode = { + default: 'AVAudioSessionModeDefault', + gameChat: 'AVAudioSessionModeGameChat', + measurement: 'AVAudioSessionModeMeasurement', + moviePlayback: 'AVAudioSessionModeMoviePlayback', + spokenAudio: 'AVAudioSessionModeSpokenAudio', + videoChat: 'AVAudioSessionModeVideoChat', + videoRecording: 'AVAudioSessionModeVideoRecording', + voiceChat: 'AVAudioSessionModeVoiceChat', + voicePrompt: 'AVAudioSessionModeVoicePrompt', +} + const CONSTANTS = { END_CALL_REASONS: { FAILED: 1, @@ -17,7 +40,19 @@ const CONSTANTS = { }, }; -export { emit, CONSTANTS }; +export { emit, CONSTANTS, AudioSessionCategoryOption, AudioSessionMode }; + +class EventListener { + constructor(type, listener, callkeep) { + this._type = type; + this._listener = listener; + this._callkeep = callkeep; + } + + remove = () => { + this._callkeep.removeEventListener(this._type, this._listener); + }; +} class RNCallKeep { constructor() { @@ -27,17 +62,32 @@ class RNCallKeep { addEventListener = (type, handler) => { const listener = listeners[type](handler); - this._callkeepEventHandlers.set(type, listener); + const listenerSet = this._callkeepEventHandlers.get(type) ?? new Set(); + listenerSet.add(listener); + + this._callkeepEventHandlers.set(type, listenerSet); + + return new EventListener(type, listener, this); }; - removeEventListener = (type) => { - const listener = this._callkeepEventHandlers.get(type); - if (!listener) { + removeEventListener = (type, listener = undefined) => { + const listenerSet = this._callkeepEventHandlers.get(type); + if (!listenerSet) { return; } - listener.remove(); - this._callkeepEventHandlers.delete(type); + if (listener) { + listenerSet.delete(listener); + listener.remove(); + if (listenerSet.size <= 0) { + this._callkeepEventHandlers.delete(type); + } + } else { + listenerSet.forEach((listener) => { + listener.remove(); + }); + this._callkeepEventHandlers.delete(type); + } }; setup = async (options) => { @@ -111,6 +161,8 @@ class RNCallKeep { ); }; + checkIsInManagedCall = async () => isIOS? false: RNCallKeepModule.checkIsInManagedCall(); + answerIncomingCall = (uuid) => { RNCallKeepModule.answerIncomingCall(uuid); }; diff --git a/ios/RNCallKeep/RNCallKeep.m b/ios/RNCallKeep/RNCallKeep.m index d74a4db8..786045fe 100644 --- a/ios/RNCallKeep/RNCallKeep.m +++ b/ios/RNCallKeep/RNCallKeep.m @@ -131,6 +131,17 @@ - (void)startObserving - (void)stopObserving { _hasListeners = FALSE; + + // Fix for https://github.com/react-native-webrtc/react-native-callkeep/issues/406 + // We use Objective-C Key Value Coding(KVC) to sync _RTCEventEmitter_ `_listenerCount`. + @try { + [self setValue:@0 forKey:@"_listenerCount"]; + } + @catch ( NSException *e ){ + NSLog(@"[RNCallKeep][stopObserving] exception: %@",e); + NSLog(@"[RNCallKeep][stopObserving] RNCallKeep parent class RTCEventEmitter might have a broken state."); + NSLog(@"[RNCallKeep][stopObserving] Please verify that the parent RTCEventEmitter.m has iVar `_listenerCount`."); + } } - (void)onAudioRouteChange:(NSNotification *)notification @@ -150,7 +161,7 @@ - (void)onAudioRouteChange:(NSNotification *)notification } - (void)sendEventWithNameWrapper:(NSString *)name body:(id)body { - NSLog(@"[[RNCallKeep]] sendEventWithNameWrapper: %@, hasListeners : %@", name, _hasListeners ? @"YES": @"NO"); + NSLog(@"[RNCallKeep] sendEventWithNameWrapper: %@, hasListeners : %@", name, _hasListeners ? @"YES": @"NO"); if (_hasListeners) { [self sendEventWithName:name body:body]; @@ -178,7 +189,16 @@ + (void)initCallKitProvider { } + (NSString *) getAudioOutput { - return [AVAudioSession sharedInstance].currentRoute.outputs.count > 0 ? [AVAudioSession sharedInstance].currentRoute.outputs[0].portType : nil; + @try{ + NSArray* outputs = [AVAudioSession sharedInstance].currentRoute.outputs; + if(outputs != nil && outputs.count > 0){ + return outputs[0].portType; + } + } @catch(NSException* error) { + NSLog(@"getAudioOutput error :%@", [error description]); + } + + return nil; } + (void)setup:(NSDictionary *)options { @@ -543,7 +563,7 @@ + (NSMutableArray *) formatAudioInputs: (NSMutableArray *)inputs { NSMutableArray *newInputs = [NSMutableArray new]; NSString * selected = [RNCallKeep getSelectedAudioRoute]; - + NSMutableDictionary *speakerDict = [[NSMutableDictionary alloc]init]; [speakerDict setObject:@"Speaker" forKey:@"name"]; [speakerDict setObject:AVAudioSessionPortBuiltInSpeaker forKey:@"type"]; @@ -621,6 +641,9 @@ + (NSString *) getAudioInputType: (NSString *) type else if ([type isEqualToString:AVAudioSessionPortBuiltInSpeaker]){ return @"Speaker"; } + else if ([type isEqualToString:AVAudioSessionPortCarAudio]) { + return @"CarAudio"; + } else{ return nil; } @@ -631,13 +654,13 @@ + (NSString *) getSelectedAudioRoute AVAudioSession* myAudioSession = [AVAudioSession sharedInstance]; AVAudioSessionRouteDescription *currentRoute = [myAudioSession currentRoute]; NSArray *selectedOutputs = currentRoute.outputs; - + AVAudioSessionPortDescription *selectedOutput = selectedOutputs[0]; - + if(selectedOutput && [selectedOutput.portType isEqualToString:AVAudioSessionPortBuiltInReceiver]) { return @"Phone"; } - + return [RNCallKeep getAudioInputType: selectedOutput.portType]; } @@ -893,10 +916,24 @@ - (void)configureAudioSession NSLog(@"[RNCallKeep][configureAudioSession] Activating audio session"); #endif + NSUInteger categoryOptions = AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP; + NSString *mode = AVAudioSessionModeDefault; + + NSDictionary *settings = [RNCallKeep getSettings]; + if (settings && settings[@"audioSession"]) { + if (settings[@"audioSession"][@"categoryOptions"]) { + categoryOptions = [settings[@"audioSession"][@"categoryOptions"] integerValue]; + } + + if (settings[@"audioSession"][@"mode"]) { + mode = settings[@"audioSession"][@"mode"]; + } + } + AVAudioSession* audioSession = [AVAudioSession sharedInstance]; - [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil]; + [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:categoryOptions error:nil]; - [audioSession setMode:AVAudioSessionModeDefault error:nil]; + [audioSession setMode:mode error:nil]; double sampleRate = 44100.0; [audioSession setPreferredSampleRate:sampleRate error:nil]; diff --git a/package.json b/package.json index 6c5a3415..b9b821b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-callkeep", - "version": "4.3.3", + "version": "4.3.16", "description": "iOS 10 CallKit and Android ConnectionService Framework For React Native", "main": "index.js", "scripts": {},