/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol;
import java.util.*;
import org.osgi.framework.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
/**
* Imposes the policy to have one call in progress i.e. to put existing calls on
* hold when a new call enters in progress.
*
* @author Lubomir Marinov
*/
public class SingleCallInProgressPolicy
{
/**
* Implements the listeners interfaces used by this policy.
*/
private class SingleCallInProgressPolicyListener
implements CallChangeListener, CallListener, ServiceListener
{
/**
* Stops tracking the state of a specific <code>Call</code> and no
* longer tries to put it on hold when it ends.
*
* @see net.java.sip.communicator.service.protocol.event.CallListener
* #callEnded(net.java.sip.communicator.service.protocol.event
* .CallEvent)
*/
public void callEnded(CallEvent callEvent)
{
SingleCallInProgressPolicy.this.handleCallEvent(
CallEvent.CALL_ENDED, callEvent);
}
/**
* Does nothing because adding <code>CallParticipant<code>s to
* <code>Call</code>s isn't related to the policy to put existing calls
* on hold when a new call becomes in-progress and just implements
* <code>CallChangeListener</code>.
*
* @see net.java.sip.communicator.service.protocol.event
* .CallChangeListener#callParticipantAdded(net.java.sip.communicator
* .service.protocol.event.CallParticipantEvent)
*/
public void callParticipantAdded(
CallParticipantEvent callParticipantEvent)
{
/*
* Not of interest, just implementing CallChangeListener in which
* only #callStateChanged(CallChangeEvent) is of interest.
*/
}
/**
* Does nothing because removing <code>CallParticipant<code>s to
* <code>Call</code>s isn't related to the policy to put existing calls
* on hold when a new call becomes in-progress and just implements
* <code>CallChangeListener</code>.
*
* @see net.java.sip.communicator.service.protocol.event
* .CallChangeListener#callParticipantRemoved(net.java.sip.communicator
* .service.protocol.event.CallParticipantEvent)
*/
public void callParticipantRemoved(
CallParticipantEvent callParticipantEvent)
{
/*
* Not of interest, just implementing CallChangeListener in which
* only #callStateChanged(CallChangeEvent) is of interest.
*/
}
/**
* Upon a <code>Call</code> changing its state to
* <code>CallState.CALL_IN_PROGRESS</code>, puts the other existing
* <code>Call</code>s on hold.
*
* @see net.java.sip.communicator.service.protocol.event
* .CallChangeListener#callStateChanged(net.java.sip.communicator
* .service.protocol.event.CallChangeEvent)
*/
public void callStateChanged(CallChangeEvent callChangeEvent)
{
SingleCallInProgressPolicy.this.callStateChanged(callChangeEvent);
}
/**
* Remembers an incoming <code>Call</code> so that it can put the other
* existing <code>Call</code>s on hold when it changes its state to
* <code>CallState.CALL_IN_PROGRESS</code>.
*
* @see net.java.sip.communicator.service.protocol.event.CallListener
* #incomingCallReceived(net.java.sip.communicator.service.protocol
* .event.CallEvent)
*/
public void incomingCallReceived(CallEvent callEvent)
{
SingleCallInProgressPolicy.this.handleCallEvent(
CallEvent.CALL_RECEIVED, callEvent);
}
/**
* Remembers an outgoing <code>Call</code> so that it can put the other
* existing <code>Call</code>s on hold when it changes its state to
* <code>CallState.CALL_IN_PROGRESS</code>.
*
* @see net.java.sip.communicator.service.protocol.event.CallListener
* #outgoingCallCreated(net.java.sip.communicator.service.protocol
* .event.CallEvent)
*/
public void outgoingCallCreated(CallEvent callEvent)
{
SingleCallInProgressPolicy.this.handleCallEvent(
CallEvent.CALL_INITIATED, callEvent);
}
/**
* Starts/stops tracking the new <code>Call</code>s originating from a
* specific <code>ProtocolProviderService</code> when it
* registers/unregisters in order to take them into account when putting
* existing calls on hold upon a new call entering its in-progress state.
*
* @param serviceEvent the <code>ServiceEvent</code> event describing a
* change in the state of a service registration which may be
* a <code>ProtocolProviderService</code> supporting
* <code>OperationSetBasicTelephony</code> and thus being
* able to create new <code>Call</code>s
*/
public void serviceChanged(ServiceEvent serviceEvent)
{
SingleCallInProgressPolicy.this.serviceChanged(serviceEvent);
}
}
private static final Logger logger =
Logger.getLogger(SingleCallInProgressPolicy.class);
/**
* The <code>BundleContext</code> to the Calls of which this policy applies.
*/
private final BundleContext bundleContext;
/**
* The <code>Call</code>s this policy manages i.e. put on hold when one of
* them enters in progress.
*/
private final List<Call> calls = new ArrayList<Call>();
/**
* The listener utilized by this policty to discover new <code>Call</code>
* and track their in-progress state.
*/
private final SingleCallInProgressPolicyListener listener =
new SingleCallInProgressPolicyListener();
/**
* Initializes a new <code>SingleCallInProgressPolicy</code> instance which
* will apply to the <code>Call</code>s of a specific
* <code>BundleContext</code>.
*
* @param bundleContext the <code>BundleContext</code> to the
* <code>Call<code>s of which the new policy should apply
*/
public SingleCallInProgressPolicy(BundleContext bundleContext)
{
this.bundleContext = bundleContext;
this.bundleContext.addServiceListener(listener);
}
/**
* Registers a specific <code>Call</code> with this policy in order to have
* the rules of the latter apply to the former.
*
* @param call the <code>Call</code> to register with this policy in order
* to have the rules of the latter apply to the former
*/
private void addCallListener(Call call)
{
synchronized (calls)
{
if (!calls.contains(call))
{
CallState callState = call.getCallState();
if ((callState != null)
&& !callState.equals(CallState.CALL_ENDED))
{
calls.add(call);
}
}
}
call.addCallChangeListener(listener);
}
/**
* Registers a specific <code>OperationSetBasicTelephony</code> with this
* policy in order to have the rules of the latter apply to the
* <code>Call</code>s created by the former.
*
* @param telephony the <code>OperationSetBasicTelephony</code> to register
* with this policy in order to have the rules of the latter
* apply to the <code>Call</code>s created by the former
*/
private void addOperationSetBasicTelephonyListener(
OperationSetBasicTelephony telephony)
{
telephony.addCallListener(listener);
}
/**
* Handles changes in the state of a <code>Call</code> this policy applies
* to in order to detect when new calls become in-progress and when the
* other calls should be put on hold.
*
* @param callChangeEvent a <code>CallChangeEvent</code> value which
* describes the <code>Call</code> and the change in its state
*/
private void callStateChanged(CallChangeEvent callChangeEvent)
{
Call call = callChangeEvent.getSourceCall();
if (CallState.CALL_IN_PROGRESS.equals(call.getCallState())
&& CallState.CALL_INITIALIZATION.equals(callChangeEvent
.getOldValue()))
{
synchronized (calls)
{
for (Iterator<Call> callIter = calls.iterator(); callIter
.hasNext();)
{
Call otherCall = callIter.next();
if (!call.equals(otherCall)
&& CallState.CALL_IN_PROGRESS.equals(otherCall
.getCallState()))
{
putOnHold(otherCall);
}
}
}
}
}
/**
* Performs end-of-life cleanup associated with this instance e.g. removes
* added listeners.
*/
public void dispose()
{
bundleContext.removeServiceListener(listener);
}
/**
* Handles the start and end of the <code>Call</code>s this policy applies
* to in order to have them or stop having them put the other existing calls
* on hold when the former change their states to
* <code>CallState.CALL_IN_PROGRESS</code>.
*
* @param type one of {@link CallEvent#CALL_ENDED},
* {@link CallEvent#CALL_INITIATED} and
* {@link CallEvent#CALL_RECEIVED} which described the type of
* the event to be handled
* @param callEvent a <code>CallEvent</code> value which describes the
* change and the <code>Call</code> associated with it
*/
private void handleCallEvent(int type, CallEvent callEvent)
{
Call call = callEvent.getSourceCall();
switch (type)
{
case CallEvent.CALL_ENDED:
removeCallListener(call);
break;
case CallEvent.CALL_INITIATED:
case CallEvent.CALL_RECEIVED:
addCallListener(call);
break;
}
}
/**
* Puts the <code>CallParticipant</code>s of a specific <code>Call</code>
* on hold.
*
* @param call the <code>Call</code> the <code>CallParticipant</code>s of
* which are to be put on hold.
*/
private void putOnHold(Call call)
{
OperationSetBasicTelephony telephony =
(OperationSetBasicTelephony) call.getProtocolProvider()
.getOperationSet(OperationSetBasicTelephony.class);
if (telephony != null)
{
for (Iterator<CallParticipant> participantIter =
call.getCallParticipants(); participantIter.hasNext();)
{
CallParticipant participant = participantIter.next();
CallParticipantState participantState = participant.getState();
if (!CallParticipantState.DISCONNECTED.equals(participantState)
&& !CallParticipantState.FAILED.equals(participantState)
&& !CallParticipantState.isOnHold(participantState))
{
try
{
telephony.putOnHold(participant);
}
catch (OperationFailedException ex)
{
logger.error("Failed to put " + participant
+ " on hold.", ex);
}
}
}
}
}
/**
* Unregisters a specific <code>Call</code> from this policy in order to
* have the rules of the latter no longer applied to the former.
*
* @param call the <code>Call</code> to unregister from this policy in order
* to have the rules of the latter no longer apply to the former
*/
private void removeCallListener(Call call)
{
call.removeCallChangeListener(listener);
synchronized (calls)
{
calls.remove(call);
}
}
/**
* Unregisters a specific <code>OperationSetBasicTelephony</code> from this
* policy in order to have the rules of the latter no longer apply to the
* <code>Call</code>s created by the former.
*
* @param telephony the <code>OperationSetBasicTelephony</code> to
* unregister from this policy in order to have the rules of the
* latter apply to the <code>Call</code>s created by the former
*/
private void removeOperationSetBasicTelephonyListener(
OperationSetBasicTelephony telephony)
{
telephony.removeCallListener(listener);
}
/**
* Handles the registering and unregistering of
* <code>OperationSetBasicTelephony</code> instances in order to apply or
* unapply the rules of this policy to the <code>Call</code>s originating
* from them.
*
* @param serviceEvent a <code>ServiceEvent</code> value which described
* a change in a OSGi service and which is to be examined for the
* registering or unregistering of a
* <code>ProtocolProviderService</code> and thus a
* <code>OperationSetBasicTelephony</code>
*/
private void serviceChanged(ServiceEvent serviceEvent)
{
Object service =
bundleContext.getService(serviceEvent.getServiceReference());
if (service instanceof ProtocolProviderService)
{
OperationSetBasicTelephony telephony =
(OperationSetBasicTelephony) ((ProtocolProviderService) service)
.getOperationSet(OperationSetBasicTelephony.class);
if (telephony != null)
{
switch (serviceEvent.getType())
{
case ServiceEvent.REGISTERED:
addOperationSetBasicTelephonyListener(telephony);
break;
case ServiceEvent.UNREGISTERING:
removeOperationSetBasicTelephonyListener(telephony);
break;
}
}
}
}
}