/* * 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 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 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); } }