示例#1
0
 public static Task <bool> CheckPasscodeIsOptional(this
                                                   ConferenceServices cs, RealTimeAddress conferenceAddress)
 {
     return(Task <bool> .Factory.FromAsync(
                cs.BeginCheckPasscodeIsOptional,
                cs.EndCheckPasscodeIsOptional, conferenceAddress, null));
 }
 public static Task<bool> CheckPasscodeIsOptional(this 
     ConferenceServices cs, RealTimeAddress conferenceAddress)
 {
     return Task<bool>.Factory.FromAsync(
         cs.BeginCheckPasscodeIsOptional,
         cs.EndCheckPasscodeIsOptional, conferenceAddress, null);
 }
示例#3
0
            public override Collection <string> FindPhoneNumbers(string sipUri)
            {
                Collection <string> list          = new Collection <string>();
                RealTimeAddress     sipUriAddress = null;

                try
                {
                    sipUriAddress = new RealTimeAddress(sipUri);
                    if (sipUriAddress.IsPhone) // Should not be phone uri
                    {
                        return(list);
                    }
                }
                catch (ArgumentException)
                {
                    return(list);
                }
                lock (this.SyncRoot)
                {
                    foreach (string key in m_directory.Keys)
                    {
                        RealTimeAddress valueAddress = new RealTimeAddress(m_directory[key]);
                        if (valueAddress == sipUriAddress)
                        {
                            list.Add(key);
                        }
                    }
                }
                return(list);
            }
示例#4
0
        private void ReceiveIncomingIMCall(object sender, CallReceivedEventArgs <InstantMessagingCall> e)
        {
            this.Logger.Log(Logger.LogLevel.Verbose,
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "IMCall {0} received.",
                                Logger.Pointer(e.Call)));

            if (this.IsTerminatingTerminated || e.IsConferenceDialOut)
            {
                var declineOptions = new CallDeclineOptions();
                declineOptions.ResponseCode = ResponseCode.RequestTerminated;
                this.RejectCall(e.Call, declineOptions);
                return;
            }
            ConversationParticipant caller     = e.Call.RemoteEndpoint.Participant;
            RealTimeAddress         uriAddress = new RealTimeAddress(caller.Uri);

            if (!uriAddress.IsPhone &&
                e.Call.RemoteEndpoint.Participant.SourceNetwork == SourceNetwork.SameEnterprise)
            {
                System.Threading.ThreadPool.QueueUserWorkItem(this.HandleIMCall, e);
            }
            else
            {
                var declineOptions = new CallDeclineOptions();
                declineOptions.ResponseCode = ResponseCode.RequestTerminated;
                this.RejectCall(e.Call, declineOptions);
                return;
            }
        }
        /// <summary>
        /// Sends the Notify message back to the PIC user
        ///
        /// </summary>
        /// <param name="e"></param>
        /// <param name="_sipAddressessOfBot"></param>
        public void SendBotsOnlineStatusToPicClient(string sipUriOfPicClient, string sipUriOfBot,
                                                    string callId, string cseq, string toHeaderValue, string fromHeaderValue)
        {
            Console.WriteLine("Sending Bot's Open status to PIC client as a NOTIFY using UCMA...");
            try
            {
                List <SignalingHeader> headers = new List <SignalingHeader>();
                headers.Add(new SignalingHeader("EVENT", "presence"));
                headers.Add(new SignalingHeader("Subscription-State", "active"));
                headers.Add(new SignalingHeader("Call-Id", callId));
                headers.Add(new SignalingHeader("CSeq", cseq));
                headers.Add(new SignalingHeader("To", toHeaderValue));
                headers.Add(new SignalingHeader("From", fromHeaderValue));

                Console.WriteLine("App-configured SignalingHeaders:");
                foreach (SignalingHeader h in headers)
                {
                    Console.WriteLine(h.Name + ": " + h.GetValue());
                }
                Console.WriteLine("PIC user URI: " + sipUriOfPicClient);
                Console.WriteLine("Bot Uri: " + sipUriOfBot);

                string displayName = this.GetDisplayNameForApplicationEndpoint(sipUriOfBot);
                System.Net.Mime.ContentType contentType = new System.Net.Mime.ContentType("application/pidf+xml");
                System.Text.UTF8Encoding    enc         = new System.Text.UTF8Encoding();
                byte[] notifyBody = enc.GetBytes(String.Format(_pidfNotifyText, "open", displayName, sipUriOfBot));
                Console.WriteLine("PIDFNotifyBody =" + System.Text.UTF8Encoding.UTF8.GetString(notifyBody));

                // Target address of the NOTIFY
                RealTimeAddress targetAddress = new RealTimeAddress(sipUriOfPicClient);

                // Shouldn't be synchronized call in production code
                _superP2PEndpoint.BeginSendMessage(
                    sipUriOfBot,
                    MessageType.Notify,
                    targetAddress,
                    contentType,
                    notifyBody,
                    headers,
                    (a) =>
                {
                    try
                    {
                        _superP2PEndpoint.EndSendMessage(a);
                    }
                    catch (RealTimeException rtex)
                    {
                        // report and ignore the failure
                        Console.WriteLine("EndSendMessage: Notify failed with exception: {0}", rtex.Message);
                    }
                },
                    null);
                Console.WriteLine("End calling SendBotsOnlineStatusToPicClient!");
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed in SendBotsOnlineStatusToPicClient: \r\n{0}\r\n{1}", e.Message, e.StackTrace);
            }
        }
示例#6
0
 public static Task <bool> VerifyPasscodeAsync(this
                                               ConferenceServices cs, RealTimeAddress conferenceAddress,
                                               string passcode)
 {
     return(Task <bool> .Factory.FromAsync(
                cs.BeginVerifyPasscode,
                cs.EndVerifyPasscode, conferenceAddress, passcode, null));
 }
示例#7
0
        /// <summary>
        /// Menu level changed handler.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MenuLevelChanged(object sender, MenuLevelChangedEventArgs e)
        {
            if (e.Level == MenuLevel.HelpDeskRequested)
            {
                lock (this.syncRoot)
                {
                    if (this.callAnchor == null)
                    {
                        this.callAnchor = new CustomerCallAnchor(this, this.logger, this.conversation);
                    }

                    try
                    {
                        var helpdeskNumber = e.HelpdeskNumber;
                        if (String.IsNullOrEmpty(helpdeskNumber))
                        {
                            Console.WriteLine("Null or empty help desk number");
                            this.logger.Log("Null or empty help desk number");
                        }
                        else
                        {
                            try
                            {
                                RealTimeAddress address = new RealTimeAddress(helpdeskNumber);

                                this.callAnchor.BeginEstablish(address,
                                                               (asyncResult) =>
                                {
                                    try
                                    {
                                        this.callAnchor.EndEstablish(asyncResult);
                                    }
                                    catch (Exception ex)
                                    {
                                        this.callAnchor.BeginTerminate((terminateAsyncResult) => { this.callAnchor.EndTerminate(terminateAsyncResult); }, null);
                                        Console.WriteLine("Call anchor failed with {0}", ex);
                                        this.logger.Log("Call anchor failed with {0}", ex);
                                    }
                                },
                                                               null);
                            }
                            catch (ArgumentException ae)
                            {
                                Console.WriteLine("Invalid help desk number {0}, Exception ={1}", helpdeskNumber, ae);
                                this.logger.Log("Invalid help desk number {0}, Exception ={1}", helpdeskNumber, ae);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Call anchor failed with {0}", ex);
                        this.logger.Log("Call anchor failed with {0}", ex);
                    }
                }
            }
        }
        /// <summary>
        /// Override process method.
        /// </summary>
        public override void Process()
        {
            bool      unhandledExceptionDetected = true;
            Exception exceptionCaught            = null;

            try
            {
                RealTimeEndpoint   innerEndpoint = this.LocalEndpoint.InnerEndpoint;
                SendMessageOptions options       = new SendMessageOptions();
                options.ContentDescription = ContactCenterDiscoveryAsyncResult.GetDiscoveryRequestContentDescription();
                RealTimeAddress targetAddress = new RealTimeAddress(this.TargetUri);
                innerEndpoint.BeginSendMessage(MessageType.Service, targetAddress, options, this.ServiceRequestCompleted, innerEndpoint /*state*/);
                unhandledExceptionDetected = false;
            }
            catch (ArgumentException ae)
            {
                Helper.Logger.Error("Exception = {0}", EventLogger.ToString(ae));
                exceptionCaught            = ae;
                unhandledExceptionDetected = false;
            }
            catch (InvalidOperationException ioe)
            {
                Helper.Logger.Info("Exception = {0}", EventLogger.ToString(ioe));
                exceptionCaught            = ioe;
                unhandledExceptionDetected = false;
            }
            catch (RealTimeException rte)
            {
                Helper.Logger.Info("Exception = {0}", EventLogger.ToString(rte));
                exceptionCaught            = rte;
                unhandledExceptionDetected = false;
            }
            finally
            {
                if (unhandledExceptionDetected)
                {
                    exceptionCaught = new Exception("Unhandled exception");
                    Helper.Logger.Info("Exception = {0}", EventLogger.ToString(exceptionCaught));
                }

                if (exceptionCaught != null)
                {
                    this.Complete(exceptionCaught);
                }
            }
        }
示例#9
0
        private void UserEndpointStateChanged(object sender, LocalEndpointStateChangedEventArgs e)
        {
            UserEndpoint    userEndpoint = sender as UserEndpoint;
            RealTimeAddress ownerAddress = new RealTimeAddress(userEndpoint.OwnerUri);

            if (e.State == LocalEndpointState.Terminating)
            {
                lock (this.SyncRoot)
                {
                    // Lost the cache.
                    if (m_userEndpoints.ContainsKey(ownerAddress))
                    {
                        m_userEndpoints.Remove(ownerAddress);
                        m_userEndpointReferenceCounts.Remove(ownerAddress);
                    }
                }
            }
        }
示例#10
0
            public override bool RemoveEntry(string telUri, string sipUri)
            {
                bool removed = false;

                lock (this.SyncRoot)
                {
                    if (!telUri.StartsWith("tel"))
                    {
                        telUri = "tel:" + telUri;
                    }
                    if (m_directory.ContainsKey(telUri))
                    {
                        bool shouldRemove = true;
                        if (!String.IsNullOrEmpty(sipUri))
                        {
                            string oldSipUri = m_directory[telUri];

                            try
                            {
                                RealTimeAddress oldSipUriAddress = new RealTimeAddress(oldSipUri);
                                RealTimeAddress telUriAddress    = new RealTimeAddress(telUri);
                                RealTimeAddress sipUriAddress    = new RealTimeAddress(sipUri);
                                if (oldSipUriAddress != sipUriAddress)
                                {
                                    shouldRemove = false; // Does not match. One can only remove their own number.
                                }
                            }
                            catch (ArgumentException)
                            {
                                shouldRemove = false;
                            }
                        }
                        if (shouldRemove)
                        {
                            m_directory.Remove(telUri);
                            removed      = true;
                            m_isModified = true;
                        }
                    }
                }
                return(removed);
            }
示例#11
0
        private void InviteUserStatementCompleted()
        {
            if (this.CustomerSession.IsTerminatingTerminated)
            {
                this.EndService(null);
                return;
            }
            this.RestartDtmfCommand();

            RealTimeAddress address = new RealTimeAddress(m_selectedContact.Uri);

            if (address.IsPhone)
            {
                this.DialOut(m_selectedContact.Uri, m_selectedContact.Uri, m_selectedContact.Uri, this.UserInvitationWorkCompleted);
            }
            else
            {
                this.InviteToConference(m_selectedContact.Uri, this.UserInvitationWorkCompleted); // Conf
            }
        }
示例#12
0
        /// <summary>
        /// Constructor.
        /// </summary>
        public EstablishAsyncResult(CustomerCallAnchor parent,
                                    Conversation trustedConversation,
                                    Conversation customerConversation,
                                    RealTimeAddress helpdeskNumber,
                                    ILogger logger,
                                    AsyncCallback asyncCallback,
                                    object state)

            : base(asyncCallback, state)
        {
            if (helpdeskNumber == null)
            {
                throw new ArgumentNullException("helpdeskNumber");
            }
            this.trustedConversation  = trustedConversation;
            this.customerConversation = customerConversation;
            this.logger         = logger;
            this.parent         = parent;
            this.helpdeskNumber = helpdeskNumber;
        }
示例#13
0
        /// <summary>
        /// Establishes the call anchor for a customer session.
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public IAsyncResult BeginEstablish(RealTimeAddress helpdeskNumber, AsyncCallback callback, object state)
        {
            EstablishAsyncResult establishAsyncResult = null;

            if (helpdeskNumber == null)
            {
                throw new ArgumentNullException("helpdeskNumber");
            }
            lock (this.syncRoot)
            {
                if (this.pendingEstablishAsyncResult != null)
                {
                    throw new InvalidOperationException("Already a pending async result is in progress");
                }
                if (this.customerRemoteParticipant == null)
                {
                    throw new InvalidOperationException("Customer has already left the conversation");
                }

                if (this.trustedConversation == null)
                {
                    ConversationSettings settings = new ConversationSettings(ConversationPriority.Normal, "Dialout", "<" + this.CustomerUri + ">");
                    this.trustedConversation = new Conversation(this.customerConversation.Endpoint, settings);

                    // Config change: We cannot impersonate with user endpoint.
                    if (!Boolean.Parse(System.Configuration.ConfigurationManager.AppSettings["UseUserEndPoint"]))
                    {
                        this.trustedConversation.Impersonate(this.customerRemoteParticipant.Uri, this.customerRemoteParticipant.PhoneUri, this.customerRemoteParticipant.DisplayName);
                    }
                    this.RegisterTrustedConversationEventHandlers(this.trustedConversation);
                }

                establishAsyncResult             = new EstablishAsyncResult(this, this.trustedConversation, this.customerConversation, helpdeskNumber, this.logger, callback, state);
                this.pendingEstablishAsyncResult = establishAsyncResult;
            }

            establishAsyncResult.Process();

            return(establishAsyncResult);
        }
示例#14
0
        public static bool TryExtractCleanPhone(string uri, out string phone)
        {
            bool result = false;

            phone = null;

            try
            {
                RealTimeAddress address = new RealTimeAddress(uri);
                if (address.IsPhone)
                {
                    phone = address.UserAtHost;
                    string[] parts = phone.Split('@');
                    phone  = parts[0]; // Just the user part.
                    result = true;
                }
            }
            catch (ArgumentException)
            {
            }

            return(result);
        }
        /// <summary>
        /// Compares if 2 uris are equal.
        /// </summary>
        /// <param name="uri1">Uri1</param>
        /// <param name="uri2">Uri2</param>
        /// <returns>True if 2 uris are equal. False otherwise.</returns>
        public static bool Equals(string uri1, string uri2)
        {
            bool areEqual = false;

            if (String.Equals(uri1, uri2, StringComparison.OrdinalIgnoreCase))
            {
                areEqual = true;
            }
            else if (!String.IsNullOrEmpty(uri1))
            {
                try
                {
                    RealTimeAddress address1 = new RealTimeAddress(uri1);
                    RealTimeAddress address2 = new RealTimeAddress(uri2);
                    areEqual = (address1 == address2);
                }
                catch (ArgumentException)
                {
                }
            }

            return(areEqual);
        }
示例#16
0
            public override bool AddEntry(string telUri, string sipUri)
            {
                bool wasAdded = false;

                try
                {
                    if (!telUri.StartsWith("tel"))
                    {
                        telUri = "tel:" + telUri;
                    }
                    RealTimeAddress telUriAddress = new RealTimeAddress(telUri);
                    if (!telUriAddress.IsPhone) // Should be phone uri.
                    {
                        return(wasAdded);
                    }
                    RealTimeAddress sipUriAddress = new RealTimeAddress(sipUri);
                    if (sipUriAddress.IsPhone) // Should not be phone uri
                    {
                        return(wasAdded);
                    }
                }
                catch (ArgumentException)
                {
                    return(wasAdded);
                }

                lock (this.SyncRoot)
                {
                    if (!m_directory.ContainsKey(telUri))
                    {
                        m_directory.Add(telUri, sipUri);
                        wasAdded     = true;
                        m_isModified = true;
                    }
                }
                return(wasAdded);
            }
示例#17
0
        /// <summary>
        /// Releases the user endpoint from usage from a component.
        /// </summary>
        /// <param name="task">The task to be done.</param>
        /// <param name="state">The user endpoint to release.</param>
        /// <remarks>If ref count is reduced to 0, the userendpoint will be terminated.</remarks>
        public void RelaseUserEndpoint(AsyncTask task, object state)
        {
            MyUserEndpoint  myUserEndpoint = state as MyUserEndpoint;
            RealTimeAddress ownerAddress   = new RealTimeAddress(myUserEndpoint.UserEndpoint.OwnerUri);
            bool            completeNeeded = true;

            lock (this.SyncRoot)
            {
                if (m_userEndpoints.ContainsKey(ownerAddress))
                {
                    MyUserEndpoint storedEndpoint = m_userEndpoints[ownerAddress];
                    if (storedEndpoint == myUserEndpoint) // What we have matches the released endpoint. Reduce ref count.
                    {
                        int count = m_userEndpointReferenceCounts[ownerAddress];
                        count--;
                        m_userEndpointReferenceCounts[ownerAddress] = count;
                        if (count == 0)
                        {
                            // Terminate the endpoint.
                            AsyncTask terminateEndpointTask = new AsyncTask(this.ShutdownUserEndpoint, myUserEndpoint.UserEndpoint);
                            terminateEndpointTask.TaskCompleted +=
                                delegate(object sender, AsyncTaskCompletedEventArgs e)
                            {
                                task.Complete(e.ActionResult.Exception);
                            };
                            terminateEndpointTask.StartTask();
                            completeNeeded = false;
                        }
                    }
                }
            }
            if (completeNeeded)
            {
                task.Complete(null);
            }
        }
 public static Task<bool> VerifyPasscodeAsync(this 
     ConferenceServices cs, RealTimeAddress conferenceAddress,
     string passcode)
 {
     return Task<bool>.Factory.FromAsync(
         cs.BeginVerifyPasscode,
         cs.EndVerifyPasscode, conferenceAddress, passcode, null);
 }
示例#19
0
        public void CreateOrGetUserEndpoint(AsyncTask task, object state)
        {
            string ownerUri = state as string;

            if (String.IsNullOrEmpty(ownerUri))
            {
                task.Complete(new InvalidOperationException("OwnerUri is needed to request a user endpoint."));
                return;
            }
            RealTimeAddress ownerAddress = null;

            try
            {
                ownerAddress = new RealTimeAddress(ownerUri);
            }
            catch (ArgumentException exception)
            {
                task.Complete(exception);
                return;
            }
            MyUserEndpoint myUserEndpoint = null;

            lock (this.SyncRoot)
            {
                if (m_userEndpoints.ContainsKey(ownerAddress))
                {
                    myUserEndpoint = m_userEndpoints[ownerAddress];
                    if (myUserEndpoint.UserEndpoint.State == LocalEndpointState.Terminating || myUserEndpoint.UserEndpoint.State == LocalEndpointState.Terminated)
                    {
                        myUserEndpoint = null; // Loose it since it is going away.
                        m_userEndpoints.Remove(ownerAddress);
                        m_userEndpointReferenceCounts.Remove(ownerAddress);
                    }
                    else
                    {
                        int count = m_userEndpointReferenceCounts[ownerAddress];
                        count++;
                        m_userEndpointReferenceCounts[ownerAddress] = count;
                    }
                }
                if (myUserEndpoint == null)
                {
                    // Create and add user endpoint into dictionary.
                    // One could use the platform discover server from uri if the topology has DNS srv records for server auto discovery.
                    // Normally, this would point to a director. Here, we will use the proxy of the application endpoint.
                    UserEndpointSettings userEndpointSettings = new UserEndpointSettings(ownerUri, m_settings.ProxyHost, m_settings.ProxyPort);
                    UserEndpoint         userEndpoint         = new UserEndpoint(m_parent.Platform, userEndpointSettings);
                    myUserEndpoint = new MyUserEndpoint(userEndpoint);
                    m_userEndpoints.Add(ownerAddress, myUserEndpoint);
                    m_userEndpointReferenceCounts.Add(ownerAddress, 1);
                    myUserEndpoint.UserEndpoint.StateChanged += UserEndpointStateChanged; // Ensures that only one registration per endpoint.
                }
                UserEndpointCreationActionResult result = new UserEndpointCreationActionResult(myUserEndpoint, null);
                task.TaskResult = result; // Store it for now.
                if (myUserEndpoint.UserEndpoint.State == LocalEndpointState.Established)
                {
                    task.Complete(null, result);
                }
                else if (myUserEndpoint.UserEndpoint.State == LocalEndpointState.Establishing)
                {
                    // Wait till the endpoint establish completes.
                    lock (this.SyncRoot)
                    {
                        m_pendingUserEndpointCreationTasks.Add(task);
                    }
                }
                else if (myUserEndpoint.UserEndpoint.State == LocalEndpointState.Idle)
                {
                    AsyncTask establishTask = new AsyncTask(this.StartupUserEndpoint, myUserEndpoint.UserEndpoint);
                    establishTask.TaskCompleted +=
                        delegate(object sender, AsyncTaskCompletedEventArgs e)
                    {
                        task.TaskResult.Exception = e.ActionResult.Exception;     // Transfer
                        task.Complete(e.ActionResult.Exception, task.TaskResult);
                        lock (this.SyncRoot)
                        {
                            // Complete pending tasks
                            foreach (AsyncTask pendingTask in m_pendingUserEndpointCreationTasks)
                            {
                                pendingTask.TaskResult.Exception = e.ActionResult.Exception;
                                pendingTask.Complete(e.ActionResult.Exception, pendingTask.TaskResult);
                            }
                            m_pendingUserEndpointCreationTasks.Clear();
                        }
                    };
                    establishTask.StartTask();
                }
            }
        }
示例#20
0
        public override async Task<AcdActionResult> Execute(LocalEndpoint localEndpoint, AudioVideoCall call, CancellationToken cancellationToken)
        {
            if (Endpoint == null)
                return AcdActionResult.Continue;

            // extract information from incoming caller
            var callParticipant = call.RemoteEndpoint.Participant;
            var callAddress = new RealTimeAddress(callParticipant.Uri, localEndpoint.DefaultDomain, localEndpoint.PhoneContext);
            var callSipUri = new SipUriParser(callAddress.Uri);
            callSipUri.RemoveParameter(new SipUriParameter("user", "phone"));
            var callUri = callSipUri.ToString();
            var callPhoneUri = callParticipant.OtherPhoneUri;
            var callName = callParticipant.DisplayName;

            // impersonate incoming caller to agent
            var remoteConversation = new Conversation(localEndpoint);
            remoteConversation.Impersonate(callUri, callPhoneUri, callName);

            // establish call to endpoint
            var remoteCall = new AudioVideoCall(remoteConversation);
            var remoteOpts = new CallEstablishOptions();
            remoteOpts.Transferor = localEndpoint.OwnerUri;
            remoteOpts.Headers.Add(new SignalingHeader("Ms-Target-Class", "secondary"));

            // initiate call with duration
            var destCallT = remoteCall.EstablishAsync(Endpoint.Uri, remoteOpts, cancellationToken);

            try
            {
                // wait for agent call to complete
                await destCallT;
            }
            catch (OperationCanceledException)
            {
                // ignore
            }
            catch (RealTimeException)
            {
                // ignore
            }

            // ensure two accepted transfers cannot both complete
            using (var lck = await call.GetContext<AsyncLock>().LockAsync())
                if (call.State == CallState.Established)
                    if (remoteCall.State == CallState.Established)
                    {
                        var participant = remoteConversation.RemoteParticipants.FirstOrDefault();
                        if (participant != null)
                        {
                            var endpoint = participant.GetEndpoints().FirstOrDefault(i => i.EndpointType == EndpointType.User);
                            if (endpoint != null)
                            {
                                var ctx = new ConversationContextChannel(remoteConversation, endpoint);

                                // establish conversation context with application
                                await ctx.EstablishAsync(
                                    new Guid("FA44026B-CC48-42DA-AAA8-B849BCB43A21"), 
                                    new ConversationContextChannelEstablishOptions());

                                // send context data
                                await ctx.SendDataAsync(
                                    new ContentType("text/plain"), 
                                    Encoding.UTF8.GetBytes("Id=123"));
                            }
                        }

                        return await TransferAsync(call, remoteCall);
                    }

            // terminate outbound call if still available
            if (remoteCall.State != CallState.Terminating &&
                remoteCall.State != CallState.Terminated)
                await remoteCall.TerminateAsync();

            // we could not complete the transfer attempt
            return AcdActionResult.Continue;
        }