diff --git a/ios/Classes/CallKeep.h b/ios/Classes/CallKeep.h index 493ad183..83d5c769 100644 --- a/ios/Classes/CallKeep.h +++ b/ios/Classes/CallKeep.h @@ -12,10 +12,24 @@ #import #import +typedef NS_ENUM(NSInteger, CallState) { + CallStateCallOut = 0, + CallStateRinging = 1, + CallStateAnswered = 2, + CallStateEnded = 3 +}; + +@interface CallInfo : NSObject +@property (strong, nonatomic, nonnull) NSString * uuid; +@property (strong, nonatomic, nonnull) NSNumber * callState; +@end + @interface CallKeep: NSObject @property (nonatomic, strong, nullable) CXCallController *callKeepCallController; @property (nonatomic, strong, nullable) CXProvider *callKeepProvider; @property (nonatomic, strong, nullable) FlutterMethodChannel* eventChannel; +@property (nonatomic, strong, nullable) NSMutableDictionary *callMap; ++ (CallKeep *_Nullable)instance; - (BOOL)handleMethodCall:(FlutterMethodCall* _Nonnull)call result:(FlutterResult _Nonnull )result; diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m index 0148c54b..a1f085be 100644 --- a/ios/Classes/CallKeep.m +++ b/ios/Classes/CallKeep.m @@ -50,12 +50,24 @@ - (instancetype)init #ifdef DEBUG NSLog(@"[CallKeep][init]"); #endif + NSLog(@"[CallKeep][init]"); if (self = [super init]) { _delayedEvents = [NSMutableArray array]; + _callMap = [[NSMutableDictionary alloc] init]; + [self voipRegistration]; } return self; } ++ (CallKeep *)instance { + static CallKeep *ins = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + ins = [[CallKeep alloc] init]; + }); + return ins; +} + + (id)allocWithZone:(NSZone *)zone { static CallKeep *sharedInstance = nil; static dispatch_once_t onceToken; @@ -143,6 +155,24 @@ - (BOOL)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [self reportUpdatedCall:argsMap[@"uuid"] contactIdentifier:argsMap[@"localizedCallerName"]]; result(nil); } + else if([@"getCallInfo" isEqualToString:method]) { + CallInfo *callInfo = [self getCallInfo:argsMap[@"callId"] serial:argsMap[@"serial"]]; + NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; + if (callInfo.uuid) { + params[@"uuid"] = callInfo.uuid; + } + if (callInfo.callState) { + params[@"state"] = callInfo.callState; + } + result(params); + } + else if ([@"generateUUID" isEqualToString:method]) { + result([self generateUUID:argsMap[@"callId"] serial:argsMap[@"serial"]]); + } + else if ([@"cleanStringeeCall" isEqualToString:method]) { + [self cleanStringeeCall]; + result(nil); + } else { return NO; } @@ -171,17 +201,18 @@ - (void)sendEventWithNameWrapper:(NSString *)name body:(id)body { } + (void)initCallKitProvider { + NSLog(@"[Callkeep][initCallKitProvider]"); if (sharedProvider == nil) { NSDictionary *settings = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"CallKeepSettings"]; - sharedProvider = [[CXProvider alloc] initWithConfiguration:[CallKeep getProviderConfiguration:settings]]; + if (settings) { + sharedProvider = [[CXProvider alloc] initWithConfiguration:[CallKeep getProviderConfiguration:settings]]; + } } } + -(void)setup:(NSDictionary *)options { -#ifdef DEBUG - NSLog(@"[CallKeep][setup] options = %@", options); -#endif _version = [[[NSProcessInfo alloc] init] operatingSystemVersion]; self.callKeepCallController = [[CXCallController alloc] init]; NSDictionary *settings = [[NSMutableDictionary alloc] initWithDictionary:options]; @@ -190,12 +221,13 @@ -(void)setup:(NSDictionary *)options [[NSUserDefaults standardUserDefaults] synchronize]; [CallKeep initCallKitProvider]; - self.callKeepProvider = sharedProvider; [self.callKeepProvider setDelegate:self queue:nil]; [self voipRegistration]; + NSLog(@"[CallKeep][setup] options = %@", options); } + #pragma mark - PushKit -(void)voipRegistration @@ -203,6 +235,7 @@ -(void)voipRegistration PKPushRegistry* voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; voipRegistry.delegate = self; voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; + NSLog(@"[CallKeep][voipRegistration]"); } - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type { @@ -218,85 +251,126 @@ - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPush } - (NSString *)createUUID { - CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault); - NSString *uuidStr = (NSString *)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuidObject)); - CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuidObject); - CFRelease(uuidObject); - return [uuidStr lowercaseString]; + return [NSUUID.UUID.UUIDString lowercaseString]; +} + + +- (void)cleanStringeeCall { + _callMap = [[NSMutableDictionary alloc] init]; +} + +- (CallInfo *)getCallInfo:(NSString *)callId serial:(NSNumber *)serial { + NSNumber *checkSerial; + if (serial == NULL || serial == 0) { + checkSerial = @(1); + } else { + checkSerial = serial; + } + NSString *keyId = [[NSString alloc] initWithFormat:@"%@-%@", callId, checkSerial]; + CallInfo *callInfo = [CallKeep.instance.callMap objectForKey:keyId]; + return callInfo; +} + +- (NSString *)generateUUID:(NSString *)callId serial:(NSNumber *)serial { + NSNumber *checkSerial; + if (serial == NULL || serial == 0) { + checkSerial = @(1); + } else { + checkSerial = serial; + } + NSString *keyId = [[NSString alloc] initWithFormat:@"%@-%@", callId, checkSerial]; + CallInfo *callInfo = [CallKeep.instance.callMap objectForKey:keyId]; + if (callInfo == nil) { + callInfo = [[CallInfo alloc] init]; + [CallKeep.instance.callMap setObject:callInfo forKey:keyId]; + } + return callInfo.uuid; +} + +- (NSString *)reportCallIfNeeded:(NSString *)callId serial:(NSNumber *)serial callerName: (NSString *)callerName hasVideo:(BOOL)hasVideo withCompletionHandler:(void (^)(void))completion { + // create uuid if need + + NSNumber *checkSerial; + if (serial == NULL || serial == 0) { + checkSerial = @(1); + } else { + checkSerial = serial; + } + + NSString *keyId = [[NSString alloc] initWithFormat:@"%@-%@", callId, checkSerial]; + CXCallObserver *callObs = [[CXCallObserver alloc] init]; + CallInfo *callInfo = [CallKeep.instance.callMap objectForKey:keyId]; + + BOOL didShow = false; + if (callInfo == nil) { + callInfo = [[CallInfo alloc] init]; + [CallKeep.instance.callMap setObject:callInfo forKey:keyId]; + } else if (callInfo.callState) { + didShow = true; + } + + for (CXCall *call in callObs.calls) { + if ([call.UUID.UUIDString.lowercaseString isEqual:callInfo.uuid]) { + didShow = true; + } + } + + if (!didShow) { + callInfo.callState = @(CallStateRinging); + [CallKeep reportNewIncomingCall:callInfo.uuid + handle:@"Stringee" + handleType:@"generic" + hasVideo:hasVideo + localizedCallerName:callerName + fromPushKit:YES + payload:@{ + @"callId": callId, + @"serial": serial, + @"hasVideo": @(hasVideo), + @"caller": callerName, + @"uuid": callInfo.uuid, + } + withCompletionHandler:completion]; + return callInfo.uuid; + } + return callInfo.uuid; } - (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[@"data"][@"map"][@"data"][@"map"]; -// -// if (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]; -// } // Sua theo data của Stringee NSString *callId = dic[@"callId"] != nil ? dic[@"callId"] : @""; - int serial = dic[@"serial"] != nil ? [(NSNumber *)dic[@"serial"] intValue] : 0; + int serial = dic[@"serial"] != nil ? [(NSNumber *)dic[@"serial"] intValue] : 1; NSString *callStatus = dic[@"callStatus"] != nil ? dic[@"callStatus"] : @""; NSString *fromAlias = dic[@"from"][@"map"][@"alias"] != nil ? dic[@"from"][@"map"][@"alias"] : @""; NSString *fromNumber = dic[@"from"][@"map"][@"number"] != nil ? dic[@"from"][@"map"][@"number"] : @""; NSString *callerName = ![fromAlias isEqual:@""] ? fromAlias : (![fromNumber isEqual:@""] ? fromNumber : @"Connecting..."); - NSString *uuid = [self createUUID]; + + if (callId == nil || callId.length == 0 || ![callStatus isEqualToString:@"started"]) { + // show fake call neu cuoc goi khong den tu StringeeServer + NSString *fakeUUID = [self createUUID]; + [CallKeep reportNewIncomingCall:fakeUUID handle:@"Stringee" handleType:@"generic" hasVideo:false localizedCallerName:@"FakeCall" fromPushKit:true payload:@{} withCompletionHandler:completion]; + [CallKeep endCallWithUUID:fakeUUID reason:1]; + return; + } + + NSString *uuid = [self reportCallIfNeeded:callId serial: @(serial) callerName:callerName hasVideo: false withCompletionHandler:completion]; NSMutableDictionary *parseData = [NSMutableDictionary new]; [parseData setValue:callId forKey:@"callId"]; [parseData setValue:@(serial) forKey:@"serial"]; [parseData setValue:callStatus forKey:@"callStatus"]; - [parseData setValue:uuid forKey:@"uuid"]; - - //NSDictionary *extra = payload.dictionaryPayload[@"extra"]; - - [CallKeep reportNewIncomingCall:uuid - handle:fromNumber - handleType:@"generic" - hasVideo:false - localizedCallerName:callerName - fromPushKit:YES - payload:parseData - withCompletionHandler:completion]; + [parseData setValue: uuid forKey:@"uuid"]; // Ban them su kien khi nhan duoc push [self sendEventWithNameWrapper:CallKeepPushKitReceivedNotification body:parseData]; } -- (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 { #ifdef DEBUG @@ -826,6 +900,11 @@ - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAct #endif [self configureAudioSession]; [self sendEventWithNameWrapper:CallKeepPerformAnswerCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; + for (CallInfo * callInfo in CallKeep.instance.callMap.allValues) { + if ([callInfo.uuid isEqualToString:action.callUUID.UUIDString.lowercaseString]) { + callInfo.callState = @(CallStateAnswered); + } + } [action fulfill]; } @@ -836,6 +915,11 @@ - (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *) NSLog(@"[CallKeep][CXProviderDelegate][provider:performEndCallAction]"); #endif [self sendEventWithNameWrapper:CallKeepPerformEndCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; + for (CallInfo * callInfo in CallKeep.instance.callMap.allValues) { + if ([callInfo.uuid isEqualToString:action.callUUID.UUIDString.lowercaseString]) { + callInfo.callState = @(CallStateEnded); + } + } [action fulfill]; } @@ -899,3 +983,17 @@ - (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSessio } @end + +@implementation CallInfo + +- (instancetype)init +{ + self = [super init]; + if (self) { + _uuid = [NSUUID.UUID.UUIDString lowercaseString]; + _callState = nil; + } + return self; +} + +@end diff --git a/ios/Classes/FlutterCallkeepPlugin.m b/ios/Classes/FlutterCallkeepPlugin.m index 2bd577da..f0f6eb30 100644 --- a/ios/Classes/FlutterCallkeepPlugin.m +++ b/ios/Classes/FlutterCallkeepPlugin.m @@ -39,6 +39,7 @@ - (instancetype)initWithChannel:(FlutterMethodChannel *)channel #endif if (self = [super init]) { _callKeep = [CallKeep allocWithZone: nil]; + [CallKeep instance]; _callKeep.eventChannel = [FlutterMethodChannel methodChannelWithName:@"FlutterCallKeep.Event" binaryMessenger:[registrar messenger]]; @@ -48,9 +49,7 @@ - (instancetype)initWithChannel:(FlutterMethodChannel *)channel - (void)dealloc { -#ifdef DEBUG NSLog(@"[FlutterCallkeepPlugin][dealloc]"); -#endif [[NSNotificationCenter defaultCenter] removeObserver:self]; _callKeep = nil; } diff --git a/lib/src/api.dart b/lib/src/api.dart index a91d926e..45efa9b2 100644 --- a/lib/src/api.dart +++ b/lib/src/api.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ffi'; import 'dart:io'; import 'package:flutter/services.dart'; import 'package:flutter/material.dart' @@ -16,6 +17,18 @@ import 'package:flutter/services.dart' show MethodChannel; import 'actions.dart'; import 'event.dart'; +enum CallState { + callOut, + ringing, + answered, + ended +} + +class CallInfo { + String? uuid = null; + CallState? state = null; +} + bool get isIOS => Platform.isIOS; bool get supportConnectionService => !isIOS && int.parse(Platform.version) >= 23; @@ -247,6 +260,29 @@ class FlutterCallkeep extends EventManager { await _channel.invokeMethod( 'setOnHold', {'uuid': uuid, 'hold': shouldHold}); + FuturecleanStringeeCall() async => + _channel.invokeMapMethod("cleanStringeeCall", {}); + + Future getCallInfo(String callId, int serial) async { + var callInfo = await _channel.invokeMethod('getCallInfo', {'callId' : callId, 'serial' : serial}); + var status = callInfo["state"]; + CallInfo info = CallInfo(); + info.uuid = callInfo["uuid"]; + if (status == 0) { + info.state = CallState.callOut; + } else if (status == 1) { + info.state = CallState.ringing; + } else if (status == 2) { + info.state = CallState.answered; + } else if (status == 3) { + info.state = CallState.ended; + } + return info; + } + Future generateUUID(String callId, int serial) async { + return await _channel.invokeMethod('generateUUID', {'callId' : callId, 'serial' : serial}); + } + Future setReachable() async { if (isIOS) { return;