Package net.java.sip.communicator.impl.gui.main.call

Source Code of net.java.sip.communicator.impl.gui.main.call.CallManager$CreateDesktopSharingThread

/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.gui.main.call;

import java.awt.*;
import java.text.*;
import java.util.*;
import java.util.List;

import javax.swing.*;

import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.customcontrols.*;
import net.java.sip.communicator.impl.gui.main.*;
import net.java.sip.communicator.impl.gui.main.contactlist.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.service.contactlist.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.ServerStoredDetails.FaxDetail;
import net.java.sip.communicator.service.protocol.ServerStoredDetails.GenericDetail;
import net.java.sip.communicator.service.protocol.ServerStoredDetails.MobilePhoneDetail;
import net.java.sip.communicator.service.protocol.ServerStoredDetails.PagerDetail;
import net.java.sip.communicator.service.protocol.ServerStoredDetails.PhoneNumberDetail;
import net.java.sip.communicator.service.protocol.ServerStoredDetails.WorkPhoneDetail;
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.swing.*;
import net.java.sip.communicator.util.swing.transparent.*;

import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.device.*;
import org.jitsi.service.resources.*;

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

    /**
     * The group of notifications dedicated to missed calls.
     */
    private static UINotificationGroup missedCallGroup;

    /**
     * A <tt>CallListener</tt>.
     */
    public static class GuiCallListener
        extends SwingCallListener
    {
        /**
         * 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)
        {
            Call sourceCall = ev.getSourceCall();
            boolean isVideoCall
                = ev.isVideoCall()
                    && ConfigurationManager.hasEnabledVideoFormat(
                            sourceCall.getProtocolProvider());
            final ReceivedCallDialog receivedCallDialog
                = new ReceivedCallDialog(
                        sourceCall,
                        isVideoCall,
                        (CallManager.getInProgressCalls().size() > 0));

            receivedCallDialog.setVisible(true);

            Iterator<? extends CallPeer> peerIter = sourceCall.getCallPeers();

            if(!peerIter.hasNext())
            {
                if (receivedCallDialog.isVisible())
                    receivedCallDialog.setVisible(false);
                return;
            }

            final String peerName = peerIter.next().getDisplayName();
            final long callTime = System.currentTimeMillis();

            sourceCall.addCallChangeListener(new CallChangeAdapter()
            {
                @Override
                public void callStateChanged(final CallChangeEvent ev)
                {
                    if(!SwingUtilities.isEventDispatchThread())
                    {
                        SwingUtilities.invokeLater(
                                new Runnable()
                                {
                                    public void run()
                                    {
                                        callStateChanged(ev);
                                    }
                                });
                        return;
                    }

                    // When the call state changes, we ensure here that the
                    // received call notification dialog is closed.
                    if (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);

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

            /*
             * Notify the existing CallPanels about the CallEvent (in case they
             * need to update their UI, for example).
             */
            forwardCallEventToCallPanels(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)
        {
            Call sourceCall = ev.getSourceCall();

            closeCallContainerIfNotNecessary(sourceCall, true);

            /*
             * Notify the existing CallPanels about the CallEvent (in case
             * they need to update their UI, for example).
             */
            forwardCallEventToCallPanels(ev);
        }

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

    /**
     * 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 first first call
     * @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 callPeer 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 createCallProtocolProviderService 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
     */
    public static void createCallProtocolProviderService protocolProvider,
                                    Contact contact)
    {
        new CreateCallThread(protocolProvider, contact, 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
     */
    public static void createVideoCall(ProtocolProviderService protocolProvider,
                                        Contact contact)
    {
        new CreateCallThread(protocolProvider, contact, 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
     */
    public static void createDesktopSharing(
            ProtocolProviderService protocolProvider,
            String contact)
    {
        // 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,
                    desktopDevices.get(0));
        }
        else if (deviceNumber > 1)
        {
            SelectScreenDialog selectDialog
                = new SelectScreenDialog(desktopDevices);

            selectDialog.setVisible(true);
            if (selectDialog.getSelectedDevice() != null)
                createDesktopSharing(
                        protocolProvider,
                        contact,
                        selectDialog.getSelectedDevice());
        }
    }

    /**
     * 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
     */
    public static void createRegionDesktopSharing(
                                    ProtocolProviderService protocolProvider,
                                    String contact)
    {
        if (showDesktopSharingWarning())
        {
            TransparentFrame frame = DesktopSharingFrame.createTransparentFrame(
                    protocolProvider, contact, 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 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,
                                    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,
                    mediaService.getMediaDeviceForPartialDesktopStreaming(
                        width,
                        height,
                        x,
                        y));
        }
    }

    /**
     * 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 mediaDevice the media device corresponding to the screen to share
     */
    private static void createDesktopSharing(
                                    ProtocolProviderService protocolProvider,
                                    String contact,
                                    MediaDevice mediaDevice)
    {
        new CreateDesktopSharingThread( protocolProvider,
                                        contact,
                                        mediaDevice).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();

            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)
        {
            enableDesktopSharing(
                    call,
                    mediaService.getMediaDeviceForPartialDesktopStreaming(
                        width,
                        height,
                        x,
                        y),
                    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
     */
    private static void enableDesktopSharing(Call call,
                                            MediaDevice mediaDevice,
                                            boolean enable)
    {
        OperationSetDesktopSharingServer desktopOpSet
            = call.getProtocolProvider().getOperationSet(
                    OperationSetDesktopSharingServer.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);
            }
        }

        if (enable && !enableSucceeded)
            getActiveCallContainer(call).setDesktopSharingButtonSelected(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 isDesktopSharingEnabled(Call call)
    {
        OperationSetDesktopSharingServer desktopOpSet
            = call.getProtocolProvider().getOperationSet(
                    OperationSetDesktopSharingServer.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)
    {
        OperationSetDesktopSharingServer desktopOpSet
            = call.getProtocolProvider().getOperationSet(
                    OperationSetDesktopSharingServer.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 enableDesktopRemoteControlCallPeer 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 createCallString 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 createCallString callString,
                                    JComponent c,
                                    CallInterfaceListener l)
    {
        callString = callString.trim();

        // Removes special characters from phone numbers.
        if (ConfigurationManager.isNormalizePhoneNumber())
            callString = PhoneNumberI18nService.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();
    }

    /**
     * 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 call the <tt>Call</tt> which is to have its associated
     * <tt>CallPanel</tt>, if any, closed
     * @param wait <tt>true</tt> to use
     * {@link CallContainer#closeWait(CallPanel)} or <tt>false</tt> to use
     * {@link CallContainer#close(CallPanel)}
     */
    private static void closeCallContainerIfNotNecessary(
            final Call call,
            final boolean wait)
    {
        if (!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(
                    new Runnable()
                    {
                        public void run()
                        {
                            closeCallContainerIfNotNecessary(call, 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.
         */

        CallConference conference = call.getConference();

        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 == conference));
                }
                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 GuiActivator
            .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 GuiActivator
            .getRegisteredProviders(OperationSetTelephonyConferencing.class);
    }

    /**
     * Returns a collection of all currently active calls.
     *
     * @return a collection of all currently active calls
     */
    public static Collection<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.
     *
     * @return a collection of all currently in progress calls.
     */
    public static Collection<Call> getInProgressCalls()
    {
        /* TODO Synchronize the access to the callPanels field. */

        Collection<Call> calls = getActiveCalls();
        List<Call> inProgressCalls = new ArrayList<Call>(calls.size());

        for (Call call : calls)
        {
            if (call.getCallState() == CallState.CALL_IN_PROGRESS)
                inProgressCalls.add(call);
        }
        return inProgressCalls;
    }

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

    /**
     * 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)
        {
            MetaContact metaContact = GuiActivator.getContactListService()
                .findMetaContactByContact(peer.getContact());

            image = metaContact.getAvatar();
        }

        // If the icon is still null we try to get an image from the call
        // peer.
        if ((image == null || image.length == 0)
                && peer.getImage() != null)
            image = peer.getImage();

        return image;
    }

    /**
     * Opens a call transfer dialog to transfer the given <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(org.jitsi.service.protocol.OperationFailedException 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.
     *
     * @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.
     *
     * @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('@');
        String aUserID;
        String aService;

        if (aServiceBegin > -1)
        {
            aUserID = a.substring(0, aServiceBegin);

            int slashIndex = a.indexOf("/");
            if (slashIndex > 0)
                aService = a.substring(aServiceBegin + 1, slashIndex);
            else
                aService = a.substring(aServiceBegin + 1);
        }
        else
        {
            aUserID = a;
            aService = null;
        }

        int bServiceBegin = b.indexOf('@');
        String bUserID;
        String bService;

        if (bServiceBegin > -1)
        {
            bUserID = b.substring(0, bServiceBegin);
            int slashIndex = b.indexOf("/");

            if (slashIndex > 0)
                bService = b.substring(bServiceBegin + 1, slashIndex);
            else
                bService = b.substring(bServiceBegin + 1);
        }
        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
     */
    public static boolean isLocalUser(ConferenceMember conferenceMember)
    {
        String localUserAddress
            = conferenceMember.getConferenceFocusCallPeer()
                .getProtocolProvider().getAccountID().getAccountAddress();

        return CallManager.addressesAreEqual(
            conferenceMember.getAddress(), localUserAddress);
    }

    /**
     * Searches for additional phone numbers found in contact information
     * @return additional phone numbers found in contact information;
     */
    public static List<UIContactDetail> getAdditionalNumbers(
                                                        MetaContact metaContact)
    {
        List<UIContactDetail> telephonyContacts
            = new ArrayList<UIContactDetail>();

        Iterator<Contact> contacts = metaContact.getContacts();

        while(contacts.hasNext())
        {
            Contact contact = contacts.next();
            OperationSetServerStoredContactInfo infoOpSet =
                contact.getProtocolProvider().getOperationSet(
                    OperationSetServerStoredContactInfo.class);
            Iterator<GenericDetail> details;
            ArrayList<String> phones = new ArrayList<String>();

            if(infoOpSet != null)
            {
                details = infoOpSet.getAllDetailsForContact(contact);

                while(details.hasNext())
                {
                    GenericDetail d = details.next();
                    if(d instanceof PhoneNumberDetail &&
                        !(d instanceof PagerDetail) &&
                        !(d instanceof FaxDetail))
                    {
                        PhoneNumberDetail pnd = (PhoneNumberDetail)d;
                        if(pnd.getNumber() != null &&
                            pnd.getNumber().length() > 0)
                        {
                            String localizedType = null;

                            if(d instanceof WorkPhoneDetail)
                            {
                                localizedType =
                                    GuiActivator.getResources().
                                        getI18NString(
                                            "service.gui.WORK_PHONE");
                            }
                            else if(d instanceof MobilePhoneDetail)
                            {
                                localizedType =
                                    GuiActivator.getResources().
                                        getI18NString(
                                            "service.gui.MOBILE_PHONE");
                            }
                            else
                            {
                                localizedType =
                                    GuiActivator.getResources().
                                        getI18NString(
                                            "service.gui.PHONE");
                            }

                            phones.add(pnd.getNumber());

                            UIContactDetail cd =
                                new UIContactDetailImpl(
                                    pnd.getNumber(),
                                    pnd.getNumber() +
                                    " (" + localizedType + ")",
                                    null,
                                    new ArrayList<String>(),
                                    null,
                                    null,
                                    null,
                                    pnd)
                            {
                                public PresenceStatus getPresenceStatus()
                                {
                                    return null;
                                }
                            };
                            telephonyContacts.add(cd);
                        }
                    }
                }
            }
        }

        return telephonyContacts;
    }

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

    /**
     * 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
    {
        private final Contact contact;

        private final ProtocolProviderService protocolProvider;

        private final String stringContact;

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

        public CreateCallThread(
                ProtocolProviderService protocolProvider,
                Contact contact,
                boolean video)
        {
            this(protocolProvider, contact, null, video);
        }

        public CreateCallThread(
                ProtocolProviderService protocolProvider,
                String contact,
                boolean video)
        {
            this(protocolProvider, null, contact, 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
         * @param stringContact
         * @param video <tt>true</tt> if this instance is to create a new video
         * (as opposed to audio-only) <tt>Call</tt>
         */
        private CreateCallThread(
                ProtocolProviderService protocolProvider,
                Contact contact,
                String stringContact,
                boolean video)
        {
            this.protocolProvider = protocolProvider;
            this.contact = contact;
            this.stringContact = stringContact;
            this.video = video;
        }

        @Override
        public void run()
        {
            Contact contact = this.contact;
            String stringContact = this.stringContact;

            if (ConfigurationManager.isNormalizePhoneNumber())
            {
                if (contact != null)
                {
                    stringContact = contact.getAddress();
                    contact = null;
                }

                stringContact = PhoneNumberI18nService.normalize(stringContact);
            }

            try
            {
                if (video)
                {
                    OperationSetVideoTelephony telephony
                        = protocolProvider.getOperationSet(
                                OperationSetVideoTelephony.class);

                    if (telephony != null)
                    {
                        if (contact != null)
                            telephony.createVideoCall(contact);
                        else if (stringContact != null)
                            telephony.createVideoCall(stringContact);
                    }
                }
                else
                {
                    OperationSetBasicTelephony<?> telephony
                        = protocolProvider.getOperationSet(
                                OperationSetBasicTelephony.class);

                    if (telephony != null)
                    {
                        if (contact != null)
                            telephony.createCall(contact);
                        else if (stringContact != null)
                            telephony.createCall(stringContact);
                    }
                }
            }
            catch (Throwable t)
            {
                if (t instanceof ThreadDeath)
                    throw (ThreadDeath) t;

                logger.error("The call could not be created: ", t);
                new ErrorDialog(
                        null,
                        GuiActivator.getResources().getI18NString(
                                "service.gui.ERROR"),
                        t.getMessage(),
                        t)
                    .showDialog();
            }
        }
    }

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

        /**
         * 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 mediaDevice the media device corresponding to the screen we
         * would like to share
         */
        public CreateDesktopSharingThread(
                                    ProtocolProviderService protocolProvider,
                                    String contact,
                                    MediaDevice mediaDevice)
        {
            this.protocolProvider = protocolProvider;
            this.stringContact = contact;
            this.mediaDevice = mediaDevice;
        }

        @Override
        public void run()
        {
            OperationSetDesktopSharingServer desktopSharingOpSet
                = protocolProvider.getOperationSet(
                        OperationSetDesktopSharingServer.class);

            /*
             * XXX If we are here and we just discover that
             * OperationSetDesktopSharingServer 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;

            try
            {
                if (mediaDevice != null)
                {
                    desktopSharingOpSet.createVideoCall(
                            stringContact,
                            mediaDevice);
                }
                else
                    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();
            }
        }
    }

    /**
     * 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 specific 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
    {
        private final Map<ProtocolProviderService, List<String>>
            callees;

        private final Call call;

        public InviteToConferenceCallThread(
                Map<ProtocolProviderService, List<String>> callees,
                Call call)
        {
            this.callees = callees;
            this.call = call;
        }

        @Override
        public void run()
        {
            CallConference conference;

            if (call == null)
            {
                conference = null;

                /*
                 * FIXME Autmagically choose whether the Jitsi VideoBridge
                 * server-side telephony conferencing technology is to be
                 * utilized.
                 */
                if (callees.size() == 1)
                {
                    Iterator<Map.Entry<ProtocolProviderService, List<String>>>
                        iter
                            = callees.entrySet().iterator();
                    Map.Entry<ProtocolProviderService, List<String>>
                        entry
                            = iter.next();
                    ProtocolProviderService pps = entry.getKey();

                    if ((pps != null)
                            && ProtocolNames.JABBER.equals(
                                    pps.getProtocolName()))
                    {
                        Object jitsiVideoBridge = null;

                        try
                        {
                            jitsiVideoBridge
                                = pps
                                    .getClass()
                                        .getMethod("getJitsiVideoBridge")
                                            .invoke(pps);
                        }
                        catch (Throwable t)
                        {
                            if (t instanceof ThreadDeath)
                                throw (ThreadDeath) t;
                            else
                            {
                                logger.error(
                                        "Failed to determine whether Jitsi"
                                            + " VideoBridge is available for "
                                            + pps.getAccountID());
                            }
                        }

                        if ((jitsiVideoBridge instanceof String)
                                && (((String) jitsiVideoBridge).length() != 0))
                        {
                            conference = new MediaAwareCallConference(true);
                        }
                    }
                }
            }
            else
                conference = 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 (ConfigurationManager.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();
                }
            }
        }
    }

    /**
     * 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(peer);
            }
            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 telephony
                = call.getProtocolProvider()
                    .getOperationSet(OperationSetVideoTelephony.class);
            boolean enableSucceeded = false;

            if (telephony != null)
            {
                // First make sure the desktop sharing is disabled.
                if (enable && isDesktopSharingEnabled(call))
                {
                    getActiveCallContainer(call)
                        .setDesktopSharingButtonSelected(false);

                    JFrame frame = DesktopSharingFrame.getFrameForCall(call);

                    if(frame != null)
                        frame.dispose();
                }

                try
                {
                    telephony.setLocalVideoAllowed(call, enable);
                    enableSucceeded = true;
                }
                catch (OperationFailedException ex)
                {
                    logger.error(
                        "Failed to toggle the streaming of local video.",
                        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(call, 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] = PhoneNumberI18nService.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);
            }
        }
    }
}
TOP

Related Classes of net.java.sip.communicator.impl.gui.main.call.CallManager$CreateDesktopSharingThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.