/* * 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.service.protocol; import java.util.*; import org.osgi.framework.*; 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 { /** * The name of the configuration property which specifies whether * SingleCallInProgressPolicy is enabled i.e. whether it should * put existing calls on hold when a new call enters in progress. */ private static final String PNAME_SINGLE_CALL_IN_PROGRESS_POLICY_ENABLED = "net.java.sip.communicator.impl.protocol.SingleCallInProgressPolicy.enabled"; /** * Implements the listeners interfaces used by this policy. */ private class SingleCallInProgressPolicyListener implements CallChangeListener, CallListener, ServiceListener { /** * Stops tracking the state of a specific Call and no * longer tries to put it on hold when it ends. * * @see CallListener#callEnded(CallEvent) */ public void callEnded(CallEvent callEvent) { SingleCallInProgressPolicy.this.handleCallEvent( CallEvent.CALL_ENDED, callEvent); } /** * Does nothing because adding CallPeers to * Calls isn't related to the policy to put existing calls * on hold when a new call becomes in-progress and just implements * CallChangeListener. * * @see CallChangeListener#callPeerAdded(CallPeerEvent) */ public void callPeerAdded( CallPeerEvent callPeerEvent) { /* * Not of interest, just implementing CallChangeListener in which * only #callStateChanged(CallChangeEvent) is of interest. */ } /** * Does nothing because removing CallPeers to * Calls isn't related to the policy to put existing calls * on hold when a new call becomes in-progress and just implements * CallChangeListener. * * @see CallChangeListener#callPeerRemoved(CallPeerEvent) */ public void callPeerRemoved( CallPeerEvent callPeerEvent) { /* * Not of interest, just implementing CallChangeListener in which * only #callStateChanged(CallChangeEvent) is of interest. */ } /** * Upon a Call changing its state to * CallState.CALL_IN_PROGRESS, puts the other existing * Calls on hold. * * @param callChangeEvent the CallChangeEvent that we are to * deliver. * * @see CallChangeListener#callStateChanged(CallChangeEvent) */ public void callStateChanged(CallChangeEvent callChangeEvent) { // we are interested only in CALL_STATE_CHANGEs if(!callChangeEvent.getEventType().equals(CallChangeEvent.CALL_STATE_CHANGE)) return; SingleCallInProgressPolicy.this.callStateChanged(callChangeEvent); } /** * Remembers an incoming Call so that it can put the other * existing Calls on hold when it changes its state to * CallState.CALL_IN_PROGRESS. * * @see CallListener#incomingCallReceived(CallEvent) */ public void incomingCallReceived(CallEvent callEvent) { SingleCallInProgressPolicy.this.handleCallEvent( CallEvent.CALL_RECEIVED, callEvent); } /** * Remembers an outgoing Call so that it can put the other * existing Calls on hold when it changes its state to * CallState.CALL_IN_PROGRESS. * * @see CallListener#outgoingCallCreated(CallEvent) */ public void outgoingCallCreated(CallEvent callEvent) { SingleCallInProgressPolicy.this.handleCallEvent( CallEvent.CALL_INITIATED, callEvent); } /** * Starts/stops tracking the new Calls originating from a * specific ProtocolProviderService 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 ServiceEvent event describing a change in * the state of a service registration which may be a * ProtocolProviderService supporting * OperationSetBasicTelephony and thus being * able to create new Calls */ public void serviceChanged(ServiceEvent serviceEvent) { SingleCallInProgressPolicy.this.serviceChanged(serviceEvent); } } /** * Our class logger */ private static final Logger logger = Logger.getLogger(SingleCallInProgressPolicy.class); /** * The BundleContext to the Calls of which this policy applies. */ private final BundleContext bundleContext; /** * The Calls this policy manages i.e. put on hold when one of * them enters in progress. */ private final List calls = new ArrayList(); /** * The listener utilized by this policy to discover new Call * and track their in-progress state. */ private final SingleCallInProgressPolicyListener listener = new SingleCallInProgressPolicyListener(); /** * Initializes a new SingleCallInProgressPolicy instance which * will apply to the Calls of a specific * BundleContext. * * @param bundleContext * the BundleContext to the * Calls of which the new policy should apply */ public SingleCallInProgressPolicy(BundleContext bundleContext) { this.bundleContext = bundleContext; this.bundleContext.addServiceListener(listener); } /** * Registers a specific Call with this policy in order to have * the rules of the latter apply to the former. * * @param call * the Call 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 OperationSetBasicTelephony with this * policy in order to have the rules of the latter apply to the * Calls created by the former. * * @param telephony * the OperationSetBasicTelephony to register with * this policy in order to have the rules of the latter apply to * the Calls created by the former */ private void addOperationSetBasicTelephonyListener( OperationSetBasicTelephony telephony) { telephony.addCallListener(listener); } /** * Handles changes in the state of a Call 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 CallChangeEvent value which describes the * Call and the change in its state */ private void callStateChanged(CallChangeEvent callChangeEvent) { Call call = callChangeEvent.getSourceCall(); if (CallState.CALL_INITIALIZATION.equals(callChangeEvent.getOldValue()) && CallState.CALL_IN_PROGRESS.equals(call.getCallState()) && ProtocolProviderActivator .getConfigurationService() .getBoolean( PNAME_SINGLE_CALL_IN_PROGRESS_POLICY_ENABLED, true)) { synchronized (calls) { for (Call otherCall : calls) { 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 Calls 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 * CallState.CALL_IN_PROGRESS. * * @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 CallEvent value which describes the change and * the Call 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 CallPeers of a specific Call on * hold. * * @param call * the Call the CallPeers of * which are to be put on hold */ private void putOnHold(Call call) { OperationSetBasicTelephony telephony = call.getProtocolProvider() .getOperationSet(OperationSetBasicTelephony.class); if (telephony != null) { for (Iterator peerIter = call.getCallPeers(); peerIter.hasNext();) { CallPeer peer = peerIter.next(); CallPeerState peerState = peer.getState(); if (!CallPeerState.DISCONNECTED.equals(peerState) && !CallPeerState.FAILED.equals(peerState) && !CallPeerState.isOnHold(peerState)) { try { telephony.putOnHold(peer); } catch (OperationFailedException ex) { logger.error("Failed to put " + peer + " on hold.", ex); } } } } } /** * Unregisters a specific Call from this policy in order to * have the rules of the latter no longer applied to the former. * * @param call * the Call 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 OperationSetBasicTelephony from this * policy in order to have the rules of the latter no longer apply to the * Calls created by the former. * * @param telephony * the OperationSetBasicTelephony to unregister from * this policy in order to have the rules of the latter apply to * the Calls created by the former */ private void removeOperationSetBasicTelephonyListener( OperationSetBasicTelephony telephony) { telephony.removeCallListener(listener); } /** * Handles the registering and unregistering of * OperationSetBasicTelephony instances in order to apply or * unapply the rules of this policy to the Calls originating * from them. * * @param serviceEvent * a ServiceEvent value which described a change in * a OSGi service and which is to be examined for the registering * or unregistering of a ProtocolProviderService and * thus a OperationSetBasicTelephony */ private void serviceChanged(ServiceEvent serviceEvent) { Object service = bundleContext.getService(serviceEvent.getServiceReference()); if (service instanceof ProtocolProviderService) { OperationSetBasicTelephony telephony = ((ProtocolProviderService) service) .getOperationSet(OperationSetBasicTelephony.class); if (telephony != null) { switch (serviceEvent.getType()) { case ServiceEvent.REGISTERED: addOperationSetBasicTelephonyListener(telephony); break; case ServiceEvent.UNREGISTERING: removeOperationSetBasicTelephonyListener(telephony); break; } } } } }