diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c311edb..570f2aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ # Changelog ----------------------------------------------- - +[0.4.3] +* fix podspec to januscaler_callkeep +[0.4.2] +* added toggle speaker support +* added support for listening PushMessagePayload [0.4.1] - 2025.02.03 * [Fix] Updates firebase messaging and android gradle diff --git a/README.md b/README.md index 09bccce3..6f67c0e5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # callkeep +[![januscaler](https://img.shields.io/badge/powered_by-JanuScaler-b?style=for-the-badge&logo=Januscaler&logoColor=%238884ED&label=Powered%20By&labelColor=white&color=%238884ED)](https://januscaler.com) -[![Financial Contributors on Open Collective](https://opencollective.com/flutter-webrtc/all/badge.svg?label=financial+contributors)](https://opencollective.com/flutter-webrtc) [![pub package](https://img.shields.io/pub/v/callkeep.svg)](https://pub.dartlang.org/packages/callkeep) [![slack](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://join.slack.com/t/flutterwebrtc/shared_invite/zt-q83o7y1s-FExGLWEvtkPKM8ku_F8cEQ) +[![pub package](https://img.shields.io/pub/v/januscaler_callkeep.svg)](https://pub.dartlang.org/packages/januscaler_callkeep) + +This is a forked project from [callkeep](https://github.com/flutter-webrtc/callkeep) in an effort to maintain and improve the package for Flutter applications. - iOS CallKit and Android ConnectionService for Flutter - Support FCM and PushKit @@ -271,8 +274,11 @@ showAlertDialog: () async { ### FAQ +> I don't receive the incoming call in ios +Receiving incoming calls in iOS requires the app to be registered with PushKit and to handle VoIP pushes correctly. Ensure that your app is configured to receive VoIP pushes and that you are using the correct payload format for incoming calls. +also make sure you have not abused the PushKit API, as device may throttle or block your app from receiving VoIP pushes if it detects misuse. -> I don't receive the incoming call +> I don't receive the incoming call(android) Receiving incoming calls depends on FCM push messages (or the system you use) for handling the call information and displaying it. Remember FCM push messages not always works due to data-only messages are classified as "low priority". Devices can throttle and ignore these messages if your application is in the background, terminated, or a variety of other conditions such as low battery or currently high CPU usage. To help improve delivery, you can bump the priority of messages. Note; this does still not guarantee delivery. More info [here](https://firebase.flutter.dev/docs/messaging/usage/#low-priority-messages) diff --git a/android/build.gradle b/android/build.gradle index 6e991c4a..3e0147d3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:8.3.0' } } diff --git a/android/src/main/java/io/wazo/callkeep/CallKeepModule.java b/android/src/main/java/io/wazo/callkeep/CallKeepModule.java index 3bb3ab03..da1c7678 100644 --- a/android/src/main/java/io/wazo/callkeep/CallKeepModule.java +++ b/android/src/main/java/io/wazo/callkeep/CallKeepModule.java @@ -36,6 +36,8 @@ import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; +import android.telecom.Connection; +import android.telecom.CallAudioState; import android.telephony.TelephonyManager; import android.util.Log; import android.view.WindowManager; @@ -100,7 +102,8 @@ public void setActivity(Activity activity) { } public void dispose() { - if (voiceBroadcastReceiver == null || this.context == null) return; + if (voiceBroadcastReceiver == null || this.context == null) + return; LocalBroadcastManager.getInstance(this.context).unregisterReceiver(voiceBroadcastReceiver); VoiceConnectionService.setPhoneAccountHandle(null); isReceiverRegistered = false; @@ -112,138 +115,141 @@ public boolean handleMethodCall(@NonNull MethodCall call, @NonNull Result result setup(new ConstraintsMap(call.argument("options"))); result.success(null); } - break; + break; case "displayIncomingCall": { displayIncomingCallImpl( call.argument("uuid"), call.argument("handle"), call.argument("callerName"), - call.argument("additionalData") - ); + call.argument("additionalData")); result.success(null); } - break; + break; case "answerIncomingCall": { answerIncomingCall(call.argument("uuid")); result.success(null); } - break; + break; case "startCall": { startCall( call.argument("uuid"), call.argument("handle"), call.argument("callerName"), - call.argument("additionalData") - ); + call.argument("additionalData")); result.success(null); } - break; + break; case "endCall": { endCall(call.argument("uuid")); result.success(null); } - break; + break; case "endAllCalls": { endAllCalls(); result.success(null); } - break; + break; case "requestPermissions": { requestPermissions(new ConstraintsArray(call.argument("additionalPermissions")), result); } - break; + break; case "checkDefaultPhoneAccount": { checkDefaultPhoneAccount(result); } - break; + break; case "setOnHold": { setOnHold(call.argument("uuid"), call.argument("hold")); result.success(null); } - break; + break; case "reportEndCallWithUUID": { reportEndCallWithUUID(call.argument("uuid"), call.argument("reason"), call.argument("notify")); result.success(null); } - break; + break; case "reportStartedCallWithUUID": { reportStartedCallWithUUID(call.argument("uuid")); result.success(null); } - break; + break; case "rejectCall": { rejectCall(call.argument("uuid")); result.success(null); } - break; + break; case "setMutedCall": { setMutedCall(call.argument("uuid"), call.argument("muted")); result.success(null); } - break; + break; + case "setSpeaker": { + setSpeaker(call.argument("uuid"), call.argument("isOn")); + result.success(null); + } + break; case "setCallAudio": { setCallAudio(call.argument("uuid"), call.argument("audioRoute")); result.success(null); } - break; + break; case "sendDTMF": { sendDTMF(call.argument("uuid"), call.argument("key")); result.success(null); } - break; + break; case "updateDisplay": { updateDisplay(call.argument("uuid"), call.argument("callerName"), call.argument("handle")); result.success(null); } - break; + break; case "hasPhoneAccount": { hasPhoneAccount(result); } - break; + break; case "hasOutgoingCall": { hasOutgoingCall(result); } - break; + break; case "hasPermissions": { hasPermissions(result); } - break; + break; case "setAvailable": { setAvailable(call.argument("available")); result.success(null); } - break; + break; case "setReachable": { setReachable(call.argument("reachable")); result.success(null); } - break; + break; case "setCurrentCallActive": { setCurrentCallActive(call.argument("uuid")); result.success(null); } - break; + break; case "openPhoneAccounts": { openPhoneAccounts(result); } - break; + break; case "backToForeground": { backToForeground(result); } - break; + break; case "foregroundService": { updateSettings(new ConstraintsMap(call.argument("settings"))); result.success(null); } - break; + break; case "isCallActive": { isCallActive(call.argument("uuid"), result); } - break; + break; case "activeCalls": { activeCalls(result); } - break; + break; default: return false; } @@ -264,7 +270,8 @@ private void setup(ConstraintsMap options) { private static boolean setupImpl(Context context, ConstraintsMap options) { boolean isServiceAvailable = isConnectionServiceAvailable(); - if (hasSetup) return isServiceAvailable; + if (hasSetup) + return isServiceAvailable; VoiceConnectionService.setAvailable(false); if (isServiceAvailable) { registerPhoneAccount(context, options); @@ -312,19 +319,19 @@ private void registerEvents() { } public static void displayIncomingCall(Context context, - String uuid, - String handle, - String callerName, - Map additionalData) { + String uuid, + String handle, + String callerName, + Map additionalData) { if (setupImpl(context, getSettings(context))) { displayIncomingCallImpl(uuid, handle, callerName, additionalData); } } private static void displayIncomingCallImpl(String uuid, - String handle, - String callerName, - Map additionalData) { + String handle, + String callerName, + Map additionalData) { Log.d(TAG, "Called displayIncomingCall"); if (!isConnectionServiceAvailable() || !hasPhoneAccount()) { return; @@ -344,7 +351,6 @@ private static void displayIncomingCallImpl(String uuid, Log.d(TAG, "Finished displayIncomingCall"); } - private void answerIncomingCall(String uuid) { if (!isConnectionServiceAvailable() || !hasPhoneAccount()) { return; @@ -358,12 +364,11 @@ private void answerIncomingCall(String uuid) { conn.onAnswer(); } - @SuppressLint("MissingPermission") private void startCall(String uuid, - String handle, - String callerName, - Map additionalData) { + String handle, + String callerName, + Map additionalData) { if (!isConnectionServiceAvailable() || !hasPhoneAccount() || !hasPermissions() || handle == null) { return; } @@ -381,7 +386,7 @@ private void startCall(String uuid, telecomManager.placeCall(uri, extras); } - private static String getHandleSchema() { + private static String getHandleSchema() { if (settings == null || settings.isNull("handleSchema")) { return PhoneAccount.SCHEME_TEL; } else { @@ -389,7 +394,8 @@ private static String getHandleSchema() { } } - private static Bundle createCallBundle(String uuid, String handle, String callerName, Map additionalData) { + private static Bundle createCallBundle(String uuid, String handle, String callerName, + Map additionalData) { Bundle extras = new Bundle(); extras.putString(EXTRA_CALL_UUID, uuid); extras.putString(EXTRA_CALLER_NAME, callerName); @@ -415,7 +421,6 @@ private void endCall(String uuid) { Log.d(TAG, "endCall executed"); } - private void endAllCalls() { Log.d(TAG, "endAllCalls called"); if (!isConnectionServiceAvailable() || !hasPhoneAccount()) { @@ -427,10 +432,10 @@ private void endAllCalls() { Log.d(TAG, "endAllCalls executed"); } - private void requestPermissions(ConstraintsArray additionalPermissions, @NonNull MethodChannel.Result result) { if (!isConnectionServiceAvailable()) { - result.error(E_CONNECTION_SERVICE_NOT_AVAILABLE, "ConnectionService not available for this version of Android.", null); + result.error(E_CONNECTION_SERVICE_NOT_AVAILABLE, + "ConnectionService not available for this version of Android.", null); return; } @@ -448,8 +453,7 @@ private void requestPermissions(ConstraintsArray additionalPermissions, @NonNull currentActivity, allPermissions.toArray(new String[0]), grantedPermissions -> result.success(grantedPermissions.size() == allPermissions.size()), - failedPermissions -> result.success(false) - ); + failedPermissions -> result.success(false)); } else { result.success(true); } @@ -474,7 +478,6 @@ private void checkDefaultPhoneAccount(@NonNull MethodChannel.Result result) { result.success(!hasSim || !hasDefaultAccount); } - private void setOnHold(String uuid, Boolean shouldHold) { Connection conn = VoiceConnectionService.getConnection(uuid); if (conn == null) { @@ -488,7 +491,6 @@ private void setOnHold(String uuid, Boolean shouldHold) { } } - private void reportEndCallWithUUID(String uuid, Integer reason, Boolean notify) { if (!isConnectionServiceAvailable() || !hasPhoneAccount()) { return; @@ -513,7 +515,6 @@ private void reportStartedCallWithUUID(String uuid) { conn.onStarted(); } - private void rejectCall(String uuid) { if (!isConnectionServiceAvailable() || !hasPhoneAccount()) { return; @@ -527,27 +528,40 @@ private void rejectCall(String uuid) { conn.onReject(); } - private void setMutedCall(String uuid, Boolean shouldMute) { VoiceConnection conn = VoiceConnectionService.getConnection(uuid); if (conn == null) { return; } - //if the requester wants to mute, do that. otherwise unmute + // if the requester wants to mute, do that. otherwise unmute conn.setMuted(Boolean.TRUE.equals(shouldMute)); } + private void setSpeaker(String uuid, Boolean isOn) { + VoiceConnection conn = VoiceConnectionService.getConnection(uuid); + if (conn == null) { + return; + } + // Toggle route + int newRoute; + if (!isOn) { + newRoute = CallAudioState.ROUTE_EARPIECE; + } else { + newRoute = CallAudioState.ROUTE_SPEAKER; + } + // Set new audio route + conn.setAudioRoute(newRoute); + } private void setCallAudio(String uuid, Integer audioRoute) { VoiceConnection conn = VoiceConnectionService.getConnection(uuid); if (conn == null) { return; } - //if the requester wants to mute, do that. otherwise unmute + // if the requester wants to mute, do that. otherwise unmute conn.setAudio(audioRoute); } - private void sendDTMF(String uuid, String key) { Connection conn = VoiceConnectionService.getConnection(uuid); if (conn == null) { @@ -565,18 +579,15 @@ private void updateDisplay(String uuid, String callerName, String handle) { conn.updateDisplay(callerName, handle); } - private void hasPhoneAccount(@NonNull MethodChannel.Result result) { ensureTelecomManagerInitialize(getAppContext()); result.success(hasPhoneAccount()); } - private void hasOutgoingCall(@NonNull MethodChannel.Result result) { result.success(VoiceConnectionService.hasOutgoingCall); } - private void hasPermissions(@NonNull MethodChannel.Result result) { result.success(this.hasPermissions()); } @@ -600,17 +611,14 @@ private void activeCalls(@NonNull MethodChannel.Result result) { result.success(VoiceConnectionService.getActiveConnections()); } - private void setAvailable(Boolean active) { VoiceConnectionService.setAvailable(active); } - private void setReachable(Boolean active) { VoiceConnectionService.setReachable(active); } - private void setCurrentCallActive(String uuid) { VoiceConnection conn = VoiceConnectionService.getConnection(uuid); if (conn == null) { @@ -647,12 +655,12 @@ private static Boolean isConnectionServiceAvailable() { return Build.VERSION.SDK_INT >= 23; } - @SuppressLint("WrongConstant") private void backToForeground(@NonNull MethodChannel.Result result) { Context context = getAppContext(); String packageName = context.getPackageName(); - Intent focusIntent = Objects.requireNonNull(context.getPackageManager().getLaunchIntentForPackage(packageName)).cloneFilter(); + Intent focusIntent = Objects.requireNonNull(context.getPackageManager().getLaunchIntentForPackage(packageName)) + .cloneFilter(); Activity activity = this.currentActivity; boolean isOpened = activity != null; Log.d(TAG, "backToForeground, app isOpened ?" + (isOpened ? "true" : "false")); @@ -683,7 +691,8 @@ private static void registerPhoneAccount(Context appContext, ConstraintsMap opti builder.setCapabilities(capabilities); if (!options.isNull("imageName")) { - int identifier = appContext.getResources().getIdentifier(settings.getString("imageName"), "drawable", appContext.getPackageName()); + int identifier = appContext.getResources().getIdentifier(settings.getString("imageName"), "drawable", + appContext.getPackageName()); Icon icon = Icon.createWithResource(appContext, identifier); builder.setIcon(icon); } @@ -734,9 +743,11 @@ private Boolean hasPermissions() { } private static boolean hasPhoneAccount() { - if (telecomManager == null) return false; + if (telecomManager == null) + return false; PhoneAccount phoneAccount = telecomManager.getPhoneAccount(accountHandle); - if (phoneAccount == null) return false; + if (phoneAccount == null) + return false; return phoneAccount.isEnabled(); } @@ -765,7 +776,6 @@ private Context getAppContext() { return this.context.getApplicationContext(); } - private void requestPermissions( Activity activity, final String[] permissions, @@ -833,7 +843,7 @@ private static void fetchStoredSettings(Context context) { JSONObject jsonObject = new JSONObject(jsonString); settings = MapUtils.convertJsonToMap(jsonObject); } - } catch(JSONException e) { + } catch (JSONException e) { Log.w(TAG, "[CallKeepModule][fetchStoredSettings] exception: " + e); } } @@ -918,12 +928,16 @@ public void onReceive(Context context, Intent intent) { sendEventToFlutter("CallKeepCheckReachability", args); break; case ACTION_WAKE_APP: - Intent headlessIntent = new Intent(CallKeepModule.this.context, CallKeepBackgroundMessagingService.class); + Intent headlessIntent = new Intent(CallKeepModule.this.context, + CallKeepBackgroundMessagingService.class); headlessIntent.putExtra("callUUID", (String) attributeMap.get(EXTRA_CALL_UUID)); headlessIntent.putExtra("name", (String) attributeMap.get(EXTRA_CALLER_NAME)); headlessIntent.putExtra("handle", (String) attributeMap.get(EXTRA_CALL_NUMBER)); headlessIntent.putExtra("additionalData", (HashMap) attributeMap.get(EXTRA_CALL_DATA)); - Log.d(TAG, "wakeUpApplication: " + attributeMap.get(EXTRA_CALL_UUID) + ", number : " + attributeMap.get(EXTRA_CALL_NUMBER) + ", displayName:" + attributeMap.get(EXTRA_CALLER_NAME)); + Log.d(TAG, + "wakeUpApplication: " + attributeMap.get(EXTRA_CALL_UUID) + ", number : " + + attributeMap.get(EXTRA_CALL_NUMBER) + ", displayName:" + + attributeMap.get(EXTRA_CALLER_NAME)); ComponentName name = CallKeepModule.this.context.startService(headlessIntent); if (name != null) { diff --git a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java index 0b477725..c5a9423a 100644 --- a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java +++ b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java @@ -70,6 +70,7 @@ import io.wazo.callkeep.utils.ConstraintsMap; // @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionService.java +@RequiresApi(api = Build.VERSION_CODES.M) public class VoiceConnectionService extends ConnectionService { private static Boolean isAvailable; private static Boolean isInitialized; @@ -275,6 +276,7 @@ private boolean wakeAndCheckAvailability(Bundle callExtras, Boolean forceWakeUp) return false; } + @RequiresApi(api = Build.VERSION_CODES.R) private void startForegroundService() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // Foreground services not required before SDK 28 @@ -324,7 +326,7 @@ private void startForegroundService() { if (!foregroundSettings.isNull("notificationId")) { notificationId = foregroundSettings.getInt("notificationId"); } - startForeground(notificationId, notification); + startForeground(notificationId, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE); } @RequiresApi(api = Build.VERSION_CODES.N) diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m index 89ba88ac..fda50250 100644 --- a/example/ios/Runner/AppDelegate.m +++ b/example/ios/Runner/AppDelegate.m @@ -1,6 +1,6 @@ #import "AppDelegate.h" #import "GeneratedPluginRegistrant.h" -#import +#import @implementation AppDelegate diff --git a/example/lib/main.dart b/example/lib/main.dart index 0e7f6d1b..62ebef6c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; -import 'package:callkeep/callkeep.dart'; +import 'package:januscaler_callkeep/januscaler_callkeep.dart'; import 'package:logger/logger.dart'; import 'package:uuid/uuid.dart'; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 53188d1a..844450d4 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -10,7 +10,7 @@ environment: sdk: ">=2.12.2 <4.0.0" dependencies: - callkeep: + januscaler_callkeep: # When depending on this package from a real application you should use: # callkeep: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index 747fa191..00000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_callkeep_example/main.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => - widget is Text && widget.data!.startsWith('Running on:'), - ), - findsOneWidget, - ); - }); -} diff --git a/ios/Classes/CallKeep.h b/ios/Classes/CallKeep.h index b5e4749a..77ec712a 100644 --- a/ios/Classes/CallKeep.h +++ b/ios/Classes/CallKeep.h @@ -20,6 +20,7 @@ static NSString *_Nonnull const CallKeepPerformEndCallAction = @"CallKeepPerform static NSString *_Nonnull const CallKeepDidActivateAudioSession = @"CallKeepDidActivateAudioSession"; static NSString *_Nonnull const CallKeepDidDeactivateAudioSession = @"CallKeepDidDeactivateAudioSession"; static NSString *_Nonnull const CallKeepDidDisplayIncomingCall = @"CallKeepDidDisplayIncomingCall"; +static NSString *_Nonnull const CallKeepPushPayload = @"CallKeepPushPayload"; static NSString *_Nonnull const CallKeepDidFailCallAction = @"CallKeepDidFailCallAction"; static NSString *_Nonnull const CallKeepDidPerformSetMutedCallAction = @"CallKeepDidPerformSetMutedCallAction"; static NSString *_Nonnull const CallKeepPerformPlayDTMFCallAction = @"CallKeepDidPerformDTMFAction"; @@ -37,7 +38,7 @@ static NSString *_Nonnull const CallKeepActionEnd = @"CallKeepActionEnd"; @property (nonatomic, strong, nullable) FlutterMethodChannel* eventChannel; - (BOOL)handleMethodCall:(FlutterMethodCall* _Nonnull)call result:(FlutterResult _Nonnull )result; - +- (void)setSpeaker:(NSString *)uuidString isOn:(BOOL)isOn; + (BOOL)application:(UIApplication * _Nonnull)application openURL:(NSURL * _Nonnull)url options:(NSDictionary * _Nonnull)options NS_AVAILABLE_IOS(9_0); diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m index 721c12d3..34a4ece6 100644 --- a/ios/Classes/CallKeep.m +++ b/ios/Classes/CallKeep.m @@ -5,936 +5,1112 @@ // Copyright 2016-2019 The CallKeep Authors (see the AUTHORS file) // SPDX-License-Identifier: ISC, MIT // -#import #import +#import #import "CallKeep.h" -@implementation CallKeep -{ - NSOperatingSystemVersion _version; - bool _hasListeners; - NSMutableArray *_delayedEvents; +@implementation CallKeep { + NSOperatingSystemVersion _version; + bool _hasListeners; + NSMutableArray *_delayedEvents; } -- (FlutterMethodChannel *)eventChannel -{ - return objc_getAssociatedObject(self, _cmd); +- (FlutterMethodChannel *)eventChannel { + return objc_getAssociatedObject(self, _cmd); } -- (void)setEventChannel:(FlutterMethodChannel *)eventChannel -{ - objc_setAssociatedObject(self, @selector(eventChannel), eventChannel, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +- (void)setEventChannel:(FlutterMethodChannel *)eventChannel { + objc_setAssociatedObject(self, @selector(eventChannel), eventChannel, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -static CXProvider* sharedProvider; +static CXProvider *sharedProvider; static NSDictionary *settings; -static NSObject* _delegate; +static NSObject *_delegate; -- (instancetype)init -{ +- (instancetype)init { #ifdef DEBUG - NSLog(@"[CallKeep][init]"); + NSLog(@"[CallKeep][init]"); #endif - if (self = [super init]) { - _delayedEvents = [NSMutableArray array]; - } - return self; + if (self = [super init]) { + _delayedEvents = [NSMutableArray array]; + } + return self; } + (id)allocWithZone:(NSZone *)zone { - static CallKeep *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [super allocWithZone:zone]; - }); - return sharedInstance; + static CallKeep *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [super allocWithZone:zone]; + }); + return sharedInstance; } -- (void)dealloc -{ +- (void)dealloc { #ifdef DEBUG - NSLog(@"[CallKeep][dealloc]"); + NSLog(@"[CallKeep][dealloc]"); #endif - [[NSNotificationCenter defaultCenter] removeObserver:self]; - - if (self.callKeepProvider != nil) { - [self.callKeepProvider invalidate]; - } - sharedProvider = nil; -} - -- (BOOL)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - NSString *method = call.method; - NSDictionary* argsMap = call.arguments; - if ([@"setup" isEqualToString:method]) { - [self setup:argsMap[@"options"]]; - result(nil); - } else if ([@"displayIncomingCall" isEqualToString:method]) { - [self displayIncomingCall:argsMap[@"uuid"] handle:argsMap[@"handle"] handleType:argsMap[@"handleType"] hasVideo:[argsMap[@"hasVideo"] boolValue] callerName:argsMap[@"callerName"] payload:argsMap[@"additionalData"]]; - result(nil); - } else if ([@ "startCall" isEqualToString:method]) { - [self startCall:argsMap[@"uuid"] handle:argsMap[@"handle"] callerName:argsMap[@"callerName"] handleType:argsMap[@"handleType"] video:[argsMap[@"hasVideo"] boolValue]]; - result(nil); - } else if ([@"isCallActive" isEqualToString:method]) { - result(@([self isCallActive:argsMap[@"uuid"]])); - } else if ([@"activeCalls" isEqualToString:method]) { - result([self activeCalls]); - } else if ([@"answerIncomingCall" isEqualToString:method]) { - [self answerIncomingCall:argsMap[@"uuid"]]; - result(nil); - } else if ([@"endCall" isEqualToString:method]) { - [self endCall:argsMap[@"uuid"]]; - result(nil); - } else if ([@"endAllCalls" isEqualToString:method]) { - [self endAllCalls]; - result(nil); - } else if ([@ "setOnHold" isEqualToString:method]) { - [self setOnHold:argsMap[@"uuid"] shouldHold:[argsMap[@"hold"] boolValue]]; - result(nil); - } else if ([@ "reportEndCallWithUUID" isEqualToString:method]) { - [self reportEndCallWithUUID:argsMap[@"uuid"] reason:[argsMap[@"reason"] intValue]]; - result(nil); - } else if ([@"setMutedCall" isEqualToString:method]) { - [self setMutedCall:argsMap[@"uuid"] muted:[argsMap[@"muted"] boolValue]]; - result(nil); - } else if ([@ "sendDTMF" isEqualToString:method]) { - [self sendDTMF:argsMap[@"uuid"] dtmf:argsMap[@"key"]]; - result(nil); - } else if ([@ "updateDisplay" isEqualToString:method]) { - [self updateDisplay:argsMap[@"uuid"] callerName:argsMap[@"callerName"] uri:argsMap[@"handle"]]; - result(nil); - } else if([@ "checkIfBusy" isEqualToString:method]){ - [self checkIfBusyWithResult:result]; - } else if([@ "checkSpeaker" isEqualToString:method]){ - [self checkSpeakerResult:result]; - } else if ([@"reportConnectingOutgoingCallWithUUID" isEqualToString:method]) { - [self reportConnectingOutgoingCallWithUUID:argsMap[@"uuid"]]; - } else if ([@"reportConnectedOutgoingCallWithUUID" isEqualToString:method]) { - [self reportConnectedOutgoingCallWithUUID:argsMap[@"uuid"]]; - } else if([@"reportUpdatedCall" isEqualToString:method]){ - [self reportUpdatedCall:argsMap[@"uuid"] contactIdentifier:argsMap[@"callerName"]]; - result(nil); - } else { - return NO; - } - return YES; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + if (self.callKeepProvider != nil) { + [self.callKeepProvider invalidate]; + } + sharedProvider = nil; } -- (void)startObserving -{ - _hasListeners = YES; - if ([_delayedEvents count] > 0) { - [self sendEventWithName:CallKeepDidLoadWithEvents body:_delayedEvents]; - } +- (BOOL)handleMethodCall:(FlutterMethodCall *)call + result:(FlutterResult)result { + NSString *method = call.method; + NSDictionary *argsMap = call.arguments; + if ([@"setup" isEqualToString:method]) { + [self setup:argsMap[@"options"]]; + result(nil); + } else if ([@"displayIncomingCall" isEqualToString:method]) { + [self displayIncomingCall:argsMap[@"uuid"] + handle:argsMap[@"handle"] + handleType:argsMap[@"handleType"] + hasVideo:[argsMap[@"hasVideo"] boolValue] + callerName:argsMap[@"callerName"] + payload:argsMap[@"additionalData"]]; + result(nil); + } else if ([@ "startCall" isEqualToString:method]) { + [self startCall:argsMap[@"uuid"] + handle:argsMap[@"handle"] + callerName:argsMap[@"callerName"] + handleType:argsMap[@"handleType"] + video:[argsMap[@"hasVideo"] boolValue]]; + result(nil); + } else if ([@"isCallActive" isEqualToString:method]) { + result(@([self isCallActive:argsMap[@"uuid"]])); + } else if ([@"activeCalls" isEqualToString:method]) { + result([self activeCalls]); + } else if ([@"answerIncomingCall" isEqualToString:method]) { + [self answerIncomingCall:argsMap[@"uuid"]]; + result(nil); + } else if ([@"endCall" isEqualToString:method]) { + [self endCall:argsMap[@"uuid"]]; + result(nil); + } else if ([@"endAllCalls" isEqualToString:method]) { + [self endAllCalls]; + result(nil); + } else if ([@ "setOnHold" isEqualToString:method]) { + [self setOnHold:argsMap[@"uuid"] shouldHold:[argsMap[@"hold"] boolValue]]; + result(nil); + } else if ([@ "reportEndCallWithUUID" isEqualToString:method]) { + [self reportEndCallWithUUID:argsMap[@"uuid"] + reason:[argsMap[@"reason"] intValue]]; + result(nil); + } else if ([@"setMutedCall" isEqualToString:method]) { + [self setMutedCall:argsMap[@"uuid"] muted:[argsMap[@"muted"] boolValue]]; + result(nil); + } else if ([@"setSpeaker" isEqualToString:method]) { + [self setSpeaker:argsMap[@"uuid"] isOn:[argsMap[@"isOn"] boolValue]]; + result(nil); + } else if ([@ "sendDTMF" isEqualToString:method]) { + [self sendDTMF:argsMap[@"uuid"] dtmf:argsMap[@"key"]]; + result(nil); + } else if ([@ "updateDisplay" isEqualToString:method]) { + [self updateDisplay:argsMap[@"uuid"] + callerName:argsMap[@"callerName"] + uri:argsMap[@"handle"]]; + result(nil); + } else if ([@ "checkIfBusy" isEqualToString:method]) { + [self checkIfBusyWithResult:result]; + } else if ([@ "checkSpeaker" isEqualToString:method]) { + [self checkSpeakerResult:result]; + } else if ([@"reportConnectingOutgoingCallWithUUID" isEqualToString:method]) { + [self reportConnectingOutgoingCallWithUUID:argsMap[@"uuid"]]; + } else if ([@"reportConnectedOutgoingCallWithUUID" isEqualToString:method]) { + [self reportConnectedOutgoingCallWithUUID:argsMap[@"uuid"]]; + } else if ([@"reportUpdatedCall" isEqualToString:method]) { + [self reportUpdatedCall:argsMap[@"uuid"] + contactIdentifier:argsMap[@"callerName"]]; + result(nil); + } else { + return NO; + } + return YES; } -- (void)stopObserving -{ - _hasListeners = FALSE; +- (void)startObserving { + _hasListeners = YES; + if ([_delayedEvents count] > 0) { + [self sendEventWithName:CallKeepDidLoadWithEvents body:_delayedEvents]; + } +} + +- (void)stopObserving { + _hasListeners = FALSE; } - (void)sendEventWithName:(NSString *)name body:(id)body { - [self.eventChannel invokeMethod:name arguments:body]; + [self.eventChannel invokeMethod:name arguments:body]; } -- (void)sendEventWithNameWrapper:(NSString *)name body:(NSDictionary*)body { - [self sendEventWithName:name body:body]; - if (_delegate && [_delegate respondsToSelector:@selector(onCallEvent:withCallData:)]) { - [_delegate onCallEvent:name withCallData:body]; - } +- (void)sendEventWithNameWrapper:(NSString *)name body:(NSDictionary *)body { + [self sendEventWithName:name body:body]; + if (_delegate && [_delegate respondsToSelector:@selector(onCallEvent: + withCallData:)]) { + [_delegate onCallEvent:name withCallData:body]; + } } + (void)initCallKitProvider { - if (settings == nil) { - settings = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"CallKeepSettings"]; - } - if (sharedProvider == nil) { - sharedProvider = [[CXProvider alloc] initWithConfiguration:[CallKeep getProviderConfiguration:settings]]; - } + if (settings == nil) { + settings = [[NSUserDefaults standardUserDefaults] + dictionaryForKey:@"CallKeepSettings"]; + } + if (sharedProvider == nil) { + sharedProvider = [[CXProvider alloc] + initWithConfiguration:[CallKeep getProviderConfiguration:settings]]; + } } --(void)setup:(NSDictionary *)options -{ +- (void)setup:(NSDictionary *)options { #ifdef DEBUG - NSLog(@"[CallKeep][setup] options = %@", options); + NSLog(@"[CallKeep][setup] options = %@", options); #endif - _version = [[[NSProcessInfo alloc] init] operatingSystemVersion]; - self.callKeepCallController = [[CXCallController alloc] init]; - NSMutableDictionary* _settings = [[NSMutableDictionary alloc] initWithDictionary:options]; - NSEnumerator *enumerator = [options keyEnumerator]; - id key; - while ((key = [enumerator nextObject])) { - id tmp = [options objectForKey:key]; - if ([tmp isKindOfClass:[NSString class]] || [tmp isKindOfClass:[NSNumber class]]) { - _settings[key] = tmp; - } else { - _settings[key] = [tmp description]; - } + _version = [[[NSProcessInfo alloc] init] operatingSystemVersion]; + self.callKeepCallController = [[CXCallController alloc] init]; + NSMutableDictionary *_settings = + [[NSMutableDictionary alloc] initWithDictionary:options]; + NSEnumerator *enumerator = [options keyEnumerator]; + id key; + while ((key = [enumerator nextObject])) { + id tmp = [options objectForKey:key]; + if ([tmp isKindOfClass:[NSString class]] || + [tmp isKindOfClass:[NSNumber class]]) { + _settings[key] = tmp; + } else { + _settings[key] = [tmp description]; } - settings = _settings; - // Store settings in NSUserDefault - [[NSUserDefaults standardUserDefaults] setObject:settings forKey:@"CallKeepSettings"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - - [CallKeep initCallKitProvider]; - - self.callKeepProvider = sharedProvider; - [self.callKeepProvider setDelegate:self queue:nil]; - [self voipRegistration]; + } + settings = _settings; + // Store settings in NSUserDefault + [[NSUserDefaults standardUserDefaults] setObject:settings + forKey:@"CallKeepSettings"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [CallKeep initCallKitProvider]; + + self.callKeepProvider = sharedProvider; + [self.callKeepProvider setDelegate:self queue:nil]; + [self voipRegistration]; } #pragma mark - PushKit -+ (void)setDelegate:(NSObject* _Nullable)delegate { - _delegate = delegate; ++ (void)setDelegate:(NSObject *_Nullable)delegate { + _delegate = delegate; } --(void)voipRegistration -{ - PKPushRegistry* voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; - voipRegistry.delegate = self; - voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; +- (void)voipRegistration { + PKPushRegistry *voipRegistry = + [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; + voipRegistry.delegate = self; + voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; } -- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type { - const unsigned *tokenBytes = [pushCredentials.token bytes]; - NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x", - ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]), - ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]), - ntohl(tokenBytes[6]), ntohl(tokenBytes[7])]; - - NSLog(@"\n[VoIP Token]: %@\n\n",hexToken); - - [self sendEventWithNameWrapper:CallKeepPushKitToken body:@{ @"token": hexToken }]; +- (void)pushRegistry:(PKPushRegistry *)registry + didUpdatePushCredentials:(PKPushCredentials *)pushCredentials + forType:(PKPushType)type { + const unsigned *tokenBytes = [pushCredentials.token bytes]; + NSString *hexToken = + [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x", + ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), + ntohl(tokenBytes[2]), ntohl(tokenBytes[3]), + ntohl(tokenBytes[4]), ntohl(tokenBytes[5]), + ntohl(tokenBytes[6]), ntohl(tokenBytes[7])]; + + NSLog(@"\n[VoIP Token]: %@\n\n", hexToken); + + [self sendEventWithNameWrapper:CallKeepPushKitToken + body:@{@"token" : hexToken}]; } - (NSString *)createUUID { - CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault); - NSString *uuidStr = (NSString *)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuidObject)); - CFRelease(uuidObject); - return [uuidStr lowercaseString]; -} - - -- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(nonnull void (^)(void))completion { - // Process the received push - NSLog(@"didReceiveIncomingPushWithPayload payload = %@", payload.type); - /* payload example. - { - "uuid": "xxxxx-xxxxx-xxxxx-xxxxx", - "caller_id": "+8618612345678", - "caller_name": "hello", - "caller_id_type": "number", - "has_video": false, - } - */ - - NSDictionary *dic = payload.dictionaryPayload; - - if (_delegate) { - dic = [_delegate mapPushPayload:dic]; - } - - if (!dic || dic[@"aps"] != nil) { - NSLog(@"Do not use the 'alert' format for push type %@.", payload.type); - if(completion != nil) { - completion(); - } - return; - } - - NSString *uuid = dic[@"uuid"]; - NSString *callerId = dic[@"caller_id"]; - NSString *callerName = dic[@"caller_name"]; - BOOL hasVideo = [dic[@"has_video"] boolValue]; - NSString *callerIdType = dic[@"caller_id_type"]; - - - if( uuid == nil) { - uuid = [self createUUID]; - } - - NSLog(@"Got here %@.", [dic description]); - - [CallKeep reportNewIncomingCall:uuid - handle:callerId - handleType:callerIdType - hasVideo:hasVideo - callerName:callerName - fromPushKit:YES - payload:dic - withCompletionHandler:completion]; + CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault); + NSString *uuidStr = (NSString *)CFBridgingRelease( + CFUUIDCreateString(kCFAllocatorDefault, uuidObject)); + CFRelease(uuidObject); + return [uuidStr lowercaseString]; } -- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type { - [self pushRegistry:registry didReceiveIncomingPushWithPayload:payload forType:type withCompletionHandler:^(){ - NSLog(@"[CallKeep] received"); - }]; +- (void)pushRegistry:(PKPushRegistry *)registry + didReceiveIncomingPushWithPayload:(PKPushPayload *)payload + forType:(PKPushType)type + withCompletionHandler:(nonnull void (^)(void))completion { + // Process the received push + NSLog(@"didReceiveIncomingPushWithPayload payload = %@", payload.type); + /* payload example. + { + "uuid": "xxxxx-xxxxx-xxxxx-xxxxx", + "caller_id": "+8618612345678", + "caller_name": "hello", + "caller_id_type": "number", + "has_video": false, + } + */ + + NSDictionary *dic = payload.dictionaryPayload; + + if (_delegate) { + dic = [_delegate mapPushPayload:dic]; + } + + CallKeep *callKeep = [CallKeep allocWithZone:nil]; + [callKeep sendEventWithNameWrapper:CallKeepPushPayload + body:dic]; + + if (!dic || dic[@"aps"] != nil) { + NSLog(@"Do not use the 'alert' format for push type %@.", payload.type); + if (completion != nil) { + completion(); + } + return; + } + + NSString *uuid = dic[@"uuid"]; + NSString *callerId = dic[@"caller_id"]; + NSString *callerName = dic[@"caller_name"]; + BOOL hasVideo = [dic[@"has_video"] boolValue]; + NSString *callerIdType = dic[@"caller_id_type"]; + + if (uuid == nil) { + uuid = [self createUUID]; + } + + NSLog(@"Got here %@.", [dic description]); + + [CallKeep reportNewIncomingCall:uuid + handle:callerId + handleType:callerIdType + hasVideo:hasVideo + callerName:callerName + fromPushKit:YES + payload:dic + withCompletionHandler:completion]; } +- (void)pushRegistry:(PKPushRegistry *)registry + didReceiveIncomingPushWithPayload:(PKPushPayload *)payload + forType:(NSString *)type { + [self pushRegistry:registry + didReceiveIncomingPushWithPayload:payload + forType:type + withCompletionHandler:^() { + NSLog(@"[CallKeep] received"); + + }]; +} --(void) checkIfBusyWithResult:(FlutterResult)result -{ +- (void)checkIfBusyWithResult:(FlutterResult)result { #ifdef DEBUG - NSLog(@"[CallKeep][checkIfBusy]"); + NSLog(@"[CallKeep][checkIfBusy]"); #endif - result(@(self.callKeepCallController.callObserver.calls.count > 0)); + result(@(self.callKeepCallController.callObserver.calls.count > 0)); } --(void) checkSpeakerResult:(FlutterResult)result -{ +- (void)checkSpeakerResult:(FlutterResult)result { #ifdef DEBUG - NSLog(@"[CallKeep][checkSpeaker]"); + NSLog(@"[CallKeep][checkSpeaker]"); #endif - NSString *output = [AVAudioSession sharedInstance].currentRoute.outputs.count > 0 ? [AVAudioSession sharedInstance].currentRoute.outputs[0].portType : nil; - result(@([output isEqualToString:@"Speaker"])); + NSString *output = + [AVAudioSession sharedInstance].currentRoute.outputs.count > 0 + ? [AVAudioSession sharedInstance].currentRoute.outputs[0].portType + : nil; + result(@([output isEqualToString:@"Speaker"])); } #pragma mark - CXCallController call actions // Display the incoming call to the user --(void) displayIncomingCall:(NSString *)uuidString +- (void)displayIncomingCall:(NSString *)uuidString handle:(NSString *)handle handleType:(NSString *)handleType hasVideo:(BOOL)hasVideo - callerName:(NSString * _Nullable)callerName - payload:(NSDictionary * _Nullable)payload -{ - [CallKeep reportNewIncomingCall: uuidString handle:handle handleType:handleType hasVideo:hasVideo callerName:callerName fromPushKit: NO payload:payload withCompletionHandler:nil]; + callerName:(NSString *_Nullable)callerName + payload:(NSDictionary *_Nullable)payload { + [CallKeep reportNewIncomingCall:uuidString + handle:handle + handleType:handleType + hasVideo:hasVideo + callerName:callerName + fromPushKit:NO + payload:payload + withCompletionHandler:nil]; } --(void) startCall:(NSString *)uuidString +- (void)startCall:(NSString *)uuidString handle:(NSString *)handle - callerName:(NSString * _Nullable)callerName + callerName:(NSString *_Nullable)callerName handleType:(NSString *)handleType - video:(BOOL)video -{ -#ifdef DEBUG - NSLog(@"[CallKeep][startCall] uuidString = %@", uuidString); -#endif - int _handleType = [CallKeep getHandleType:handleType]; - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXHandle *callHandle = [[CXHandle alloc] initWithType:_handleType value:handle]; - CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle]; - [startCallAction setVideo:video]; - [startCallAction setContactIdentifier:callerName]; - - CXTransaction *transaction = [[CXTransaction alloc] initWithAction:startCallAction]; - [self requestTransaction:transaction withSuccessListener:^(CXAction* action) { - // CXStartCallAction - if ([action isKindOfClass:[CXStartCallAction class]]) { - CXStartCallAction *startCallAction = (CXStartCallAction *)action; - CXCallUpdate* callUpdate = [CallKeep createCallUpdate]; - callUpdate.remoteHandle = startCallAction.handle; - callUpdate.hasVideo = startCallAction.video; - callUpdate.localizedCallerName = startCallAction.contactIdentifier; - [sharedProvider reportCallWithUUID:startCallAction.callUUID updated:callUpdate]; - } - }]; -} - --(void) answerIncomingCall:(NSString *)uuidString -{ -#ifdef DEBUG - NSLog(@"[CallKeep][answerIncomingCall] uuidString = %@", uuidString); -#endif - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXAnswerCallAction *answerCallAction = [[CXAnswerCallAction alloc] initWithCallUUID:uuid]; - CXTransaction *transaction = [[CXTransaction alloc] initWithAction:answerCallAction]; - - [self requestTransaction:transaction]; + video:(BOOL)video { +#ifdef DEBUG + NSLog(@"[CallKeep][startCall] uuidString = %@", uuidString); +#endif + int _handleType = [CallKeep getHandleType:handleType]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXHandle *callHandle = [[CXHandle alloc] initWithType:_handleType + value:handle]; + CXStartCallAction *startCallAction = + [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle]; + [startCallAction setVideo:video]; + [startCallAction setContactIdentifier:callerName]; + + CXTransaction *transaction = + [[CXTransaction alloc] initWithAction:startCallAction]; + [self requestTransaction:transaction + withSuccessListener:^(CXAction *action) { + // CXStartCallAction + if ([action isKindOfClass:[CXStartCallAction class]]) { + CXStartCallAction *startCallAction = (CXStartCallAction *)action; + CXCallUpdate *callUpdate = [CallKeep createCallUpdate]; + callUpdate.remoteHandle = startCallAction.handle; + callUpdate.hasVideo = startCallAction.video; + callUpdate.localizedCallerName = startCallAction.contactIdentifier; + [sharedProvider reportCallWithUUID:startCallAction.callUUID + updated:callUpdate]; + } + }]; } --(void) endCall:(NSString *)uuidString -{ +- (void)answerIncomingCall:(NSString *)uuidString { #ifdef DEBUG - NSLog(@"[CallKeep][endCall] uuidString = %@", uuidString); + NSLog(@"[CallKeep][answerIncomingCall] uuidString = %@", uuidString); #endif - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid]; - CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction]; - - [self requestTransaction:transaction]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXAnswerCallAction *answerCallAction = + [[CXAnswerCallAction alloc] initWithCallUUID:uuid]; + CXTransaction *transaction = + [[CXTransaction alloc] initWithAction:answerCallAction]; + + [self requestTransaction:transaction]; } --(void) endAllCalls -{ +- (void)endCall:(NSString *)uuidString { #ifdef DEBUG - NSLog(@"[CallKeep][endAllCalls] calls = %@", self.callKeepCallController.callObserver.calls); + NSLog(@"[CallKeep][endCall] uuidString = %@", uuidString); #endif - for (CXCall *call in self.callKeepCallController.callObserver.calls) { - CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:call.UUID]; - CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction]; - [self requestTransaction:transaction]; - } + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXEndCallAction *endCallAction = + [[CXEndCallAction alloc] initWithCallUUID:uuid]; + CXTransaction *transaction = + [[CXTransaction alloc] initWithAction:endCallAction]; + + [self requestTransaction:transaction]; } --(NSArray*)activeCalls -{ +- (void)endAllCalls { #ifdef DEBUG - NSLog(@"[CallKeep][activeCalls]"); + NSLog(@"[CallKeep][endAllCalls] calls = %@", + self.callKeepCallController.callObserver.calls); #endif - CXCallObserver *callObserver = [[CXCallObserver alloc] init]; - - NSMutableString *uuids = [NSMutableString string]; - - for(CXCall *call in callObserver.calls){ - NSLog(@"[CallKeep] activeCall %@ ", call.UUID); - NSString *uuid = [call.UUID UUIDString]; - [uuids appendString: uuid]; - } - - return [NSArray arrayWithObject:uuids]; + for (CXCall *call in self.callKeepCallController.callObserver.calls) { + CXEndCallAction *endCallAction = + [[CXEndCallAction alloc] initWithCallUUID:call.UUID]; + CXTransaction *transaction = + [[CXTransaction alloc] initWithAction:endCallAction]; + [self requestTransaction:transaction]; + } } --(void) setOnHold:(NSString *)uuidString shouldHold:(BOOL)shouldHold -{ +- (NSArray *)activeCalls { #ifdef DEBUG - NSLog(@"[CallKeep][setOnHold] uuidString = %@, shouldHold = %d", uuidString, shouldHold); + NSLog(@"[CallKeep][activeCalls]"); #endif - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXSetHeldCallAction *setHeldCallAction = [[CXSetHeldCallAction alloc] initWithCallUUID:uuid onHold:shouldHold]; - CXTransaction *transaction = [[CXTransaction alloc] init]; - [transaction addAction:setHeldCallAction]; - - [self requestTransaction:transaction]; + CXCallObserver *callObserver = [[CXCallObserver alloc] init]; + + NSMutableString *uuids = [NSMutableString string]; + + for (CXCall *call in callObserver.calls) { + NSLog(@"[CallKeep] activeCall %@ ", call.UUID); + NSString *uuid = [call.UUID UUIDString]; + [uuids appendString:uuid]; + } + + return [NSArray arrayWithObject:uuids]; +} + +- (void)setOnHold:(NSString *)uuidString shouldHold:(BOOL)shouldHold { +#ifdef DEBUG + NSLog(@"[CallKeep][setOnHold] uuidString = %@, shouldHold = %d", uuidString, + shouldHold); +#endif + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXSetHeldCallAction *setHeldCallAction = + [[CXSetHeldCallAction alloc] initWithCallUUID:uuid onHold:shouldHold]; + CXTransaction *transaction = [[CXTransaction alloc] init]; + [transaction addAction:setHeldCallAction]; + + [self requestTransaction:transaction]; } --(void) reportConnectingOutgoingCallWithUUID:(NSString *)uuidString -{ - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - [self.callKeepProvider reportOutgoingCallWithUUID:uuid startedConnectingAtDate:[NSDate date]]; +- (void)reportConnectingOutgoingCallWithUUID:(NSString *)uuidString { + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + [self.callKeepProvider reportOutgoingCallWithUUID:uuid + startedConnectingAtDate:[NSDate date]]; } --(void) reportConnectedOutgoingCallWithUUID:(NSString *)uuidString -{ - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - [self.callKeepProvider reportOutgoingCallWithUUID:uuid connectedAtDate:[NSDate date]]; +- (void)reportConnectedOutgoingCallWithUUID:(NSString *)uuidString { + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + [self.callKeepProvider reportOutgoingCallWithUUID:uuid + connectedAtDate:[NSDate date]]; } --(void) reportEndCallWithUUID:(NSString *)uuidString reason:(int)reason -{ - [CallKeep endCallWithUUID: uuidString reason:reason]; +- (void)reportEndCallWithUUID:(NSString *)uuidString reason:(int)reason { + [CallKeep endCallWithUUID:uuidString reason:reason]; } --(void) updateDisplay:(NSString *)uuidString callerName:(NSString *)callerName uri:(NSString *)uri -{ +- (void)updateDisplay:(NSString *)uuidString + callerName:(NSString *)callerName + uri:(NSString *)uri { #ifdef DEBUG - NSLog(@"[CallKeep][updateDisplay] uuidString = %@ displayName = %@ uri = %@", uuidString, callerName, uri); + NSLog(@"[CallKeep][updateDisplay] uuidString = %@ displayName = %@ uri = %@", + uuidString, callerName, uri); #endif - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:uri]; - CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; - callUpdate.localizedCallerName = callerName; - callUpdate.remoteHandle = callHandle; - [self.callKeepProvider reportCallWithUUID:uuid updated:callUpdate]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber + value:uri]; + CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; + callUpdate.localizedCallerName = callerName; + callUpdate.remoteHandle = callHandle; + [self.callKeepProvider reportCallWithUUID:uuid updated:callUpdate]; } --(void) setMutedCall:(NSString *)uuidString muted:(BOOL)muted -{ +- (void)setMutedCall:(NSString *)uuidString muted:(BOOL)muted { #ifdef DEBUG - NSLog(@"[CallKeep][setMutedCall] muted = %i", muted); + NSLog(@"[CallKeep][setMutedCall] muted = %i", muted); #endif - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXSetMutedCallAction *setMutedAction = [[CXSetMutedCallAction alloc] initWithCallUUID:uuid muted:muted]; - CXTransaction *transaction = [[CXTransaction alloc] init]; - [transaction addAction:setMutedAction]; - [self requestTransaction:transaction]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXSetMutedCallAction *setMutedAction = + [[CXSetMutedCallAction alloc] initWithCallUUID:uuid muted:muted]; + CXTransaction *transaction = [[CXTransaction alloc] init]; + [transaction addAction:setMutedAction]; + [self requestTransaction:transaction]; } --(void) sendDTMF:(NSString *)uuidString dtmf:(NSString *)key -{ +- (void)setSpeaker:(NSString *)uuidString isOn:(BOOL)isOn { #ifdef DEBUG - NSLog(@"[CallKeep][sendDTMF] key = %@", key); + NSLog(@"[CallKeep][setSpeaker] isOn = %i", isOn); #endif - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXPlayDTMFCallAction *dtmfAction = [[CXPlayDTMFCallAction alloc] initWithCallUUID:uuid digits:key type:CXPlayDTMFCallActionTypeHardPause]; - CXTransaction *transaction = [[CXTransaction alloc] init]; - [transaction addAction:dtmfAction]; - - [self requestTransaction:transaction]; + + NSError *error = nil; + AVAudioSession *session = [AVAudioSession sharedInstance]; + + // Set audio session category for call use + BOOL success = [session setCategory:AVAudioSessionCategoryPlayAndRecord + withOptions:AVAudioSessionCategoryOptionAllowBluetooth + error:&error]; + if (!success) { + NSLog(@"[CallKeep][Audio] Failed to set category: %@", error); + return; + } + + // Activate audio session + success = [session setActive:YES error:&error]; + if (!success) { + NSLog(@"[CallKeep][Audio] Failed to activate session: %@", error); + return; + } + + // Route to speaker or earpiece + AVAudioSessionPortOverride override = + isOn ? AVAudioSessionPortOverrideSpeaker : AVAudioSessionPortOverrideNone; + + success = [session overrideOutputAudioPort:override error:&error]; + if (!success) { + NSLog(@"[CallKeep][Audio] Failed to override audio port: %@", error); + } } --(BOOL) isCallActive:(NSString *)uuidString -{ +- (void)sendDTMF:(NSString *)uuidString dtmf:(NSString *)key { #ifdef DEBUG - NSLog(@"[CallKeep][isCallActive] uuid = %@", uuidString); + NSLog(@"[CallKeep][sendDTMF] key = %@", key); #endif - return [CallKeep isCallActive: uuidString]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXPlayDTMFCallAction *dtmfAction = [[CXPlayDTMFCallAction alloc] + initWithCallUUID:uuid + digits:key + type:CXPlayDTMFCallActionTypeHardPause]; + CXTransaction *transaction = [[CXTransaction alloc] init]; + [transaction addAction:dtmfAction]; + + [self requestTransaction:transaction]; } -- (void)requestTransaction:(CXTransaction *)transaction -{ - [self requestTransaction:transaction withSuccessListener:nil]; +- (BOOL)isCallActive:(NSString *)uuidString { +#ifdef DEBUG + NSLog(@"[CallKeep][isCallActive] uuid = %@", uuidString); +#endif + return [CallKeep isCallActive:uuidString]; +} + +- (void)requestTransaction:(CXTransaction *)transaction { + [self requestTransaction:transaction withSuccessListener:nil]; } - (void)requestTransaction:(CXTransaction *)transaction - withSuccessListener:(void(^)(CXAction*))onSuccess -{ + withSuccessListener:(void (^)(CXAction *))onSuccess { #ifdef DEBUG - NSLog(@"[CallKeep][requestTransaction] transaction = %@", transaction); + NSLog(@"[CallKeep][requestTransaction] transaction = %@", transaction); #endif - if (self.callKeepCallController == nil) { - self.callKeepCallController = [[CXCallController alloc] init]; - } - [self.callKeepCallController requestTransaction:transaction completion:^(NSError * _Nullable error) { - if (error != nil) { - NSLog(@"[CallKeep][requestTransaction] Error requesting transaction (%@): (%@)", transaction.actions, error); - } else { - NSLog(@"[CallKeep][requestTransaction] Requested transaction successfully"); - if (onSuccess) { - onSuccess([transaction.actions firstObject]); - } - } - }]; -} - -+ (BOOL)isCallActive:(NSString *)uuidString -{ - CXCallObserver *callObserver = [[CXCallObserver alloc] init]; - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - - for(CXCall *call in callObserver.calls){ - NSLog(@"[CallKeep] isCallActive %@ %d ?", call.UUID, [call.UUID isEqual:uuid]); - if([call.UUID isEqual:[[NSUUID alloc] initWithUUIDString:uuidString]] && !call.hasConnected){ - return true; - } - } - return false; -} - -+ (void)endCallWithUUID:(NSString *)uuidString - reason:(int)reason -{ -#ifdef DEBUG - NSLog(@"[CallKeep][reportEndCallWithUUID] uuidString = %@ reason = %d", uuidString, reason); -#endif - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXCallEndedReason reasonO; - CXCallEndedReason* reasonE = nil; - switch (reason) { - case 1: - reasonO = CXCallEndedReasonFailed; - reasonE = &reasonO; - break; - case 2: - case 6: - reasonO = CXCallEndedReasonRemoteEnded; - reasonE = &reasonO; - break; - case 3: - reasonO = CXCallEndedReasonUnanswered; - reasonE = &reasonO; - break; - case 4: - reasonO = CXCallEndedReasonAnsweredElsewhere; - reasonE = &reasonO; - break; - case 5: - reasonO = CXCallEndedReasonDeclinedElsewhere; - reasonE = &reasonO; - break; - default: - break; - } - if (reasonE) { - [sharedProvider reportCallWithUUID:uuid endedAtDate:[NSDate date] reason:*reasonE]; + if (self.callKeepCallController == nil) { + self.callKeepCallController = [[CXCallController alloc] init]; + } + [self.callKeepCallController + requestTransaction:transaction + completion:^(NSError *_Nullable error) { + if (error != nil) { + NSLog(@"[CallKeep][requestTransaction] Error requesting " + @"transaction (%@): (%@)", + transaction.actions, error); + } else { + NSLog(@"[CallKeep][requestTransaction] Requested transaction " + @"successfully"); + if (onSuccess) { + onSuccess([transaction.actions firstObject]); + } + } + }]; +} + ++ (BOOL)isCallActive:(NSString *)uuidString { + CXCallObserver *callObserver = [[CXCallObserver alloc] init]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + + for (CXCall *call in callObserver.calls) { + NSLog(@"[CallKeep] isCallActive %@ %d ?", call.UUID, + [call.UUID isEqual:uuid]); + if ([call.UUID isEqual:[[NSUUID alloc] initWithUUIDString:uuidString]] && + !call.hasConnected) { + return true; } + } + return false; +} + ++ (void)endCallWithUUID:(NSString *)uuidString reason:(int)reason { +#ifdef DEBUG + NSLog(@"[CallKeep][reportEndCallWithUUID] uuidString = %@ reason = %d", + uuidString, reason); +#endif + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXCallEndedReason reasonO; + CXCallEndedReason *reasonE = nil; + switch (reason) { + case 1: + reasonO = CXCallEndedReasonFailed; + reasonE = &reasonO; + break; + case 2: + case 6: + reasonO = CXCallEndedReasonRemoteEnded; + reasonE = &reasonO; + break; + case 3: + reasonO = CXCallEndedReasonUnanswered; + reasonE = &reasonO; + break; + case 4: + reasonO = CXCallEndedReasonAnsweredElsewhere; + reasonE = &reasonO; + break; + case 5: + reasonO = CXCallEndedReasonDeclinedElsewhere; + reasonE = &reasonO; + break; + default: + break; + } + if (reasonE) { + [sharedProvider reportCallWithUUID:uuid + endedAtDate:[NSDate date] + reason:*reasonE]; + } } + (void)reportNewIncomingCall:(NSString *)uuidString handle:(NSString *)handle handleType:(NSString *)handleType hasVideo:(BOOL)hasVideo - callerName:(NSString * _Nullable)callerName + callerName:(NSString *_Nullable)callerName fromPushKit:(BOOL)fromPushKit - payload:(NSDictionary * _Nullable)payload -{ - [CallKeep reportNewIncomingCall:uuidString handle:handle handleType:handleType hasVideo:hasVideo callerName:callerName fromPushKit:fromPushKit payload:payload withCompletionHandler:nil]; + payload:(NSDictionary *_Nullable)payload { + [CallKeep reportNewIncomingCall:uuidString + handle:handle + handleType:handleType + hasVideo:hasVideo + callerName:callerName + fromPushKit:fromPushKit + payload:payload + withCompletionHandler:nil]; } - + (void)reportNewIncomingCall:(NSString *)uuidString handle:(NSString *)handle handleType:(NSString *)handleType hasVideo:(BOOL)hasVideo - callerName:(NSString * _Nullable)callerName - fromPushKit:(BOOL)fromPushKit -{ - [CallKeep reportNewIncomingCall: uuidString handle:handle handleType:handleType hasVideo:hasVideo callerName:callerName fromPushKit: fromPushKit payload:nil withCompletionHandler:nil]; + callerName:(NSString *_Nullable)callerName + fromPushKit:(BOOL)fromPushKit { + [CallKeep reportNewIncomingCall:uuidString + handle:handle + handleType:handleType + hasVideo:hasVideo + callerName:callerName + fromPushKit:fromPushKit + payload:nil + withCompletionHandler:nil]; } + (void)reportNewIncomingCall:(NSString *)uuidString handle:(NSString *)handle handleType:(NSString *)handleType hasVideo:(BOOL)hasVideo - callerName:(NSString * _Nullable)callerName + callerName:(NSString *_Nullable)callerName fromPushKit:(BOOL)fromPushKit - payload:(NSDictionary * _Nullable)payload - withCompletionHandler:(void (^_Nullable)(void))completion -{ -#ifdef DEBUG - NSLog(@"[CallKeep][reportNewIncomingCall] uuidString = %@", uuidString); -#endif - [CallKeep initCallKitProvider]; - - int _handleType = [CallKeep getHandleType:handleType]; - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - - CXCallUpdate *callUpdate = [CallKeep createCallUpdate]; - callUpdate.remoteHandle = [[CXHandle alloc] initWithType:_handleType value:handle]; - callUpdate.hasVideo = hasVideo; - callUpdate.localizedCallerName = callerName; - - [sharedProvider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError * _Nullable error) { - CallKeep *callKeep = [CallKeep allocWithZone: nil]; - [callKeep sendEventWithNameWrapper:CallKeepDidDisplayIncomingCall body:@{ - @"error": error && error.localizedDescription ? error.localizedDescription : @"", - @"callUUID": uuidString, - @"handle": handle, - @"name": callerName ? callerName : @"", - @"hasVideo": @(hasVideo), - @"fromPushKit": @(fromPushKit), - @"additionalData": payload ? payload : @"", - }]; - if (error == nil) { - // Workaround per https://forums.developer.apple.com/message/169511 - if ([callKeep lessThanIos10_2]) { - [callKeep configureAudioSession]; - } - } - if (completion != nil) { - completion(); - } - }]; -} - -+(CXCallUpdate*)createCallUpdate -{ - CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; - callUpdate.supportsDTMF = settings[@"supportsDTMF"] ? [settings[@"supportsDTMF"] boolValue] : NO; - callUpdate.supportsHolding = settings[@"supportsHolding"] ? [settings[@"supportsHolding"] boolValue] : NO; - callUpdate.supportsGrouping = settings[@"supportsGrouping"] ? [settings[@"supportsGrouping"] boolValue] : NO; - callUpdate.supportsUngrouping = settings[@"supportsUngrouping"] ? [settings[@"supportsUngrouping"] boolValue] : NO; - - return callUpdate; + payload:(NSDictionary *_Nullable)payload + withCompletionHandler:(void (^_Nullable)(void))completion { +#ifdef DEBUG + NSLog(@"[CallKeep][reportNewIncomingCall] uuidString = %@", uuidString); +#endif + [CallKeep initCallKitProvider]; + + int _handleType = [CallKeep getHandleType:handleType]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + + CXCallUpdate *callUpdate = [CallKeep createCallUpdate]; + callUpdate.remoteHandle = [[CXHandle alloc] initWithType:_handleType + value:handle]; + callUpdate.hasVideo = hasVideo; + callUpdate.localizedCallerName = callerName; + + [sharedProvider + reportNewIncomingCallWithUUID:uuid + update:callUpdate + completion:^(NSError *_Nullable error) { + CallKeep *callKeep = [CallKeep allocWithZone:nil]; + [callKeep + sendEventWithNameWrapper: + CallKeepDidDisplayIncomingCall + body:@{ + @"error" : error && + error + .localizedDescription + ? error + .localizedDescription + : @"", + @"callUUID" : uuidString, + @"handle" : handle, + @"name" : callerName + ? callerName + : @"", + @"hasVideo" : @(hasVideo), + @"fromPushKit" : + @(fromPushKit), + @"additionalData" : payload + ? payload + : @"", + }]; + if (error == nil) { + // Workaround per + // https://forums.developer.apple.com/message/169511 + if ([callKeep lessThanIos10_2]) { + [callKeep configureAudioSession]; + } + } + if (completion != nil) { + completion(); + } + }]; +} + ++ (CXCallUpdate *)createCallUpdate { + CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; + callUpdate.supportsDTMF = + settings[@"supportsDTMF"] ? [settings[@"supportsDTMF"] boolValue] : NO; + callUpdate.supportsHolding = settings[@"supportsHolding"] + ? [settings[@"supportsHolding"] boolValue] + : NO; + callUpdate.supportsGrouping = settings[@"supportsGrouping"] + ? [settings[@"supportsGrouping"] boolValue] + : NO; + callUpdate.supportsUngrouping = + settings[@"supportsUngrouping"] + ? [settings[@"supportsUngrouping"] boolValue] + : NO; + + return callUpdate; } // Update call contact info // @deprecated --(void) reportUpdatedCall:(NSString *)uuidString contactIdentifier:(NSString *)contactIdentifier -{ +- (void)reportUpdatedCall:(NSString *)uuidString + contactIdentifier:(NSString *)contactIdentifier { #ifdef DEBUG - NSLog(@"[CallKeep][reportUpdatedCall] contactIdentifier = %@", contactIdentifier); + NSLog(@"[CallKeep][reportUpdatedCall] contactIdentifier = %@", + contactIdentifier); #endif - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; - callUpdate.localizedCallerName = contactIdentifier; - - [self.callKeepProvider reportCallWithUUID:uuid updated:callUpdate]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; + callUpdate.localizedCallerName = contactIdentifier; + + [self.callKeepProvider reportCallWithUUID:uuid updated:callUpdate]; } -- (BOOL)lessThanIos10_2 -{ - if (_version.majorVersion < 10) { - return YES; - } else if (_version.majorVersion > 10) { - return NO; - } else { - return _version.minorVersion < 2; - } +- (BOOL)lessThanIos10_2 { + if (_version.majorVersion < 10) { + return YES; + } else if (_version.majorVersion > 10) { + return NO; + } else { + return _version.minorVersion < 2; + } } -+ (int)getHandleType:(NSString *)handleType -{ - int _handleType; - if ([handleType isEqualToString:@"generic"]) { - _handleType = CXHandleTypeGeneric; - } else if ([handleType isEqualToString:@"number"]) { - _handleType = CXHandleTypePhoneNumber; - } else if ([handleType isEqualToString:@"email"]) { - _handleType = CXHandleTypeEmailAddress; - } else { - _handleType = CXHandleTypeGeneric; - } - return _handleType; ++ (int)getHandleType:(NSString *)handleType { + int _handleType; + if ([handleType isEqualToString:@"generic"]) { + _handleType = CXHandleTypeGeneric; + } else if ([handleType isEqualToString:@"number"]) { + _handleType = CXHandleTypePhoneNumber; + } else if ([handleType isEqualToString:@"email"]) { + _handleType = CXHandleTypeEmailAddress; + } else { + _handleType = CXHandleTypeGeneric; + } + return _handleType; } -+ (CXProviderConfiguration *)getProviderConfiguration:(NSDictionary*)settings -{ ++ (CXProviderConfiguration *)getProviderConfiguration:(NSDictionary *)settings { #ifdef DEBUG - NSLog(@"[CallKeep][getProviderConfiguration]"); + NSLog(@"[CallKeep][getProviderConfiguration]"); #endif - NSString *appName = @"Unknown App"; - if (settings != nil) { - appName = settings[@"appName"]; - } - CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:appName]; - providerConfiguration.supportsVideo = YES; - providerConfiguration.maximumCallGroups = 3; - providerConfiguration.maximumCallsPerCallGroup = 1; - if(settings[@"handleType"]){ - int _handleType = [CallKeep getHandleType:settings[@"handleType"]]; - providerConfiguration.supportedHandleTypes = [NSSet setWithObjects:[NSNumber numberWithInteger:_handleType], nil]; - }else{ - providerConfiguration.supportedHandleTypes = [NSSet setWithObjects:[NSNumber numberWithInteger:CXHandleTypePhoneNumber], nil]; - } - if (settings[@"supportsVideo"]) { - providerConfiguration.supportsVideo = [settings[@"supportsVideo"] boolValue]; - } - if (settings[@"maximumCallGroups"]) { - providerConfiguration.maximumCallGroups = [settings[@"maximumCallGroups"] integerValue]; - } - if (settings[@"maximumCallsPerCallGroup"]) { - providerConfiguration.maximumCallsPerCallGroup = [settings[@"maximumCallsPerCallGroup"] integerValue]; - } - if (settings[@"imageName"]) { - providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:settings[@"imageName"]]); - } - if (settings[@"ringtoneSound"]) { - providerConfiguration.ringtoneSound = settings[@"ringtoneSound"]; - } - if (@available(iOS 11.0, *)) { - if (settings[@"includesCallsInRecents"]) { - providerConfiguration.includesCallsInRecents = [settings[@"includesCallsInRecents"] boolValue]; - } + NSString *appName = @"Unknown App"; + if (settings != nil) { + appName = settings[@"appName"]; + } + CXProviderConfiguration *providerConfiguration = + [[CXProviderConfiguration alloc] initWithLocalizedName:appName]; + providerConfiguration.supportsVideo = YES; + providerConfiguration.maximumCallGroups = 3; + providerConfiguration.maximumCallsPerCallGroup = 1; + if (settings[@"handleType"]) { + int _handleType = [CallKeep getHandleType:settings[@"handleType"]]; + providerConfiguration.supportedHandleTypes = + [NSSet setWithObjects:[NSNumber numberWithInteger:_handleType], nil]; + } else { + providerConfiguration.supportedHandleTypes = [NSSet + setWithObjects:[NSNumber numberWithInteger:CXHandleTypePhoneNumber], + nil]; + } + if (settings[@"supportsVideo"]) { + providerConfiguration.supportsVideo = + [settings[@"supportsVideo"] boolValue]; + } + if (settings[@"maximumCallGroups"]) { + providerConfiguration.maximumCallGroups = + [settings[@"maximumCallGroups"] integerValue]; + } + if (settings[@"maximumCallsPerCallGroup"]) { + providerConfiguration.maximumCallsPerCallGroup = + [settings[@"maximumCallsPerCallGroup"] integerValue]; + } + if (settings[@"imageName"]) { + providerConfiguration.iconTemplateImageData = + UIImagePNGRepresentation([UIImage imageNamed:settings[@"imageName"]]); + } + if (settings[@"ringtoneSound"]) { + providerConfiguration.ringtoneSound = settings[@"ringtoneSound"]; + } + if (@available(iOS 11.0, *)) { + if (settings[@"includesCallsInRecents"]) { + providerConfiguration.includesCallsInRecents = + [settings[@"includesCallsInRecents"] boolValue]; } - return providerConfiguration; + } + return providerConfiguration; } -- (void)configureAudioSession -{ +- (void)configureAudioSession { #ifdef DEBUG - NSLog(@"[CallKeep][configureAudioSession] Activating audio session"); + NSLog(@"[CallKeep][configureAudioSession] Activating audio session"); #endif - - AVAudioSession* audioSession = [AVAudioSession sharedInstance]; - [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil]; - - [audioSession setMode:AVAudioSessionModeVoiceChat error:nil]; - - double sampleRate = 44100.0; - [audioSession setPreferredSampleRate:sampleRate error:nil]; - - NSTimeInterval bufferDuration = .005; - [audioSession setPreferredIOBufferDuration:bufferDuration error:nil]; - [audioSession setActive:TRUE error:nil]; + + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord + withOptions:AVAudioSessionCategoryOptionAllowBluetooth + error:nil]; + + [audioSession setMode:AVAudioSessionModeVoiceChat error:nil]; + + double sampleRate = 44100.0; + [audioSession setPreferredSampleRate:sampleRate error:nil]; + + NSTimeInterval bufferDuration = .005; + [audioSession setPreferredIOBufferDuration:bufferDuration error:nil]; + [audioSession setActive:TRUE error:nil]; } + (BOOL)application:(UIApplication *)application openURL:(NSURL *)url - options:(NSDictionary *)options NS_AVAILABLE_IOS(9_0) -{ -#ifdef DEBUG - NSLog(@"[CallKeep][application:openURL]"); -#endif - /* - NSString *handle = [url startCallHandle]; - if (handle != nil && handle.length > 0 ){ - NSDictionary *userInfo = @{ - @"handle": handle, - @"video": @NO - }; - [[NSNotificationCenter defaultCenter] postNotificationName:CallKeepHandleStartCallNotification - object:self - userInfo:userInfo]; - return YES; - } - return NO; - */ - return YES; + options:(NSDictionary *)options + NS_AVAILABLE_IOS(9_0) { +#ifdef DEBUG + NSLog(@"[CallKeep][application:openURL]"); +#endif + /* + NSString *handle = [url startCallHandle]; + if (handle != nil && handle.length > 0 ){ + NSDictionary *userInfo = @{ + @"handle": handle, + @"video": @NO + }; + [[NSNotificationCenter defaultCenter] + postNotificationName:CallKeepHandleStartCallNotification object:self + userInfo:userInfo]; + return YES; + } + return NO; + */ + return YES; } + (BOOL)application:(UIApplication *)application -continueUserActivity:(NSUserActivity *)userActivity - restorationHandler:(void(^)(NSArray> * _Nonnull restorableObjects))restorationHandler -{ -#ifdef DEBUG - NSLog(@"[CallKeep][application:continueUserActivity]"); -#endif - INInteraction *interaction = userActivity.interaction; - INPerson *contact; - NSString *handle; - NSString *displayName; - BOOL isAudioCall; - BOOL isVideoCall; - - //HACK TO AVOID XCODE 10 COMPILE CRASH - //REMOVE ON NEXT MAJOR RELEASE OF CALLKIT + continueUserActivity:(NSUserActivity *)userActivity + restorationHandler: + (void (^)(NSArray> + *_Nonnull restorableObjects))restorationHandler { +#ifdef DEBUG + NSLog(@"[CallKeep][application:continueUserActivity]"); +#endif + INInteraction *interaction = userActivity.interaction; + INPerson *contact; + NSString *handle; + NSString *displayName; + BOOL isAudioCall; + BOOL isVideoCall; + + // HACK TO AVOID XCODE 10 COMPILE CRASH + // REMOVE ON NEXT MAJOR RELEASE OF CALLKIT #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 - //XCode 11 - // iOS 13 returns an INStartCallIntent userActivity type - if (@available(iOS 13, *)) { - INStartCallIntent *intent = (INStartCallIntent*)interaction.intent; - // callCapability is not available on iOS > 13.2, but it is in 13.1 weirdly... - if ([intent respondsToSelector:@selector(callCapability)]) { - isAudioCall = intent.callCapability == INCallCapabilityAudioCall; - isVideoCall = intent.callCapability == INCallCapabilityVideoCall; - } else { - isAudioCall = [userActivity.activityType isEqualToString:INStartAudioCallIntentIdentifier]; - isVideoCall = [userActivity.activityType isEqualToString:INStartVideoCallIntentIdentifier]; - } + // XCode 11 + // iOS 13 returns an INStartCallIntent userActivity type + if (@available(iOS 13, *)) { + INStartCallIntent *intent = (INStartCallIntent *)interaction.intent; + // callCapability is not available on iOS > 13.2, but it is in 13.1 + // weirdly... + if ([intent respondsToSelector:@selector(callCapability)]) { + isAudioCall = intent.callCapability == INCallCapabilityAudioCall; + isVideoCall = intent.callCapability == INCallCapabilityVideoCall; } else { + isAudioCall = [userActivity.activityType + isEqualToString:INStartAudioCallIntentIdentifier]; + isVideoCall = [userActivity.activityType + isEqualToString:INStartVideoCallIntentIdentifier]; + } + } else { #endif - //XCode 10 and below - isAudioCall = [userActivity.activityType isEqualToString:INStartAudioCallIntentIdentifier]; - isVideoCall = [userActivity.activityType isEqualToString:INStartVideoCallIntentIdentifier]; - //HACK TO AVOID XCODE 10 COMPILE CRASH - //REMOVE ON NEXT MAJOR RELEASE OF CALLKIT + // XCode 10 and below + isAudioCall = [userActivity.activityType + isEqualToString:INStartAudioCallIntentIdentifier]; + isVideoCall = [userActivity.activityType + isEqualToString:INStartVideoCallIntentIdentifier]; + // HACK TO AVOID XCODE 10 COMPILE CRASH + // REMOVE ON NEXT MAJOR RELEASE OF CALLKIT #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 - } + } #endif - - if (isAudioCall) { - INStartAudioCallIntent *startAudioCallIntent = (INStartAudioCallIntent *)interaction.intent; - contact = [startAudioCallIntent.contacts firstObject]; - } else if (isVideoCall) { - INStartVideoCallIntent *startVideoCallIntent = (INStartVideoCallIntent *)interaction.intent; - contact = [startVideoCallIntent.contacts firstObject]; - } - - if (contact != nil) { - handle = contact.personHandle.value; - displayName = contact.displayName; - } - - if (handle != nil && handle.length > 0 ){ - NSDictionary *userInfo = @{ - @"handle": handle, - @"name": displayName, - @"video": @(isVideoCall) - }; - - CallKeep *callKeep = [CallKeep allocWithZone: nil]; - [callKeep sendEventWithNameWrapper:CallKeepDidReceiveStartCallAction body:userInfo]; - return YES; - } - return NO; -} -+ (BOOL)requiresMainQueueSetup -{ + if (isAudioCall) { + INStartAudioCallIntent *startAudioCallIntent = + (INStartAudioCallIntent *)interaction.intent; + contact = [startAudioCallIntent.contacts firstObject]; + } else if (isVideoCall) { + INStartVideoCallIntent *startVideoCallIntent = + (INStartVideoCallIntent *)interaction.intent; + contact = [startVideoCallIntent.contacts firstObject]; + } + + if (contact != nil) { + handle = contact.personHandle.value; + displayName = contact.displayName; + } + + if (handle != nil && handle.length > 0) { + NSDictionary *userInfo = + @{@"handle" : handle, @"name" : displayName, @"video" : @(isVideoCall)}; + + CallKeep *callKeep = [CallKeep allocWithZone:nil]; + [callKeep sendEventWithNameWrapper:CallKeepDidReceiveStartCallAction + body:userInfo]; return YES; + } + return NO; +} + ++ (BOOL)requiresMainQueueSetup { + return YES; } #pragma mark - CXProviderDelegate -- (void)providerDidReset:(CXProvider *)provider{ +- (void)providerDidReset:(CXProvider *)provider { #ifdef DEBUG - NSLog(@"[CallKeep][providerDidReset]"); + NSLog(@"[CallKeep][providerDidReset]"); #endif - //this means something big changed, so tell the JS. The JS should - //probably respond by hanging up all calls. - [self sendEventWithNameWrapper:CallKeepProviderReset body:@{}]; + // this means something big changed, so tell the JS. The JS should + // probably respond by hanging up all calls. + [self sendEventWithNameWrapper:CallKeepProviderReset body:@{}]; } // Starting outgoing call -- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action -{ +- (void)provider:(CXProvider *)provider + performStartCallAction:(CXStartCallAction *)action { #ifdef DEBUG - NSLog(@"[CallKeep][CXProviderDelegate][provider:performStartCallAction]"); + NSLog(@"[CallKeep][CXProviderDelegate][provider:performStartCallAction]"); #endif - //do this first, audio sessions are flakey - [self configureAudioSession]; - //tell the JS to actually make the call - [self sendEventWithNameWrapper:CallKeepDidReceiveStartCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString], @"handle": action.handle.value }]; - [action fulfill]; + // do this first, audio sessions are flakey + [self configureAudioSession]; + // tell the JS to actually make the call + [self sendEventWithNameWrapper:CallKeepDidReceiveStartCallAction + body:@{ + @"callUUID" : + [action.callUUID.UUIDString lowercaseString], + @"handle" : action.handle.value + }]; + [action fulfill]; } // Answering incoming call -- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action -{ +- (void)provider:(CXProvider *)provider + performAnswerCallAction:(CXAnswerCallAction *)action { #ifdef DEBUG - NSLog(@"[CallKeep][CXProviderDelegate][provider:performAnswerCallAction]"); + NSLog(@"[CallKeep][CXProviderDelegate][provider:performAnswerCallAction]"); #endif - [self configureAudioSession]; - [self sendEventWithNameWrapper:CallKeepPerformAnswerCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; - [action fulfill]; + [self configureAudioSession]; + [self sendEventWithNameWrapper:CallKeepPerformAnswerCallAction + body:@{ + @"callUUID" : + [action.callUUID.UUIDString lowercaseString] + }]; + [action fulfill]; } // Ending incoming call -- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action -{ +- (void)provider:(CXProvider *)provider + performEndCallAction:(CXEndCallAction *)action { #ifdef DEBUG - NSLog(@"[CallKeep][CXProviderDelegate][provider:performEndCallAction]"); + NSLog(@"[CallKeep][CXProviderDelegate][provider:performEndCallAction]"); #endif - [self sendEventWithNameWrapper:CallKeepPerformEndCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; - [action fulfill]; + [self sendEventWithNameWrapper:CallKeepPerformEndCallAction + body:@{ + @"callUUID" : + [action.callUUID.UUIDString lowercaseString] + }]; + [action fulfill]; } --(void)provider:(CXProvider *)provider performSetHeldCallAction:(CXSetHeldCallAction *)action -{ +- (void)provider:(CXProvider *)provider + performSetHeldCallAction:(CXSetHeldCallAction *)action { #ifdef DEBUG - NSLog(@"[CallKeep][CXProviderDelegate][provider:performSetHeldCallAction]"); + NSLog(@"[CallKeep][CXProviderDelegate][provider:performSetHeldCallAction]"); #endif - - [self sendEventWithNameWrapper:CallKeepDidToggleHoldAction body:@{ @"hold": @(action.onHold), @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; - [action fulfill]; + + [self sendEventWithNameWrapper:CallKeepDidToggleHoldAction + body:@{ + @"hold" : @(action.onHold), + @"callUUID" : + [action.callUUID.UUIDString lowercaseString] + }]; + [action fulfill]; } -- (void)provider:(CXProvider *)provider performPlayDTMFCallAction:(CXPlayDTMFCallAction *)action { +- (void)provider:(CXProvider *)provider + performPlayDTMFCallAction:(CXPlayDTMFCallAction *)action { #ifdef DEBUG - NSLog(@"[CallKeep][CXProviderDelegate][provider:performPlayDTMFCallAction]"); + NSLog(@"[CallKeep][CXProviderDelegate][provider:performPlayDTMFCallAction]"); #endif - [self sendEventWithNameWrapper:CallKeepPerformPlayDTMFCallAction body:@{ @"digits": action.digits, @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; - [action fulfill]; + [self sendEventWithNameWrapper:CallKeepPerformPlayDTMFCallAction + body:@{ + @"digits" : action.digits, + @"callUUID" : + [action.callUUID.UUIDString lowercaseString] + }]; + [action fulfill]; } --(void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action -{ +- (void)provider:(CXProvider *)provider + performSetMutedCallAction:(CXSetMutedCallAction *)action { #ifdef DEBUG - NSLog(@"[CallKeep][CXProviderDelegate][provider:performSetMutedCallAction]"); + NSLog(@"[CallKeep][CXProviderDelegate][provider:performSetMutedCallAction]"); #endif - - [self sendEventWithNameWrapper:CallKeepDidPerformSetMutedCallAction body:@{ @"muted": @(action.muted), @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; - [action fulfill]; + + [self sendEventWithNameWrapper:CallKeepDidPerformSetMutedCallAction + body:@{ + @"muted" : @(action.muted), + @"callUUID" : + [action.callUUID.UUIDString lowercaseString] + }]; + [action fulfill]; } -- (void)provider:(CXProvider *)provider timedOutPerformingAction:(CXAction *)action -{ +- (void)provider:(CXProvider *)provider + timedOutPerformingAction:(CXAction *)action { #ifdef DEBUG - NSLog(@"[CallKeep][CXProviderDelegate][provider:timedOutPerformingAction]"); + NSLog(@"[CallKeep][CXProviderDelegate][provider:timedOutPerformingAction]"); #endif - NSDictionary* body; - if ([action isKindOfClass:[CXAnswerCallAction class]]) { - CXAnswerCallAction* answerAction = ((CXAnswerCallAction*)action); - body = @{ @"callUUID": [answerAction.callUUID.UUIDString lowercaseString], @"action": CallKeepActionAnswer }; - } else if ([action isKindOfClass:[CXEndCallAction class]]) { - CXEndCallAction* answerAction = ((CXEndCallAction*)action); - body = @{ @"callUUID": [answerAction.callUUID.UUIDString lowercaseString], @"action": CallKeepActionEnd }; - } - - if (body) { - [self sendEventWithNameWrapper:CallKeepDidFailCallAction body:body]; - } + NSDictionary *body; + if ([action isKindOfClass:[CXAnswerCallAction class]]) { + CXAnswerCallAction *answerAction = ((CXAnswerCallAction *)action); + body = @{ + @"callUUID" : [answerAction.callUUID.UUIDString lowercaseString], + @"action" : CallKeepActionAnswer + }; + } else if ([action isKindOfClass:[CXEndCallAction class]]) { + CXEndCallAction *answerAction = ((CXEndCallAction *)action); + body = @{ + @"callUUID" : [answerAction.callUUID.UUIDString lowercaseString], + @"action" : CallKeepActionEnd + }; + } + + if (body) { + [self sendEventWithNameWrapper:CallKeepDidFailCallAction body:body]; + } } -- (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession -{ +- (void)provider:(CXProvider *)provider + didActivateAudioSession:(AVAudioSession *)audioSession { #ifdef DEBUG - NSLog(@"[CallKeep][CXProviderDelegate][provider:didActivateAudioSession]"); + NSLog(@"[CallKeep][CXProviderDelegate][provider:didActivateAudioSession]"); #endif - [self sendDefaultAudioInterruptionNotificationToStartAudioResource]; - [self configureAudioSession]; - [self sendEventWithNameWrapper:CallKeepDidActivateAudioSession body:@{}]; + [self sendDefaultAudioInterruptionNotificationToStartAudioResource]; + [self configureAudioSession]; + [self sendEventWithNameWrapper:CallKeepDidActivateAudioSession body:@{}]; } -- (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession -{ +- (void)provider:(CXProvider *)provider + didDeactivateAudioSession:(AVAudioSession *)audioSession { #ifdef DEBUG - NSLog(@"[CallKeep][CXProviderDelegate][provider:didDeactivateAudioSession]"); + NSLog(@"[CallKeep][CXProviderDelegate][provider:didDeactivateAudioSession]"); #endif - [self sendEventWithNameWrapper:CallKeepDidDeactivateAudioSession body:@{}]; + [self sendEventWithNameWrapper:CallKeepDidDeactivateAudioSession body:@{}]; } --(void)sendDefaultAudioInterruptionNotificationToStartAudioResource -{ - NSDictionary *userInfo = @{ - AVAudioSessionInterruptionTypeKey: [NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded], - AVAudioSessionInterruptionOptionKey: [NSNumber numberWithInt:AVAudioSessionInterruptionOptionShouldResume] - }; - [[NSNotificationCenter defaultCenter] postNotificationName:AVAudioSessionInterruptionNotification object:nil userInfo:userInfo]; +- (void)sendDefaultAudioInterruptionNotificationToStartAudioResource { + NSDictionary *userInfo = @{ + AVAudioSessionInterruptionTypeKey : + [NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded], + AVAudioSessionInterruptionOptionKey : + [NSNumber numberWithInt:AVAudioSessionInterruptionOptionShouldResume] + }; + [[NSNotificationCenter defaultCenter] + postNotificationName:AVAudioSessionInterruptionNotification + object:nil + userInfo:userInfo]; } @end diff --git a/ios/callkeep.podspec b/ios/januscaler_callkeep.podspec similarity index 95% rename from ios/callkeep.podspec rename to ios/januscaler_callkeep.podspec index 041da0ca..738d1096 100644 --- a/ios/callkeep.podspec +++ b/ios/januscaler_callkeep.podspec @@ -3,7 +3,7 @@ # Run `pod lib lint flutter_callkeep.podspec' to validate before publishing. # Pod::Spec.new do |s| - s.name = 'callkeep' + s.name = 'januscaler_callkeep' s.version = '0.0.1' s.summary = 'A new flutter plugin project.' s.description = <<-DESC diff --git a/lib/callkeep.dart b/lib/januscaler_callkeep.dart similarity index 100% rename from lib/callkeep.dart rename to lib/januscaler_callkeep.dart diff --git a/lib/src/actions.dart b/lib/src/actions.dart index 18331589..05b8f0ab 100644 --- a/lib/src/actions.dart +++ b/lib/src/actions.dart @@ -1,4 +1,4 @@ -import 'package:callkeep/src/call.dart'; +import 'package:januscaler_callkeep/src/call.dart'; import 'event.dart'; class CallKeepDidReceiveStartCallAction extends EventType { @@ -67,6 +67,12 @@ class CallKeepDidDisplayIncomingCall extends EventType { final CallData callData; } +class CallKeepPushPayload extends EventType { + CallKeepPushPayload.fromMap(Map payload) + : payload = payload; + final Map payload; +} + class CallKeepDidPerformSetMutedCallAction extends EventType { CallKeepDidPerformSetMutedCallAction.fromMap(Map arguments) : callUUID = arguments['callUUID'], diff --git a/lib/src/api.dart b/lib/src/api.dart index 45a8ea99..92a12454 100644 --- a/lib/src/api.dart +++ b/lib/src/api.dart @@ -47,8 +47,10 @@ class FlutterCallkeep extends EventManager { if (isIOS) { return; } - return _channel - .invokeMethod('registerPhoneAccount', {}); + return _channel.invokeMethod( + 'registerPhoneAccount', + {}, + ); } Future registerAndroidEvents() async { @@ -58,9 +60,7 @@ class FlutterCallkeep extends EventManager { return _channel.invokeMethod('registerEvents', {}); } - Future hasDefaultPhoneAccount( - Map options, - ) async { + Future hasDefaultPhoneAccount(Map options) async { if (!isIOS) { return await _hasDefaultPhoneAccount(options); } @@ -111,15 +111,14 @@ class FlutterCallkeep extends EventManager { 'handleType': handleType, 'hasVideo': hasVideo, 'callerName': callerName, - 'additionalData': additionalData + 'additionalData': additionalData, }); } Future answerIncomingCall(String uuid) async { - await _channel.invokeMethod( - 'answerIncomingCall', - {'uuid': uuid}, - ); + await _channel.invokeMethod('answerIncomingCall', { + 'uuid': uuid, + }); } Future startCall({ @@ -136,30 +135,36 @@ class FlutterCallkeep extends EventManager { 'callerName': callerName, 'handleType': handleType, 'hasVideo': hasVideo, - 'additionalData': additionalData + 'additionalData': additionalData, }); } Future reportConnectingOutgoingCallWithUUID(String uuid) async { //only available on iOS if (isIOS) { - await _channel.invokeMethod('reportConnectingOutgoingCallWithUUID', - {'uuid': uuid}); + await _channel.invokeMethod( + 'reportConnectingOutgoingCallWithUUID', + {'uuid': uuid}, + ); } } Future reportConnectedOutgoingCallWithUUID(String uuid) async { //only available on iOS if (isIOS) { - await _channel.invokeMethod('reportConnectedOutgoingCallWithUUID', - {'uuid': uuid}); + await _channel.invokeMethod( + 'reportConnectedOutgoingCallWithUUID', + {'uuid': uuid}, + ); } } Future reportStartedCallWithUUID(String uuid) async { if (!isIOS) { await _channel.invokeMethod( - 'reportStartedCallWithUUID', {'uuid': uuid}); + 'reportStartedCallWithUUID', + {'uuid': uuid}, + ); } } @@ -170,11 +175,7 @@ class FlutterCallkeep extends EventManager { }) async { return await _channel.invokeMethod( 'reportEndCallWithUUID', - { - 'uuid': uuid, - 'reason': reason, - 'notify': notify, - }, + {'uuid': uuid, 'reason': reason, 'notify': notify}, ); } @@ -184,17 +185,21 @@ class FlutterCallkeep extends EventManager { */ Future rejectCall(String uuid) async { if (!isIOS) { - await _channel - .invokeMethod('rejectCall', {'uuid': uuid}); + await _channel.invokeMethod('rejectCall', { + 'uuid': uuid, + }); } else { - await _channel - .invokeMethod('endCall', {'uuid': uuid}); + await _channel.invokeMethod('endCall', { + 'uuid': uuid, + }); } } Future isCallActive(String uuid) async { - var resp = await _channel - .invokeMethod('isCallActive', {'uuid': uuid}); + var resp = await _channel.invokeMethod( + 'isCallActive', + {'uuid': uuid}, + ); if (resp != null) { return resp; } @@ -202,8 +207,10 @@ class FlutterCallkeep extends EventManager { } Future> activeCalls() async { - var resp = await _channel - .invokeMethod?>('activeCalls', {}); + var resp = await _channel.invokeMethod?>( + 'activeCalls', + {}, + ); if (resp != null) { var uuids = []; resp.forEach((element) { @@ -216,8 +223,10 @@ class FlutterCallkeep extends EventManager { return []; } - Future endCall(String uuid) async => await _channel - .invokeMethod('endCall', {'uuid': uuid}); + Future endCall(String uuid) async => await _channel.invokeMethod( + 'endCall', + {'uuid': uuid}, + ); Future endAllCalls() async => await _channel.invokeMethod('endAllCalls', {}); @@ -226,8 +235,10 @@ class FlutterCallkeep extends EventManager { if (isIOS) { return true; } - var resp = await _channel - .invokeMethod('hasPhoneAccount', {}); + var resp = await _channel.invokeMethod( + 'hasPhoneAccount', + {}, + ); if (resp != null) { return resp; } @@ -238,22 +249,35 @@ class FlutterCallkeep extends EventManager { if (isIOS) { return true; } - var resp = await _channel - .invokeMethod('hasOutgoingCall', {}); + var resp = await _channel.invokeMethod( + 'hasOutgoingCall', + {}, + ); if (resp != null) { return resp; } return false; } - Future setMutedCall( - {required String uuid, required bool shouldMute}) async => - await _channel.invokeMethod( - 'setMutedCall', {'uuid': uuid, 'muted': shouldMute}); + Future setMutedCall({ + required String uuid, + required bool shouldMute, + }) async => await _channel.invokeMethod( + 'setMutedCall', + {'uuid': uuid, 'muted': shouldMute}, + ); + + Future setSpeaker({required String uuid, required bool isOn}) async => + await _channel.invokeMethod('setSpeaker', { + 'uuid': uuid, + 'isOn': isOn, + }); Future sendDTMF({required String uuid, required String key}) async => - await _channel.invokeMethod( - 'sendDTMF', {'uuid': uuid, 'key': key}); + await _channel.invokeMethod('sendDTMF', { + 'uuid': uuid, + 'key': key, + }); Future checkIfBusy() async => isIOS ? await _channel.invokeMethod('checkIfBusy', {}) @@ -268,8 +292,9 @@ class FlutterCallkeep extends EventManager { return; } // Tell android that we are able to make outgoing calls - await _channel.invokeMethod( - 'setAvailable', {'available': available}); + await _channel.invokeMethod('setAvailable', { + 'available': available, + }); } Future setCurrentCallActive(String callUUID) async { @@ -277,25 +302,27 @@ class FlutterCallkeep extends EventManager { return; } - await _channel.invokeMethod( - 'setCurrentCallActive', {'uuid': callUUID}); + await _channel.invokeMethod('setCurrentCallActive', { + 'uuid': callUUID, + }); } Future updateDisplay({ required String uuid, required String callerName, required String handle, - }) async => - await _channel.invokeMethod('updateDisplay', { - 'uuid': uuid, - 'callerName': callerName, - 'handle': handle - }); + }) async => await _channel.invokeMethod( + 'updateDisplay', + {'uuid': uuid, 'callerName': callerName, 'handle': handle}, + ); - Future setOnHold( - {required String uuid, required bool shouldHold}) async => - await _channel.invokeMethod( - 'setOnHold', {'uuid': uuid, 'hold': shouldHold}); + Future setOnHold({ + required String uuid, + required bool shouldHold, + }) async => await _channel.invokeMethod('setOnHold', { + 'uuid': uuid, + 'hold': shouldHold, + }); Future setReachable({bool reachable = true}) async { if (isIOS) { @@ -312,24 +339,27 @@ class FlutterCallkeep extends EventManager { required String callerName, }) async { logger.d( - 'CallKeep.reportUpdatedCall is deprecated, use CallKeep.updateDisplay instead'); + 'CallKeep.reportUpdatedCall is deprecated, use CallKeep.updateDisplay instead', + ); return isIOS - ? await _channel - .invokeMethod('reportUpdatedCall', { - 'uuid': uuid, - 'callerName': callerName, - }) + ? await _channel.invokeMethod( + 'reportUpdatedCall', + {'uuid': uuid, 'callerName': callerName}, + ) : throw Exception( - 'CallKeep.reportUpdatedCall was called from unsupported OS'); + 'CallKeep.reportUpdatedCall was called from unsupported OS', + ); } Future backToForeground() async { if (isIOS) { return false; } - var resp = await _channel - .invokeMethod('backToForeground', {}); + var resp = await _channel.invokeMethod( + 'backToForeground', + {}, + ); if (resp != null) { return resp; } @@ -342,10 +372,12 @@ class FlutterCallkeep extends EventManager { } if (options['appName'] is String == false) { throw Exception( - 'CallKeep.setup: option "appName" should be of type "string"'); + 'CallKeep.setup: option "appName" should be of type "string"', + ); } - return await _channel - .invokeMethod('setup', {'options': options}); + return await _channel.invokeMethod('setup', { + 'options': options, + }); } Future _setupAndroid({ @@ -388,10 +420,10 @@ class FlutterCallkeep extends EventManager { if (isIOS) { return true; } - var resp = await _channel - .invokeMethod('requestPermissions', { - 'additionalPermissions': optionalPermissions ?? [], - }); + var resp = await _channel.invokeMethod( + 'requestPermissions', + {'additionalPermissions': optionalPermissions ?? []}, + ); return resp ?? false; } @@ -418,13 +450,14 @@ class FlutterCallkeep extends EventManager { return; } await _channel.invokeMethod('foregroundService', { - 'settings': {'foregroundService': settings} + 'settings': {'foregroundService': settings}, }); } Future eventListener(MethodCall call) async { logger.d( - '[CallKeep] INFO: received event "${call.method}" ${call.arguments}'); + '[CallKeep] INFO: received event "${call.method}" ${call.arguments}', + ); final data = call.arguments as Map; switch (call.method) { case 'CallKeepDidReceiveStartCallAction': @@ -448,6 +481,9 @@ class FlutterCallkeep extends EventManager { case 'CallKeepDidDisplayIncomingCall': emit(CallKeepDidDisplayIncomingCall.fromMap(data)); break; + case 'CallKeepPushPayload': + emit(CallKeepPushPayload.fromMap(data)); + break; case 'CallKeepDidPerformSetMutedCallAction': emit(CallKeepDidPerformSetMutedCallAction.fromMap(data)); break; diff --git a/pubspec.yaml b/pubspec.yaml index 7124e68f..99352bab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,14 +1,14 @@ -name: callkeep +name: januscaler_callkeep description: iOS CallKit framework and Android ConnectionService for Flutter. -version: 0.4.1 -homepage: https://github.com/flutter-webrtc/callkeep +version: 0.4.3 +homepage: https://github.com/januscaler/callkeep environment: sdk: ">=2.12.0 <4.0.0" flutter: ">=1.22.0" dependencies: - firebase_messaging: ^15.2.1 + firebase_messaging: ^16.0.0 flutter: sdk: flutter logger: ^2.5.0