6 Managing calls

Implementing call control lets users manage calls in your application, directly from their Jabra device. For example, accepting calls, rejecting calls, putting a call on hold, or resuming a held call.

To manage calls, the Jabra library provides the following two modules, Advanced Call Control (ACC) and Easy Call Control (ECC) The main difference between them is the level of abstraction.

The following description can help you decide the most optimal path to integration.

  • Easy Call Control: Offers a higher-level solution and is intended to be quicker and easier to integrate. The high-level methods handle most of the complexities regarding control of the state of the device and interpreting signals that come from the device. This module also enables you to integrate Single Call or Multiple Call functionalities. Choose Single call if you intend to only handle one call at a time.

However, if your application supports features such as handling multiple ongoing/active calls simultaneously, putting them on hold, etc., you can choose Multiple Call functionalities. The approach of integrating with the ECC module with Single Call functionality partly removes the complexity of a manual integration. This is the recommended path.

  • Advanced Call Control: This module provides a lower-level, more advanced API allowing greater flexibility and control. However, this path is more complex to integrate, and can be more prone to mistakes than ECC. When you choose this module, be aware that you must ensure that all operations are executed correctly.

If you choose the ACC module, it is strongly recommended that you familiarize yourself well with the Jabra Integrator's Guide, to help you understand the complexities of integrating. For example, muting/unmuting of your Jabra device requires an understanding of the Muting and unmuting chapter in the Jabra Integrator's Guide, as well as an understanding of device signals, signal acknowledgement and other concepts.

The functionalities of both Single and Multiple Call are also available in the ACC module; however, you must handle everything manually, including complications related to supporting Single or Multiple Call. For example, in Single or Multiple Call you must set your Jabra devices to offhook or onhook mode, ring the Jabra device, handle offhooking, as well as handling the flash signal from the device, among others.

6.1 Easy Call Control (ECC)

To implement ECC, the following list are the suggested steps to follow. If you are already familiar with the topics suggested, you can proceed with initializing the module.

  • Read about Handling connection changes, exceptions, and the initial state
  • Read about Supported features that you would like to integrate
  • Choose to integrate with Single Call or Multiple Call functionalities
  • Initialize the ECC module

To use call control functionalities via the ECC module, you must first initialize the module using the EasyCallControlFactory class. This class provides helper functions for taking control over IDevice instances and gives access to call control for the device.

The ECC module can be initialized with either one of the following: Single Call or Multiple Call functionalities.

To initialize the module with Single Call functionalities, see the following code sample.

using Jabra.NET.Sdk.Core;
using Jabra.NET.Sdk.Modules.EasyCallControl;
using Jabra.NET.Sdk.ToolsAndHelpers;

namespace InitializeECCSingleCall
{
    class ECCSingleCall
    {
        static async Task Main()
        {
            // Initialize the Jabra SDK
            try
            {
                var jabraSdk = Init.InitSdk(new Config());

                // Set up for ISingleCallControl
                var easyCallControlFactory = new EasyCallControlFactory(jabraSdk);

                // Whenever the core module detects new devices
                jabraSdk.DeviceAdded.Subscribe(
                    async device =>
                    {
                        // Create Easy Call Control for single call handling
                        if (easyCallControlFactory.SupportsEasyCallControl(device))
                        {
                            var singleCallControl = await easyCallControlFactory.CreateSingleCallControl(device);

                            // Call single-call-control related functionality, for example
                            // Start a call, wait five seconds, end the call.
                            await singleCallControl.StartCall();
                            System.Threading.Thread.Sleep(5000);
                            await singleCallControl.EndCall();
                            // etc.
                        }
                    });
            }
            catch (JabraException ex)
            {
                Console.WriteLine(ex.Message);
            }

            // Prevent the app from terminating, to allow for attaching and detaching.
            await Task.Delay(System.Threading.Timeout.Infinite);
        }
    }
}

To initialize the module with Multiple Call functionalities, see the following code sample.

using Jabra.NET.Sdk.Core;
using Jabra.NET.Sdk.Modules.EasyCallControl;
using Jabra.NET.Sdk.ToolsAndHelpers;

namespace InitializeECCMultiCall
{
    class ECCMultiCall
    {
        static async Task Main()
        {
            // Initialize the Jabra SDK
            try
            {
                var jabraSdk = Init.InitSdk(new Config());

                // Set up for ISingleCallControl
                var easyCallControlFactory = new EasyCallControlFactory(jabraSdk);

                // Whenever the core module detects new devices
                jabraSdk.DeviceAdded.Subscribe(
                    async device =>
                    {
                        // Create Easy Call Control for multi call handling
                        // Note different creation method from the previous example
                        if (easyCallControlFactory.SupportsEasyCallControl(device))
                        {
                            var multiCallControl = await easyCallControlFactory.CreateMultiCallControl(device);

                            // Call multi-call-control related functionality, for example
                            // Start a call, wait five seconds, put call on hold, wait, resume, wait, then end the call.
                            await multiCallControl.StartCall();
                            System.Threading.Thread.Sleep(5000);
                            await multiCallControl.Hold();
                            System.Threading.Thread.Sleep(5000);
                            await multiCallControl.Resume();
                            System.Threading.Thread.Sleep(5000);
                            await multiCallControl.EndCall();
                            // etc.
                        }
                    });
            }
            catch (JabraException ex)
            {
                Console.WriteLine(ex.Message);
            }

            // Prevent the app from terminating, to allow for attaching and detaching.
            await Task.Delay(System.Threading.Timeout.Infinite);
        }
    }
}

6.1.1 Handling connection changes, exceptions, and the initial state

To help you avoid common pitfalls during integration, it is recommended that you read about handling changes, exceptions, and the initial state.

Handling connection changes

When setting up ECC, you must know how to handle disconnection cases. For example, when a user physically unplugs the device.

To listen for disconnect events, ensure you setup a subscription as follows:

multiCallControl.OnDisconnect.subscribe(
    _ =>
    {
        // this callback is triggered if the device disconnects
    });

// or

singleCallControl.OnDisconnect.subscribe(
    _ =>
    {
        // this callback is triggered if the device disconnects
    });

When a device is disconnected, the IMultiCallControl or ISingleCallControl instances become invalid and can no longer be used. To continue using ECC on a new device, you must create a new instance of either IMultiCallControl or ISingleCallControl.

The Teardown method

You can use the Teardown method whenever you want to deactivate a IMultiCallControl or ISingleCallControl instance.

The method removes internal event subscriptions to avoid memory leaks and, if taken, clear the internal call lock. The most common use case is when you are handling a change of device.

If you create a single-page web application or work on a similar project with a persistent state, you must call Teardown whenever the IMultiCallControl or ISingleCallControl instance is no longer needed. For example, when changing views in the UI.

Failure to do so can cause undesirable behavior and memory leaks.

Initial state

If a device is disconnected -- or the user decides to change devices in the middle of a call, you must recover the device state when instantiating a new one.

You can set an initial state from the easyCallControlFactory.CreateMultiCallControl and easyCallControlFactory.CreateSingleCallControl methods, which take an optional second argument containing the initial state. The following is an example for IMultiCallControl.

if (easyCallControlFactory.SupportsEasyCallControl(device))
{
    MultiInitialState initialState = new MultiInitialState
    (
        ongoingCalls: 2,
        isMuted: true,
        isOnHold: false
    );
    var multiCallControl = await easyCallControlFactory.CreateMultiCallControl(device, initialState);

    // The device will have two ongoing calls and be muted at startup
}

And for ISingleCallControl.

if (easyCallControlFactory.SupportsEasyCallControl(device))
{
    SingleInitialState initialState = new SingleInitialState(callActive: true, isMuted: true);
    var singleCallControl = await easyCallControlFactory.CreateSingleCallControl(device, initialState);

    // The device will be in a call and be muted at startup
}

Handling a change of device

If the user wants to select another device, you must first Teardown the old IMultiCallControl or ISingleCallControl instance before creating a new one.

The following is a pseudo-code example of how that might look:

MultiInitialState yourAppState = new MultiInitialState
{
    OngoingCalls = 2,
    IsMuted = true,
    IsOnHold = false
};

IDevice newDevice; // The device selected by the user in your GUI

// Teardown old instance
// This will clear internal call locking and event subscriptions to avoid leaks
oldMultiCallControl.Teardown();

// Set initial state for the new device
MultiInitialState initialState = new MultiInitialState
{
    OngoingCalls = yourAppState.OngoingCalls,
    IsMuted = yourAppState.IsMuted,
    IsOnHold = yourAppState.IsOnHold
};

// Create new instance
var newMultiCallControl = await easyCallControlFactory.CreateMultiCallControl(
    newDevice,
    initialState
);

// Now, you can continue your program using newMultiCallControl

Handling exceptions

In certain scenarios, the module can throw an exception. For example, if a Jabra device is being used by another application running on the computer (e.g., another application is using it for a call, then an exception is thrown if you try to use the device).

Most of the exceptions are not strictly avoidable and can happen due to the current state of the device.

For this reason, it is recommended that you have a strategy for handling potential exceptions from every method in the ISingleCallControl or IMultiCallControl class that has potential exceptions listed.

6.1.2 Supported features in ECC

Provided that an integration exists between the Jabra device and your application, end-users can manage and control a call with several features.

The following is not an exhaustive list of features. In addition to basic functions such as ringing a Jabra device, accepting/rejecting a call, end-users can also control a call using muting/unmuting and hold/resume functionalities.

Muting/Unmuting

You can unmute both from the device and your application.

From the device:

When a user mutes the device by physically interacting with the Jabra device (e.g., muting by rotating the boom arm), some Jabra devices stop transmitting audio from the microphone and send a signal to the library.

The application then listens for these signals, acknowledges that it has received the signal, and stops sending audio to other attendees in the call.

From the application:

When muting through the application UI, the application must also request the device to mute through the Jabra library. Be aware that this is like a fire-and-forget API whereby the Jabra device does not respond to a mute request from the application.

For more information on muting and unmuting, see the Muting and unmuting chapter in the Jabra Integrator's Guide.

Hold/Resume

The active call was either put on hold or the current active call was resumed.

6.1.3 Implementing Single or Multiple Call functionalities

After initializing the ECC module, you must implement either Single or Multiple Call functionalities.

If you are only to support a single call at a time, you must start with Single Call. This is the preferred and easiest combination for an accelerated path to integration.

Be aware that you can start with Single Call functionalities and later extend to Multiple Call functionalities if your requirements are to change in the future.

On the other hand, if you have already decided to handle multiple calls and your application supports it, you can choose Multiple Call functionalities.

  • Single Call: Refers to an API that enables a single call to be accepted or rejected.

  • Multiple Call: This allows the use of the IMultiCallControl interface to handle multiple calls simultaneously. This interface extends the ECC module to instruct the Jabra device that there are multiple call(s) on hold, start multiple calls simultaneously, and swap calls.

Be aware that Multiple Call functionalities have a higher level of complexity than Single Call functionalities.

Also, be aware that in IMultiCallControl, the CallActive observable is replaced by the OngoingCalls observable.

For more information about these and other APIs, see the Jabra .NET Library API Reference page.

6.1.4 Single and Multiple Call functionalities

To make your integration easier, Single and Multiple Call functionalities, including code samples have been divided as follows:

  • Handling new calls (Single and Multiple)
  • Handling active or ongoing calls (Single and Multiple)
  • Terminating a call (Single and Multiple)
  • Other functionalities (Single and Multiple)

6.1.5 Handling new calls -- Single Call

Ringing the device

You can indicate to a device to start its ringing effect. This notifies the device that there is an incoming call. The ringing effect depends on the capabilities of the device, for example a flashing green LED light.

The following code sample shows how your application must react when receiving an incoming call and starts to ring the device:

using System.Runtime.ExceptionServices;

ISingleCallControl singleCallControl; // assume `ISingleCallControl` is already instantiated
                                      // See 6.1 Initializing the ECC module

var wasAccepted = await singleCallControl.SignalIncomingCall();
if (wasAccepted)
{
   // Call has started, proceed to update your UI
}
else
{
   // Call was rejected, proceed to update your UI
}

SignalIncomingCall returns a task that resolves to true or false depending on whether the call was accepted or rejected; the task is resolved either from interacting with the (physical) device buttons or by calling the functions AcceptIncomingCall or RejectIncomingCall.

Accepting or rejecting an incoming call

If your application has received a new incoming call and you have indicated that to the device (as mentioned in Start ringing the device), the device rings.

If the end-user interacts with your UI, you can either accept or reject the call. The acceptance or rejection is done via the AcceptIncomingCall and RejectIncomingCall methods, respectively.

Similar to interacting with the device, executing either of these methods resolves the task returned by SignalIncomingCall.

  • Accepting a call

The following code sample shows how to accept the call from your application:

ISingleCallControl singleCallControl; // assume `ISingleCallControl` is already instantiated
                                      // See 6.1 Initializing the ECC module

var incomingCall = singleCallControl.SignalIncomingCall();
await singleCallControl.AcceptIncomingCall(); // accept the call
var wasAccepted = await incomingCall; // wasAccepted will be true
  • Rejecting a call

The following code sample shows how to reject the call from your application:

ISingleCallControl singleCallControl; // assume `ISingleCallControl` is already instantiated
                                      // See 6.1 Initializing the ECC module

var incomingCall = singleCallControl.SignalIncomingCall();
singleCallControl.RejectIncomingCall(); // reject the call
var wasAccepted = await incomingCall; // wasAccepted will be false

Accepting or rejecting calls by interacting with the device

When the end-user interacts with the Jabra device and the device rings, the user can accept or reject the call by physically interacting with the device via a button press.

Be aware that this can be different depending on your device.

  • Accepting a call

When the call is accepted (or the device is offhook), it resolves the task returned by SignalIncomingCall. The task is then resolved with a true value.

In both cases, regardless of whether the task is resolved with a true or false value, the device stops ringing.

See the following code sample:

ISingleCallControl singleCallControl; // assume `ISingleCallControl` is already instantiated
                                      // See 6.1 Initializing the ECC module
try
{
    var wasAccepted = await singleCallControl.SignalIncomingCall();

    // User presses start-call button on the device
    if (wasAccepted)
    {
        // Call was accepted, proceed to update your UI
    }
    else if (!wasAccepted)
    {
        // Call was rejected, proceed to update your UI
    }
}
catch (err)
{
    // Something bad happened.
}
  • Rejecting a call

Rejecting a call (or when the device is onhook) can be handled similarly to accepting a call. For example, the task returned by SignalIncomingCall will be resolved. However, it resolves with a false value. The following code sample shows how you can handle this scenario:

ISingleCallControl singleCallControl; // assume `ISingleCallControl` is already instantiated
                                      // See 6.1 Initializing the ECC module

var wasAccepted = await singleCallControl.SignalIncomingCall();

// User presses end-call button on the device
if (!wasAccepted)
{
   // Call was rejected, proceed to update your UI
}

Starting a new call from your application

From the Jabra device's perspective, starting a new outgoing call and being in an active call (i.e., talking to someone) is handled in the same way -- you must indicate to the device that it is in an active call.

See the following code sample about the user interacting with your application and wanting to start a new call:

try
{
    ISingleCallControl singleCallControl; // assume `ISingleCallControl` is already instantiated
                                          // See 6.1 Initializing the ECC module

    await singleCallControl.StartCall();
}
catch (err)
{
    // Something bad happened. Inspect error.
    // A common mistake is to call \`StartCall()\` while the device is already in a call - that would
    // throw an exception for Single Call Control.
}

Once the call is answered, nothing should change on the device, as the StartCall function has already been called.

If the other party rejects the call, the call is considered ended. You can find more details and a sample scenario in Ending a call from your application.

6.1.6 Handling new calls -- Multiple Calls

Start Call command

The StartCall command reacts differently from the Single Call equivalent and lets you handle multiple calls in parallel.

When you start a call while another is already active, the current call gets put on hold, and the new call is activated.

Whenever a call is started, the OngoingCalls observable emits a count incremented by one. See the following code sample:

try
{
    IMultiCallControl multiCallControl; // assume `IMultiCallControl` is already instantiated
                                        // See 6.1 Initializing the ECC module for multi call handling

    await multiCallControl.StartCall();
    await multiCallControl.StartCall();
    multiCallControl.OngoingCalls.Subscribe(
        (ongoingCalls) =>
        {
            // OngoingCalls = 2
            // because we started two calls.
        });
}
catch (err)
{
   // Something bad happened. Inspect error.
}

Accept an incoming call

The AcceptIncomingCall method generally works the same way as the ISingleCallControl equivalent, which has already been described.

The IMultiCallControl interface allows you to specify what happens when a call is already active, and you can either end the call (default) or put the current call on hold.

For details on how to accept the incoming call and put the current call on hold, see the following code sample:

// End current call...
try
{
    IMultiCallControl multiCallControl; // assume `IMultiCallControl` is already instantiated
                                        // See 6.1 Initializing the ECC module for multi call handling

    await multiCallControl.StartCall(); // OngoingCalls = 1
    multiCallControl.SignalIncoming();
    await multiCallControl.AcceptIncomingCall(AcceptIncomingCallBehavior.END_CURRENT);
    multiCallControl.OngoingCalls.Subscribe(
        (ongoingCalls) => {
            // OngoingCalls = 1
            // because we ended the current call and accepted the incoming call
        });
}
catch (err)
{
   // Something bad happened. Inspect error.
}

// Put current call on hold...
try
{
    IMultiCallControl multiCallControl; // assume `IMultiCallControl` is already instantiated
                                        // See 6.1 Initializing the ECC module for multi call handling

    await multiCallControl.StartCall(); // OngoingCalls = 1
    multiCallControl.SignalIncoming();
    await multiCallControl.AcceptIncomingCall(AcceptIncomingCallBehavior.HoldCurrent);
    multiCallControl.OngoingCalls.Subscribe(
        (ongoingCalls) =>
        {
             // OngoingCalls = 2
             // because we put the current call on hold and accepted the incoming call
        });
}
catch (err)
{
    // Something bad happened. Inspect error.
}

6.1.7 Handling active or ongoing calls -- Single and Multiple Calls

Changing and keeping track of the mute state of the device

During the lifetime of a call, the user can mute or unmute their microphone many times. Depending on the design of your application, the user could mute / unmute the microphone by pressing a button on the Jabra device.

When the user does so, it is important that you also indicate to the Jabra device to do this.

Most Jabra devices can mute by interacting with the device itself (e.g., physically manipulating the boom arm). This interaction is described in more detail in Keeping track of the mute state .

Interacting with your application requires executing Mute or Unmute. Moreover, to keep your application's state synchronized with the device state, ensure you keep track of what the device is doing or when any changes occur.

The following code sample shows how to subscribe to changes in the mute state, as well as how to mute and unmute:

ISingleCallControl singleCallControl; // assume `ISingleCallControl` is already instantiated
                                      // See 6.1 Initializing the ECC module

await singleCallControl.StartCall(); // Cannot mute/unmute if not in a call
singleCallControl.Mute(); // indicates that the microphone should mute
singleCallControl.Unmute(); // indicates that the microphone should not be muted anymore
singleCallControl.MuteState.Subscribe(
    (muteState) =>
    {
        // muteState = MuteState.NO_ONGOING_CALL
        // muteState = MuteState.UNMUTED (will unmute by default when starting a call)
        // muteState = MuteState.MUTED
        // muteState = MuteState.UNMUTED
    });

6.1.8 Handling active or ongoing calls -- Multiple Calls

Put a call on hold or resume a call

The IMultiCallControl interface lets you put an active call on hold with the Hold command and resume it with the Resume command.

These commands are only available when the device is in an active call; otherwise, an exception is thrown. See the following sample code, showing how to put a call on hold or resume a call:

try
{
   IMultiCallControl multiCallControl; // assume `IMultiCallControl` is already instantiated
                                        // See 6.1 Initializing the ECC module for multi call handling

    await multiCallControl.StartCall(); // hold only works if a call is started
    await multiCallControl.Hold();
    multiCallControl.HoldState.Subscribe(
        (holdState) =>
        {
            // HoldState = ON_HOLD
        });
}
catch (err)
{
    // Something bad happened. Inspect error.
}

In the following code sample, you can see a full sequence, whereby HoldState changes when calling the different commands in the sequence.

try
{
    IMultiCallControl multiCallControl; // assume `IMultiCallControl` is already instantiated
                                        // See 6.1 Initializing the ECC module for multi call handling

    await multiCallControl.StartCall(); // HoldState = NOT_ON_HOLD
    await multiCallControl.Hold(); // HoldState = ON_HOLD
    await multiCallControl.Resume(); // HoldState = NOT_ON_HOLD
    await multiCallControl.EndCall(); // HoldState = NO_ONGOING_CALLS
}
catch (err)
{
    // Something bad happened. Inspect error.
}

Swap call requests

A swap call request means that the user wants to swap from the current (active) call to the next call on hold.

When starting two or more calls, the device can emit swap call requests. On your device, it is triggered by the same button that activates the Hold functionality when one call is ongoing.

See the following sample code for two ongoing calls.

IMultiCallControl multiCallControl; // assume `IMultiCallControl` is already instantiated
                                    // See 6.1 Initializing the ECC module for multi call handling

// Swap works with a minimum of two ongoing calls
await multiCallControl.StartCall();
await multiCallControl.StartCall();

// User long-presses the multi-function-button on the device
multiCallControl.SwapRequest.Subscribe(
    (_) =>
    {
        // The observable callback is called
        // On the softphone side, put the active call on hold and resume the previously held call
    });

If in doubt, ignore the swap call request and handle it through the UI. See the following code sample on how to resume the last current call.

// Example of how to resume the "last current call"

IMultiCallControl multiCallControl; // assume `IMultiCallControl` is already instantiated
                                    // See 6.1 Initializing the ECC module for multi call handling

await multiCallControl.StartCall(); // first call started
await multiCallControl.StartCall(); // second call started, it is now the current call while firstCall is on hold.
await multiCallControl.Hold(); // hold current call, meaning that both calls are on hold

// Subscribe to changes in HoldState so we know the current state
HoldState currentHoldState;
multiCallControl.HoldState.Subscribe(
    (newHoldState) =>
    {
        currentHoldState = newHoldState;
    });

// User long-presses the multi-function-button on the device
multiCallControl.SwapRequest.Subscribe(
    async (_) =>
    {
        // Assess the current HoldState
        if (currentHoldState == HoldState.ON_HOLD)
        {
            // `Resume()` will put the device back into an active call state,
            // however, you need to handle on the softphone side which of the held calls
            // to resume

            await multiCallControl.Resume();
        }
    });

Ongoing calls

As previously explained in Handling New Calls -- Multiple Calls, you can subscribe to the OngoingCalls counter. See the following code sample:

IMultiCallControl multiCallControl; // assume `IMultiCallControl` is already instantiated
                                    // See 6.1 Initializing the ECC module for multi call handling

multiCallControl.OngoingCalls.Subscribe(
    (ongoingCalls) =>
    {
        // OngoingCalls equals to the number of calls started
    });

Whenever you start a call -- either through StartCall or AcceptIncomingCall -- this count increments by one. It does not matter if the ongoing/active calls are on hold or not -- it is the total sum of calls.

At the same time, whenever a call is ended by calling EndCall or by pressing the End call button on the device, the count decrements by one.

When the count gets back to zero, the device ends the active call state and returns to an idle or inactive state. You can read more about ending a call in Terminating a call.

6.1.9 Terminating a call -- Single and Multiple Calls

Ending a call from your application

During an active call, the user may want to end the call. Your application may offer this functionality to the user, for example a red handset button in the UI, indicating the ability to hang up or end the call.

When the user presses the button, the call ends on your application. However, the Jabra device remains in an active call state until you indicate to it that the call has ended.

The following code sample shows how to end the call (or onhook):

try
{
    ISingleCallControl singleCallControl; // assume `ISingleCallControl` is already instantiated
                                          // See 6.1 Initializing the ECC module

    await singleCallControl.EndCall();
}
catch (err)
{
    // Something bad happened. Inspect error.
}

Ending a call by interacting with the device

Jabra devices let you end a call from the device itself. Therefore, your application must stop the call when the device indicates that the user interacted with it and wants to hang up or end the call.

You can do this by subscribing to the call state and acting appropriately when transitions/state changes occur from in a call (active) to not in a call state.

6.1.10 Other functionalities

Change the device used in an active call -- Multiple Calls

The ECC module uses a device-based abstraction. Therefore, changing the device requires creating a new instance, and passing a reference to the new device.

Additionally, you must also indicate to the old device that it is no longer used in that call. To do so, ensure you execute the Teardown method.

To stop receiving updates for that device, you must also unsubscribe all observables. See the following code sample to change the device used in an active call.

var oldDeviceCallControl; // assume `ISingleCallControl` or `IMultiCallControl` is already instantiated
var newDeviceCallControl; // assume `ISingleCallControl` or `IMultiCallControl` is already instantiated

// For IMultiCallControl you should follow the same pattern for OngoingCalls, HoldState and SwapRequest
var muteStateSub = oldDeviceCallControl.MuteState.Subscribe(); // assume you have stored Subscriptions for MuteState
var callActiveSub = oldDeviceCallControl.CallActive.Subscribe(); // assume you have stored Subscriptions for CallActive

// End the call on the old device
await oldDeviceCallControl.EndCall();

// Unsubscribe
muteStateSub.Unsubscribe();
callActiveSub.Unsubscribe();

// Teardown old instance
oldDeviceCallControl.Teardown();

// Subscribe to state changes for the new device
muteStateSub = newDeviceCallControl.MuteState.Subscribe();
callActiveSub = newDeviceCallControl.CallState.Subscribe();

try
{
    await newDeviceCallControl.StartCall();
}
catch (err)
{
    // Something bad happened. Inspect error.
}

Keeping track of the Active Call state of the device -- Single Call

In addition to keeping track of the mute state, it is also important to keep track of the CallActive or Active Call state. If you do not do so, it can mean that your call ends, but your application will not reflect that the call has ended.

When a user accepts or starts a call from your application, the application must execute commands and reflect the changes in the state of the device.

Any call-related actions such as accepting an incoming call, starting, or ending a call, and others, trigger the CallActive observable to emit a change in call state.

The following code sample shows how to subscribe to changes in the call state:

ISingleCallControl singleCallControl; // assume `ISingleCallControl` is already instantiated
                                      // See 6.1 Initializing the ECC module

singleCallControl.CallActive.Subscribe(
    (callActive) =>
    {
        if (callActive)
        {
            // the device is in an "active call state", reflect on the changes (e.g. update the UI)
        }
        else
        {
            // the device is no longer in a call state, reflect on the changes (e.g. update the UI)
        }
    });

6.2 Advanced Call Control (ACC)

The ACC module is the most advanced of the integration techniques, with a more advanced API and is more complex to integrate than the ECC module.

When implementing this module, ensure that your Jabra device supports call control before creating a call control instance. You must also acquire a call lock, handle exceptions, connection changes, and others.

To help guide you through the integration, it is strongly recommended that you first familiarize yourself with the Jabra Integrator's Guide, especially the request/acknowledge signals concept, located in the Interacting with Jabra devices chapter in the Jabra Integrator's Guide.

The following code sample does the following:

  • Initializes the ACC module
  • Checks that your Jabra device supports call control (i.e., whether it can perform call control functionalities)
  • Creates an instance of ICallControl, letting you perform call control with the specified Jabra device
using Jabra.NET.Sdk.Core;
using Jabra.NET.Sdk.Modules.CallControl;
using Jabra.NET.Sdk.ToolsAndHelpers;

namespace InitializeACC
{
    class InitializeACC
    {
        static async Task Main()
        {
            // Initialize the Jabra SDK
            try
            {
                var jabraSdk = Init.InitSdk(new Config());

                // Set up for call control
                var callControlFactory = new CallControlFactory(jabraSdk);

                jabraSdk.DeviceAdded.Subscribe(
                    async (device) =>
                    {
                        if (!callControlFactory.SupportsCallControl(device))
                        {
                            return;
                        }
                        var deviceCallControl = await callControlFactory.CreateCallControl(device);
                        Console.WriteLine("Device Call Control ready.");

                        // Try example operations on the call control device
                        // To do this we must take the call lock
                        var gotLock = await deviceCallControl.TakeCallLock();
                        if (gotLock)
                        {
                            deviceCallControl.OffHook(true);
                            Console.WriteLine("Device Off Hook.");
                            System.Threading.Thread.Sleep(3000);
                            deviceCallControl.OffHook(false);
                            Console.WriteLine("Device Back On Hook.");
                            deviceCallControl.ReleaseCallLock();
                        }
                    });
            }
            catch (JabraException ex)
            {
                Console.WriteLine(ex.Message);
            }

            // Prevent the app from terminating, to allow for attaching and detaching.
            await Task.Delay(System.Threading.Timeout.Infinite);
        }
    }
}

You can initialize the module via the CallControlFactory class. Once the module is initialized, you can create an ICallControl object by passing an IDevice instance.

6.2.1 Acquiring a call lock

After you initialize the library and discover devices, the following code sample shows how to acquire a call lock.

// See the call lock code sample in section 6.3 for how to find the device
var deviceCallControl = await callControlFactory.CreateCallControl(device);
try
{
    var gotCallLock = await deviceCallControl.TakeCallLock();
    if (gotCallLock)
    {
        // You have the call lock and can now set device states
        // ...
        Console.WriteLine("Call lock obtained.");
    }
}
catch (JabraException ex)
{
    // An exception will be thrown if you already have the call lock
    Console.WriteLine(ex.Message);
}

6.2.2 Releasing a call lock

The lock remains active for the duration of the call. When the call finishes, the lock must be released, which lets other applications use the device.

After acquiring a call lock, it can be released as shown.

try
{
    deviceCallControl.ReleaseCallLock();
    Console.WriteLine("Call lock released.");
}
catch (JabraException ex)
{
    // An exception will be thrown if you did not have the call lock.
    Console.WriteLine(ex.Message);
}

6.2.3 Call lock code sample

Using call control, see the following code sample showing how to:

  • Acquire and release a call lock
  • Ring a device
  • Stop the ringer after two seconds
using System.Reactive.Linq;
using Jabra.NET.Sdk.Core;
using Jabra.NET.Sdk.Core.Types;
using Jabra.NET.Sdk.Modules.CallControl;
using Jabra.NET.Sdk.ToolsAndHelpers;

namespace NETSdkSourceExample
{
    internal static class Program
    {
        private static async Task RingDevice(CallControlFactory callControlFactory, IDevice device)
        {
            // Initialize call control
            var deviceCallControl = await callControlFactory.CreateCallControl(device);
            Console.WriteLine("Call Control ready for device.");

            // Acquire call lock
            var gotCallLock = await deviceCallControl.TakeCallLock();
            if (gotCallLock)
            {
                Console.WriteLine("Call Lock obtained.");

                // Ring the device, wait and then stop ringing. Assume user did not answer.
                deviceCallControl.Ring(true);
                Console.WriteLine("Ringing device.");

                System.Threading.Thread.Sleep(2000);

                deviceCallControl.Ring(false);
                Console.WriteLine("Stop ringing device.");

                // Release the call lock. In general care should be taken to always release this,
                // even if an error occurs.
                deviceCallControl.ReleaseCallLock();
                Console.WriteLine("Call Lock released.");
            }
        }

        static async Task Main()
        {
            try
            {
                var jabraSdk = Init.InitSdk(new Config());
                var callControlFactory = new CallControlFactory(jabraSdk);

                jabraSdk.DeviceList
                    .Where(devices => devices.Any(d => callControlFactory.SupportsCallControl(d)))
                    .Take(1)
                    .Subscribe(
                    async devices =>
                    {
                        var firstDevice = devices.Find((device) => callControlFactory.SupportsCallControl(device));
                        if (firstDevice != null)
                        {
                            Console.WriteLine("Using: " + firstDevice.Name + " - " + firstDevice.ProductId);

                            // Ring the device
                            await RingDevice(callControlFactory, firstDevice);
                        }
                        else
                        {
                            Console.WriteLine("Unable to find any devices which support call control.");
                        }
                    }
                );
            }
            catch (JabraException ex)
            {
                Console.WriteLine(ex.Message);
            }

            // Prevent the app from terminating, to give enough time for the
            // attached devices to be detected.
            await Task.Delay(System.Threading.Timeout.Infinite);
        }
    }
}

6.2.4 Handling connection changes and exceptions

Handling connection changes

Physical devices can have multiple connections. For example, a Jabra Evolve2 85 headset might be connected both via USB and through a Bluetooth Jabra Link 380 dongle, with both connections having call control capabilities.

When the ICallControl instance is created, it uses exactly one of the device's connections. If that connection is removed, but the device has another connection capable of call control, you must create a replacement ICallControl instance.

Additionally, the device's internal state resets, so if your application was currently manipulating the device in any way, you will have to restore that state manually. This includes taking the call lock as the mandatory prerequisite step.

Moreover, the ICallControl interface exposes an OnDisconnect observable, which emits whenever the connection used by the ICallControl instance disconnects.

The following sample code shows the basic steps in handling OnDisconnect.

using Jabra.NET.Sdk.Core;
using Jabra.NET.Sdk.Modules.CallControl;
using Jabra.NET.Sdk.ToolsAndHelpers;

namespace ACCConnectionChanges
{
    class ACCConnectionChanges
    {
        static async Task Main()
        {
            // Initialize the Jabra SDK
            try
            {
                var jabraSdk = Init.InitSdk(new Config());

                // Set up an ICallControl object
                var callControlFactory = new CallControlFactory(jabraSdk);

                // When a device is added, set things up so we update things correctly
                // when a connection to that device is removed.
                jabraSdk.DeviceAdded.Subscribe(
                    async device =>
                    {
                        if (callControlFactory.SupportsCallControl(device))
                        {
                            var deviceCallControl = await callControlFactory.CreateCallControl(device);
                            Console.WriteLine(String.Format("Device connected: {0}", device.Name));
                            deviceCallControl.OnDisconnect.Subscribe(
                                async (_) =>
                                {
                                    if (!callControlFactory.SupportsCallControl(device))
                                    {
                                        return;
                                    }

                                    // There is an available connection, create a new ICallControl
                                    deviceCallControl = await callControlFactory.CreateCallControl(device);
                                    Console.WriteLine("Connection remains - creating a new call control interface.");

                                    // Restore the state of the device.
                                });
                        }
                    });
            }
            catch (JabraException ex)
            {
                Console.WriteLine(ex.Message);
            }

            // Prevent the app from terminating, to allow for attaching and detaching.
            await Task.Delay(System.Threading.Timeout.Infinite);
        }
    }
}

Handling exceptions

In certain scenarios, the ACC module can throw an exception. For example, if a Jabra device is being used by another application (an application is using it for a call) an exception is thrown if you try to use the device.

Most of these exceptions are not strictly avoidable and can happen due to the device's current state.

For this reason, it is recommended that you have a strategy for handling potential exceptions from every method in the ICallControl class.

6.2.5 Supported features in the ACC module

Provided that an integration exists between the Jabra device and your application, an end-user can manage and control a call with the following supported features:

Setting the ringer ON/OFF

Notifies that there is an incoming call. This is an important signal as it is used to notify of an incoming call and can have different effects in different scenarios.

For example, a device might respond with a flashing green LED whilst your application might play its own ringtone.

Change call status

Notifies the device that a call was either started or ended. The behavior of a device depends on the call status, for example, the device cannot be muted unless it is in an active call.

Additional effects depend on the capabilities of your Jabra devices, for example, setting a solid red LED light on your headset during a call.

Mute/Unmute

For information on how to mute/unmute from the device and application, forcing a Mute state, diagrams, reacting to device signals, common issues, and others, refer to Muting and unmuting in the Jabra Integrator's Guide.

Hold/Resume

Notifies the device that the call state changed, but that the call has not ended yet. This means that the call was either put on hold or was resumed. The effect depends on the firmware of the device.