/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.gui.main.call; import java.awt.*; import java.lang.ref.*; import java.text.*; import java.util.*; import java.util.List; import java.util.regex.*; import javax.swing.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.*; import net.java.sip.communicator.impl.gui.main.contactlist.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.desktoputil.transparent.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.contactsource.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.service.protocol.media.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.Logger; import net.java.sip.communicator.util.account.*; import org.jitsi.service.neomedia.*; import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.format.*; import org.jitsi.service.resources.*; import org.jitsi.util.*; /** * The <tt>CallManager</tt> 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 * @author Lyubomir Marinov * @author Boris Grozev */ public class CallManager { /** * The <tt>Logger</tt> used by the <tt>CallManager</tt> class and its * instances for logging output. */ private static final Logger logger = Logger.getLogger(CallManager.class); /** * The name of the property which indicates whether the user should be * warned when starting a desktop sharing session. */ private static final String desktopSharingWarningProperty = "net.java.sip.communicator.impl.gui.main" + ".call.SHOW_DESKTOP_SHARING_WARNING"; /** * The name of the property which indicates whether the preferred provider * will be used when calling UIContact (call history). */ private static final String IGNORE_PREFERRED_PROVIDER_PROP = "net.java.sip.communicator.impl.gui.main" + ".call.IGNORE_PREFERRED_PROVIDER_PROP"; /** * The <tt>CallPanel</tt>s opened by <tt>CallManager</tt> (because * <tt>CallContainer</tt> does not give access to such lists.) */ private static final Map<CallConference, CallPanel> callPanels = new HashMap<CallConference, CallPanel>(); /** * A map of active outgoing calls per <tt>UIContactImpl</tt>. */ private static Map<Call, UIContactImpl> uiContactCalls; /** * The group of notifications dedicated to missed calls. */ private static UINotificationGroup missedCallGroup; /** * A <tt>CallListener</tt>. */ public static class GuiCallListener extends SwingCallListener { /** * Maps for incoming call handlers. The handlers needs to be created * in the protocol thread while their method * incomingCallReceivedInEventDispatchThread will be called on EDT. * On the protocol thread a call state changed listener is added, * if this is done on the EDT there is a almost no gap between incoming * CallEvent and call state changed when doing auto answer and we * end up with call answered and dialog for incoming call. */ private Map<CallEvent,WeakReference<IncomingCallHandler>> inCallHandlers = Collections.synchronizedMap( new WeakHashMap<CallEvent, WeakReference<IncomingCallHandler>>()); /** * Delivers the <tt>CallEvent</tt> in the protocol thread. */ public void incomingCallReceived(CallEvent ev) { inCallHandlers.put( ev, new WeakReference<IncomingCallHandler>( new IncomingCallHandler(ev.getSourceCall()))); super.incomingCallReceived(ev); } /** * Implements {@link CallListener#incomingCallReceived(CallEvent)}. When * a call is received, creates a <tt>ReceivedCallDialog</tt> and plays * the ring phone sound to the user. * * @param ev the <tt>CallEvent</tt> */ @Override public void incomingCallReceivedInEventDispatchThread(CallEvent ev) { WeakReference<IncomingCallHandler> ihRef = inCallHandlers.remove(ev); if(ihRef != null) { ihRef.get().incomingCallReceivedInEventDispatchThread(ev); } } /** * Implements CallListener.callEnded. Stops sounds that are playing at * the moment if there're any. Removes the <tt>CallPanel</tt> and * disables the hang-up button. * * @param ev the <tt>CallEvent</tt> which specifies the <tt>Call</tt> * that has ended */ @Override public void callEndedInEventDispatchThread(CallEvent ev) { CallConference callConference = ev.getCallConference(); closeCallContainerIfNotNecessary(callConference); /* * Notify the existing CallPanels about the CallEvent (in case * they need to update their UI, for example). */ forwardCallEventToCallPanels(ev); // If we're currently in the call history view, refresh // it. TreeContactList contactList = GuiActivator.getContactList(); if (contactList.getCurrentFilter().equals( TreeContactList.historyFilter)) { contactList.applyFilter( TreeContactList.historyFilter); } } /** * Creates and opens a call dialog. Implements * {@link CallListener#outgoingCallCreated(CallEvent)}. * * @param ev the <tt>CallEvent</tt> */ @Override public void outgoingCallCreatedInEventDispatchThread(CallEvent ev) { Call sourceCall = ev.getSourceCall(); openCallContainerIfNecessary(sourceCall); /* * Notify the existing CallPanels about the CallEvent (in case they * need to update their UI, for example). */ forwardCallEventToCallPanels(ev); } } /** * Handles incoming calls. Must be created on the protocol thread while the * method incomingCallReceivedInEventDispatchThread is executed on the EDT. */ private static class IncomingCallHandler extends CallChangeAdapter { /** * The dialog shown */ private ReceivedCallDialog receivedCallDialog; /** * Peer name. */ private String peerName; /** * The time of the incoming call. */ private long callTime; /** * Construct * @param sourceCall */ IncomingCallHandler(Call sourceCall) { Iterator<? extends CallPeer> peerIter = sourceCall.getCallPeers(); if(!peerIter.hasNext()) { return; } peerName = peerIter.next().getDisplayName(); callTime = System.currentTimeMillis(); sourceCall.addCallChangeListener(this); } /** * State has changed. * @param ev */ @Override public void callStateChanged(final CallChangeEvent ev) { if(!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { callStateChanged(ev); } }); return; } if (!CallChangeEvent.CALL_STATE_CHANGE .equals(ev.getPropertyName())) return; // When the call state changes, we ensure here that the // received call notification dialog is closed. if (receivedCallDialog != null && receivedCallDialog.isVisible()) receivedCallDialog.setVisible(false); // Ensure that the CallDialog is created, because it is the // one that listens for CallPeers. Object newValue = ev.getNewValue(); Call call = ev.getSourceCall(); if (CallState.CALL_INITIALIZATION.equals(newValue) || CallState.CALL_IN_PROGRESS.equals(newValue)) { openCallContainerIfNecessary(call); } else if (CallState.CALL_ENDED.equals(newValue)) { if (ev.getOldValue().equals( CallState.CALL_INITIALIZATION)) { // If the call was answered elsewhere, don't mark it // as missed. CallPeerChangeEvent cause = ev.getCause(); if ((cause == null) || (cause.getReasonCode() != CallPeerChangeEvent .NORMAL_CALL_CLEARING)) { addMissedCallNotification(peerName, callTime); } } call.removeCallChangeListener(this); } } /** * Executed on EDT cause will create dialog and will show it. * @param ev */ public void incomingCallReceivedInEventDispatchThread(CallEvent ev) { Call sourceCall = ev.getSourceCall(); boolean isVideoCall = ev.isVideoCall() && ConfigurationUtils.hasEnabledVideoFormat( sourceCall.getProtocolProvider()); receivedCallDialog = new ReceivedCallDialog( sourceCall, isVideoCall, (CallManager.getInProgressCalls().size() > 0), ev.isDesktopStreaming()); receivedCallDialog.setVisible(true); Iterator<? extends CallPeer> peerIter = sourceCall.getCallPeers(); if(!peerIter.hasNext()) { if (receivedCallDialog.isVisible()) receivedCallDialog.setVisible(false); return; } /* * Notify the existing CallPanels about the CallEvent (in case they * need to update their UI, for example). */ forwardCallEventToCallPanels(ev); } } /** * Answers the given call. * * @param call the call to answer */ public static void answerCall(Call call) { answerCall(call, null, false /* without video */); } /** * Answers a specific <tt>Call</tt> with or without video and, optionally, * does that in a telephony conference with an existing <tt>Call</tt>. * * @param call * @param existingCall * @param video */ private static void answerCall(Call call, Call existingCall, boolean video) { if (existingCall == null) openCallContainerIfNecessary(call); new AnswerCallThread(call, existingCall, video).start(); } /** * Answers the given call in an existing call. It will end up with a * conference call. * * @param call the call to answer */ public static void answerCallInFirstExistingCall(Call call) { // Find the first existing call. Iterator<Call> existingCallIter = getInProgressCalls().iterator(); Call existingCall = existingCallIter.hasNext() ? existingCallIter.next() : null; answerCall(call, existingCall, false /* without video */); } /** * Merges specific existing <tt>Call</tt>s into a specific telephony * conference. * * @param conference the conference * @param calls list of calls */ public static void mergeExistingCalls( CallConference conference, Collection<Call> calls) { new MergeExistingCalls(conference, calls).start(); } /** * Answers the given call with video. * * @param call the call to answer */ public static void answerVideoCall(Call call) { answerCall(call, null, true /* with video */); } /** * Hang ups the given call. * * @param call the call to hang up */ public static void hangupCall(Call call) { new HangupCallThread(call).start(); } /** * Hang ups the given <tt>callPeer</tt>. * * @param peer the <tt>CallPeer</tt> to hang up */ public static void hangupCallPeer(CallPeer peer) { new HangupCallThread(peer).start(); } /** * Asynchronously hangs up the <tt>Call</tt>s participating in a specific * <tt>CallConference</tt>. * * @param conference the <tt>CallConference</tt> whose participating * <tt>Call</tt>s are to be hanged up */ public static void hangupCalls(CallConference conference) { new HangupCallThread(conference).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, false /* audio-only */) .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 * @param uiContact the meta contact we're calling */ public static void createCall( ProtocolProviderService protocolProvider, String contact, UIContactImpl uiContact) { new CreateCallThread(protocolProvider, null, null, uiContact, contact, null, null, false /* audio-only */).start(); } /** * Creates a video 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 createVideoCall(ProtocolProviderService protocolProvider, String contact) { new CreateCallThread(protocolProvider, contact, true /* video */) .start(); } /** * Creates a video 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 * @param uiContact the <tt>UIContactImpl</tt> we're calling */ public static void createVideoCall( ProtocolProviderService protocolProvider, String contact, UIContactImpl uiContact) { new CreateCallThread(protocolProvider, null, null, uiContact, contact, null, null, true /* video */).start(); } /** * Enables/disables local video for a specific <tt>Call</tt>. * * @param call the <tt>Call</tt> to enable/disable to local video for * @param enable <tt>true</tt> to enable the local video; otherwise, * <tt>false</tt> */ public static void enableLocalVideo(Call call, boolean enable) { new EnableLocalVideoThread(call, enable).start(); } /** * Indicates if the desktop sharing is currently enabled for the given * <tt>call</tt>. * * @param call the <tt>Call</tt>, for which we would to check if the desktop * sharing is currently enabled * @return <tt>true</tt> if the desktop sharing is currently enabled for the * given <tt>call</tt>, <tt>false</tt> otherwise */ public static boolean isLocalVideoEnabled(Call call) { OperationSetVideoTelephony telephony = call.getProtocolProvider().getOperationSet( OperationSetVideoTelephony.class); return (telephony != null) && telephony.isLocalVideoAllowed(call); } /** * Creates a desktop sharing 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 * @param uiContact the <tt>UIContactImpl</tt> we're calling */ private static void createDesktopSharing( ProtocolProviderService protocolProvider, String contact, UIContactImpl uiContact) { // If the user presses cancel on the desktop sharing warning then we // have nothing more to do here. if (!showDesktopSharingWarning()) return; MediaService mediaService = GuiActivator.getMediaService(); List<MediaDevice> desktopDevices = mediaService.getDevices(MediaType.VIDEO, MediaUseCase.DESKTOP); int deviceNumber = desktopDevices.size(); if (deviceNumber == 1) { createDesktopSharing( protocolProvider, contact, uiContact, desktopDevices.get(0), true); } else if (deviceNumber > 1) { SelectScreenDialog selectDialog = new SelectScreenDialog(desktopDevices); selectDialog.setVisible(true); if (selectDialog.getSelectedDevice() != null) createDesktopSharing( protocolProvider, contact, uiContact, selectDialog.getSelectedDevice(), true); } } /** * Creates a region desktop sharing through the given * <tt>protocolProvider</tt> with the given <tt>contact</tt>. * * @param protocolProvider the <tt>ProtocolProviderService</tt>, through * which the sharing session will be established * @param contact the address of the contact recipient * @param uiContact the <tt>UIContactImpl</tt> we're calling */ private static void createRegionDesktopSharing( ProtocolProviderService protocolProvider, String contact, UIContactImpl uiContact) { if (showDesktopSharingWarning()) { TransparentFrame frame = DesktopSharingFrame .createTransparentFrame( protocolProvider, contact, uiContact, true); frame.setLocationRelativeTo(null); frame.setVisible(true); } } /** * Creates a desktop sharing 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 * @param uiContact the <tt>MetaContact</tt> we're calling * @param x the x coordinate of the shared region * @param y the y coordinated of the shared region * @param width the width of the shared region * @param height the height of the shared region */ public static void createRegionDesktopSharing( ProtocolProviderService protocolProvider, String contact, UIContactImpl uiContact, int x, int y, int width, int height) { MediaService mediaService = GuiActivator.getMediaService(); List<MediaDevice> desktopDevices = mediaService.getDevices( MediaType.VIDEO, MediaUseCase.DESKTOP); int deviceNumber = desktopDevices.size(); if (deviceNumber > 0) { createDesktopSharing( protocolProvider, contact, uiContact, mediaService.getMediaDeviceForPartialDesktopStreaming( width, height, x, y), false); } } /** * Creates a desktop sharing 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 * @param uiContact the <tt>UIContactImpl</tt> we're calling * @param mediaDevice the media device corresponding to the screen to share * @param fullscreen whether we are sharing the fullscreen */ private static void createDesktopSharing( ProtocolProviderService protocolProvider, String contact, UIContactImpl uiContact, MediaDevice mediaDevice, boolean fullscreen) { new CreateDesktopSharingThread( protocolProvider, contact, uiContact, mediaDevice, fullscreen).start(); } /** * Enables the desktop sharing in an existing <tt>call</tt>. * * @param call the call for which desktop sharing should be enabled * @param enable indicates if the desktop sharing should be enabled or * disabled */ public static void enableDesktopSharing(Call call, boolean enable) { if (!enable) enableDesktopSharing(call, null, enable); else if (showDesktopSharingWarning()) { MediaService mediaService = GuiActivator.getMediaService(); List<MediaDevice> desktopDevices = mediaService.getDevices(MediaType.VIDEO, MediaUseCase.DESKTOP); int deviceNumber = desktopDevices.size(); new FullScreenShareIndicator(call); if (deviceNumber == 1) enableDesktopSharing(call, null, enable); else if (deviceNumber > 1) { SelectScreenDialog selectDialog = new SelectScreenDialog(desktopDevices); selectDialog.setVisible(true); if (selectDialog.getSelectedDevice() != null) enableDesktopSharing( call, selectDialog.getSelectedDevice(), enable); } } // in case we switch to video, disable remote control if it was // enabled enableDesktopRemoteControl(call.getCallPeers().next(), false); } /** * Enables the region desktop sharing for the given call. * * @param call the call, for which the region desktop sharing should be * enabled * @param enable indicates if the desktop sharing should be enabled or * disabled */ public static void enableRegionDesktopSharing(Call call, boolean enable) { if (!enable) enableDesktopSharing(call, null, enable); else if (showDesktopSharingWarning()) { TransparentFrame frame = DesktopSharingFrame.createTransparentFrame(call, true); frame.setVisible(true); } } /** * Creates a desktop sharing call to the contact represented by the given * string. * * @param call the call for which desktop sharing should be enabled * @param x the x coordinate of the shared region * @param y the y coordinated of the shared region * @param width the width of the shared region * @param height the height of the shared region */ public static void enableRegionDesktopSharing( Call call, int x, int y, int width, int height) { // Use the default media device corresponding to the screen to share MediaService mediaService = GuiActivator.getMediaService(); List<MediaDevice> desktopDevices = mediaService.getDevices( MediaType.VIDEO, MediaUseCase.DESKTOP); int deviceNumber = desktopDevices.size(); if (deviceNumber > 0) { boolean succeed = enableDesktopSharing( call, mediaService.getMediaDeviceForPartialDesktopStreaming( width, height, x, y), true); // If the region sharing succeed, then display the frame of the // current region shared. if(succeed) { TransparentFrame frame = DesktopSharingFrame.createTransparentFrame(call, false); frame.setVisible(true); } } // in case we switch to video, disable remote control if it was // enabled enableDesktopRemoteControl(call.getCallPeers().next(), false); } /** * Enables the desktop sharing in an existing <tt>call</tt>. * * @param call the call for which desktop sharing should be enabled * @param mediaDevice the media device corresponding to the screen to share * @param enable indicates if the desktop sharing should be enabled or * disabled * * @return True if the desktop sharing succeed (we are currently sharing the * whole or a part of the desktop). False, otherwise. */ private static boolean enableDesktopSharing(Call call, MediaDevice mediaDevice, boolean enable) { OperationSetDesktopStreaming desktopOpSet = call.getProtocolProvider().getOperationSet( OperationSetDesktopStreaming.class); boolean enableSucceeded = false; // This shouldn't happen at this stage, because we disable the button // if the operation set isn't available. if (desktopOpSet != null) { // First make sure the local video button is disabled. if (enable && isLocalVideoEnabled(call)) getActiveCallContainer(call).setVideoButtonSelected(false); try { if (mediaDevice != null) { desktopOpSet.setLocalVideoAllowed( call, mediaDevice, enable); } else desktopOpSet.setLocalVideoAllowed(call, enable); enableSucceeded = true; } catch (OperationFailedException ex) { logger.error( "Failed to toggle the streaming of local video.", ex); } } return (enable && enableSucceeded); } /** * Indicates if the desktop sharing is currently enabled for the given * <tt>call</tt>. * * @param call the <tt>Call</tt>, for which we would to check if the desktop * sharing is currently enabled * @return <tt>true</tt> if the desktop sharing is currently enabled for the * given <tt>call</tt>, <tt>false</tt> otherwise */ public static boolean isDesktopSharingEnabled(Call call) { OperationSetDesktopStreaming desktopOpSet = call.getProtocolProvider().getOperationSet( OperationSetDesktopStreaming.class); if (desktopOpSet != null && desktopOpSet.isLocalVideoAllowed(call)) return true; return false; } /** * Indicates if the desktop sharing is currently enabled for the given * <tt>call</tt>. * * @param call the <tt>Call</tt>, for which we would to check if the desktop * sharing is currently enabled * @return <tt>true</tt> if the desktop sharing is currently enabled for the * given <tt>call</tt>, <tt>false</tt> otherwise */ public static boolean isRegionDesktopSharingEnabled(Call call) { OperationSetDesktopStreaming desktopOpSet = call.getProtocolProvider().getOperationSet( OperationSetDesktopStreaming.class); if (desktopOpSet != null && desktopOpSet.isPartialStreaming(call)) return true; return false; } /** * Enables/disables remote control when in a desktop sharing session with * the given <tt>callPeer</tt>. * * @param callPeer the call peer for which we enable/disable remote control * @param isEnable indicates if the remote control should be enabled */ public static void enableDesktopRemoteControl( CallPeer callPeer, boolean isEnable) { OperationSetDesktopSharingServer sharingOpSet = callPeer.getProtocolProvider().getOperationSet( OperationSetDesktopSharingServer.class); if (sharingOpSet == null) return; if (isEnable) sharingOpSet.enableRemoteControl(callPeer); else sharingOpSet.disableRemoteControl(callPeer); } /** * Creates a call to the given call string. The given component indicates * where should be shown the "call via" menu if needed. * * @param callString the string to call * @param c the component, which indicates where should be shown the "call * via" menu if needed */ public static void createCall( String callString, JComponent c) { createCall(callString, c, null); } /** * Creates a call to the given call string. The given component indicates * where should be shown the "call via" menu if needed. * * @param callString the string to call * @param c the component, which indicates where should be shown the "call * via" menu if needed * @param l listener that is notified when the call interface has been * started after call was created */ public static void createCall( String callString, JComponent c, CallInterfaceListener l) { callString = callString.trim(); // Removes special characters from phone numbers. if (ConfigurationUtils.isNormalizePhoneNumber() && !NetworkUtils.isValidIPAddress(callString)) { callString = GuiActivator.getPhoneNumberI18nService() .normalize(callString); } List<ProtocolProviderService> telephonyProviders = CallManager.getTelephonyProviders(); if (telephonyProviders.size() == 1) { CallManager.createCall( telephonyProviders.get(0), callString); if (l != null) l.callInterfaceStarted(); } else if (telephonyProviders.size() > 1) { /* * Allow plugins which do not have a (Jitsi) UI to create calls by * automagically picking up a telephony provider. */ if (c == null) { ProtocolProviderService preferredTelephonyProvider = null; for (ProtocolProviderService telephonyProvider : telephonyProviders) { try { OperationSetPresence presenceOpSet = telephonyProvider.getOperationSet( OperationSetPresence.class); if ((presenceOpSet != null) && (presenceOpSet.findContactByID(callString) != null)) { preferredTelephonyProvider = telephonyProvider; break; } } catch (Throwable t) { if (t instanceof ThreadDeath) throw (ThreadDeath) t; } } if (preferredTelephonyProvider == null) preferredTelephonyProvider = telephonyProviders.get(0); CallManager.createCall(preferredTelephonyProvider, callString); if (l != null) l.callInterfaceStarted(); } else { ChooseCallAccountPopupMenu chooseAccountDialog = new ChooseCallAccountPopupMenu( c, callString, telephonyProviders, l); chooseAccountDialog.setLocation(c.getLocation()); chooseAccountDialog.showPopupMenu(); } } else { ResourceManagementService resources = GuiActivator.getResources(); new ErrorDialog( null, resources.getI18NString("service.gui.WARNING"), resources.getI18NString( "service.gui.NO_ONLINE_TELEPHONY_ACCOUNT")) .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) { Map<ProtocolProviderService, List<String>> crossProtocolCallees = new HashMap<ProtocolProviderService, List<String>>(); crossProtocolCallees.put(protocolProvider, Arrays.asList(callees)); createConferenceCall(crossProtocolCallees); } /** * Invites the given list of <tt>callees</tt> to the given conference * <tt>call</tt>. * * @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) { Map<ProtocolProviderService, List<String>> crossProtocolCallees = new HashMap<ProtocolProviderService, List<String>>(); crossProtocolCallees.put( call.getProtocolProvider(), Arrays.asList(callees)); inviteToConferenceCall(crossProtocolCallees, call); } /** * Invites the given list of <tt>callees</tt> to the given conference * <tt>call</tt>. * * @param callees the list of contacts to invite * @param call existing call */ public static void inviteToConferenceCall( Map<ProtocolProviderService, List<String>> callees, Call call) { new InviteToConferenceCallThread(callees, call).start(); } /** * Invites specific <tt>callees</tt> to a specific telephony conference. * * @param callees the list of contacts to invite * @param conference the telephony conference to invite the specified * <tt>callees</tt> into */ public static void inviteToConferenceCall( Map<ProtocolProviderService, List<String>> callees, CallConference conference) { /* * InviteToConferenceCallThread takes a specific Call but actually * invites to the telephony conference associated with the specified * Call (if any). In order to not change the signature of its * constructor at this time, just pick up a Call participating in the * specified telephony conference (if any). */ Call call = null; if (conference != null) { List<Call> calls = conference.getCalls(); if (!calls.isEmpty()) call = calls.get(0); } new InviteToConferenceCallThread(callees, call).start(); } /** * Asynchronously creates a new conference <tt>Call</tt> with a specific * list of participants/callees. * * @param callees the list of participants/callees to invite to a * newly-created conference <tt>Call</tt> */ public static void createConferenceCall( Map<ProtocolProviderService, List<String>> callees) { new InviteToConferenceCallThread(callees, null).start(); } /** * Asynchronously creates a new video bridge conference <tt>Call</tt> with * a specific list of participants/callees. * * @param callProvider the <tt>ProtocolProviderService</tt> to use for * creating the call * @param callees the list of participants/callees to invite to the * newly-created video bridge conference <tt>Call</tt> */ public static void createJitsiVideobridgeConfCall( ProtocolProviderService callProvider, String[] callees) { new InviteToConferenceBridgeThread(callProvider, callees, null).start(); } /** * Invites the given list of <tt>callees</tt> to the given conference * <tt>call</tt>. * * @param callees the list of contacts to invite * @param call the protocol provider to which this call belongs */ public static void inviteToJitsiVideobridgeConfCall(String[] callees, Call call) { new InviteToConferenceBridgeThread( call.getProtocolProvider(), callees, call).start(); } /** * Puts on or off hold the given <tt>callPeer</tt>. * @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 <tt>peer</tt> to the given <tt>target</tt>. * @param peer the <tt>CallPeer</tt> to transfer * @param target the <tt>CallPeer</tt> 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 <tt>peer</tt> to the given <tt>target</tt>. * @param peer the <tt>CallPeer</tt> 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); } } } /** * Closes the <tt>CallPanel</tt> of a specific <tt>Call</tt> if it is no * longer necessary (i.e. is not used by other <tt>Call</tt>s participating * in the same telephony conference as the specified <tt>Call</tt>.) * * @param callConference The <tt>CallConference</tt> which is to have its * associated <tt>CallPanel</tt>, if any */ private static void closeCallContainerIfNotNecessary( final CallConference callConference) { CallPanel callPanel = callPanels.get(callConference); if (callPanel != null) closeCallContainerIfNotNecessary( callConference, callPanel.isCloseWaitAfterHangup()); } /** * Closes the <tt>CallPanel</tt> of a specific <tt>Call</tt> if it is no * longer necessary (i.e. is not used by other <tt>Call</tt>s participating * in the same telephony conference as the specified <tt>Call</tt>.) * * @param callConference The <tt>CallConference</tt> which is to have its * associated <tt>CallPanel</tt>, if any, closed * @param wait <tt>true</tt> to set <tt>delay</tt> param of * {@link CallContainer#close(CallPanel, boolean)} (CallPanel)} */ private static void closeCallContainerIfNotNecessary( final CallConference callConference, final boolean wait) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { closeCallContainerIfNotNecessary( callConference, wait); } }); return; } /* * XXX The integrity of the execution of the method may be compromised * if it is not invoked on the AWT event dispatching thread because * findCallPanel and callPanels.remove must be atomically executed. The * uninterrupted execution (with respect to the synchronization) is * guaranteed by requiring all modifications to callPanels to be made on * the AWT event dispatching thread. */ for (Iterator<Map.Entry<CallConference, CallPanel>> entryIter = callPanels.entrySet().iterator(); entryIter.hasNext();) { Map.Entry<CallConference, CallPanel> entry = entryIter.next(); CallConference aConference = entry.getKey(); boolean notNecessary = aConference.isEnded(); if (notNecessary) { CallPanel aCallPanel = entry.getValue(); CallContainer window = aCallPanel.getCallWindow(); try { window.close( aCallPanel, wait && (aConference == callConference)); } finally { /* * We allow non-modifications i.e. reads of callPanels on * threads other than the AWT event dispatching thread so we * have to make sure that we will not cause * ConcurrentModificationException. */ synchronized (callPanels) { entryIter.remove(); } aCallPanel.dispose(); } } } } /** * Opens a <tt>CallPanel</tt> for a specific <tt>Call</tt> if there is none. * <p> * <b>Note</b>: The method can be called only on the AWT event dispatching * thread. * </p> * * @param call the <tt>Call</tt> to open a <tt>CallPanel</tt> for * @return the <tt>CallPanel</tt> associated with the <tt>Call</tt> * @throws RuntimeException if the method is not called on the AWT event * dispatching thread */ private static CallPanel openCallContainerIfNecessary(Call call) { /* * XXX The integrity of the execution of the method may be compromised * if it is not invoked on the AWT event dispatching thread because * findCallPanel and callPanels.put must be atomically executed. The * uninterrupted execution (with respect to the synchronization) is * guaranteed by requiring all modifications to callPanels to be made on * the AWT event dispatching thread. */ assertIsEventDispatchingThread(); /* * CallPanel displays a CallConference (which may contain multiple * Calls.) */ CallConference conference = call.getConference(); CallPanel callPanel = findCallPanel(conference); if (callPanel == null) { // If we're in single-window mode, the single window is the // CallContainer. CallContainer callContainer = GuiActivator.getUIService().getSingleWindowContainer(); // If we're in multi-window mode, we create the CallDialog. if (callContainer == null) callContainer = new CallDialog(); callPanel = new CallPanel(conference, callContainer); callContainer.addCallPanel(callPanel); synchronized (callPanels) { callPanels.put(conference, callPanel); } } return callPanel; } /** * Returns a list of all currently registered telephony providers. * @return a list of all currently registered telephony providers */ public static List<ProtocolProviderService> getTelephonyProviders() { return AccountUtils .getRegisteredProviders(OperationSetBasicTelephony.class); } /** * Returns a list of all currently registered telephony providers supporting * conferencing. * * @return a list of all currently registered telephony providers supporting * conferencing */ public static List<ProtocolProviderService> getTelephonyConferencingProviders() { return AccountUtils .getRegisteredProviders(OperationSetTelephonyConferencing.class); } /** * Returns a list of all currently active calls. * * @return a list of all currently active calls */ private static List<Call> getActiveCalls() { CallConference[] conferences; synchronized (callPanels) { Set<CallConference> keySet = callPanels.keySet(); conferences = keySet.toArray(new CallConference[keySet.size()]); } List<Call> calls = new ArrayList<Call>(); for (CallConference conference : conferences) { for (Call call : conference.getCalls()) { if (call.getCallState() == CallState.CALL_IN_PROGRESS) calls.add(call); } } return calls; } /** * Returns a collection of all currently in progress calls. A call is active * if it is in progress so the method merely delegates to * * @return a collection of all currently in progress calls. */ public static Collection<Call> getInProgressCalls() { return getActiveCalls(); } /** * Returns the <tt>CallContainer</tt> corresponding to the given * <tt>call</tt>. If the call has been finished and no active * <tt>CallContainer</tt> could be found it returns null. * * @param call the <tt>Call</tt>, which dialog we're looking for * @return the <tt>CallContainer</tt> corresponding to the given * <tt>call</tt> */ public static CallPanel getActiveCallContainer(Call call) { return findCallPanel(call.getConference()); } /** * A informative text to show for the peer. If display name is missing * return the address. * @param peer the peer. * @param listener the listener to fire change events for later resolutions * of display name and image, if exist. * @return the text contain display name. */ public static String getPeerDisplayName(CallPeer peer, DetailsResolveListener listener) { String displayName = null; // We try to find the <tt>UIContact</tt>, to which the call was // created if this was an outgoing call. UIContactImpl uiContact = CallManager.getCallUIContact(peer.getCall()); if(uiContact != null) { if(uiContact.getDescriptor() instanceof SourceContact) { // if it is source contact (history record) // search for cusax contact match Contact contact = getPeerCusaxContact(peer, (SourceContact)uiContact.getDescriptor()); if(contact != null) displayName = contact.getDisplayName(); } if(StringUtils.isNullOrEmpty(displayName, true)) displayName = uiContact.getDisplayName(); } // We search for a contact corresponding to this call peer and // try to get its display name. if (StringUtils.isNullOrEmpty(displayName, true) && peer.getContact() != null) { displayName = peer.getContact().getDisplayName(); } // We try to find the an alternative peer address. if (StringUtils.isNullOrEmpty(displayName, true)) { String imppAddress = peer.getAlternativeIMPPAddress(); if (!StringUtils.isNullOrEmpty(imppAddress)) { int protocolPartIndex = imppAddress.indexOf(":"); imppAddress = (protocolPartIndex >= 0) ? imppAddress.substring(protocolPartIndex + 1) : imppAddress; Collection<ProtocolProviderService> cusaxProviders = AccountUtils.getRegisteredProviders( OperationSetCusaxUtils.class); if (cusaxProviders != null && cusaxProviders.size() > 0) { Iterator<ProtocolProviderService> iter = cusaxProviders.iterator(); while(iter.hasNext()) { Contact contact = getPeerContact( peer, iter.next(), imppAddress); displayName = (contact != null) ? contact.getDisplayName() : null; if(!StringUtils.isNullOrEmpty(displayName, true)) break; } } else { MetaContact metaContact = getPeerMetaContact(peer, imppAddress); displayName = (metaContact != null) ? metaContact.getDisplayName() : null; } } } if (StringUtils.isNullOrEmpty(displayName, true)) { displayName = (!StringUtils.isNullOrEmpty (peer.getDisplayName(), true)) ? peer.getDisplayName() : peer.getAddress(); // Try to resolve the display name String resolvedName = queryContactSource(displayName, listener); if(resolvedName != null) { displayName = resolvedName; } } return displayName; } /** * Returns the image corresponding to the given <tt>peer</tt>. * * @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) { image = getContactImage(peer.getContact()); } // We try to find the <tt>UIContact</tt>, to which the call was // created if this was an outgoing call. if (image == null || image.length == 0) { UIContactImpl uiContact = CallManager.getCallUIContact(peer.getCall()); if (uiContact != null) { if(uiContact.getDescriptor() instanceof SourceContact && ((SourceContact)uiContact.getDescriptor()) .isDefaultImage()) { // if it is source contact (history record) // search for cusax contact match Contact contact = getPeerCusaxContact(peer, (SourceContact)uiContact.getDescriptor()); if(contact != null) image = contact.getImage(); } else image = uiContact.getAvatar(); } } // We try to find the an alternative peer address. if (image == null || image.length == 0) { String imppAddress = peer.getAlternativeIMPPAddress(); if (!StringUtils.isNullOrEmpty(imppAddress)) { int protocolPartIndex = imppAddress.indexOf(":"); imppAddress = (protocolPartIndex >= 0) ? imppAddress.substring(protocolPartIndex + 1) : imppAddress; Collection<ProtocolProviderService> cusaxProviders = AccountUtils.getRegisteredProviders( OperationSetCusaxUtils.class); if (cusaxProviders != null && cusaxProviders.size() > 0) { Iterator<ProtocolProviderService> iter = cusaxProviders.iterator(); while(iter.hasNext()) { Contact contact = getPeerContact( peer, iter.next(), imppAddress); image = (contact != null) ? getContactImage(contact) : null; if(image != null) break; } } else { MetaContact metaContact = getPeerMetaContact(peer, imppAddress); image = (metaContact != null) ? metaContact.getAvatar() : null; } } } // 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; } /** * Searches the cusax enabled providers for a contact with * the detail (address) of the call peer if found and the contact * is provided by a provider which is IM capable, return the contact. * @param peer the peer we are calling. * @return the im capable contact corresponding the <tt>peer</tt>. */ public static Contact getIMCapableCusaxContact(CallPeer peer) { // We try to find the <tt>UIContact</tt>, to which the call was // created if this was an outgoing call. UIContactImpl uiContact = CallManager.getCallUIContact(peer.getCall()); if (uiContact != null) { if(uiContact.getDescriptor() instanceof MetaContact) { MetaContact metaContact = (MetaContact)uiContact.getDescriptor(); Iterator<Contact> iter = metaContact.getContacts(); while(iter.hasNext()) { Contact contact = iter.next(); if(contact.getProtocolProvider() .getOperationSet( OperationSetBasicInstantMessaging.class) != null) return contact; } } else if(uiContact.getDescriptor() instanceof SourceContact) { // if it is source contact (history record) // search for cusax contact match Contact contact = getPeerCusaxContact(peer, (SourceContact)uiContact.getDescriptor()); if(contact != null && contact.getProtocolProvider().getOperationSet( OperationSetBasicInstantMessaging.class) != null) return contact; } } // We try to find the an alternative peer address. String imppAddress = peer.getAlternativeIMPPAddress(); if (!StringUtils.isNullOrEmpty(imppAddress)) { int protocolPartIndex = imppAddress.indexOf(":"); imppAddress = (protocolPartIndex >= 0) ? imppAddress.substring(protocolPartIndex + 1) : imppAddress; Collection<ProtocolProviderService> cusaxProviders = AccountUtils.getRegisteredProviders( OperationSetCusaxUtils.class); if (cusaxProviders != null && cusaxProviders.size() > 0) { Iterator<ProtocolProviderService> iter = cusaxProviders.iterator(); while(iter.hasNext()) { ProtocolProviderService cusaxProvider = iter.next(); Contact contact = getPeerContact( peer, cusaxProvider, imppAddress); if(contact != null && cusaxProvider.getOperationSet( OperationSetBasicInstantMessaging.class) != null) { return contact; } } } } return null; } /** * Find is there a linked cusax protocol provider for this source contact, * if it exist we try to resolve current peer to one of its contacts * or details of a contact (numbers). * @param peer the peer to check * @param sourceContact the currently selected source contact. * @return matching cusax contact. */ private static Contact getPeerCusaxContact( CallPeer peer, SourceContact sourceContact) { ProtocolProviderService linkedCusaxProvider = null; for(ContactDetail detail : sourceContact.getContactDetails()) { ProtocolProviderService pps = detail.getPreferredProtocolProvider( OperationSetBasicTelephony.class); if(pps != null) { OperationSetCusaxUtils cusaxOpSet = pps.getOperationSet(OperationSetCusaxUtils.class); if(cusaxOpSet != null) { linkedCusaxProvider = cusaxOpSet.getLinkedCusaxProvider(); break; } } } // if we do not have preferred protocol, lets check the one // used to dial the peer if(linkedCusaxProvider == null) { ProtocolProviderService pps = peer.getProtocolProvider(); OperationSetCusaxUtils cusaxOpSet = pps.getOperationSet(OperationSetCusaxUtils.class); if(cusaxOpSet != null) { linkedCusaxProvider = cusaxOpSet.getLinkedCusaxProvider(); } } if(linkedCusaxProvider != null) { OperationSetPersistentPresence opSetPersistentPresence = linkedCusaxProvider.getOperationSet( OperationSetPersistentPresence.class); if(opSetPersistentPresence != null) { String peerAddress = peer.getAddress(); // will strip the @server-address part, as the regular expression // will match it int index = peerAddress.indexOf("@"); String peerUserID = (index > -1) ? peerAddress.substring(0, index) : peerAddress; // searches for the whole number/username or with the @serverpart String peerUserIDQ = Pattern.quote(peerUserID); Pattern pattern = Pattern.compile( "^(" + peerUserIDQ + "|" + peerUserIDQ + "@.*)$"); return findContactByPeer( peerUserID, pattern, opSetPersistentPresence.getServerStoredContactListRoot(), linkedCusaxProvider.getOperationSet( OperationSetCusaxUtils.class)); } } return null; } /** * Finds a matching cusax contact. * @param peerUserID the userID of the call peer to search for * @param searchPattern the pattern (userID | userID@...) * @param parent the parent group of the groups and contacts to search in * @param cusaxOpSet the opset of the provider which will be used to match * contact's details to peer userID (stored numbers). * @return a cusax matching contac */ private static Contact findContactByPeer( String peerUserID, Pattern searchPattern, ContactGroup parent, OperationSetCusaxUtils cusaxOpSet) { Iterator<Contact> contactIterator = parent.contacts(); while(contactIterator.hasNext()) { Contact contact = contactIterator.next(); if(searchPattern.matcher(contact.getAddress()).find() || cusaxOpSet.doesDetailBelong(contact, peerUserID)) { return contact; } } Iterator<ContactGroup> groupsIterator = parent.subgroups(); while(groupsIterator.hasNext()) { ContactGroup gr = groupsIterator.next(); Contact contact = findContactByPeer( peerUserID, searchPattern, gr, cusaxOpSet); if(contact != null) return contact; } return null; } /** * Returns the image for the given contact. * * @param contact the <tt>Contact</tt>, which image we're looking for * @return the array of bytes representing the image for the given contact * or null if such image doesn't exist */ private static byte[] getContactImage(Contact contact) { MetaContact metaContact = GuiActivator.getContactListService() .findMetaContactByContact(contact); if (metaContact != null) return metaContact.getAvatar(); return null; } /** * Returns the peer contact for the given <tt>alternativePeerAddress</tt> by * checking the if the <tt>callPeer</tt> exists as a detail in the given * <tt>cusaxProvider</tt>. * * @param callPeer the <tt>CallPeer</tt> to check in the cusax provider * details * @param cusaxProvider the linked cusax <tt>ProtocolProviderService</tt> * @param alternativePeerAddress the alternative peer address to obtain the * image from * @return the protocol <tt>Contact</tt> corresponding to the given * <tt>alternativePeerAddress</tt> */ private static Contact getPeerContact( CallPeer callPeer, ProtocolProviderService cusaxProvider, String alternativePeerAddress) { OperationSetPresence presenceOpSet = cusaxProvider.getOperationSet(OperationSetPresence.class); if (presenceOpSet == null) return null; Contact contact = presenceOpSet.findContactByID(alternativePeerAddress); if (contact == null) return null; OperationSetCusaxUtils cusaxOpSet = cusaxProvider.getOperationSet(OperationSetCusaxUtils.class); if (cusaxOpSet != null && cusaxOpSet.doesDetailBelong( contact, callPeer.getAddress())) return contact; return null; } /** * Returns the metacontact for the given <tt>CallPeer</tt> by * checking the if the <tt>callPeer</tt> contact exists, if not checks the * contacts in our contact list that are provided by cusax enabled * providers. * * @param peer the <tt>CallPeer</tt> to check in contact details * @return the <tt>MetaContact</tt> corresponding to the given * <tt>peer</tt>. */ public static MetaContact getPeerMetaContact(CallPeer peer) { if(peer == null) return null; if(peer.getContact() != null) return GuiActivator.getContactListService() .findMetaContactByContact(peer.getContact()); // We try to find the <tt>UIContact</tt>, to which the call was // created if this was an outgoing call. UIContactImpl uiContact = CallManager.getCallUIContact(peer.getCall()); if (uiContact != null) { if(uiContact.getDescriptor() instanceof MetaContact) { return (MetaContact)uiContact.getDescriptor(); } else if(uiContact.getDescriptor() instanceof SourceContact) { // if it is a source contact check for matching cusax contact Contact contact = getPeerCusaxContact(peer, (SourceContact)uiContact.getDescriptor()); if(contact != null) return GuiActivator.getContactListService() .findMetaContactByContact(contact); } } String imppAddress = peer.getAlternativeIMPPAddress(); if (!StringUtils.isNullOrEmpty(imppAddress)) { int protocolPartIndex = imppAddress.indexOf(":"); imppAddress = (protocolPartIndex >= 0) ? imppAddress.substring(protocolPartIndex + 1) : imppAddress; Collection<ProtocolProviderService> cusaxProviders = AccountUtils.getRegisteredProviders( OperationSetCusaxUtils.class); if (cusaxProviders != null && cusaxProviders.size() > 0) { Iterator<ProtocolProviderService> iter = cusaxProviders.iterator(); while(iter.hasNext()) { Contact contact = getPeerContact( peer, iter.next(), imppAddress); MetaContact res = GuiActivator.getContactListService() .findMetaContactByContact(contact); if(res != null) return res; } } else { return getPeerMetaContact(peer, imppAddress); } } return null; } /** * Returns the image for the given <tt>alternativePeerAddress</tt> by * checking the if the <tt>callPeer</tt> exists as a detail in one of the * contacts in our contact list. * * @param callPeer the <tt>CallPeer</tt> to check in contact details * @param alternativePeerAddress the alternative peer address to obtain the * image from * @return the <tt>MetaContact</tt> corresponding to the given * <tt>alternativePeerAddress</tt> */ private static MetaContact getPeerMetaContact( CallPeer callPeer, String alternativePeerAddress) { Iterator<MetaContact> metaContacts = GuiActivator.getContactListService() .findAllMetaContactsForAddress(alternativePeerAddress); while (metaContacts.hasNext()) { MetaContact metaContact = metaContacts.next(); UIPhoneUtil phoneUtil = UIPhoneUtil.getPhoneUtil(metaContact); List<UIContactDetail> additionalNumbers = phoneUtil.getAdditionalNumbers(); if (additionalNumbers == null || additionalNumbers.size() > 0) continue; Iterator<UIContactDetail> numbersIter = additionalNumbers.iterator(); while (numbersIter.hasNext()) { if (numbersIter.next().getAddress() .equals(callPeer.getAddress())) return metaContact; } } return null; } /** * Opens a call transfer dialog to transfer the given <tt>peer</tt>. * @param peer the <tt>CallPeer</tt> 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() { /* * Overrides * CallChangeAdapter#callStateChanged(CallChangeEvent). */ @Override 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.pack(); dialog.setVisible(true); } finally { call.removeCallChangeListener(callChangeListener); } } /** * Checks whether the <tt>callPeer</tt> supports setting video * quality presets. If quality controls is null, its not supported. * @param callPeer the peer, which video quality we're checking * @return whether call peer supports setting quality preset. */ public static boolean isVideoQualityPresetSupported(CallPeer callPeer) { ProtocolProviderService provider = callPeer.getProtocolProvider(); OperationSetVideoTelephony videoOpSet = provider.getOperationSet(OperationSetVideoTelephony.class); if (videoOpSet == null) return false; return videoOpSet.getQualityControl(callPeer) != null; } /** * Sets the given quality preset for the video of the given call peer. * * @param callPeer the peer, which video quality we're setting * @param qualityPreset the new quality settings */ public static void setVideoQualityPreset(final CallPeer callPeer, final QualityPreset qualityPreset) { ProtocolProviderService provider = callPeer.getProtocolProvider(); final OperationSetVideoTelephony videoOpSet = provider.getOperationSet(OperationSetVideoTelephony.class); if (videoOpSet == null) return; final QualityControl qualityControl = videoOpSet.getQualityControl(callPeer); if (qualityControl != null) { new Thread(new Runnable() { public void run() { try { qualityControl.setPreferredRemoteSendMaxPreset( qualityPreset); } catch(Exception e) { logger.info("Unable to change video quality.", e); ResourceManagementService resources = GuiActivator.getResources(); new ErrorDialog( null, resources.getI18NString("service.gui.WARNING"), resources.getI18NString( "service.gui.UNABLE_TO_CHANGE_VIDEO_QUALITY"), e) .showDialog(); } } }).start(); } } /** * Indicates if we have video streams to show in this interface. * * @param call the call to check for video streaming * @return <tt>true</tt> if we have video streams to show in this interface; * otherwise, <tt>false</tt> */ public static boolean isVideoStreaming(Call call) { return isVideoStreaming(call.getConference()); } /** * Indicates if we have video streams to show in this interface. * * @param conference the conference we check for video streaming * @return <tt>true</tt> if we have video streams to show in this interface; * otherwise, <tt>false</tt> */ public static boolean isVideoStreaming(CallConference conference) { for (Call call : conference.getCalls()) { OperationSetVideoTelephony videoTelephony = call.getProtocolProvider().getOperationSet( OperationSetVideoTelephony.class); if (videoTelephony == null) continue; if (videoTelephony.isLocalVideoStreaming(call)) return true; Iterator<? extends CallPeer> callPeers = call.getCallPeers(); while (callPeers.hasNext()) { List<Component> remoteVideos = videoTelephony.getVisualComponents(callPeers.next()); if ((remoteVideos != null) && (remoteVideos.size() > 0)) return true; } } return false; } /** * Determines whether two specific addresses refer to one and the same * peer/resource/contact. * <p> * <b>Warning</b>: Use the functionality sparingly because it assumes that * an unspecified service is equal to any service. * </p> * * @param a one of the addresses to be compared * @param b the other address to be compared to <tt>a</tt> * @return <tt>true</tt> if <tt>a</tt> and <tt>b</tt> name one and the same * peer/resource/contact; <tt>false</tt>, otherwise */ public static boolean addressesAreEqual(String a, String b) { if (a.equals(b)) return true; int aProtocolIndex = a.indexOf(':'); if(aProtocolIndex != -1) a = a.substring(aProtocolIndex + 1); int bProtocolIndex = b.indexOf(':'); if(bProtocolIndex != -1) b = b.substring(bProtocolIndex + 1); if (a.equals(b)) return true; int aServiceBegin = a.indexOf('@', aProtocolIndex); String aUserID; String aService; if (aServiceBegin != -1) { aUserID = a.substring(0, aServiceBegin); ++aServiceBegin; int aResourceBegin = a.indexOf('/', aServiceBegin); if (aResourceBegin != -1) aService = a.substring(aServiceBegin, aResourceBegin); else aService = a.substring(aServiceBegin); } else { aUserID = a; aService = null; } int bServiceBegin = b.indexOf('@', bProtocolIndex); String bUserID; String bService; if (bServiceBegin != -1) { bUserID = b.substring(0, bServiceBegin); ++bServiceBegin; int bResourceBegin = b.indexOf('/', bServiceBegin); if (bResourceBegin != -1) bService = b.substring(bServiceBegin, bResourceBegin); else bService = b.substring(bServiceBegin); } else { bUserID = b; bService = null; } boolean userIDsAreEqual; if ((aUserID == null) || (aUserID.length() < 1)) userIDsAreEqual = ((bUserID == null) || (bUserID.length() < 1)); else userIDsAreEqual = aUserID.equals(bUserID); if (!userIDsAreEqual) return false; boolean servicesAreEqual; /* * It's probably a veeery long shot but it's assumed here that an * unspecified service is equal to any service. Such a case is, for * example, RegistrarLess SIP. */ if (((aService == null) || (aService.length() < 1)) || ((bService == null) || (bService.length() < 1))) servicesAreEqual = true; else servicesAreEqual = aService.equals(bService); return servicesAreEqual; } /** * Indicates if the given <tt>ConferenceMember</tt> corresponds to the local * user. * * @param conferenceMember the conference member to check * @return <tt>true</tt> if the given <tt>conferenceMember</tt> is the local * user, <tt>false</tt> - otherwise */ public static boolean isLocalUser(ConferenceMember conferenceMember) { String localUserAddress = conferenceMember.getConferenceFocusCallPeer() .getProtocolProvider().getAccountID().getAccountAddress(); return CallManager.addressesAreEqual( conferenceMember.getAddress(), localUserAddress); } /** * Adds a missed call notification. * * @param peerName the name of the peer * @param callTime the time of the call */ private static void addMissedCallNotification(String peerName, long callTime) { if (missedCallGroup == null) { missedCallGroup = new UINotificationGroup( "MissedCalls", GuiActivator.getResources().getI18NString( "service.gui.MISSED_CALLS_TOOL_TIP")); } UINotificationManager.addNotification( new UINotification(peerName, callTime, missedCallGroup)); } /** * Returns of supported/enabled list of audio formats for a provider. * @param device the <tt>MediaDevice</tt>, which audio formats we're * looking for * @param protocolProvider the provider to check. * @return list of supported/enabled auido formats or empty list * otherwise. */ private static List<MediaFormat> getAudioFormats( MediaDevice device, ProtocolProviderService protocolProvider) { List<MediaFormat> res = new ArrayList<MediaFormat>(); Map<String, String> accountProperties = protocolProvider.getAccountID().getAccountProperties(); String overrideEncodings = accountProperties.get(ProtocolProviderFactory.OVERRIDE_ENCODINGS); List<MediaFormat> formats; if(Boolean.parseBoolean(overrideEncodings)) { /* * The account properties associated with account * override the global EncodingConfiguration. */ EncodingConfiguration encodingConfiguration = ProtocolMediaActivator.getMediaService() .createEmptyEncodingConfiguration(); encodingConfiguration.loadProperties( accountProperties, ProtocolProviderFactory.ENCODING_PROP_PREFIX); formats = device.getSupportedFormats( null, null, encodingConfiguration); } else /* The global EncodingConfiguration is in effect. */ { formats = device.getSupportedFormats(); } // skip the special telephony event for(MediaFormat format : formats) { if(!format.getEncoding().equals(Constants.TELEPHONE_EVENT)) res.add(format); } return res; } /** * Creates a new (audio-only or video) <tt>Call</tt> to a contact specified * as a <tt>Contact</tt> instance or a <tt>String</tt> contact * address/identifier. */ private static class CreateCallThread extends Thread { /** * The contact to call. */ private final Contact contact; /** * The specific contact resource to call. */ private final ContactResource contactResource; /** * The <tt>UIContactImpl</tt> we're calling. */ private final UIContactImpl uiContact; /** * The protocol provider through which the call goes. */ private final ProtocolProviderService protocolProvider; /** * The string to call. */ private final String stringContact; /** * The description of a conference to call, if any. */ private final ConferenceDescription conferenceDescription; /** * The indicator which determines whether this instance is to create a * new video (as opposed to audio-only) <tt>Call</tt>. */ private final boolean video; /** * The chat room associated with the call. */ private final ChatRoom chatRoom; /** * Creates an instance of <tt>CreateCallThread</tt>. * * @param protocolProvider the protocol provider through which the call * is going. * @param contact the contact to call * @param contactResource the specific <tt>ContactResource</tt> we're * calling * @param video indicates if this is a video call */ public CreateCallThread( ProtocolProviderService protocolProvider, Contact contact, ContactResource contactResource, boolean video) { this(protocolProvider, contact, contactResource, null, null, null, null, video); } /** * Creates an instance of <tt>CreateCallThread</tt>. * * @param protocolProvider the protocol provider through which the call * is going. * @param contact the contact to call * @param video indicates if this is a video call */ public CreateCallThread( ProtocolProviderService protocolProvider, String contact, boolean video) { this(protocolProvider, null, null, null, contact, null, null, video); } /** * Initializes a new <tt>CreateCallThread</tt> instance which is to * create a new <tt>Call</tt> to a conference specified via a * <tt>ConferenceDescription</tt>. * @param protocolProvider the <tt>ProtocolProviderService</tt> which is * to perform the establishment of the new <tt>Call</tt>. * @param conferenceDescription the description of the conference to * call. * @param chatRoom the chat room associated with the call. */ public CreateCallThread( ProtocolProviderService protocolProvider, ConferenceDescription conferenceDescription, ChatRoom chatRoom) { this(protocolProvider, null, null, null, null, conferenceDescription, chatRoom, false /* video */); } /** * Initializes a new <tt>CreateCallThread</tt> instance which is to * create a new <tt>Call</tt> to a contact specified either as a * <tt>Contact</tt> instance or as a <tt>String</tt> contact * address/identifier. * <p> * The constructor is private because it relies on its arguments being * validated prior to its invocation. * </p> * * @param protocolProvider the <tt>ProtocolProviderService</tt> which is * to perform the establishment of the new <tt>Call</tt> * @param contact the contact to call * @param contactResource the specific contact resource to call * @param uiContact the ui contact we're calling * @param stringContact the string to call * @param video <tt>true</tt> if this instance is to create a new video * (as opposed to audio-only) <tt>Call</tt> * @param conferenceDescription the description of a conference to call * @param chatRoom the chat room associated with the call. */ public CreateCallThread( ProtocolProviderService protocolProvider, Contact contact, ContactResource contactResource, UIContactImpl uiContact, String stringContact, ConferenceDescription conferenceDescription, ChatRoom chatRoom, boolean video) { this.protocolProvider = protocolProvider; this.contact = contact; this.contactResource = contactResource; this.uiContact = uiContact; this.stringContact = stringContact; this.video = video; this.conferenceDescription = conferenceDescription; this.chatRoom = chatRoom; } @Override public void run() { if(!video) { // if it is not video let's check for available audio codecs // and available audio devices MediaService mediaService = GuiActivator.getMediaService(); MediaDevice dev = mediaService.getDefaultDevice( MediaType.AUDIO, MediaUseCase.CALL); List<MediaFormat> formats = getAudioFormats(dev, protocolProvider); String errorMsg = null; if(!dev.getDirection().allowsSending()) errorMsg = GuiActivator.getResources().getI18NString( "service.gui.CALL_NO_AUDIO_DEVICE"); else if(formats.isEmpty()) { errorMsg = GuiActivator.getResources().getI18NString( "service.gui.CALL_NO_AUDIO_CODEC"); } if(errorMsg != null) { if(GuiActivator.getUIService() .getPopupDialog().showConfirmPopupDialog( errorMsg + " " + GuiActivator.getResources().getI18NString( "service.gui.CALL_NO_DEVICE_CODECS_Q"), GuiActivator.getResources().getI18NString( "service.gui.CALL"), PopupDialog.YES_NO_OPTION, PopupDialog.QUESTION_MESSAGE) == PopupDialog.NO_OPTION) { return; } } } Contact contact = this.contact; String stringContact = this.stringContact; if (ConfigurationUtils.isNormalizePhoneNumber() && !NetworkUtils.isValidIPAddress(stringContact)) { if (contact != null) { stringContact = contact.getAddress(); contact = null; } if (stringContact != null) { stringContact = GuiActivator.getPhoneNumberI18nService() .normalize(stringContact); } } try { if (conferenceDescription != null) { internalCall( protocolProvider, conferenceDescription, chatRoom); } else { if (video) { internalCallVideo( protocolProvider, contact, uiContact, stringContact); } else { internalCall( protocolProvider, contact, stringContact, contactResource, uiContact); } } } catch (Throwable t) { if (t instanceof ThreadDeath) throw (ThreadDeath) t; logger.error("The call could not be created: ", t); String message = GuiActivator.getResources() .getI18NString("servoce.gui.CREATE_CALL_FAILED"); if (t.getMessage() != null) message += " " + t.getMessage(); new ErrorDialog( null, GuiActivator.getResources().getI18NString( "service.gui.ERROR"), message, t) .showDialog(); } } } /** * Creates a video call through the given <tt>protocolProvider</tt>. * * @param protocolProvider the <tt>ProtocolProviderService</tt> through * which to make the call * @param contact the <tt>Contact</tt> to call * @param uiContact the <tt>UIContactImpl</tt> we're calling * @param stringContact the contact string to call * * @throws OperationFailedException thrown if the call operation fails * @throws ParseException thrown if the contact string is malformated */ private static void internalCallVideo( ProtocolProviderService protocolProvider, Contact contact, UIContactImpl uiContact, String stringContact) throws OperationFailedException, ParseException { OperationSetVideoTelephony telephony = protocolProvider.getOperationSet( OperationSetVideoTelephony.class); Call createdCall = null; if (telephony != null) { if (contact != null) { createdCall = telephony.createVideoCall(contact); } else if (stringContact != null) createdCall = telephony.createVideoCall(stringContact); } if (uiContact != null && createdCall != null) addUIContactCall(uiContact, createdCall); } /** * Creates a call through the given <tt>protocolProvider</tt>. * * @param protocolProvider the <tt>ProtocolProviderService</tt> through * which to make the call * @param contact the <tt>Contact</tt> to call * @param stringContact the contact string to call * @param contactResource the specific <tt>ContactResource</tt> to call * @param uiContact the <tt>UIContactImpl</tt> we're calling * * @throws OperationFailedException thrown if the call operation fails * @throws ParseException thrown if the contact string is malformated */ private static void internalCall( ProtocolProviderService protocolProvider, Contact contact, String stringContact, ContactResource contactResource, UIContactImpl uiContact) throws OperationFailedException, ParseException { OperationSetBasicTelephony<?> telephony = protocolProvider.getOperationSet( OperationSetBasicTelephony.class); OperationSetResourceAwareTelephony resourceTelephony = protocolProvider.getOperationSet( OperationSetResourceAwareTelephony.class); Call createdCall = null; if (resourceTelephony != null && contactResource != null) { if (contact != null) createdCall = resourceTelephony.createCall(contact, contactResource); else if (!StringUtils.isNullOrEmpty(stringContact)) createdCall = resourceTelephony.createCall( stringContact, contactResource.getResourceName()); } else if (telephony != null) { if (contact != null) { createdCall = telephony.createCall(contact); } else if (!StringUtils.isNullOrEmpty(stringContact)) createdCall = telephony.createCall(stringContact); } if (uiContact != null && createdCall != null) addUIContactCall(uiContact, createdCall); } /** * Creates a call through the given <tt>protocolProvider</tt>. * * @param protocolProvider the <tt>ProtocolProviderService</tt> through * which to make the call * @param conferenceDescription the description of the conference to call * @param chatRoom the chat room associated with the call. */ private static void internalCall(ProtocolProviderService protocolProvider, ConferenceDescription conferenceDescription, ChatRoom chatRoom) throws OperationFailedException { OperationSetBasicTelephony<?> telephony = protocolProvider.getOperationSet( OperationSetBasicTelephony.class); if (telephony != null) { telephony.createCall(conferenceDescription, chatRoom); } } /** * Returns the <tt>MetaContact</tt>, to which the given <tt>Call</tt> * was initially created. * * @param call the <tt>Call</tt>, which corresponding <tt>MetaContact</tt> * we're looking for * @return the <tt>UIContactImpl</tt>, to which the given <tt>Call</tt> * was initially created */ public static UIContactImpl getCallUIContact(Call call) { if (uiContactCalls != null) return uiContactCalls.get(call); return null; } /** * Adds a call for a <tt>metaContact</tt>. * * @param uiContact the <tt>UIContact</tt> corresponding to the call * @param call the <tt>Call</tt> corresponding to the <tt>MetaContact</tt> */ private static void addUIContactCall( UIContactImpl uiContact, Call call) { if (uiContactCalls == null) uiContactCalls = new WeakHashMap<Call, UIContactImpl>(); uiContactCalls.put(call, uiContact); } /** * Creates a desktop sharing session with the given Contact or a given * String. */ private static class CreateDesktopSharingThread extends Thread { /** * The string contact to share the desktop with. */ private final String stringContact; /** * The protocol provider through which we share our desktop. */ private final ProtocolProviderService protocolProvider; /** * The media device corresponding to the screen we would like to share. */ private final MediaDevice mediaDevice; /** * The <tt>UIContactImpl</tt> we're calling. */ private final UIContactImpl uiContact; /** * Whether user has selected sharing full screen or region. */ private boolean fullscreen = false; /** * Creates a desktop sharing session thread. * * @param protocolProvider protocol provider through which we share our * desktop * @param contact the contact to share the desktop with * @param uiContact the <tt>UIContact</tt>, which initiated the desktop * sharing session * @param mediaDevice the media device corresponding to the screen we * would like to share */ public CreateDesktopSharingThread( ProtocolProviderService protocolProvider, String contact, UIContactImpl uiContact, MediaDevice mediaDevice, boolean fullscreen) { this.protocolProvider = protocolProvider; this.stringContact = contact; this.uiContact = uiContact; this.mediaDevice = mediaDevice; this.fullscreen = fullscreen; } @Override public void run() { OperationSetDesktopStreaming desktopSharingOpSet = protocolProvider.getOperationSet( OperationSetDesktopStreaming.class); /* * XXX If we are here and we just discover that * OperationSetDesktopStreaming is not supported, then we're * already in trouble - we've already started a whole new thread * just to check that a reference is null. */ if (desktopSharingOpSet == null) return; Throwable exception = null; Call createdCall = null; try { if (mediaDevice != null) { createdCall = desktopSharingOpSet.createVideoCall( stringContact, mediaDevice); } else createdCall = desktopSharingOpSet.createVideoCall(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(); } if (uiContact != null && createdCall != null) addUIContactCall(uiContact, createdCall); if(createdCall != null && fullscreen) { new FullScreenShareIndicator(createdCall); } } } /** * Answers to all <tt>CallPeer</tt>s associated with a specific * <tt>Call</tt> and, optionally, does that in a telephony conference with * an existing <tt>Call</tt>. */ private static class AnswerCallThread extends Thread { /** * The <tt>Call</tt> which is to be answered. */ private final Call call; /** * The existing <tt>Call</tt>, if any, which represents a telephony * conference in which {@link #call} is to be answered. */ private final Call existingCall; /** * The indicator which determines whether this instance is to answer * {@link #call} with video. */ private final boolean video; public AnswerCallThread(Call call, Call existingCall, boolean video) { this.call = call; this.existingCall = existingCall; this.video = video; } @Override public void run() { if (existingCall != null) call.setConference(existingCall.getConference()); ProtocolProviderService pps = call.getProtocolProvider(); Iterator<? extends CallPeer> peers = call.getCallPeers(); while (peers.hasNext()) { CallPeer peer = peers.next(); if (video) { OperationSetVideoTelephony telephony = pps.getOperationSet(OperationSetVideoTelephony.class); try { telephony.answerVideoCallPeer(peer); } catch (OperationFailedException ofe) { logger.error( "Could not answer " + peer + " with video" + " because of the following exception: " + ofe); } } else { OperationSetBasicTelephony<?> telephony = pps.getOperationSet(OperationSetBasicTelephony.class); try { telephony.answerCallPeer(peer); } catch (OperationFailedException ofe) { logger.error( "Could not answer " + peer + " because of the following exception: ", ofe); } } } } } /** * Invites a list of callees to a conference <tt>Call</tt>. If the specified * <tt>Call</tt> is <tt>null</tt>, creates a brand new telephony conference. */ private static class InviteToConferenceCallThread extends Thread { /** * The addresses of the callees to be invited into the telephony * conference to be organized by this instance. For further details, * refer to the documentation on the <tt>callees</tt> parameter of the * respective <tt>InviteToConferenceCallThread</tt> constructor. */ private final Map<ProtocolProviderService, List<String>> callees; /** * The <tt>Call</tt>, if any, into the telephony conference of which * {@link #callees} are to be invited. If non-<tt>null</tt>, its * <tt>CallConference</tt> state will be shared with all <tt>Call</tt>s * established by this instance for the purposes of having the * <tt>callees</tt> into the same telephony conference. */ private final Call call; /** * Initializes a new <tt>InviteToConferenceCallThread</tt> instance * which is to invite a list of callees to a conference <tt>Call</tt>. * If the specified <tt>call</tt> is <tt>null</tt>, creates a brand new * telephony conference. * * @param callees the addresses of the callees to be invited into a * telephony conference. The addresses are provided in multiple * <tt>List<String></tt>s. Each such list of addresses is mapped * by the <tt>ProtocolProviderService</tt> through which they are to be * invited into the telephony conference. If there are multiple * <tt>ProtocolProviderService</tt>s in the specified <tt>Map</tt>, the * resulting telephony conference is known by the name * "cross-protocol". It is also allowed to have a list of * addresses mapped to <tt>null</tt> which means that the new instance * will automatically choose a <tt>ProtocolProviderService</tt> to * invite the respective callees into the telephony conference. * @param call the <tt>Call</tt> to invite the specified * <tt>callees</tt> into. If <tt>null</tt>, this instance will create a * brand new telephony conference. Technically, a <tt>Call</tt> instance * is protocol/account-specific and it is possible to have * cross-protocol/account telephony conferences. That's why the * specified <tt>callees</tt> are invited into one and the same * <tt>CallConference</tt>: the one in which the specified <tt>call</tt> * is participating or a new one if <tt>call</tt> is <tt>null</tt>. Of * course, an attempt is made to have all callees from one and the same * protocol/account into one <tt>Call</tt> instance. */ public InviteToConferenceCallThread( Map<ProtocolProviderService, List<String>> callees, Call call) { this.callees = callees; this.call = call; } /** * Invites {@link #callees} into a telephony conference which is * optionally specified by {@link #call}. */ @Override public void run() { CallConference conference = (call == null) ? null : call.getConference(); for(Map.Entry<ProtocolProviderService, List<String>> entry : callees.entrySet()) { ProtocolProviderService pps = entry.getKey(); /* * We'd like to allow specifying callees without specifying an * associated ProtocolProviderService. */ if (pps != null) { OperationSetBasicTelephony<?> basicTelephony = pps.getOperationSet(OperationSetBasicTelephony.class); if(basicTelephony == null) continue; } List<String> contactList = entry.getValue(); String[] contactArray = contactList.toArray(new String[contactList.size()]); if (ConfigurationUtils.isNormalizePhoneNumber()) normalizePhoneNumbers(contactArray); /* Try to have a single Call per ProtocolProviderService. */ Call ppsCall; if ((call != null) && call.getProtocolProvider().equals(pps)) ppsCall = call; else { ppsCall = null; if (conference != null) { List<Call> conferenceCalls = conference.getCalls(); if (pps == null) { /* * We'd like to allow specifying callees without * specifying an associated ProtocolProviderService. * The simplest approach is to just choose the first * ProtocolProviderService involved in the telephony * conference. */ if (call == null) { if (!conferenceCalls.isEmpty()) { ppsCall = conferenceCalls.get(0); pps = ppsCall.getProtocolProvider(); } } else { ppsCall = call; pps = ppsCall.getProtocolProvider(); } } else { for (Call conferenceCall : conferenceCalls) { if (pps.equals( conferenceCall.getProtocolProvider())) { ppsCall = conferenceCall; break; } } } } } OperationSetTelephonyConferencing telephonyConferencing = pps.getOperationSet( OperationSetTelephonyConferencing.class); try { if (ppsCall == null) { ppsCall = telephonyConferencing.createConfCall( contactArray, conference); if (conference == null) conference = ppsCall.getConference(); } else { for (String contact : contactArray) { telephonyConferencing.inviteCalleeToCall( contact, ppsCall); } } } catch(Exception e) { logger.error( "Failed to invite callees: " + Arrays.toString(contactArray), e); new ErrorDialog( null, GuiActivator.getResources().getI18NString( "service.gui.ERROR"), e.getMessage(), ErrorDialog.ERROR) .showDialog(); } } } } /** * Invites a list of callees to a specific conference <tt>Call</tt>. If the * specified <tt>Call</tt> is <tt>null</tt>, creates a brand new telephony * conference. */ private static class InviteToConferenceBridgeThread extends Thread { private final ProtocolProviderService callProvider; private final String[] callees; private final Call call; public InviteToConferenceBridgeThread( ProtocolProviderService callProvider, String[] callees, Call call) { this.callProvider = callProvider; this.callees = callees; this.call = call; } @Override public void run() { OperationSetVideoBridge opSetVideoBridge = callProvider.getOperationSet( OperationSetVideoBridge.class); // Normally if this method is called then this should not happen // but we check in order to be sure to be able to proceed. if (opSetVideoBridge == null || !opSetVideoBridge.isActive()) return; if (ConfigurationUtils.isNormalizePhoneNumber()) normalizePhoneNumbers(callees); try { if (call == null) { opSetVideoBridge.createConfCall(callees); } else { for (String contact : callees) opSetVideoBridge.inviteCalleeToCall(contact, call); } } catch(Exception e) { logger.error( "Failed to invite callees: " + Arrays.toString(callees), e); new ErrorDialog( null, GuiActivator.getResources().getI18NString( "service.gui.ERROR"), e.getMessage(), e) .showDialog(); } } } /** * Hangs up a specific <tt>Call</tt> (i.e. all <tt>CallPeer</tt>s associated * with a <tt>Call</tt>), <tt>CallConference</tt> (i.e. all <tt>Call</tt>s * participating in a <tt>CallConference</tt>), or <tt>CallPeer</tt>. */ private static class HangupCallThread extends Thread { private final Call call; private final CallConference conference; private final CallPeer peer; /** * Initializes a new <tt>HangupCallThread</tt> instance which is to hang * up a specific <tt>Call</tt> i.e. all <tt>CallPeer</tt>s associated * with the <tt>Call</tt>. * * @param call the <tt>Call</tt> whose associated <tt>CallPeer</tt>s are * to be hanged up */ public HangupCallThread(Call call) { this(call, null, null); } /** * Initializes a new <tt>HangupCallThread</tt> instance which is to hang * up a specific <tt>CallConference</tt> i.e. all <tt>Call</tt>s * participating in the <tt>CallConference</tt>. * * @param conference the <tt>CallConference</tt> whose participating * <tt>Call</tt>s re to be hanged up */ public HangupCallThread(CallConference conference) { this(null, conference, null); } /** * Initializes a new <tt>HangupCallThread</tt> instance which is to hang * up a specific <tt>CallPeer</tt>. * * @param peer the <tt>CallPeer</tt> to hang up */ public HangupCallThread(CallPeer peer) { this(null, null, peer); } /** * Initializes a new <tt>HangupCallThread</tt> instance which is to hang * up a specific <tt>Call</tt>, <tt>CallConference</tt>, or * <tt>CallPeer</tt>. * * @param call the <tt>Call</tt> whose associated <tt>CallPeer</tt>s are * to be hanged up * @param conference the <tt>CallConference</tt> whose participating * <tt>Call</tt>s re to be hanged up * @param peer the <tt>CallPeer</tt> to hang up */ private HangupCallThread( Call call, CallConference conference, CallPeer peer) { this.call = call; this.conference = conference; this.peer = peer; } @Override public void run() { /* * There is only an OperationSet which hangs up a CallPeer at a time * so prepare a list of all CallPeers to be hanged up. */ Set<CallPeer> peers = new HashSet<CallPeer>(); if (call != null) { Iterator<? extends CallPeer> peerIter = call.getCallPeers(); while (peerIter.hasNext()) peers.add(peerIter.next()); } if (conference != null) peers.addAll(conference.getCallPeers()); if (peer != null) peers.add(peer); for (CallPeer peer : peers) { OperationSetBasicTelephony<?> basicTelephony = peer.getProtocolProvider().getOperationSet( OperationSetBasicTelephony.class); try { basicTelephony.hangupCallPeer(peer); } catch (OperationFailedException ofe) { logger.error("Could not hang up: " + peer, ofe); } } } } /** * Creates the enable local video call thread. */ private static class EnableLocalVideoThread extends Thread { private final Call call; private final boolean enable; /** * Creates the enable local video call thread. * * @param call the call, for which to enable/disable * @param enable */ public EnableLocalVideoThread(Call call, boolean enable) { this.call = call; this.enable = enable; } @Override public void run() { OperationSetVideoTelephony videoTelephony = call.getProtocolProvider().getOperationSet( OperationSetVideoTelephony.class); boolean enableSucceeded = false; if (videoTelephony != null) { // First make sure the desktop sharing is disabled. if (enable && isDesktopSharingEnabled(call)) { JFrame frame = DesktopSharingFrame.getFrameForCall(call); if (frame != null) frame.dispose(); } try { videoTelephony.setLocalVideoAllowed(call, enable); enableSucceeded = true; } catch (OperationFailedException ex) { logger.error( "Failed to toggle the streaming of local video.", ex); ResourceManagementService r = GuiActivator.getResources(); String title = r.getI18NString( "service.gui.LOCAL_VIDEO_ERROR_TITLE"); String message = r.getI18NString( "service.gui.LOCAL_VIDEO_ERROR_MESSAGE"); GuiActivator.getAlertUIService().showAlertPopup( title, message, ex); } } // If the operation didn't succeeded for some reason, make sure the // video button doesn't remain selected. if (enable && !enableSucceeded) getActiveCallContainer(call).setVideoButtonSelected(false); } } /** * Puts on hold the given <tt>CallPeer</tt>. */ 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; } @Override public void run() { OperationSetBasicTelephony<?> telephony = callPeer.getProtocolProvider().getOperationSet( OperationSetBasicTelephony.class); try { if (isOnHold) telephony.putOnHold(callPeer); else telephony.putOffHold(callPeer); } catch (OperationFailedException ex) { logger.error( "Failed to put" + callPeer.getAddress() + (isOnHold ? " on hold." : " off hold."), ex); } } } /** * Merges specific existing <tt>Call</tt>s into a specific telephony * conference. */ private static class MergeExistingCalls extends Thread { /** * The telephony conference in which {@link #calls} are to be merged. */ private final CallConference conference; /** * Second call. */ private final Collection<Call> calls; /** * Initializes a new <tt>MergeExistingCalls</tt> instance which is to * merge specific existing <tt>Call</tt>s into a specific telephony * conference. * * @param conference the telephony conference in which the specified * <tt>Call</tt>s are to be merged * @param calls the <tt>Call</tt>s to be merged into the specified * telephony conference */ public MergeExistingCalls( CallConference conference, Collection<Call> calls) { this.conference = conference; this.calls = calls; } /** * Puts off hold the <tt>CallPeer</tt>s of a specific <tt>Call</tt> * which are locally on hold. * * @param call the <tt>Call</tt> which is to have its <tt>CallPeer</tt>s * put off hold */ private void putOffHold(Call call) { Iterator<? extends CallPeer> peers = call.getCallPeers(); OperationSetBasicTelephony<?> telephony = call.getProtocolProvider().getOperationSet( OperationSetBasicTelephony.class); while (peers.hasNext()) { CallPeer callPeer = peers.next(); boolean putOffHold = true; if(callPeer instanceof MediaAwareCallPeer) { putOffHold = ((MediaAwareCallPeer<?,?,?>) callPeer) .getMediaHandler() .isLocallyOnHold(); } if(putOffHold) { try { telephony.putOffHold(callPeer); Thread.sleep(400); } catch(Exception ofe) { logger.error("Failed to put off hold.", ofe); } } } } @Override public void run() { // conference for (Call call : conference.getCalls()) putOffHold(call); // calls if (!calls.isEmpty()) { for(Call call : calls) { if (conference.containsCall(call)) continue; putOffHold(call); /* * Dispose of the CallPanel associated with the Call which * is to be merged. */ closeCallContainerIfNotNecessary(conference, false); call.setConference(conference); } } } } /** * Shows a warning window to warn the user that she's about to start a * desktop sharing session. * * @return <tt>true</tt> if the user has accepted the desktop sharing * session; <tt>false</tt>, otherwise */ private static boolean showDesktopSharingWarning() { Boolean isWarningEnabled = GuiActivator.getConfigurationService().getBoolean( desktopSharingWarningProperty, true); if (isWarningEnabled.booleanValue()) { ResourceManagementService resources = GuiActivator.getResources(); MessageDialog warningDialog = new MessageDialog( null, resources.getI18NString("service.gui.WARNING"), resources.getI18NString( "service.gui.DESKTOP_SHARING_WARNING"), true); switch (warningDialog.showDialog()) { case MessageDialog.OK_RETURN_CODE: return true; case MessageDialog.CANCEL_RETURN_CODE: return false; case MessageDialog.OK_DONT_ASK_CODE: GuiActivator.getConfigurationService().setProperty( desktopSharingWarningProperty, false); return true; } } return true; } /** * Normalizes the phone numbers (if any) in a list of <tt>String</tt> * contact addresses or phone numbers. * * @param callees the list of contact addresses or phone numbers to be * normalized */ private static void normalizePhoneNumbers(String callees[]) { for (int i = 0 ; i < callees.length ; i++) callees[i] = GuiActivator.getPhoneNumberI18nService() .normalize(callees[i]); } /** * Throws a <tt>RuntimeException</tt> if the current thread is not the AWT * event dispatching thread. */ public static void assertIsEventDispatchingThread() { if (!SwingUtilities.isEventDispatchThread()) { throw new RuntimeException( "The methon can be called only on the AWT event dispatching" + " thread."); } } /** * Finds the <tt>CallPanel</tt>, if any, which depicts a specific * <tt>CallConference</tt>. * <p> * <b>Note</b>: The method can be called only on the AWT event dispatching * thread. * </p> * * @param conference the <tt>CallConference</tt> to find the depicting * <tt>CallPanel</tt> of * @return the <tt>CallPanel</tt> which depicts the specified * <tt>CallConference</tt> if such a <tt>CallPanel</tt> exists; otherwise, * <tt>null</tt> * @throws RuntimeException if the method is not called on the AWT event * dispatching thread */ private static CallPanel findCallPanel(CallConference conference) { synchronized (callPanels) { return callPanels.get(conference); } } /** * Notifies {@link #callPanels} about a specific <tt>CallEvent</tt> received * by <tt>CallManager</tt> (because they may need to update their UI, for * example). * <p> * <b>Note</b>: The method can be called only on the AWT event dispatching * thread. * </p> * * @param ev the <tt>CallEvent</tt> received by <tt>CallManager</tt> which * is to be forwarded to <tt>callPanels</tt> for further * <tt>CallPanel</tt>-specific handling * @throws RuntimeException if the method is not called on the AWT event * dispatching thread */ private static void forwardCallEventToCallPanels(CallEvent ev) { assertIsEventDispatchingThread(); CallPanel[] callPanels; synchronized (CallManager.callPanels) { Collection<CallPanel> values = CallManager.callPanels.values(); callPanels = values.toArray(new CallPanel[values.size()]); } for (CallPanel callPanel : callPanels) { try { callPanel.onCallEvent(ev); } catch (Exception ex) { /* * There is no practical reason while the failure of a CallPanel * to handle the CallEvent should cause the other CallPanels to * be left out-of-date. */ logger.error("A CallPanel failed to handle a CallEvent", ex); } } } /** * Creates a call for the supplied operation set. * @param opSetClass the operation set to use to create call. * @param protocolProviderService the protocol provider * @param contact the contact address to call */ static void createCall(Class<? extends OperationSet> opSetClass, ProtocolProviderService protocolProviderService, String contact) { createCall(opSetClass, protocolProviderService, contact, null); } /** * Creates a call for the supplied operation set. * @param opSetClass the operation set to use to create call. * @param protocolProviderService the protocol provider * @param contact the contact address to call * @param uiContact the <tt>MetaContact</tt> we're calling */ static void createCall( Class<? extends OperationSet> opSetClass, ProtocolProviderService protocolProviderService, String contact, UIContactImpl uiContact) { if (opSetClass.equals(OperationSetBasicTelephony.class)) { createCall(protocolProviderService, contact, uiContact); } else if (opSetClass.equals(OperationSetVideoTelephony.class)) { createVideoCall(protocolProviderService, contact, uiContact); } else if (opSetClass.equals(OperationSetDesktopStreaming.class)) { createDesktopSharing( protocolProviderService, contact, uiContact); } } /** * Creates a call for the default contact of the metacontact * * @param metaContact the metacontact that will be called. * @param isVideo if <tt>true</tt> will create video call. * @param isDesktop if <tt>true</tt> will share the desktop. * @param shareRegion if <tt>true</tt> will share a region of the desktop. */ public static void call(MetaContact metaContact, boolean isVideo, boolean isDesktop, boolean shareRegion) { Contact contact = metaContact .getDefaultContact(getOperationSetForCall(isVideo, isDesktop)); call(contact, isVideo, isDesktop, shareRegion); } /** * A particular contact has been selected no options to select * will just call it. * @param contact the contact to call * @param contactResource the specific contact resource * @param isVideo is video enabled * @param isDesktop is desktop sharing enabled * @param shareRegion is sharing the whole desktop or just a region. */ public static void call(Contact contact, ContactResource contactResource, boolean isVideo, boolean isDesktop, boolean shareRegion) { if(isDesktop) { if(shareRegion) { createRegionDesktopSharing( contact.getProtocolProvider(), contact.getAddress(), null); } else createDesktopSharing(contact.getProtocolProvider(), contact.getAddress(), null); } else { new CreateCallThread( contact.getProtocolProvider(), contact, contactResource, isVideo).start(); } } /** * Creates a call to the conference described in * <tt>conferenceDescription</tt> through <tt>protocolProvider</tt> * * @param protocolProvider the protocol provider through which to create * the call * @param conferenceDescription the description of the conference to call * @param chatRoom the chat room associated with the call. */ public static void call(ProtocolProviderService protocolProvider, ConferenceDescription conferenceDescription, ChatRoom chatRoom) { new CreateCallThread(protocolProvider, conferenceDescription, chatRoom) .start(); } /** * A particular contact has been selected no options to select * will just call it. * @param contact the contact to call * @param isVideo is video enabled * @param isDesktop is desktop sharing enabled * @param shareRegion is sharing the whole desktop or just a region. */ public static void call(Contact contact, boolean isVideo, boolean isDesktop, boolean shareRegion) { if(isDesktop) { if(shareRegion) { createRegionDesktopSharing( contact.getProtocolProvider(), contact.getAddress(), null); } else createDesktopSharing(contact.getProtocolProvider(), contact.getAddress(), null); } else { new CreateCallThread( contact.getProtocolProvider(), contact, null, isVideo).start(); } } /** * Calls a phone showing a dialog to choose a provider. * @param phone phone to call * @param isVideo if <tt>true</tt> will create video call. * @param isDesktop if <tt>true</tt> will share the desktop. * @param shareRegion if <tt>true</tt> will share a region of the desktop. */ public static void call(final String phone, boolean isVideo, boolean isDesktop, final boolean shareRegion) { call(phone, null, isVideo, isDesktop, shareRegion); } /** * Calls a phone showing a dialog to choose a provider. * @param phone phone to call * @param uiContact the <tt>UIContactImpl</tt> we're calling * @param isVideo if <tt>true</tt> will create video call. * @param isDesktop if <tt>true</tt> will share the desktop. * @param shareRegion if <tt>true</tt> will share a region of the desktop. */ public static void call(final String phone, final UIContactImpl uiContact, boolean isVideo, boolean isDesktop, final boolean shareRegion) { List<ProtocolProviderService> providers = CallManager.getTelephonyProviders(); if(providers.size() > 1) { ChooseCallAccountDialog chooseAccount = new ChooseCallAccountDialog( phone, getOperationSetForCall(isVideo, isDesktop), providers) { @Override public void callButtonPressed() { if(shareRegion) { createRegionDesktopSharing( getSelectedProvider(), phone, uiContact); } else super.callButtonPressed(); } }; chooseAccount.setUIContact(uiContact); chooseAccount.setVisible(true); } else { createCall(providers.get(0), phone, uiContact); } } /** * Obtain operation set checking the params. * @param isVideo if <tt>true</tt> use OperationSetVideoTelephony. * @param isDesktop if <tt>true</tt> use OperationSetDesktopStreaming. * @return the operation set, default is OperationSetBasicTelephony. */ private static Class<? extends OperationSet> getOperationSetForCall( boolean isVideo, boolean isDesktop) { if(isVideo) { if(isDesktop) return OperationSetDesktopStreaming.class; else return OperationSetVideoTelephony.class; } else return OperationSetBasicTelephony.class; } /** * Call any of the supplied details. * * @param uiContactDetailList the list with details to choose for calling * @param uiContact the <tt>UIContactImpl</tt> to check what is enabled, * available. * @param isVideo if <tt>true</tt> will create video call. * @param isDesktop if <tt>true</tt> will share the desktop. * @param invoker the invoker component * @param location the location where this was invoked. */ public static void call(List<UIContactDetail> uiContactDetailList, UIContactImpl uiContact, boolean isVideo, boolean isDesktop, JComponent invoker, Point location) { call(uiContactDetailList, uiContact, isVideo, isDesktop, invoker, location, true); } /** * Call any of the supplied details. * * @param uiContactDetailList the list with details to choose for calling * @param uiContact the <tt>UIContactImpl</tt> to check what is enabled, * available. * @param isVideo if <tt>true</tt> will create video call. * @param isDesktop if <tt>true</tt> will share the desktop. * @param invoker the invoker component * @param location the location where this was invoked. * @param usePreferredProvider whether to use the <tt>uiContact</tt> * preferredProvider if provided. */ private static void call(List<UIContactDetail> uiContactDetailList, UIContactImpl uiContact, boolean isVideo, boolean isDesktop, JComponent invoker, Point location, boolean usePreferredProvider) { ChooseCallAccountPopupMenu chooseCallAccountPopupMenu = null; Class<? extends OperationSet> opsetClass = getOperationSetForCall(isVideo, isDesktop); UIPhoneUtil contactPhoneUtil = null; if (uiContact != null && uiContact.getDescriptor() instanceof MetaContact) contactPhoneUtil = UIPhoneUtil .getPhoneUtil((MetaContact) uiContact.getDescriptor()); if(contactPhoneUtil != null) { boolean addAdditionalNumbers = false; if(!isVideo || ConfigurationUtils .isRouteVideoAndDesktopUsingPhoneNumberEnabled()) { addAdditionalNumbers = true; } else { if(isVideo && contactPhoneUtil != null) { // lets check is video enabled in additional numbers addAdditionalNumbers = contactPhoneUtil.isVideoCallEnabled() ? isDesktop ? contactPhoneUtil.isDesktopSharingEnabled() : true : false; } } if(addAdditionalNumbers) { uiContactDetailList.addAll( contactPhoneUtil.getAdditionalNumbers()); } } if (uiContactDetailList.size() == 1) { UIContactDetail detail = uiContactDetailList.get(0); ProtocolProviderService preferredProvider = null; if(usePreferredProvider) preferredProvider = detail.getPreferredProtocolProvider(opsetClass); List<ProtocolProviderService> providers = null; String protocolName = null; if (preferredProvider != null) { if (preferredProvider.isRegistered()) { createCall(opsetClass, preferredProvider, detail.getAddress(), uiContact); } // If we have a provider, but it's not registered we try to // obtain all registered providers for the same protocol as the // given preferred provider. else { protocolName = preferredProvider.getProtocolName(); providers = AccountUtils.getRegisteredProviders(protocolName, opsetClass); } } // If we don't have a preferred provider we try to obtain a // preferred protocol name and all registered providers for it. else { protocolName = detail.getPreferredProtocol(opsetClass); if (protocolName != null) providers = AccountUtils.getRegisteredProviders(protocolName, opsetClass); else providers = AccountUtils.getRegisteredProviders(opsetClass); } // If our call didn't succeed, try to call through one of the other // protocol providers obtained above. if (providers != null) { int providersCount = providers.size(); if (providersCount <= 0) { new ErrorDialog(null, GuiActivator.getResources().getI18NString( "service.gui.CALL_FAILED"), GuiActivator.getResources().getI18NString( "service.gui.NO_ONLINE_TELEPHONY_ACCOUNT")) .showDialog(); } else if (providersCount == 1) { createCall( opsetClass, providers.get(0), detail.getAddress(), uiContact); } else if (providersCount > 1) { chooseCallAccountPopupMenu = new ChooseCallAccountPopupMenu( invoker, detail.getAddress(), providers, opsetClass); } } } else if (uiContactDetailList.size() > 1) { chooseCallAccountPopupMenu = new ChooseCallAccountPopupMenu(invoker, uiContactDetailList, opsetClass); } // If the choose dialog is created we're going to show it. if (chooseCallAccountPopupMenu != null) { if (uiContact != null) chooseCallAccountPopupMenu.setUIContact(uiContact); chooseCallAccountPopupMenu.showPopupMenu(location.x, location.y); } } /** * Call the ui contact. * * @param uiContact the contact to call. * @param isVideo if <tt>true</tt> will create video call. * @param isDesktop if <tt>true</tt> will share the desktop. * @param invoker the invoker component * @param location the location where this was invoked. */ public static void call(UIContact uiContact, boolean isVideo, boolean isDesktop, JComponent invoker, Point location) { UIContactImpl uiContactImpl = null; if(uiContact instanceof UIContactImpl) { uiContactImpl = (UIContactImpl) uiContact; } List<UIContactDetail> telephonyContacts = uiContact.getContactDetailsForOperationSet( getOperationSetForCall(isVideo, isDesktop)); boolean ignorePreferredProvider = GuiActivator.getConfigurationService().getBoolean( IGNORE_PREFERRED_PROVIDER_PROP, false); call( telephonyContacts, uiContactImpl, isVideo, isDesktop, invoker, location, !ignorePreferredProvider); } /** * Tries to resolves a peer address into a display name, by reqesting the * <tt>ContactSourceService</tt>s. This function returns only the * first match. * * @param peerAddress The peer address. * @param listener the listener to fire change events for later resolutions * of display name and image, if exist. * @return The corresponding display name, if there is a match. Null * otherwise. */ private static String queryContactSource( String peerAddress, DetailsResolveListener listener) { String displayName = null; if(!StringUtils.isNullOrEmpty(peerAddress)) { ContactSourceSearcher searcher = new ContactSourceSearcher(peerAddress, listener); if(listener == null) { searcher.run(); displayName = searcher.displayName; } else new Thread(searcher, searcher.getClass().getName()).start(); } return displayName; } /** * Runnable that will search for a source contact and when found will * fire events to inform that display name or contact image is found. */ private static class ContactSourceSearcher implements Runnable { private final DetailsResolveListener listener; private final String peerAddress; private String displayName; private byte[] displayImage; private ContactSourceSearcher( String peerAddress, DetailsResolveListener listener) { this.peerAddress = peerAddress; this.listener = listener; } @Override public void run() { Vector<ResolveAddressToDisplayNameContactQueryListener> resolvers = new Vector<ResolveAddressToDisplayNameContactQueryListener> (1, 1); // will strip the @server-address part, as the regular expression // will match it int index = peerAddress.indexOf("@"); String peerUserID = (index > -1) ? peerAddress.substring(0, index) : peerAddress; // searches for the whole number/username or with the @serverpart String quotedPeerUserID = Pattern.quote(peerUserID); Pattern pattern = Pattern.compile( "^(" + quotedPeerUserID + "|" + quotedPeerUserID + "@.*)$"); // Queries all available resolvers for(ContactSourceService css : GuiActivator.getContactSources()) { if(css.getType() != ContactSourceService.SEARCH_TYPE) continue; ContactQuery query; if(css instanceof ExtendedContactSourceService) { // use the pattern method of (ExtendedContactSourceService) query = ((ExtendedContactSourceService)css) .createContactQuery(pattern); } else { query = css.createContactQuery(peerUserID); } if(query == null) continue; resolvers.add( new ResolveAddressToDisplayNameContactQueryListener(query)); query.start(); } long startTime = System.currentTimeMillis(); // The detault timeout is set to 500ms. long timeout = listener == null ? 500 : -1; boolean hasRunningResolver = true; // Loops until we found a valid display name and image, // or waits for timeout if any. while((displayName == null || displayImage == null) && hasRunningResolver && (listener == null || listener.isInterested())) { hasRunningResolver = false; for(int i = 0; i < resolvers.size() && (displayName == null || displayImage == null) && (listener == null || listener.isInterested()); ++i) { ResolveAddressToDisplayNameContactQueryListener resolver = resolvers.get(i); if(!resolver.isRunning()) { if(displayName == null && resolver.isFoundName()) { displayName = resolver.getResolvedName(); // If this is the same result as the peer address, // then that is not what we are looking for. Then, // continue the search. if(displayName.equals(peerAddress)) { displayName = null; } if(listener != null && displayName != null) { // fire listener.displayNameUpdated(displayName); } } if(displayImage == null && resolver.isFoundImage()) { displayImage = resolver.getResolvedImage(); String name = resolver.getResolvedName(); // If this is the same result as the peer address, // then that is not what we are looking for. Then, // continue the search. if(name != null && name.equals(peerAddress)) { displayImage = null; } else if(listener != null && displayImage != null) { // fire listener.imageUpdated(displayImage); } } } else hasRunningResolver = true; } Thread.yield(); if( timeout > 0 && System.currentTimeMillis() - startTime >= timeout) break; } // Free lasting resolvers. for(int i = 0; i < resolvers.size(); ++i) { ResolveAddressToDisplayNameContactQueryListener resolver = resolvers.get(i); if(resolver.isRunning()) { resolver.stop(); } } } } /** * A listener that will be notified for found source contacts details. */ public static interface DetailsResolveListener { /** * When a display name is found. * @param displayName the name that was found. */ public void displayNameUpdated(String displayName); /** * The image that was found. * @param image the image that was found. */ public void imageUpdated(byte[] image); /** * Whether the listener is still interested in the events. * When the window/panel using this resolver listener is closed * will return false; * @return whether the listener is still interested in the events. */ public boolean isInterested(); } }