/*
* Jitsi, 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.beans.*;
import java.util.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
/**
* A representation of a call. Call instances must only be created by
* users (i.e. telephony protocols) of the PhoneUIService such as a SIP protocol
* implementation. Extensions of this class might have names like
* CallSipImpl, CallJabberImpl, or
* CallAnyOtherTelephonyProtocolImpl.
*
* @author Emil Ivov
* @author Emanuel Onica
* @author Lyubomir Marinov
* @author Boris Grozev
*/
public abstract class Call
{
/**
* Our class logger.
*/
private static final Logger logger = Logger.getLogger(Call.class);
/**
* The name of the Call property which represents its telephony
* conference-related state.
*/
public static final String CONFERENCE = "conference";
/**
* The name of the Call property which indicates whether the local
* peer/user represented by the respective Call is acting as a
* conference focus.
*/
public static final String CONFERENCE_FOCUS
= "conferenceFocus";
/**
* An identifier uniquely representing the call.
*/
private final String callID;
/**
* A list of all listeners currently registered for
* CallChangeEvents
*/
private final List callListeners
= new Vector();
/**
* A reference to the ProtocolProviderService instance that created us.
*/
private final ProtocolProviderService protocolProvider;
/**
* If this flag is set to true according to the account properties
* related with the sourceProvider the associated CallSession will start
* encrypted by default (where applicable)
*/
private final boolean defaultEncryption;
/**
* If this flag is set to true according to the account properties
* related with the sourceProvider the associated CallSession will set
* the SIP/SDP attribute (where applicable)
*/
private final boolean sipZrtpAttribute;
/**
* The state that this call is currently in.
*/
private CallState callState = CallState.CALL_INITIALIZATION;
/**
* The telephony conference-related state of this Call. Since a
* non-conference Call may be converted into a conference
* Call at any time, every Call instance maintains a
* CallConference instance regardless of whether the Call
* in question is participating in a telephony conference.
*/
private CallConference conference;
/**
* The flag that specifies whether incoming calls into this Call
* should be auto-answered.
*/
private boolean isAutoAnswer = false;
/**
* Creates a new Call instance.
*
* @param sourceProvider the proto provider that created us.
*/
protected Call(ProtocolProviderService sourceProvider)
{
//create the uid
this.callID = String.valueOf(System.currentTimeMillis())
+ String.valueOf(super.hashCode());
this.protocolProvider = sourceProvider;
AccountID accountID = protocolProvider.getAccountID();
defaultEncryption
= accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true);
sipZrtpAttribute
= accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_SIPZRTP_ATTRIBUTE,
true);
}
/**
* Returns the id of the specified Call.
* @return a String uniquely identifying the call.
*/
public String getCallID()
{
return callID;
}
/**
* Compares the specified object with this call and returns true if it the
* specified object is an instance of a Call object and if the
* extending telephony protocol considers the calls represented by both
* objects to be the same.
*
* @param obj the call to compare this one with.
* @return true in case both objects are pertaining to the same call and
* false otherwise.
*/
@Override
public boolean equals(Object obj)
{
if ((obj == null) || !(obj instanceof Call))
return false;
return (obj == this) || ((Call)obj).getCallID().equals(getCallID());
}
/**
* Returns a hash code value for this call.
*
* @return a hash code value for this call.
*/
@Override
public int hashCode()
{
return getCallID().hashCode();
}
/**
* Adds a call change listener to this call so that it could receive events
* on new call peers, theme changes and others.
*
* @param listener the listener to register
*/
public void addCallChangeListener(CallChangeListener listener)
{
synchronized(callListeners)
{
if(!callListeners.contains(listener))
callListeners.add(listener);
}
}
/**
* Removes listener to this call so that it won't receive further
* CallChangeEvents.
* @param listener the listener to register
*/
public void removeCallChangeListener(CallChangeListener listener)
{
synchronized(callListeners)
{
callListeners.remove(listener);
}
}
/**
* Returns a reference to the ProtocolProviderService instance
* that created this call.
* @return a reference to the ProtocolProviderService instance that
* created this call.
*/
public ProtocolProviderService getProtocolProvider()
{
return this.protocolProvider;
}
/**
* Creates a CallPeerEvent with
* sourceCallPeer and eventID and dispatches it on
* all currently registered listeners.
*
* @param sourceCallPeer the source CallPeer for the
* newly created event.
* @param eventID the ID of the event to create (see constants defined in
* CallPeerEvent)
*/
protected void fireCallPeerEvent(CallPeer sourceCallPeer, int eventID)
{
fireCallPeerEvent(sourceCallPeer, eventID, false);
}
/**
* Creates a CallPeerEvent with
* sourceCallPeer and eventID and dispatches it on
* all currently registered listeners.
*
* @param sourceCallPeer the source CallPeer for the
* newly created event.
* @param eventID the ID of the event to create (see constants defined in
* CallPeerEvent)
* @param delayed true if the adding/removing of the peer from the
* GUI should be delayed and false if not.
*/
protected void fireCallPeerEvent(CallPeer sourceCallPeer,
int eventID,
boolean delayed)
{
CallPeerEvent event
= new CallPeerEvent(sourceCallPeer, this, eventID, delayed);
if (logger.isDebugEnabled())
{
logger.debug(
"Dispatching a CallPeer event to "
+ callListeners.size()
+" listeners. The event is: "
+ event);
}
Iterator listeners;
synchronized(callListeners)
{
listeners
= new ArrayList(callListeners).iterator();
}
while(listeners.hasNext())
{
CallChangeListener listener = listeners.next();
if(eventID == CallPeerEvent.CALL_PEER_ADDED)
listener.callPeerAdded(event);
else if (eventID == CallPeerEvent.CALL_PEER_REMOVED)
listener.callPeerRemoved(event);
}
}
/**
* Returns a string textually representing this Call.
*
* @return a string representation of the object.
*/
@Override
public String toString()
{
return "Call: id=" + getCallID() + " peers=" + getCallPeerCount();
}
/**
* Creates a CallChangeEvent with this class as
* sourceCall, and the specified eventID and old and new
* values and dispatches it on all currently registered listeners.
*
* @param type the type of the event to create (see CallChangeEvent member
* ints)
* @param oldValue the value of the call property that changed, before the
* event had occurred.
* @param newValue the value of the call property that changed, after the
* event has occurred.
*/
protected void fireCallChangeEvent( String type,
Object oldValue,
Object newValue)
{
fireCallChangeEvent(type, oldValue, newValue, null);
}
/**
* Creates a CallChangeEvent with this class as
* sourceCall, and the specified eventID and old and new
* values and dispatches it on all currently registered listeners.
*
* @param type the type of the event to create (see CallChangeEvent member
* ints)
* @param oldValue the value of the call property that changed, before the
* event had occurred.
* @param newValue the value of the call property that changed, after the
* event has occurred.
* @param cause the event that is the initial cause of the current one.
*/
protected void fireCallChangeEvent( String type,
Object oldValue,
Object newValue,
CallPeerChangeEvent cause)
{
CallChangeEvent event
= new CallChangeEvent(
this,
type,
oldValue, newValue,
cause);
if (logger.isDebugEnabled())
{
logger.debug(
"Dispatching a CallChange event to "
+ callListeners.size()
+ " listeners. The event is: "
+ event);
}
CallChangeListener[] listeners;
synchronized(callListeners)
{
listeners
= callListeners.toArray(
new CallChangeListener[callListeners.size()]);
}
for (CallChangeListener listener : listeners)
listener.callStateChanged(event);
}
/**
* Returns the state that this call is currently in.
*
* @return a reference to the CallState instance that the call is
* currently in.
*/
public CallState getCallState()
{
return callState;
}
/**
* Sets the state of this call and fires a call change event notifying
* registered listeners for the change.
*
* @param newState a reference to the CallState instance that the
* call is to enter.
*/
protected void setCallState(CallState newState)
{
setCallState(newState, null);
}
/**
* Sets the state of this Call and fires a new
* CallChangeEvent notifying the registered
* CallChangeListeners about the change of the state.
*
* @param newState the CallState into which this Call is
* to enter
* @param cause the CallPeerChangeEvent which is the cause for the
* request to have this Call enter the specified CallState
*/
protected void setCallState(CallState newState, CallPeerChangeEvent cause)
{
CallState oldState = getCallState();
if (oldState != newState)
{
this.callState = newState;
try
{
fireCallChangeEvent(
CallChangeEvent.CALL_STATE_CHANGE,
oldState, this.callState,
cause);
}
finally
{
if (CallState.CALL_ENDED.equals(getCallState()))
setConference(null);
}
}
}
/**
* Returns the default call encryption flag
*
* @return the default call encryption flag
*/
public boolean isDefaultEncrypted()
{
return defaultEncryption;
}
/**
* Check if to include the ZRTP attribute to SIP/SDP
*
* @return include the ZRTP attribute to SIP/SDP
*/
public boolean isSipZrtpAttribute()
{
return sipZrtpAttribute;
}
/**
* Returns an iterator over all call peers.
*
* @return an Iterator over all peers currently involved in the call.
*/
public abstract Iterator extends CallPeer> getCallPeers();
/**
* Returns the number of peers currently associated with this call.
*
* @return an int indicating the number of peers currently
* associated with this call.
*/
public abstract int getCallPeerCount();
/**
* Gets the indicator which determines whether the local peer represented by
* this Call is acting as a conference focus. In the case of SIP,
* for example, it determines whether the local peer should send the
* "isfocus" parameter in the Contact headers of its outgoing SIP
* signaling.
*
* @return true if the local peer represented by this Call
* is acting as a conference focus; otherwise, false
*/
public abstract boolean isConferenceFocus();
/**
* Adds a specific SoundLevelListener to the list of
* listeners interested in and notified about changes in local sound level
* information.
*
* @param l the SoundLevelListener to add
*/
public abstract void addLocalUserSoundLevelListener(SoundLevelListener l);
/**
* Removes a specific SoundLevelListener from the list of
* listeners interested in and notified about changes in local sound level
* information.
*
* @param l the SoundLevelListener to remove
*/
public abstract void removeLocalUserSoundLevelListener(
SoundLevelListener l);
/**
* Creates a new CallConference instance which is to represent the
* telephony conference-related state of this Call.
* Allows extenders to override and customize the runtime type of the
* CallConference to used by this Call.
*
* @return a new CallConference instance which is to represent the
* telephony conference-related state of this Call
*/
protected CallConference createConference()
{
return new CallConference();
}
/**
* Gets the telephony conference-related state of this Call. Since
* a non-conference Call may be converted into a conference
* Call at any time, every Call instance maintains a
* CallConference instance regardless of whether the Call
* in question is participating in a telephony conference.
*
* @return a CallConference instance which represents the
* telephony conference-related state of this Call.
*/
public CallConference getConference()
{
if (conference == null)
{
CallConference newValue = createConference();
if (newValue == null)
{
/*
* Call is documented to always have a telephony
* conference-related state because there is an expectation that
* a 1-to-1 Call can always be turned into a conference Call.
*/
throw new IllegalStateException("conference");
}
else
{
setConference(newValue);
}
}
return conference;
}
/**
* Sets the telephony conference-related state of this Call. If the
* invocation modifies this instance, it adds this Call to the
* newly set CallConference and fires a
* PropertyChangeEvent for the CONFERENCE property to its
* listeners.
*
* @param conference the CallConference instance to represent the
* telephony conference-related state of this Call
*/
public void setConference(CallConference conference)
{
if (this.conference != conference)
{
CallConference oldValue = this.conference;
this.conference = conference;
CallConference newValue = this.conference;
if (oldValue != null)
oldValue.removeCall(this);
if (newValue != null)
newValue.addCall(this);
firePropertyChange(CONFERENCE, oldValue, newValue);
}
}
/**
* Adds a specific PropertyChangeListener to the list of listeners
* interested in and notified about changes in the values of the properties
* of this Call.
*
* @param listener a PropertyChangeListener to be notified about
* changes in the values of the properties of this Call. If the
* specified listener is already in the list of interested listeners (i.e.
* it has been previously added), it is not added again.
*/
public abstract void addPropertyChangeListener(
PropertyChangeListener listener);
/**
* Fires a new PropertyChangeEvent to the
* PropertyChangeListeners registered with this Call in
* order to notify about a change in the value of a specific property which
* had its old value modified to a specific new value.
*
* @param property the name of the property of this Call which had
* its value changed
* @param oldValue the value of the property with the specified name before
* the change
* @param newValue the value of the property with the specified name after
* the change
*/
protected abstract void firePropertyChange(
String property,
Object oldValue, Object newValue);
/**
* Removes a specific PropertyChangeListener from the list of
* listeners interested in and notified about changes in the values of the
* properties of this Call.
*
* @param listener a PropertyChangeListener to no longer be
* notified about changes in the values of the properties of this Call
*/
public abstract void removePropertyChangeListener(
PropertyChangeListener listener);
/**
* Returns true iff incoming calls into this Call should
* be auto-answered.
*
* @return true iff incoming calls into this Call should
* be auto-answered.
*/
public boolean isAutoAnswer()
{
return isAutoAnswer;
}
/**
* Sets the flag that specifies whether incoming calls into this
* Call should be auto-answered.
* @param autoAnswer whether incoming calls into this Call should
* be auto-answered.
*/
public void setAutoAnswer(boolean autoAnswer)
{
isAutoAnswer = autoAnswer;
}
}