Avaya Client SDK

< Back to Package Overview

iOS Call Kit Integration

Overview

CallKit is a new iOS 10 framework that allows VoIP applications to integrate with the iPhone UI. Your application does not need to integrate with CallKit in order to use the Client SDK. If your application does not integrate with CallKit, you may disregard this article.

All Client SDK API changes are backwards compatible such that pre-existing applications will continue to work as they did with previous Client SDK release.

With CallKit, iOS manages the AudioSession independently of the call signalling, requiring two small changes to the Client SDK API. The call flow below shows the how your application can use the Client SDK API to become a CallKit application.

  1. Your application must configure the Client SDK to expect media path setup and signalling path to be setup independently. See CSClientConfiguration.cellularCallDetectionEnabled
  2. Your application must inform the Client SDK when the media path has been setup upon accepting a call. See CSClient.audioSessionDidBecomeActive(Bool)
  3. Your application must inform the Client SDK when the media path has been released upon ending a call. See CSClient.audioSessionDidBecomeActive(Bool)

iOS CallKit integration call flow

Answering an Audio Call

In order to the CallKit framework can identify calls, your application need to use UUIDs. It is useful to create the new category for the CSCall class and add the method which generates and returns UUID.

#import 

@interface CSCall (Additions)

@property (nonatomic) NSUUID *UUID;

@end
#import "CSCall+Additions.h"
#import 

@implementation CSCall (Additions)

- (NSUUID *)UUID {
    NSUUID* uuid = objc_getAssociatedObject(self, @selector(UUID));
    if (!uuid) {
        uuid = [NSUUID UUID];
        self.UUID = uuid;
    }
    return uuid;
}

- (void)setUUID:(NSUUID *)UUID {
    objc_setAssociatedObject(self, @selector(UUID), UUID, 
                             OBJC_ASSOCIATION_RETAIN);
}

@end

Then you are ready to report the incoming call UUID to CallKit framework. Your application should configure the audio session prior to reporting the call to ensure the audio path is set for the incoming call.

- (void)configureAudioSession { 
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSError* err;
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err];
    if (err) {
        NSLog(@"%s Error setting audio category %@", __PRETTY_FUNCTION__, err);
    }
    [audioSession setMode:AVAudioSessionModeVoiceChat error:&err];
    if (err) {
        NSLog(@"%s Error setting audio Mode %@", __PRETTY_FUNCTION__, err);
    }
}

- (void)callService:(CSCallService *)callService
        didReceiveIncomingCall:(CSCall *)call { 

    CXHandle *callHandle = [[CXHandle alloc] 
        initWithType:CXHandleTypeGeneric value:call.remoteNumber];

    CXCallUpdate *cxCallUpdate = [[CXCallUpdate alloc] init];
    cxCallUpdate.remoteHandle = callHandle;
    cxCallUpdate.supportsDTMF = call.sendDigitCapability.allowed;
    cxCallUpdate.supportsUngrouping = NO;
    cxCallUpdate.supportsGrouping = !call.isConference;
    cxCallUpdate.supportsDTMF = YES;
    cxCallUpdate.supportsHolding = YES;
    cxCallUpdate.supportsGrouping = YES;
    cxCallUpdate.localizedCallerName = call.remoteNumber;

    [self configureAudioSession];
    [self.callKitProvider reportNewIncomingCallWithUUID:call.UUID 
        update:cxCallUpdate completion:^(NSError * _Nullable error) {
            if (!error)
            {
                NSLog(@"reportNewIncomingCall Call is reported successfully:%@", 
                      error);
            }
            else
            {
                NSLog(@"reportNewIncomingCall error:%@", error);
            }
        }
    ];
}

Then, if user answers the call, your application will receieve the provider:performAnswerCallAction: message. You need to wait for the provider:didActivateAudioSession: message before accept Client SDK call.

- (void)provider:(CXProvider *)provider
        performAnswerCallAction:(CXAnswerCallAction *)action {

    CSCall *call = [self callWithUUID:action.callUUID];
    if (call) {
       [action fulfill];
       @synchronized (self) {
           self.waitingForActivation = ^{
               [call accept];
           };
       }
    }
    else {
        [action fail];
    }
}

When the provider:didActivateAudioSession: message is received from CallKit provider, send the audioSessionDidBecomeActive: message to Client SDK instance then accept the Client SDK call.

- (void)provider:(CXProvider *)provider
        didActivateAudioSession:(AVAudioSession *)audioSession {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    [self.client audioSessionDidBecomeActive:YES];
    @synchronized (self) {
        if (self.waitingForActivation) {
            self.waitingForActivation();
            self.waitingForActivation = nil;
        }
    }
}

Ending an Audio Call

When user ends a call using native iOS dialog, the provider:performEndCallAction: message is received from CallKit provider. Call [call end] then wait for call end from Client SDK before calling [action fulfilled].

- (void)provider:(CXProvider *)provider
        performEndCallAction:(CXEndCallAction *)action {
    CSCall *call = [self callWithUUID:action.callUUID];
    NSLog(@"%s", __PRETTY_FUNCTION__);
    if (call) {
        [call end];
#define kMaxWaitForCallEnd 20 /* 2 seconds */
        NSUInteger count = 0;
        while (call.state != CSCallStateEnded && 
               call.state != CSCallStateEnding) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
            beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
            count++;
            if (count >= kMaxWaitForCallEnd) {
                break;
            }
#undef kMaxWaitForCallEnd
        }
        [action fulfill];
    }
    else
    {
        [action fail];
    }
}

Then the provider:didDeactivateAudioSession: message is received from CallKit provider, Send the audioSessionDidBecomeActive: message to the Client SDK instance.

- (void)provider:(CXProvider *)provider
        didDeactivateAudioSession:(AVAudioSession *)audioSession {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    [self.client audioSessionDidBecomeActive:NO];
}