/*
* 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.gui.main.call;
import java.awt.event.*;
import java.text.*;
import java.util.*;
import javax.swing.Timer;
import org.osgi.framework.*;
import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.customcontrols.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.service.contactlist.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
/**
* The CallManager is the one that handles calls. It contains also
* the "Call" and "Hang up" buttons panel. Here are handles incoming and
* outgoing calls from and to the call operation set.
*
* @author Yana Stamcheva
*/
public class CallManager
{
/**
* Our class logger.
*/
private static final Logger logger = Logger.getLogger(CallManager.class);
/**
* A table mapping protocol Call objects to the GUI dialogs
* that are currently used to display them.
*/
private static Hashtable activeCalls
= new Hashtable();
/**
* A list of the currently missed calls. Only the names of the first
* participant of each call is stored in the list.
*/
private static Collection missedCalls;
/**
* Listener notified for changes in missed calls count.
*/
private static MissedCallsListener missedCallsListener;
/**
* A call listener.
*/
public static class GuiCallListener implements CallListener
{
/**
* Implements CallListener.incomingCallReceived. When a call is received
* creates a ReceivedCallDialog and plays the
* ring phone sound to the user.
* @param event the CallEvent
*/
public void incomingCallReceived(CallEvent event)
{
Call sourceCall = event.getSourceCall();
ReceivedCallDialog receivedCallDialog
= new ReceivedCallDialog(sourceCall);
receivedCallDialog.pack();
receivedCallDialog.setVisible(true);
final String peerName
= sourceCall.getCallPeers().next().getDisplayName();
final Date callDate = new Date();
NotificationManager.fireNotification(
NotificationManager.INCOMING_CALL,
"",
GuiActivator.getResources()
.getI18NString("service.gui.INCOMING_CALL",
new String[]{peerName}));
sourceCall.addCallChangeListener(new CallChangeAdapter()
{
public void callStateChanged(CallChangeEvent evt)
{
if (evt.getNewValue().equals(CallState.CALL_ENDED)
&& evt.getOldValue()
.equals(CallState.CALL_INITIALIZATION))
{
addMissedCall(new MissedCall(peerName, callDate));
evt.getSourceCall().removeCallChangeListener(this);
}
}
});
}
/**
* Implements CallListener.callEnded. Stops sounds that are playing at
* the moment if there're any. Removes the call panel and disables the
* hang up button.
* @param event the CallEvent
*/
public void callEnded(CallEvent event)
{
Call sourceCall = event.getSourceCall();
// Stop all telephony related sounds.
stopAllSounds();
// Play the hangup sound.
NotificationManager.fireNotification(NotificationManager.HANG_UP);
if (activeCalls.get(sourceCall) != null)
{
CallDialog callDialog = activeCalls.get(sourceCall);
disposeCallDialogWait(callDialog);
}
}
/**
* Creates and opens a call dialog. Implements
* CallListener.outGoingCallCreated.
* @param event the CallEvent
*/
public void outgoingCallCreated(CallEvent event)
{
Call sourceCall = event.getSourceCall();
CallManager.openCallDialog(sourceCall);
}
}
/**
* Removes the given call panel tab.
*
* @param callDialog the CallDialog to remove
*/
public static void disposeCallDialogWait(CallDialog callDialog)
{
Timer timer
= new Timer(5000, new DisposeCallDialogListener(callDialog));
timer.setRepeats(false);
timer.start();
}
/**
* Removes the given CallPanel from the main tabbed pane.
*/
private static class DisposeCallDialogListener
implements ActionListener
{
private final CallDialog callDialog;
public DisposeCallDialogListener(CallDialog callDialog)
{
this.callDialog = callDialog;
}
public void actionPerformed(ActionEvent e)
{
callDialog.dispose();
Call call = callDialog.getCall();
if(call != null)
activeCalls.remove(call);
}
}
/**
* Answers the given call.
*
* @param call the call to answer
*/
public static void answerCall(final Call call)
{
CallManager.openCallDialog(call);
new AnswerCallThread(call).start();
}
/**
* Hang ups the given call.
*
* @param call the call to hang up
*/
public static void hangupCall(final Call call)
{
new HangupCallThread(call).start();
}
/**
* Hang ups the given callPeer.
*
* @param callPeer the CallPeer to hang up
*/
public static void hangupCallPeer(final CallPeer callPeer)
{
stopAllSounds();
NotificationManager.fireNotification(NotificationManager.HANG_UP);
new HangupCallPeerThread(callPeer).start();
}
/**
* Creates a call to the contact represented by the given string.
*
* @param protocolProvider the protocol provider to which this call belongs.
* @param contact the contact to call to
*/
public static void createCall( ProtocolProviderService protocolProvider,
String contact)
{
new CreateCallThread(protocolProvider, contact).start();
}
/**
* Creates a call to the contact represented by the given string.
*
* @param protocolProvider the protocol provider to which this call belongs.
* @param contact the contact to call to
*/
public static void createCall( ProtocolProviderService protocolProvider,
Contact contact)
{
new CreateCallThread(protocolProvider, contact).start();
}
/**
* Creates a call to the contact represented by the given string through the
* default (most connected) protocol provider. If none of the providers is
* registered or online does nothing.
*
* @param contact the contact to call to
*/
public static void createCall(String contact)
{
ProtocolProviderService telProvider = null;
int status = 0;
List telProviders = getTelephonyProviders();
for (ProtocolProviderService provider : telProviders)
{
if (!provider.isRegistered())
continue;
OperationSetPresence presence
= provider.getOperationSet(OperationSetPresence.class);
int presenceStatus
= (presence == null)
? PresenceStatus.AVAILABLE_THRESHOLD
: presence.getPresenceStatus().getStatus();
if (status < presenceStatus)
{
status = presenceStatus;
telProvider = provider;
}
}
if (status >= PresenceStatus.ONLINE_THRESHOLD)
new CreateCallThread(telProvider, contact).start();
else
{
logger.error("There's no online telephony"
+ " provider to create this call.");
new ErrorDialog(
null,
GuiActivator.getResources()
.getI18NString("service.gui.WARNING"),
GuiActivator.getResources()
.getI18NString(
"service.gui.NO_ONLINE_TELEPHONY_ACCOUNT"),
ErrorDialog.WARNING)
.showDialog();
}
}
/**
* Creates a call to the given list of contacts.
*
* @param protocolProvider the protocol provider to which this call belongs.
* @param callees the list of contacts to call to
*/
public static void createConferenceCall(
String[] callees,
ProtocolProviderService protocolProvider)
{
new CreateConferenceCallThread(callees, protocolProvider).start();
}
/**
* Invites the given list of callees to the given conference
* call.
*
* @param callees the list of contacts to invite
* @param call the protocol provider to which this call belongs
*/
public static void inviteToConferenceCall( String[] callees,
Call call)
{
new InviteToConferenceCallThread(callees, call).start();
}
/**
* Puts on or off hold the given callPeer.
* @param callPeer the peer to put on/off hold
* @param isOnHold indicates the action (on hold or off hold)
*/
public static void putOnHold(CallPeer callPeer, boolean isOnHold)
{
new PutOnHoldCallPeerThread(callPeer, isOnHold).start();
}
/**
* Transfers the given peer to the given target.
* @param peer the CallPeer to transfer
* @param target the CallPeer target to transfer to
*/
public static void transferCall(CallPeer peer, CallPeer target)
{
OperationSetAdvancedTelephony> telephony
= peer.getCall().getProtocolProvider()
.getOperationSet(OperationSetAdvancedTelephony.class);
if (telephony != null)
{
try
{
telephony.transfer(peer, target);
}
catch (OperationFailedException ex)
{
logger.error("Failed to transfer " + peer.getAddress()
+ " to " + target, ex);
}
}
}
/**
* Transfers the given peer to the given target.
* @param peer the CallPeer to transfer
* @param target the target of the transfer
*/
public static void transferCall(CallPeer peer, String target)
{
OperationSetAdvancedTelephony> telephony
= peer.getCall().getProtocolProvider()
.getOperationSet(OperationSetAdvancedTelephony.class);
if (telephony != null)
{
try
{
telephony.transfer(peer, target);
}
catch (OperationFailedException ex)
{
logger.error("Failed to transfer " + peer.getAddress()
+ " to " + target, ex);
}
}
}
/**
* Opens a call dialog.
*
* @param call the call object to pass to the call dialog
* @return the opened call dialog
*/
public static CallDialog openCallDialog(Call call)
{
CallDialog callDialog = new CallDialog(call);
activeCalls.put(call, callDialog);
callDialog.setVisible(true, true);
return callDialog;
}
/**
* Returns a list of all currently registered telephony providers.
* @return a list of all currently registered telephony providers
*/
public static List getTelephonyProviders()
{
List telephonyProviders
= new LinkedList();
for (ProtocolProviderFactory providerFactory : GuiActivator
.getProtocolProviderFactories().values())
{
ServiceReference serRef;
ProtocolProviderService protocolProvider;
for (AccountID accountID : providerFactory.getRegisteredAccounts())
{
serRef = providerFactory.getProviderForAccount(accountID);
protocolProvider
= (ProtocolProviderService) GuiActivator.bundleContext
.getService(serRef);
if (protocolProvider.getOperationSet(
OperationSetBasicTelephony.class) != null
&& protocolProvider.isRegistered())
{
telephonyProviders.add(protocolProvider);
}
}
}
return telephonyProviders;
}
/**
* Returns a list of all currently registered telephony providers for the
* given protocol name.
* @param protocolName the protocol name
* @return a list of all currently registered telephony providers for the
* given protocol name
*/
public static List getTelephonyProviders(
String protocolName)
{
List telephonyProviders
= new LinkedList();
ProtocolProviderFactory providerFactory
= GuiActivator.getProtocolProviderFactory(protocolName);
if (providerFactory != null)
{
ServiceReference serRef;
ProtocolProviderService protocolProvider;
for (AccountID accountID : providerFactory.getRegisteredAccounts())
{
serRef = providerFactory.getProviderForAccount(accountID);
protocolProvider
= (ProtocolProviderService) GuiActivator.bundleContext
.getService(serRef);
if (protocolProvider.getOperationSet(
OperationSetBasicTelephony.class) != null
&& protocolProvider.isRegistered())
{
telephonyProviders.add(protocolProvider);
}
}
}
return telephonyProviders;
}
/**
* Returns an Iterator over a list of all currently active calls.
* @return an Iterator over a list of all currently active calls
*/
public static Iterator getActiveCalls()
{
return activeCalls.keySet().iterator();
}
/**
* Sets the given MissedCallsListener that would be notified on
* any changes in missed calls count.
* @param l the listener to set
*/
public static void setMissedCallsListener(MissedCallsListener l)
{
missedCallsListener = l;
}
/**
* Adds a missed call.
* @param missedCall the missed call to add to the list of missed calls
*/
private static void addMissedCall(MissedCall missedCall)
{
if (missedCalls == null)
{
missedCalls = new LinkedList();
}
missedCalls.add(missedCall);
fireMissedCallCountChangeEvent(missedCalls);
}
/**
* Clears the count of missed calls. Sets it to 0.
*/
public static void clearMissedCalls()
{
missedCalls = null;
}
/**
* Notifies interested MissedCallListener that the count has
* changed.
* @param missedCalls the new missed calls
*/
private static void fireMissedCallCountChangeEvent(
Collection missedCalls)
{
if (missedCallsListener != null)
missedCallsListener.missedCallCountChanged(missedCalls);
}
/**
* Returns the image corresponding to the given peer.
*
* @param peer the call peer, for which we're returning an image
* @return the peer image
*/
public static byte[] getPeerImage(CallPeer peer)
{
byte[] image = null;
// We search for a contact corresponding to this call peer and
// try to get its image.
if (peer.getContact() != null)
{
MetaContact metaContact = GuiActivator.getContactListService()
.findMetaContactByContact(peer.getContact());
image = metaContact.getAvatar();
}
// If the icon is still null we try to get an image from the call
// peer.
if ((image == null || image.length == 0)
&& peer.getImage() != null)
image = peer.getImage();
return image;
}
/**
* Opens a call transfer dialog to transfer the given peer.
* @param peer the CallPeer to transfer
*/
public static void openCallTransferDialog(CallPeer peer)
{
final TransferCallDialog dialog
= new TransferCallDialog(peer);
final Call call = peer.getCall();
/*
* Transferring a call works only when the call is in progress
* so close the dialog (if it's not already closed, of course)
* once the dialog ends.
*/
CallChangeListener callChangeListener = new CallChangeAdapter()
{
/*
* Implements
* CallChangeAdapter#callStateChanged(CallChangeEvent).
*/
public void callStateChanged(CallChangeEvent evt)
{
// we are interested only in CALL_STATE_CHANGEs
if(!evt.getEventType().equals(
CallChangeEvent.CALL_STATE_CHANGE))
return;
if (!CallState.CALL_IN_PROGRESS.equals(call
.getCallState()))
{
dialog.setVisible(false);
dialog.dispose();
}
}
};
call.addCallChangeListener(callChangeListener);
try
{
dialog.setModal(true);
dialog.pack();
dialog.setVisible(true);
}
finally
{
call.removeCallChangeListener(callChangeListener);
}
}
/**
* Creates a call from a given Contact or a given String.
*/
private static class CreateCallThread
extends Thread
{
private final String stringContact;
private final Contact contact;
private final ProtocolProviderService protocolProvider;
public CreateCallThread(ProtocolProviderService protocolProvider,
String contact)
{
this.protocolProvider = protocolProvider;
this.stringContact = contact;
this.contact = null;
}
public CreateCallThread(ProtocolProviderService protocolProvider,
Contact contact)
{
this.protocolProvider = protocolProvider;
this.contact = contact;
this.stringContact = null;
}
public void run()
{
OperationSetBasicTelephony> telephonyOpSet
= protocolProvider
.getOperationSet(OperationSetBasicTelephony.class);
/*
* XXX If we are here and we just discover that
* OperationSetBasicTelephony is not supported, then we're already
* in trouble. At the very least, we've already started a whole new
* thread just to check that a reference is null.
*/
if (telephonyOpSet == null)
return;
Throwable exception = null;
try
{
if (contact != null)
telephonyOpSet.createCall(contact);
else if (stringContact != null)
telephonyOpSet.createCall(stringContact);
}
catch (OperationFailedException e)
{
exception = e;
}
catch (ParseException e)
{
exception = e;
}
if (exception != null)
{
logger.error("The call could not be created: ", exception);
new ErrorDialog(
null,
GuiActivator.getResources()
.getI18NString("service.gui.ERROR"),
exception.getMessage(),
ErrorDialog.ERROR)
.showDialog();
}
}
}
/**
* Answers all call peers in the given call.
*/
private static class AnswerCallThread
extends Thread
{
private final Call call;
public AnswerCallThread(Call call)
{
this.call = call;
}
public void run()
{
ProtocolProviderService pps = call.getProtocolProvider();
Iterator extends CallPeer> peers = call.getCallPeers();
while (peers.hasNext())
{
CallPeer peer = peers.next();
OperationSetBasicTelephony> telephony =
pps.getOperationSet(OperationSetBasicTelephony.class);
try
{
telephony.answerCallPeer(peer);
}
catch (OperationFailedException e)
{
logger.error("Could not answer to : " + peer
+ " caused by the following exception: " + e);
}
}
}
}
/**
* Creates a conference call from a given list of contact addresses
*/
private static class CreateConferenceCallThread
extends Thread
{
private final String[] callees;
private final ProtocolProviderService protocolProvider;
public CreateConferenceCallThread(
String[] callees,
ProtocolProviderService protocolProvider)
{
this.callees = callees;
this.protocolProvider = protocolProvider;
}
@Override
public void run()
{
OperationSetTelephonyConferencing confOpSet
= protocolProvider.getOperationSet(
OperationSetTelephonyConferencing.class);
/*
* XXX If we are here and we just discover that
* OperationSetTelephonyConferencing is not supported, then we're
* already in trouble. At the very least, we've already started a
* whole new thread just to check that a reference is null.
*/
if (confOpSet == null)
return;
Throwable exception = null;
try
{
confOpSet.createConfCall(callees);
}
catch (OperationFailedException ofe)
{
exception = ofe;
}
catch (OperationNotSupportedException onse)
{
exception = onse;
}
catch (IllegalArgumentException iae)
{
exception = iae;
}
if (exception != null)
{
logger.error("Failed to create conference call. " + exception);
new ErrorDialog(
null,
GuiActivator
.getResources().getI18NString("service.gui.ERROR"),
exception.getMessage(),
ErrorDialog.ERROR)
.showDialog();
}
}
}
/**
* Invites a list of callees to a conference call.
*/
private static class InviteToConferenceCallThread
extends Thread
{
private final String[] callees;
private final Call call;
public InviteToConferenceCallThread(String[] callees, Call call)
{
this.callees = callees;
this.call = call;
}
@Override
public void run()
{
OperationSetTelephonyConferencing confOpSet
= call.getProtocolProvider()
.getOperationSet(
OperationSetTelephonyConferencing.class);
/*
* XXX If we are here and we just discover that
* OperationSetTelephonyConferencing is not supported, then we're
* already in trouble. At the very least, we've already started a
* whole new thread just to check that a reference is null.
*/
if (confOpSet == null)
return;
for (String callee : callees)
{
Throwable exception = null;
try
{
confOpSet.inviteCalleeToCall(callee, call);
}
catch (OperationFailedException ofe)
{
exception = ofe;
}
catch (OperationNotSupportedException onse)
{
exception = onse;
}
catch (IllegalArgumentException iae)
{
exception = iae;
}
if (exception != null)
{
logger
.error("Failed to invite callee: " + callee, exception);
new ErrorDialog(
null,
GuiActivator
.getResources()
.getI18NString("service.gui.ERROR"),
exception.getMessage(),
ErrorDialog.ERROR)
.showDialog();
}
}
}
}
/**
* Hang-ups all call peers in the given call.
*/
private static class HangupCallThread
extends Thread
{
private final Call call;
public HangupCallThread(Call call)
{
this.call = call;
}
public void run()
{
ProtocolProviderService pps = call.getProtocolProvider();
Iterator extends CallPeer> peers = call.getCallPeers();
while (peers.hasNext())
{
CallPeer peer = peers.next();
OperationSetBasicTelephony> telephony
= pps.getOperationSet(OperationSetBasicTelephony.class);
try
{
telephony.hangupCallPeer(peer);
}
catch (OperationFailedException e)
{
logger.error("Could not hang up : " + peer
+ " caused by the following exception: " + e);
}
}
}
}
/**
* Hang-ups the given CallPeer.
*/
private static class HangupCallPeerThread
extends Thread
{
private final CallPeer callPeer;
public HangupCallPeerThread(CallPeer callPeer)
{
this.callPeer = callPeer;
}
public void run()
{
ProtocolProviderService pps = callPeer.getProtocolProvider();
OperationSetBasicTelephony> telephony =
pps.getOperationSet(OperationSetBasicTelephony.class);
try
{
telephony.hangupCallPeer(callPeer);
}
catch (OperationFailedException e)
{
logger.error("Could not hang up : " + callPeer
+ " caused by the following exception: " + e);
}
}
}
/**
* Puts on hold the given CallPeer.
*/
private static class PutOnHoldCallPeerThread
extends Thread
{
private final CallPeer callPeer;
private final boolean isOnHold;
public PutOnHoldCallPeerThread(CallPeer callPeer, boolean isOnHold)
{
this.callPeer = callPeer;
this.isOnHold = isOnHold;
}
public void run()
{
OperationSetBasicTelephony> telephony =
callPeer.getProtocolProvider()
.getOperationSet(OperationSetBasicTelephony.class);
try
{
if (isOnHold)
telephony.putOnHold(callPeer);
else
telephony.putOffHold(callPeer);
}
catch (OperationFailedException ex)
{
String callPeerAddress = callPeer.getAddress();
if (isOnHold)
logger.error("Failed to put"
+ callPeerAddress + " on hold.", ex);
else
logger.error("Failed to put"
+ callPeerAddress + " off hold.", ex);
}
}
}
/**
* Stops all telephony related sounds.
*/
private static void stopAllSounds()
{
NotificationManager.stopSound(NotificationManager.DIALING);
NotificationManager.stopSound(NotificationManager.BUSY_CALL);
NotificationManager.stopSound(NotificationManager.INCOMING_CALL);
NotificationManager.stopSound(NotificationManager.OUTGOING_CALL);
}
}