/** @author Symphorien Wanko */ public class PopupMessageHandlerSLick extends TestSuite implements BundleActivator { /** Logger for this class */ private static Logger logger = Logger.getLogger(PopupMessageHandlerSLick.class); /** our bundle context */ protected static BundleContext bundleContext = null; /** implements BundleActivator.start() */ public void start(BundleContext bc) throws Exception { logger.info("starting popup message test "); bundleContext = bc; setName("PopupMessageHandlerSLick"); Hashtable<String, String> properties = new Hashtable<String, String>(); properties.put("service.pid", getName()); // we maybe are running on machine without WM and systray // (test server machine), skip tests if (ServiceUtils.getService(bc, SystrayService.class) != null) { addTest(TestPopupMessageHandler.suite()); } bundleContext.registerService(getClass().getName(), this, properties); } /** implements BundleActivator.stop() */ public void stop(BundleContext bc) throws Exception {} }
/** * The <tt>Resources</tt> class manages the access to the internationalization properties files and * the image resources used in this plugin. * * @author Yana Stamcheva */ public class Resources { private static Logger log = Logger.getLogger(Resources.class); /** The name of the resource, where internationalization strings for this plugin are stored. */ private static final String STRING_RESOURCE_NAME = "resources.languages.plugin.contactinfo.resources"; /** The name of the resource, where paths to images used in this bundle are stored. */ private static final String IMAGE_RESOURCE_NAME = "net.java.sip.communicator.plugin.contactinfo.resources"; /** The string resource bundle. */ private static final ResourceBundle STRING_RESOURCE_BUNDLE = ResourceBundle.getBundle(STRING_RESOURCE_NAME); /** The image resource bundle. */ private static final ResourceBundle IMAGE_RESOURCE_BUNDLE = ResourceBundle.getBundle(IMAGE_RESOURCE_NAME); /** * Returns an internationalized string corresponding to the given key. * * @param key The key of the string. * @return An internationalized string corresponding to the given key. */ public static String getString(String key) { try { return STRING_RESOURCE_BUNDLE.getString(key); } catch (MissingResourceException e) { return '!' + key + '!'; } } /** * Loads an image from a given image identifier. * * @param imageID The identifier of the image. * @return The image for the given identifier. */ public static ImageIcon getImage(String imageID) { BufferedImage image = null; String path = IMAGE_RESOURCE_BUNDLE.getString(imageID); try { image = ImageIO.read(Resources.class.getClassLoader().getResourceAsStream(path)); } catch (IOException e) { log.error("Failed to load image:" + path, e); } return new ImageIcon(image); } }
/** * Bundle adds Android specific notification handlers. * * @author Pawel Domas */ public class NotificationActivator implements BundleActivator { /** The logger */ private final Logger logger = Logger.getLogger(NotificationActivator.class); /** OSGI bundle context. */ protected static BundleContext bundleContext; /** Notification service instance. */ private static NotificationService notificationService; /** Vibrate handler instance. */ private VibrateHandlerImpl vibrateHandler; /** {@inheritDoc} */ public void start(BundleContext bc) throws Exception { bundleContext = bc; try { logger.logEntry(); logger.info("Android notification handler Service...[ STARTED ]"); // Get the notification service implementation ServiceReference notifReference = bundleContext.getServiceReference(NotificationService.class.getName()); notificationService = (NotificationService) bundleContext.getService(notifReference); vibrateHandler = new VibrateHandlerImpl(); notificationService.addActionHandler(vibrateHandler); logger.info("Android notification handler Service...[REGISTERED]"); } finally { logger.logExit(); } } /** {@inheritDoc} */ public void stop(BundleContext bc) throws Exception { notificationService.removeActionHandler(vibrateHandler.getActionType()); logger.info("Android notification handler Service ...[STOPPED]"); } }
/** * Indicates that the security is time-outed, is not supported by the other end. * * @param evt Details about the event that caused this message. */ @Override public void securityTimeout(CallPeerSecurityTimeoutEvent evt) { timer.cancel(); // fail peer, call if (evt.getSource() instanceof AbstractCallPeer) { try { CallPeer peer = (CallPeer) evt.getSource(); OperationSetBasicTelephony<?> telephony = peer.getProtocolProvider().getOperationSet(OperationSetBasicTelephony.class); telephony.hangupCallPeer( peer, OperationSetBasicTelephony.HANGUP_REASON_ENCRYPTION_REQUIRED, "Encryption Required!"); } catch (OperationFailedException ex) { Logger.getLogger(getClass()).error("Failed to hangup peer", ex); } } }
/** * An implementation of the <tt>LogMessageNotificationHandler</tt> interface. * * @author Yana Stamcheva */ public class LogMessageNotificationHandlerImpl implements LogMessageNotificationHandler { /** The logger that will be used to log messages. */ private Logger logger = Logger.getLogger(LogMessageNotificationHandlerImpl.class); /** {@inheritDoc} */ public String getActionType() { return NotificationAction.ACTION_LOG_MESSAGE; } /** * Logs a message through the sip communicator Logger. * * @param action the action to act upon * @param message the message coming from the event */ public void logMessage(LogMessageNotificationAction action, String message) { if (action.getLogType().equals(ERROR_LOG_TYPE)) logger.error(message); else if (action.getLogType().equals(INFO_LOG_TYPE)) logger.info(message); else if (action.getLogType().equals(TRACE_LOG_TYPE)) logger.trace(message); } }
/** * Android implementation of <tt>VerifyCertificateDialogService</tt>. * * @author Pawel Domas */ class CertificateDialogServiceImpl implements VerifyCertificateDialogService { /** The logger. */ private static final Logger logger = Logger.getLogger(CertificateDialogServiceImpl.class); /** * Maps request ids to <tt>VerifyCertDialog</tt> so that they can be retrieved by Android * <tt>Activity</tt> or <tt>Fragments</tt>. */ private Map<Long, VerifyCertDialog> requestMap = new HashMap<Long, VerifyCertDialog>(); /** {@inheritDoc} */ @Override public VerifyCertificateDialog createDialog(Certificate[] certs, String title, String message) { if (title == null) title = JitsiApplication.getResString(R.string.service_gui_CERT_DIALOG_TITLE); Long requestId = System.currentTimeMillis(); VerifyCertDialog verifyCertDialog = new VerifyCertDialog(requestId, certs[0], title, message); requestMap.put(requestId, verifyCertDialog); logger.debug(hashCode() + " creating dialog: " + requestId); return verifyCertDialog; } /** * Retrieves the dialog for given <tt>requestId</tt>. * * @param requestId dialog's request identifier assigned during dialog creation. * @return the dialog for given <tt>requestId</tt>. */ public VerifyCertDialog retrieveDialog(Long requestId) { logger.debug(hashCode() + " getting dialog: " + requestId); return requestMap.get(requestId); } }
/** * A wrapper of media quality control. * * @author Damian Minkov */ public class QualityControlWrapper extends AbstractQualityControlWrapper<CallPeerSipImpl> { /** Our class logger. */ private static final Logger logger = Logger.getLogger(QualityControlWrapper.class); /** * Creates quality control for peer. * * @param peer peer */ QualityControlWrapper(CallPeerSipImpl peer) { super(peer); } /** * Changes the current video settings for the peer with the desired quality settings and inform * the peer to stream the video with those settings. * * @param preset the desired video settings * @throws MediaException when the re-invite fails */ @Override public void setPreferredRemoteSendMaxPreset(QualityPreset preset) throws MediaException { QualityControl qControls = getMediaQualityControl(); if (qControls != null) { qControls.setRemoteSendMaxPreset(preset); try { // re-invites the peer with the new settings peer.sendReInvite(); } catch (Throwable cause) { String message = "Failed to re-invite for video quality change."; logger.error(message, cause); throw new MediaException(message, MediaException.GENERAL_ERROR, cause); } } } }
/** * Activator for the Metacafe source bundle. * * @author Purvesh Sahoo */ public class MetacafeActivator implements BundleActivator { /** The <tt>Logger</tt> used by the <tt>MetacafeActivator</tt> class. */ private static final Logger logger = Logger.getLogger(MetacafeActivator.class); /** The metacafe service registration. */ private ServiceRegistration metacafeServReg = null; /** The source implementation reference. */ private static ReplacementService metacafeSource = null; /** * Starts the Metacafe replacement source bundle * * @param context the <tt>BundleContext</tt> as provided from the OSGi framework * @throws Exception if anything goes wrong */ public void start(BundleContext context) throws Exception { Hashtable<String, String> hashtable = new Hashtable<String, String>(); hashtable.put( ReplacementService.SOURCE_NAME, ReplacementServiceMetacafeImpl.METACAFE_CONFIG_LABEL); metacafeSource = new ReplacementServiceMetacafeImpl(); metacafeServReg = context.registerService(ReplacementService.class.getName(), metacafeSource, hashtable); logger.info("Metacafe source implementation [STARTED]."); } /** * Unregisters the Metacafe replacement service. * * @param context BundleContext * @throws Exception if anything goes wrong */ public void stop(BundleContext context) throws Exception { metacafeServReg.unregister(); logger.info("Metacafe source implementation [STOPPED]."); } }
public class StyledHTMLEditorPane extends JEditorPane { private final Logger logger = Logger.getLogger(StyledHTMLEditorPane.class); private final HTMLDocument document; public StyledHTMLEditorPane() { this.setContentType("text/html"); this.document = (HTMLDocument) this.getDocument(); this.setDocument(document); Constants.loadSimpleStyle(document.getStyleSheet()); } public void appendToEnd(String text) { Element root = document.getDefaultRootElement(); try { document.insertAfterEnd(root.getElement(root.getElementCount() - 1), text); } catch (BadLocationException e) { logger.error("Insert in the HTMLDocument failed.", e); } catch (IOException e) { logger.error("Insert in the HTMLDocument failed.", e); } } public void insertAfterStart(String text) { Element root = this.document.getDefaultRootElement(); try { this.document.insertBeforeStart(root.getElement(0), text); } catch (BadLocationException e) { logger.error("Insert in the HTMLDocument failed.", e); } catch (IOException e) { logger.error("Insert in the HTMLDocument failed.", e); } } }
/** * Implements a <tt>CandidateHarvester</tt> which gathers <tt>Candidate</tt>s for a specified {@link * Component} using Jingle Nodes as defined in XEP 278 "Jingle Relay Nodes". * * @author Sebastien Vincent */ public class JingleNodesHarvester extends AbstractCandidateHarvester { /** * The <tt>Logger</tt> used by the <tt>JingleNodesHarvester</tt> class and its instances for * logging output. */ private static final Logger logger = Logger.getLogger(JingleNodesHarvester.class.getName()); /** XMPP connection. */ private SmackServiceNode serviceNode = null; /** * JingleNodes relay allocate two address/port couple for us. Due to the architecture of Ice4j * that harvest address for each component, we store the second address/port couple. */ private TransportAddress localAddressSecond = null; /** * JingleNodes relay allocate two address/port couple for us. Due to the architecture of Ice4j * that harvest address for each component, we store the second address/port couple. */ private TransportAddress relayedAddressSecond = null; /** * Constructor. * * @param serviceNode the <tt>SmackServiceNode</tt> */ public JingleNodesHarvester(SmackServiceNode serviceNode) { this.serviceNode = serviceNode; } /** * Gathers Jingle Nodes candidates for all host <tt>Candidate</tt>s that are already present in * the specified <tt>component</tt>. This method relies on the specified <tt>component</tt> to * already contain all its host candidates so that it would resolve them. * * @param component the {@link Component} that we'd like to gather candidate Jingle Nodes * <tt>Candidate</tt>s for * @return the <tt>LocalCandidate</tt>s gathered by this <tt>CandidateHarvester</tt> */ @Override public synchronized Collection<LocalCandidate> harvest(Component component) { logger.info("harvest Jingle Nodes"); Collection<LocalCandidate> candidates = new HashSet<LocalCandidate>(); String ip = null; int port = -1; /* if we have already a candidate (RTCP) allocated, get it */ if (localAddressSecond != null && relayedAddressSecond != null) { LocalCandidate candidate = createJingleNodesCandidate(relayedAddressSecond, component, localAddressSecond); // try to add the candidate to the component and then only add it to // the harvest not redundant (not sure how it could be red. but ...) if (component.addLocalCandidate(candidate)) { candidates.add(candidate); } localAddressSecond = null; relayedAddressSecond = null; return candidates; } XMPPConnection conn = serviceNode.getConnection(); JingleChannelIQ ciq = null; if (serviceNode != null) { final TrackerEntry preferred = serviceNode.getPreferedRelay(); if (preferred != null) { ciq = SmackServiceNode.getChannel(conn, preferred.getJid()); } } if (ciq != null) { ip = ciq.getHost(); port = ciq.getRemoteport(); if (logger.isInfoEnabled()) { logger.info( "JN relay: " + ip + " remote port:" + port + " local port: " + ciq.getLocalport()); } if (ip == null || ciq.getRemoteport() == 0) { logger.warn("JN relay ignored because ip was null or port 0"); return candidates; } // Drop the scope or interface name if the relay sends it // along in its IPv6 address. The scope/ifname is only valid on the // host that owns the IP and we don't need it here. int scopeIndex = ip.indexOf('%'); if (scopeIndex > 0) { logger.warn("Dropping scope from assumed IPv6 address " + ip); ip = ip.substring(0, scopeIndex); } /* RTP */ TransportAddress relayedAddress = new TransportAddress(ip, port, Transport.UDP); TransportAddress localAddress = new TransportAddress(ip, ciq.getLocalport(), Transport.UDP); LocalCandidate local = createJingleNodesCandidate(relayedAddress, component, localAddress); /* RTCP */ relayedAddressSecond = new TransportAddress(ip, port + 1, Transport.UDP); localAddressSecond = new TransportAddress(ip, ciq.getLocalport() + 1, Transport.UDP); // try to add the candidate to the component and then only add it to // the harvest not redundant (not sure how it could be red. but ...) if (component.addLocalCandidate(local)) { candidates.add(local); } } return candidates; } /** * Creates a new <tt>JingleNodesRelayedCandidate</tt> instance which is to represent a specific * <tt>TransportAddress</tt>. * * @param transportAddress the <tt>TransportAddress</tt> allocated by the relay * @param component the <tt>Component</tt> for which the candidate will be added * @param localEndPoint <tt>TransportAddress</tt> of the Jingle Nodes relay where we will send our * packet. * @return a new <tt>JingleNodesRelayedCandidate</tt> instance which represents the specified * <tt>TransportAddress</tt> */ protected JingleNodesCandidate createJingleNodesCandidate( TransportAddress transportAddress, Component component, TransportAddress localEndPoint) { JingleNodesCandidate cand = null; try { cand = new JingleNodesCandidate(transportAddress, component, localEndPoint); IceSocketWrapper stunSocket = cand.getStunSocket(null); cand.getStunStack().addSocket(stunSocket); } catch (Throwable e) { logger.debug("Exception occurred when creating JingleNodesCandidate: " + e); } return cand; } }
/** * This implementation of the Network Address Manager allows you to intelligently retrieve the * address of your localhost according to preferences specified in a number of properties like: <br> * net.java.sip.communicator.STUN_SERVER_ADDRESS - the address of the stun server to use for NAT * traversal <br> * net.java.sip.communicator.STUN_SERVER_PORT - the port of the stun server to use for NAT traversal * <br> * java.net.preferIPv6Addresses - a system property specifying weather ipv6 addresses are to be * preferred in address resolution (default is false for backward compatibility) <br> * net.java.sip.communicator.common.PREFERRED_NETWORK_ADDRESS - the address that the user would like * to use. (If this is a valid address it will be returned in getLocalhost() calls) <br> * net.java.sip.communicator.common.PREFERRED_NETWORK_INTERFACE - the network interface that the * user would like to use for fommunication (addresses belonging to that interface will be prefered * when selecting a localhost address) * * @todo further explain the way the service works. explain address selection algorithms and * priorities. * @author Emil Ivov */ public class NetworkAddressManagerServiceImpl implements NetworkAddressManagerService, VetoableChangeListener { private static Logger logger = Logger.getLogger(NetworkAddressManagerServiceImpl.class); /** The name of the property containing the stun server address. */ private static final String PROP_STUN_SERVER_ADDRESS = "net.java.sip.communicator.impl.netaddr.STUN_SERVER_ADDRESS"; /** The port number of the stun server to use for NAT traversal */ private static final String PROP_STUN_SERVER_PORT = "net.java.sip.communicator.impl.netaddr.STUN_SERVER_PORT"; /** A stun4j address resolver */ private SimpleAddressDetector detector = null; /** Specifies whether or not STUN should be used for NAT traversal */ private boolean useStun = false; /** The address of the stun server that we're currently using. */ private StunAddress stunServerAddress = null; /** * The socket that we use for dummy connections during selection of a local address that has to be * used when communicating with a specific location. */ DatagramSocket localHostFinderSocket = null; /** * A random (unused)local port to use when trying to select a local host address to use when * sending messages to a specific destination. */ private static final int RANDOM_ADDR_DISC_PORT = 55721; /** * The prefix used for Dynamic Configuration of IPv4 Link-Local Addresses. <br> * {@link http://ietf.org/rfc/rfc3927.txt} */ private static final String DYNAMIC_CONF_FOR_IPV4_ADDR_PREFIX = "169.254"; /** * The name of the property containing the number of binds that we should should execute in case a * port is already bound to (each retry would be on a new random port). */ public static final String BIND_RETRIES_PROPERTY_NAME = "net.java.sip.communicator.service.netaddr.BIND_RETRIES"; /** Default STUN server address. */ public static final String DEFAULT_STUN_SERVER_ADDRESS = "stun.iptel.org"; /** Default STUN server port. */ public static final int DEFAULT_STUN_SERVER_PORT = 3478; /** * Initializes this network address manager service implementation and starts all * processes/threads associated with this address manager, such as a stun firewall/nat detector, * keep alive threads, binding lifetime discovery threads and etc. The method may also be used * after a call to stop() as a reinitialization technique. */ public void start() { // init stun String stunAddressStr = null; int port = -1; stunAddressStr = NetaddrActivator.getConfigurationService().getString(PROP_STUN_SERVER_ADDRESS); String portStr = NetaddrActivator.getConfigurationService().getString(PROP_STUN_SERVER_PORT); this.localHostFinderSocket = initRandomPortSocket(); if (stunAddressStr == null || portStr == null) { useStun = false; // we use the default stun server address only for chosing a public // route and not for stun queries. stunServerAddress = new StunAddress(DEFAULT_STUN_SERVER_ADDRESS, DEFAULT_STUN_SERVER_PORT); logger.info( "Stun server address(" + stunAddressStr + ")/port(" + portStr + ") not set (or invalid). Disabling STUN."); } else { try { port = Integer.valueOf(portStr).intValue(); } catch (NumberFormatException ex) { logger.error(portStr + " is not a valid port number. " + "Defaulting to 3478", ex); port = 3478; } stunServerAddress = new StunAddress(stunAddressStr, port); detector = new SimpleAddressDetector(stunServerAddress); if (logger.isDebugEnabled()) { logger.debug( "Created a STUN Address detector for the following " + "STUN server: " + stunAddressStr + ":" + port); } detector.start(); logger.debug("STUN server detector started;"); // make sure that someone doesn't set invalid stun address and port NetaddrActivator.getConfigurationService() .addVetoableChangeListener(PROP_STUN_SERVER_ADDRESS, this); NetaddrActivator.getConfigurationService() .addVetoableChangeListener(PROP_STUN_SERVER_PORT, this); // now start a thread query to the stun server and only set the // useStun flag to true if it succeeds. launchStunServerTest(); } } /** * Kills all threads/processes lauched by this thread and prepares it for shutdown. You may use * this method as a reinitialization technique ( you'll have to call start afterwards) */ public void stop() { try { try { detector.shutDown(); } catch (Exception ex) { logger.debug("Failed to properly shutdown a stun detector: " + ex.getMessage()); } detector = null; useStun = false; // remove the listeners NetaddrActivator.getConfigurationService() .removeVetoableChangeListener(PROP_STUN_SERVER_ADDRESS, this); NetaddrActivator.getConfigurationService() .removeVetoableChangeListener(PROP_STUN_SERVER_PORT, this); } finally { logger.logExit(); } } /** * Returns an InetAddress instance that represents the localhost, and that a socket can bind upon * or distribute to peers as a contact address. * * @param intendedDestination the destination that we'd like to use the localhost address with. * @return an InetAddress instance representing the local host, and that a socket can bind upon or * distribute to peers as a contact address. */ public synchronized InetAddress getLocalHost(InetAddress intendedDestination) { // no point in making sure that the localHostFinderSocket is initialized. // better let it through a NullPointerException. InetAddress localHost = null; localHostFinderSocket.connect(intendedDestination, this.RANDOM_ADDR_DISC_PORT); localHost = localHostFinderSocket.getLocalAddress(); localHostFinderSocket.disconnect(); // windows socket implementations return the any address so we need to // find something else here ... InetAddress.getLocalHost seems to work // better on windows so lets hope it'll do the trick. if (localHost.isAnyLocalAddress()) { try { // all that's inside the if is an ugly IPv6 hack // (good ol' IPv6 - always causing more problems than it solves.) if (intendedDestination instanceof Inet6Address) { // return the first globally routable ipv6 address we find // on the machine (and hope it's a good one) Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface iface = (NetworkInterface) interfaces.nextElement(); Enumeration addresses = iface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress address = (InetAddress) addresses.nextElement(); if (address instanceof Inet6Address) { if (!address.isAnyLocalAddress() && !address.isLinkLocalAddress() && !address.isSiteLocalAddress() && !address.isLoopbackAddress()) { return address; } } } } } else localHost = InetAddress.getLocalHost(); /** @todo test on windows for ipv6 cases */ } catch (Exception ex) { // sigh ... ok return 0.0.0.0 logger.warn("Failed to get localhost ", ex); } } return localHost; } /** * The method queries a Stun server for a binding for the specified port. * * @param port the port to resolve (the stun message gets sent trhough that port) * @return StunAddress the address returned by the stun server or null if an error occurred or no * address was returned * @throws IOException if an error occurs while stun4j is using sockets. * @throws BindException if the port is already in use. */ private StunAddress queryStunServer(int port) throws IOException, BindException { StunAddress mappedAddress = null; if (detector != null && useStun) { mappedAddress = detector.getMappingFor(port); if (logger.isDebugEnabled()) logger.debug( "For port:" + port + "a Stun server returned the " + "following mapping [" + mappedAddress); } return mappedAddress; } /** * The method queries a Stun server for a binding for the port and address that <tt>sock</tt> is * bound on. * * @param sock the socket whose port and address we'dlike to resolve (the stun message gets sent * trhough that socket) * @return StunAddress the address returned by the stun server or null if an error occurred or no * address was returned * @throws IOException if an error occurs while stun4j is using sockets. * @throws BindException if the port is already in use. */ private StunAddress queryStunServer(DatagramSocket sock) throws IOException, BindException { StunAddress mappedAddress = null; if (detector != null && useStun) { mappedAddress = detector.getMappingFor(sock); if (logger.isTraceEnabled()) { logger.trace( "For socket with address " + sock.getLocalAddress().getHostAddress() + " and port " + sock.getLocalPort() + " the stun server returned the " + "following mapping [" + mappedAddress + "]"); } } return mappedAddress; } /** * Tries to obtain a mapped/public address for the specified port (possibly by executing a STUN * query). * * @param dst the destination that we'd like to use this address with. * @param port the port whose mapping we are interested in. * @return a public address corresponding to the specified port or null if all attempts to * retrieve such an address have failed. * @throws IOException if an error occurs while stun4j is using sockets. * @throws BindException if the port is already in use. */ public InetSocketAddress getPublicAddressFor(InetAddress dst, int port) throws IOException, BindException { if (!useStun || (dst instanceof Inet6Address)) { logger.debug( "Stun is disabled for destination " + dst + ", skipping mapped address recovery (useStun=" + useStun + ", IPv6@=" + (dst instanceof Inet6Address) + ")."); // we'll still try to bind though so that we could notify the caller // if the port has been taken already. DatagramSocket bindTestSocket = new DatagramSocket(port); bindTestSocket.close(); // if we're here then the port was free. return new InetSocketAddress(getLocalHost(dst), port); } StunAddress mappedAddress = queryStunServer(port); InetSocketAddress result = null; if (mappedAddress != null) result = mappedAddress.getSocketAddress(); else { // Apparently STUN failed. Let's try to temporarily disble it // and use algorithms in getLocalHost(). ... We should probably // eveng think about completely disabling stun, and not only // temporarily. // Bug report - John J. Barton - IBM InetAddress localHost = getLocalHost(dst); result = new InetSocketAddress(localHost, port); } if (logger.isDebugEnabled()) logger.debug("Returning mapping for port:" + port + " as follows: " + result); return result; } /** * Tries to obtain a mapped/public address for the specified port (possibly by executing a STUN * query). * * @param port the port whose mapping we are interested in. * @return a public address corresponding to the specified port or null if all attempts to * retrieve such an address have failed. * @throws IOException if an error occurs while stun4j is using sockets. * @throws BindException if the port is already in use. */ public InetSocketAddress getPublicAddressFor(int port) throws IOException, BindException { return getPublicAddressFor(this.stunServerAddress.getSocketAddress().getAddress(), port); } /** * This method gets called when a bound property is changed. * * @param evt A PropertyChangeEvent object describing the event source and the property that has * changed. */ public void propertyChange(PropertyChangeEvent evt) { // there's no point in implementing this method as we have no way of // knowing whether the current property change event is the only event // we're going to get or whether another one is going to follow.. // in the case of a STUN_SERVER_ADDRESS property change for example // there's no way of knowing whether a STUN_SERVER_PORT property change // will follow or not. // Reinitializaion will therefore only happen if the reinitialize() // method is called. } /** * This method gets called when a property we're interested in is about to change. In case we * don't like the new value we throw a PropertyVetoException to prevent the actual change from * happening. * * @param evt a <tt>PropertyChangeEvent</tt> object describing the event source and the property * that will change. * @exception PropertyVetoException if we don't want the change to happen. */ public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { if (evt.getPropertyName().equals(PROP_STUN_SERVER_ADDRESS)) { // make sure that we have a valid fqdn or ip address. // null or empty port is ok since it implies turning STUN off. if (evt.getNewValue() == null) return; String host = evt.getNewValue().toString(); if (host.trim().length() == 0) return; boolean ipv6Expected = false; if (host.charAt(0) == '[') { // This is supposed to be an IPv6 litteral if (host.length() > 2 && host.charAt(host.length() - 1) == ']') { host = host.substring(1, host.length() - 1); ipv6Expected = true; } else { // This was supposed to be a IPv6 address, but it's not! throw new PropertyVetoException("Invalid address string" + host, evt); } } for (int i = 0; i < host.length(); i++) { char c = host.charAt(i); if (Character.isLetterOrDigit(c)) continue; if ((c != '.' && c != ':') || (c == '.' && ipv6Expected) || (c == ':' && !ipv6Expected)) throw new PropertyVetoException(host + " is not a valid address nor host name", evt); } } // is prop_stun_server_address else if (evt.getPropertyName().equals(PROP_STUN_SERVER_PORT)) { // null or empty port is ok since it implies turning STUN off. if (evt.getNewValue() == null) return; String port = evt.getNewValue().toString(); if (port.trim().length() == 0) return; try { Integer.valueOf(evt.getNewValue().toString()); } catch (NumberFormatException ex) { throw new PropertyVetoException(port + " is not a valid port! " + ex.getMessage(), evt); } } } /** * Initializes and binds a socket that on a random port number. The method would try to bind on a * random port and retry 5 times until a free port is found. * * @return the socket that we have initialized on a randomport number. */ private DatagramSocket initRandomPortSocket() { DatagramSocket resultSocket = null; String bindRetriesStr = NetaddrActivator.getConfigurationService().getString(BIND_RETRIES_PROPERTY_NAME); int bindRetries = 5; if (bindRetriesStr != null) { try { bindRetries = Integer.parseInt(bindRetriesStr); } catch (NumberFormatException ex) { logger.error( bindRetriesStr + " does not appear to be an integer. " + "Defaulting port bind retries to " + bindRetries, ex); } } int currentlyTriedPort = NetworkUtils.getRandomPortNumber(); // we'll first try to bind to a random port. if this fails we'll try // again (bindRetries times in all) until we find a free local port. for (int i = 0; i < bindRetries; i++) { try { resultSocket = new DatagramSocket(currentlyTriedPort); // we succeeded - break so that we don't try to bind again break; } catch (SocketException exc) { if (exc.getMessage().indexOf("Address already in use") == -1) { logger.fatal( "An exception occurred while trying to create" + "a local host discovery socket.", exc); resultSocket = null; return null; } // port seems to be taken. try another one. logger.debug("Port " + currentlyTriedPort + " seems in use."); currentlyTriedPort = NetworkUtils.getRandomPortNumber(); logger.debug("Retrying bind on port " + currentlyTriedPort); } } return resultSocket; } /** * Runs a test query agains the stun server. If it works we set useStun to true, otherwise we set * it to false. */ private void launchStunServerTest() { Thread stunServerTestThread = new Thread("StunServerTestThread") { public void run() { DatagramSocket randomSocket = initRandomPortSocket(); try { StunAddress stunAddress = detector.getMappingFor(randomSocket); randomSocket.disconnect(); if (stunAddress != null) { useStun = true; logger.trace( "StunServer check succeeded for server: " + detector.getServerAddress() + " and local port: " + randomSocket.getLocalPort()); } else { useStun = false; logger.trace( "StunServer check failed for server: " + detector.getServerAddress() + " and local port: " + randomSocket.getLocalPort() + ". No address returned by server."); } } catch (Throwable ex) { logger.error( "Failed to run a stun query against " + "server :" + detector.getServerAddress(), ex); if (randomSocket.isConnected()) randomSocket.disconnect(); useStun = false; } } }; stunServerTestThread.setDaemon(true); stunServerTestThread.start(); } }
/** * Implements a Google Talk <tt>CallPeer</tt>. * * @author Sebastien Vincent * @author Lyubomir Marinov */ public class CallPeerGTalkImpl extends AbstractCallPeerJabberGTalkImpl< CallGTalkImpl, CallPeerMediaHandlerGTalkImpl, SessionIQ> { /** * The <tt>Logger</tt> used by the <tt>CallPeerGTalkImpl</tt> class and its instances for logging * output. */ private static final Logger logger = Logger.getLogger(CallPeerGTalkImpl.class); /** * Returns whether or not the <tt>CallPeer</tt> is an Android phone or a call pass throught Google * Voice or uses Google Talk client. * * <p>We base the detection of the JID's resource which in the case of Android is * android/Vtok/Talk.vXXXXXXX (where XXXXXX is a suite of numbers/letters). */ private static boolean isAndroidOrVtokOrTalkClient(String fullJID) { int idx = fullJID.indexOf('/'); if (idx != -1) { String res = fullJID.substring(idx + 1); if (res.startsWith("android") || res.startsWith("Vtok") || res.startsWith("Talk.v")) { return true; } } if (fullJID.contains("@" + ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) return true; return false; } /** * Temporary variable for handling client like FreeSwitch that sends "accept" message before * sending candidates. */ private SessionIQ sessAcceptedWithNoCands = null; /** Session ID. */ private String sid = null; /** * Creates a new call peer with address <tt>peerAddress</tt>. * * @param peerAddress the Google Talk address of the new call peer. * @param owningCall the call that contains this call peer. */ public CallPeerGTalkImpl(String peerAddress, CallGTalkImpl owningCall) { super(peerAddress, owningCall); setMediaHandler(new CallPeerMediaHandlerGTalkImpl(this)); } /** * Indicates a user request to answer an incoming call from this <tt>CallPeer</tt>. * * <p>Sends an OK response to <tt>callPeer</tt>. Make sure that the call peer contains an SDP * description when you call this method. * * @throws OperationFailedException if we fail to create or send the response. */ public synchronized void answer() throws OperationFailedException { RtpDescriptionPacketExtension answer = null; try { getMediaHandler().getTransportManager().wrapupConnectivityEstablishment(); answer = getMediaHandler().generateSessionAccept(true); } catch (IllegalArgumentException e) { sessAcceptedWithNoCands = new SessionIQ(); // HACK apparently FreeSwitch need to have accept before answer = getMediaHandler().generateSessionAccept(false); SessionIQ response = GTalkPacketFactory.createSessionAccept( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), getSID(), answer); getProtocolProvider().getConnection().sendPacket(response); return; } catch (Exception exc) { logger.info("Failed to answer an incoming call", exc); // send an error response String reasonText = "Error: " + exc.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), Reason.FAILED_APPLICATION, reasonText); setState(CallPeerState.FAILED, reasonText); getProtocolProvider().getConnection().sendPacket(errResp); return; } SessionIQ response = GTalkPacketFactory.createSessionAccept( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), getSID(), answer); // send the packet first and start the stream later in case the media // relay needs to see it before letting hole punching techniques through. if (sessAcceptedWithNoCands == null) getProtocolProvider().getConnection().sendPacket(response); try { getMediaHandler().start(); } catch (UndeclaredThrowableException e) { Throwable exc = e.getUndeclaredThrowable(); logger.info("Failed to establish a connection", exc); // send an error response String reasonText = "Error: " + exc.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), Reason.GENERAL_ERROR, reasonText); getMediaHandler().getTransportManager().close(); setState(CallPeerState.FAILED, reasonText); getProtocolProvider().getConnection().sendPacket(errResp); return; } // tell everyone we are connecting so that the audio notifications would // stop setState(CallPeerState.CONNECTED); } /** * Returns the session ID of the Jingle session associated with this call. * * @return the session ID of the Jingle session associated with this call. */ @Override public String getSID() { return sessionInitIQ != null ? sessionInitIQ.getID() : sid; } /** * Ends the call with for this <tt>CallPeer</tt>. Depending on the state of the peer the method * would send a CANCEL, BYE, or BUSY_HERE message and set the new state to DISCONNECTED. * * @param failed indicates if the hangup is following to a call failure or simply a disconnect * @param reasonText the text, if any, to be set on the <tt>ReasonPacketExtension</tt> as the * value of its * @param reasonOtherExtension the <tt>PacketExtension</tt>, if any, to be set on the * <tt>ReasonPacketExtension</tt> as the value of its <tt>otherExtension</tt> property */ public void hangup(boolean failed, String reasonText, PacketExtension reasonOtherExtension) { // do nothing if the call is already ended if (CallPeerState.DISCONNECTED.equals(getState()) || CallPeerState.FAILED.equals(getState())) { if (logger.isDebugEnabled()) logger.debug("Ignoring a request to hangup a call peer " + "that is already DISCONNECTED"); return; } CallPeerState prevPeerState = getState(); getMediaHandler().getTransportManager().close(); if (failed) setState(CallPeerState.FAILED, reasonText); else setState(CallPeerState.DISCONNECTED, reasonText); SessionIQ responseIQ = null; if (prevPeerState.equals(CallPeerState.CONNECTED) || CallPeerState.isOnHold(prevPeerState)) { responseIQ = GTalkPacketFactory.createBye(getProtocolProvider().getOurJID(), peerJID, getSID()); responseIQ.setInitiator(isInitiator() ? getAddress() : getProtocolProvider().getOurJID()); } else if (CallPeerState.CONNECTING.equals(prevPeerState) || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState) || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState)) { responseIQ = GTalkPacketFactory.createCancel(getProtocolProvider().getOurJID(), peerJID, getSID()); responseIQ.setInitiator(isInitiator() ? getAddress() : getProtocolProvider().getOurJID()); } else if (prevPeerState.equals(CallPeerState.INCOMING_CALL)) { responseIQ = GTalkPacketFactory.createBusy(getProtocolProvider().getOurJID(), peerJID, getSID()); responseIQ.setInitiator(isInitiator() ? getAddress() : getProtocolProvider().getOurJID()); } else if (prevPeerState.equals(CallPeerState.BUSY) || prevPeerState.equals(CallPeerState.FAILED)) { // For FAILED and BUSY we only need to update CALL_STATUS // as everything else has been done already. } else { logger.info("Could not determine call peer state!"); } if (responseIQ != null) { if (reasonOtherExtension != null) { ReasonPacketExtension reason = (ReasonPacketExtension) responseIQ.getExtension( ReasonPacketExtension.ELEMENT_NAME, ReasonPacketExtension.NAMESPACE); if (reason == null) { if (reasonOtherExtension instanceof ReasonPacketExtension) { responseIQ.setReason((ReasonPacketExtension) reasonOtherExtension); } } else reason.setOtherExtension(reasonOtherExtension); } getProtocolProvider().getConnection().sendPacket(responseIQ); } } /** * Initiate a Google Talk session {@link SessionIQ}. * * @param sessionInitiateExtensions a collection of additional and optional * <tt>PacketExtension</tt>s to be added to the <tt>initiate</tt> {@link SessionIQ} which is * to initiate the session with this <tt>CallPeerGTalkImpl</tt> * @throws OperationFailedException exception */ protected synchronized void initiateSession(Iterable<PacketExtension> sessionInitiateExtensions) throws OperationFailedException { sid = SessionIQ.generateSID(); initiator = false; // Create the media description that we'd like to send to the other side. RtpDescriptionPacketExtension offer = getMediaHandler().createDescription(); ProtocolProviderServiceJabberImpl protocolProvider = getProtocolProvider(); sessionInitIQ = GTalkPacketFactory.createSessionInitiate( protocolProvider.getOurJID(), this.peerJID, sid, offer); if (sessionInitiateExtensions != null) { for (PacketExtension sessionInitiateExtension : sessionInitiateExtensions) { sessionInitIQ.addExtension(sessionInitiateExtension); } } protocolProvider.getConnection().sendPacket(sessionInitIQ); // for Google Voice JID without resource we do not harvest and send // candidates if (getAddress().endsWith(ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) { return; } getMediaHandler() .harvestCandidates( offer.getPayloadTypes(), new CandidatesSender() { public void sendCandidates(Iterable<GTalkCandidatePacketExtension> candidates) { CallPeerGTalkImpl.this.sendCandidates(candidates); } }); } /** * Process candidates received. * * @param sessionInitIQ The {@link SessionIQ} that created the session we are handling here */ public void processCandidates(SessionIQ sessionInitIQ) { Collection<PacketExtension> extensions = sessionInitIQ.getExtensions(); List<GTalkCandidatePacketExtension> candidates = new ArrayList<GTalkCandidatePacketExtension>(); for (PacketExtension ext : extensions) { if (ext.getElementName().equalsIgnoreCase(GTalkCandidatePacketExtension.ELEMENT_NAME)) { GTalkCandidatePacketExtension cand = (GTalkCandidatePacketExtension) ext; candidates.add(cand); } } try { getMediaHandler().processCandidates(candidates); } catch (OperationFailedException ofe) { logger.warn("Failed to process an incoming candidates", ofe); // send an error response String reasonText = "Error: " + ofe.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), Reason.GENERAL_ERROR, reasonText); getMediaHandler().getTransportManager().close(); setState(CallPeerState.FAILED, reasonText); getProtocolProvider().getConnection().sendPacket(errResp); return; } // HACK for FreeSwitch that send accept message before sending // candidates if (sessAcceptedWithNoCands != null) { if (isInitiator()) { try { answer(); } catch (OperationFailedException e) { logger.info("Failed to answer call (FreeSwitch hack)"); } } else { final SessionIQ sess = sessAcceptedWithNoCands; sessAcceptedWithNoCands = null; // run in another thread to not block smack receive thread and // possibly delay others candidates messages. new Thread() { @Override public void run() { processSessionAccept(sess); } }.start(); } sessAcceptedWithNoCands = null; } } /** * Processes the session initiation {@link SessionIQ} that we were created with, passing its * content to the media handler. * * @param sessionInitIQ The {@link SessionIQ} that created the session that we are handling here. */ public void processSessionAccept(SessionIQ sessionInitIQ) { this.sessionInitIQ = sessionInitIQ; CallPeerMediaHandlerGTalkImpl mediaHandler = getMediaHandler(); Collection<PacketExtension> extensions = sessionInitIQ.getExtensions(); RtpDescriptionPacketExtension answer = null; for (PacketExtension ext : extensions) { if (ext.getElementName().equalsIgnoreCase(RtpDescriptionPacketExtension.ELEMENT_NAME)) { answer = (RtpDescriptionPacketExtension) ext; break; } } try { mediaHandler.getTransportManager().wrapupConnectivityEstablishment(); mediaHandler.processAnswer(answer); } catch (IllegalArgumentException e) { // HACK for FreeSwitch that send accept message before sending // candidates sessAcceptedWithNoCands = sessionInitIQ; return; } catch (Exception exc) { if (logger.isInfoEnabled()) logger.info("Failed to process a session-accept", exc); // send an error response String reasonText = "Error: " + exc.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), Reason.GENERAL_ERROR, reasonText); getMediaHandler().getTransportManager().close(); setState(CallPeerState.FAILED, reasonText); getProtocolProvider().getConnection().sendPacket(errResp); return; } // tell everyone we are connecting so that the audio notifications would // stop setState(CallPeerState.CONNECTED); mediaHandler.start(); } /** * Processes the session initiation {@link SessionIQ} that we were created with, passing its * content to the media handler and then sends either a "session-info/ringing" or a "terminate" * response. * * @param sessionInitIQ The {@link SessionIQ} that created the session that we are handling here. */ protected synchronized void processSessionInitiate(SessionIQ sessionInitIQ) { // Do initiate the session. this.sessionInitIQ = sessionInitIQ; this.initiator = true; RtpDescriptionPacketExtension description = null; for (PacketExtension ext : sessionInitIQ.getExtensions()) { if (ext.getElementName().equals(RtpDescriptionPacketExtension.ELEMENT_NAME)) { description = (RtpDescriptionPacketExtension) ext; break; } } if (description == null) { logger.info("No description in incoming session initiate"); // send an error response; String reasonText = "Error: no description"; SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), Reason.INCOMPATIBLE_PARAMETERS, reasonText); setState(CallPeerState.FAILED, reasonText); getProtocolProvider().getConnection().sendPacket(errResp); return; } try { getMediaHandler().processOffer(description); } catch (Exception ex) { logger.info("Failed to process an incoming session initiate", ex); // send an error response; String reasonText = "Error: " + ex.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), Reason.INCOMPATIBLE_PARAMETERS, reasonText); setState(CallPeerState.FAILED, reasonText); getProtocolProvider().getConnection().sendPacket(errResp); return; } // If we do not get the info about the remote peer yet. Get it right // now. if (this.getDiscoveryInfo() == null) { String calleeURI = sessionInitIQ.getFrom(); retrieveDiscoveryInfo(calleeURI); } } /** * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a reason to the user, if * there is one. * * @param sessionIQ the {@link SessionIQ} that's terminating our session. */ public void processSessionReject(SessionIQ sessionIQ) { processSessionTerminate(sessionIQ); } /** * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a reason to the user, if * there is one. * * @param sessionIQ the {@link SessionIQ} that's terminating our session. */ public void processSessionTerminate(SessionIQ sessionIQ) { String reasonStr = "Call ended by remote side."; ReasonPacketExtension reasonExt = sessionIQ.getReason(); if (reasonStr != null && reasonExt != null) { Reason reason = reasonExt.getReason(); if (reason != null) reasonStr += " Reason: " + reason.toString() + "."; String text = reasonExt.getText(); if (text != null) reasonStr += " " + text; } getMediaHandler().getTransportManager().close(); setState(CallPeerState.DISCONNECTED, reasonStr); } /** * Sends local candidate addresses from the local peer to the remote peer using the * <tt>candidates</tt> {@link SessionIQ}. * * @param candidates the local candidate addresses to be sent from the local peer to the remote * peer using the <tt>candidates</tt> {@link SessionIQ} */ protected void sendCandidates(Iterable<GTalkCandidatePacketExtension> candidates) { ProtocolProviderServiceJabberImpl protocolProvider = getProtocolProvider(); SessionIQ candidatesIQ = new SessionIQ(); candidatesIQ.setGTalkType(GTalkType.CANDIDATES); candidatesIQ.setFrom(protocolProvider.getOurJID()); candidatesIQ.setInitiator(isInitiator() ? getAddress() : protocolProvider.getOurJID()); candidatesIQ.setID(getSID()); candidatesIQ.setTo(getAddress()); candidatesIQ.setType(IQ.Type.SET); for (GTalkCandidatePacketExtension candidate : candidates) { // Android phone and Google Talk client does not seems to like IPv6 // candidates since it reject the IQ candidates with an error // so do not send IPv6 candidates to Android phone or Talk client if (isAndroidOrVtokOrTalkClient(getAddress()) && NetworkUtils.isIPv6Address(candidate.getAddress())) continue; candidatesIQ.addExtension(candidate); } protocolProvider.getConnection().sendPacket(candidatesIQ); } /** {@inheritDoc} */ public String getEntity() { return getAddress(); } /** * {@inheritDoc} * * <p>Uses the direction of the media stream as a fallback. TODO: return the direction of the * GTalk session? */ @Override public MediaDirection getDirection(MediaType mediaType) { MediaStream stream = getMediaHandler().getStream(mediaType); if (stream != null) { MediaDirection direction = stream.getDirection(); return direction == null ? MediaDirection.INACTIVE : direction; } return MediaDirection.INACTIVE; } }
/** * The <tt>NewStatusMessageDialog</tt> is the dialog containing the form for changing the status * message for a protocol provider. * * @author Yana Stamcheva * @author Adam Netocny */ public class NewStatusMessageDialog extends SIPCommDialog implements ActionListener, Skinnable { /** The Object used for logging. */ private final Logger logger = Logger.getLogger(NewStatusMessageDialog.class); /** The field, containing the status message. */ private final JTextField messageTextField = new JTextField(); /** The button, used to cancel this dialog. */ private final JButton cancelButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL")); /** The presence operation set through which we change the status message. */ private final OperationSetPresence presenceOpSet; /** Message panel. */ private JPanel messagePanel; /** * Creates an instance of <tt>NewStatusMessageDialog</tt>. * * @param protocolProvider the <tt>ProtocolProviderService</tt>. */ public NewStatusMessageDialog(ProtocolProviderService protocolProvider) { presenceOpSet = (OperationSetPersistentPresence) protocolProvider.getOperationSet(OperationSetPresence.class); this.init(); pack(); } /** Initializes the <tt>NewStatusMessageDialog</tt> by adding the buttons, fields, etc. */ private void init() { JLabel messageLabel = new JLabel(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE")); JPanel dataPanel = new TransparentPanel(new BorderLayout(5, 5)); JTextArea infoArea = new JTextArea(GuiActivator.getResources().getI18NString("service.gui.STATUS_MESSAGE_INFO")); JLabel infoTitleLabel = new JLabel(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE")); JPanel labelsPanel = new TransparentPanel(new GridLayout(0, 1)); JButton okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK")); JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); this.setTitle(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE")); this.getRootPane().setDefaultButton(okButton); this.setPreferredSize(new Dimension(500, 200)); infoArea.setEditable(false); infoArea.setLineWrap(true); infoArea.setWrapStyleWord(true); infoArea.setOpaque(false); dataPanel.add(messageLabel, BorderLayout.WEST); messageTextField.setText(presenceOpSet.getCurrentStatusMessage()); dataPanel.add(messageTextField, BorderLayout.CENTER); infoTitleLabel.setHorizontalAlignment(JLabel.CENTER); infoTitleLabel.setFont(infoTitleLabel.getFont().deriveFont(Font.BOLD, 18.0f)); labelsPanel.add(infoTitleLabel); labelsPanel.add(infoArea); labelsPanel.add(dataPanel); messagePanel = new TransparentPanel(new GridBagLayout()); GridBagConstraints messagePanelConstraints = new GridBagConstraints(); messagePanelConstraints.anchor = GridBagConstraints.NORTHWEST; messagePanelConstraints.fill = GridBagConstraints.NONE; messagePanelConstraints.gridx = 0; messagePanelConstraints.gridy = 0; messagePanelConstraints.insets = new Insets(5, 0, 5, 10); messagePanelConstraints.weightx = 0; messagePanelConstraints.weighty = 0; messagePanel.add( new ImageCanvas(ImageLoader.getImage(ImageLoader.RENAME_DIALOG_ICON)), messagePanelConstraints); messagePanelConstraints.anchor = GridBagConstraints.NORTH; messagePanelConstraints.fill = GridBagConstraints.HORIZONTAL; messagePanelConstraints.gridx = 1; messagePanelConstraints.insets = new Insets(0, 0, 0, 0); messagePanelConstraints.weightx = 1; messagePanel.add(labelsPanel, messagePanelConstraints); okButton.setName("ok"); cancelButton.setName("cancel"); okButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic("service.gui.OK")); cancelButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic("service.gui.CANCEL")); okButton.addActionListener(this); cancelButton.addActionListener(this); buttonsPanel.add(okButton); buttonsPanel.add(cancelButton); JPanel mainPanel = new TransparentPanel(new GridBagLayout()); mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 10)); GridBagConstraints mainPanelConstraints = new GridBagConstraints(); mainPanelConstraints.anchor = GridBagConstraints.NORTH; mainPanelConstraints.fill = GridBagConstraints.BOTH; mainPanelConstraints.gridx = 0; mainPanelConstraints.gridy = 0; mainPanelConstraints.weightx = 1; mainPanelConstraints.weighty = 1; mainPanel.add(messagePanel, mainPanelConstraints); mainPanelConstraints.anchor = GridBagConstraints.SOUTHEAST; mainPanelConstraints.fill = GridBagConstraints.NONE; mainPanelConstraints.gridy = 1; mainPanelConstraints.weightx = 0; mainPanelConstraints.weighty = 0; mainPanel.add(buttonsPanel, mainPanelConstraints); this.getContentPane().add(mainPanel); } /** * Handles the <tt>ActionEvent</tt>. In order to change the status message with the new one calls * the <tt>PublishStatusMessageThread</tt>. * * @param e the event that notified us of the action */ public void actionPerformed(ActionEvent e) { JButton button = (JButton) e.getSource(); String name = button.getName(); if (name.equals("ok")) { new PublishStatusMessageThread(messageTextField.getText()).start(); } this.dispose(); } /** Requests the focus in the text field contained in this dialog. */ public void requestFocusInField() { this.messageTextField.requestFocus(); } /** This class allow to use a thread to change the presence status message. */ private class PublishStatusMessageThread extends Thread { private String message; private PresenceStatus currentStatus; public PublishStatusMessageThread(String message) { this.message = message; this.currentStatus = presenceOpSet.getPresenceStatus(); } public void run() { try { presenceOpSet.publishPresenceStatus(currentStatus, message); } catch (IllegalArgumentException e1) { logger.error("Error - changing status", e1); } catch (IllegalStateException e1) { logger.error("Error - changing status", e1); } catch (OperationFailedException e1) { if (e1.getErrorCode() == OperationFailedException.GENERAL_ERROR) { logger.error("General error occured while " + "publishing presence status.", e1); } else if (e1.getErrorCode() == OperationFailedException.NETWORK_FAILURE) { logger.error("Network failure occured while " + "publishing presence status.", e1); } else if (e1.getErrorCode() == OperationFailedException.PROVIDER_NOT_REGISTERED) { logger.error("Protocol provider must be" + "registered in order to change status.", e1); } } } } /** * Artificially clicks the cancel button when this panel is escaped. * * @param isEscaped indicates if this dialog is closed by the Esc shortcut */ protected void close(boolean isEscaped) { if (isEscaped) cancelButton.doClick(); } /** Reloads icon. */ public void loadSkin() { if (messagePanel != null) { for (Component component : messagePanel.getComponents()) { if (component instanceof ImageCanvas) { ImageCanvas cmp = (ImageCanvas) component; cmp.setImage(ImageLoader.getImage(ImageLoader.RENAME_DIALOG_ICON)); } } } } }
/** * Tests in this class verify whether a precreated contact list is still there and whether it * creating contact groups works as expected. * * @author Emil Ivov */ public class TestOperationSetPersistentPresence extends TestCase { private static final Logger logger = Logger.getLogger(TestOperationSetPersistentPresence.class); private GibberishSlickFixture fixture = new GibberishSlickFixture(); private OperationSetPersistentPresence opSetPersPresence1 = null; private OperationSetPersistentPresence opSetPersPresence2 = null; private static final String testGroupName = "NewGroup"; private static final String testGroupName2 = "Renamed"; public TestOperationSetPersistentPresence(String name) { super(name); } /** * Creates a test suite containing all tests of this class followed by test methods that we want * executed in a specified order. * * @return the Test suite to run */ public static Test suite() { TestSuite suite = new TestSuite(); // the following 2 need to be run in the specified order. // (postTestRemoveGroup() needs the group created from // postTestCreateGroup() ) suite.addTest(new TestOperationSetPersistentPresence("postTestCreateGroup")); // rename suite.addTest(new TestOperationSetPersistentPresence("postTestRenameGroup")); suite.addTest(new TestOperationSetPersistentPresence("postTestRemoveGroup")); // create the contact list suite.addTest(new TestOperationSetPersistentPresence("prepareContactList")); suite.addTestSuite(TestOperationSetPersistentPresence.class); return suite; } @Override protected void setUp() throws Exception { super.setUp(); fixture.setUp(); Map<String, OperationSet> supportedOperationSets1 = fixture.provider1.getSupportedOperationSets(); if (supportedOperationSets1 == null || supportedOperationSets1.size() < 1) throw new NullPointerException( "No OperationSet implementations are supported by " + "this Gibberish implementation. "); // get the operation set presence here. opSetPersPresence1 = (OperationSetPersistentPresence) supportedOperationSets1.get(OperationSetPersistentPresence.class.getName()); // if still null then the implementation doesn't offer a presence // operation set which is unacceptable for gibberish. if (opSetPersPresence1 == null) throw new NullPointerException( "An implementation of the gibberish service must provide an " + "implementation of at least the one of the Presence " + "Operation Sets"); // lets do it once again for the second provider Map<String, OperationSet> supportedOperationSets2 = fixture.provider2.getSupportedOperationSets(); if (supportedOperationSets2 == null || supportedOperationSets2.size() < 1) throw new NullPointerException( "No OperationSet implementations are supported by " + "this Gibberish implementation. "); // get the operation set presence here. opSetPersPresence2 = (OperationSetPersistentPresence) supportedOperationSets2.get(OperationSetPersistentPresence.class.getName()); // if still null then the implementation doesn't offer a presence // operation set which is unacceptable for Gibberish. if (opSetPersPresence2 == null) throw new NullPointerException( "An implementation of the Gibberish service must provide an " + "implementation of at least the one of the Presence " + "Operation Sets"); } @Override protected void tearDown() throws Exception { fixture.tearDown(); super.tearDown(); } /** * Retrieves a server stored contact list and checks whether it contains all contacts that have * been added there during the initialization phase by the testerAgent. */ public void testRetrievingServerStoredContactList() { ContactGroup rootGroup = opSetPersPresence1.getServerStoredContactListRoot(); logger.debug("=========== Server Stored Contact List ================="); logger.debug( "rootGroup=" + rootGroup.getGroupName() + " rootGroup.childContacts=" + rootGroup.countContacts() + "rootGroup.childGroups=" + rootGroup.countSubgroups() + "Printing rootGroupContents=\n" + rootGroup.toString()); Hashtable<String, List<String>> expectedContactList = GibberishSlickFixture.preInstalledBuddyList; logger.debug("============== Expected Contact List ==================="); logger.debug(expectedContactList); // Go through the contact list retrieved by the persistence presence set // and remove the name of every contact and group that we find there from // the expected contct list hashtable. Iterator<ContactGroup> groups = rootGroup.subgroups(); while (groups.hasNext()) { ContactGroup group = groups.next(); List<String> expectedContactsInGroup = expectedContactList.get(group.getGroupName()); // When sending the offline message // the sever creates a group NotInContactList, // because the buddy we are sending message to is not in // the contactlist. So this group must be ignored if (!group.getGroupName().equals("NotInContactList")) { assertNotNull( "Group " + group.getGroupName() + " was returned by " + "the server but was not in the expected contact list.", expectedContactsInGroup); Iterator<Contact> contactsIter = group.contacts(); while (contactsIter.hasNext()) { String contactID = contactsIter.next().getAddress(); expectedContactsInGroup.remove(contactID); } // If we've removed all the sub contacts, remove the group too. if (expectedContactsInGroup.size() == 0) expectedContactList.remove(group.getGroupName()); } } // whatever we now have in the expected contact list snapshot are groups, // that have been added by the testerAgent but that were not retrieved // by the persistent presence operation set. assertTrue( "The following contacts were on the server sidec contact " + "list, but were not returned by the pers. pres. op. set" + expectedContactList.toString(), expectedContactList.isEmpty()); } /** * Creates a group in the server stored contact list, makes sure that the corresponding event has * been generated and verifies that the group is in the list. * * @throws java.lang.Exception */ public void postTestCreateGroup() throws Exception { // first clear the list fixture.clearProvidersLists(); Object o = new Object(); synchronized (o) { o.wait(3000); } logger.trace("testing creation of server stored groups"); // first add a listener GroupChangeCollector groupChangeCollector = new GroupChangeCollector(); opSetPersPresence1.addServerStoredGroupChangeListener(groupChangeCollector); // create the group opSetPersPresence1.createServerStoredContactGroup( opSetPersPresence1.getServerStoredContactListRoot(), testGroupName); groupChangeCollector.waitForEvent(10000); opSetPersPresence1.removeServerStoredGroupChangeListener(groupChangeCollector); // check whether we got group created event assertEquals("Collected Group Change events: ", 1, groupChangeCollector.collectedEvents.size()); assertEquals( "Group name.", testGroupName, ((ServerStoredGroupEvent) groupChangeCollector.collectedEvents.get(0)) .getSourceGroup() .getGroupName()); // check whether the group is retrievable ContactGroup group = opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName); assertNotNull("A newly created group was not in the contact list.", group); assertEquals("New group name", testGroupName, group.getGroupName()); // when opearting with groups . the group must have entries // so changes to take effect. Otherwise group will be lost after loggingout try { opSetPersPresence1.subscribe(group, fixture.userID2); synchronized (o) { o.wait(1500); } } catch (Exception ex) { fail("error adding entry to group : " + group.getGroupName() + " " + ex.getMessage()); } } /** * Removes the group created in the server stored contact list by the create group test, makes * sure that the corresponding event has been generated and verifies that the group is not in the * list any more. */ public void postTestRemoveGroup() { logger.trace("testing removal of server stored groups"); // first add a listener GroupChangeCollector groupChangeCollector = new GroupChangeCollector(); opSetPersPresence1.addServerStoredGroupChangeListener(groupChangeCollector); try { // remove the group opSetPersPresence1.removeServerStoredContactGroup( opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName2)); } catch (OperationFailedException ex) { logger.error("error removing group", ex); } groupChangeCollector.waitForEvent(10000); opSetPersPresence1.removeServerStoredGroupChangeListener(groupChangeCollector); // check whether we got group created event assertEquals("Collected Group Change event", 1, groupChangeCollector.collectedEvents.size()); assertEquals( "Group name.", testGroupName2, ((ServerStoredGroupEvent) groupChangeCollector.collectedEvents.get(0)) .getSourceGroup() .getGroupName()); // check whether the group is still on the contact list ContactGroup group = opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName2); assertNull("A freshly removed group was still on the contact list.", group); } /** * Renames our test group and checks whether corresponding events are triggered. Verifies whether * the group has really changed its name and whether it is findable by its new name. Also makes * sure that it does not exist under its previous name any more. */ public void postTestRenameGroup() { logger.trace("Testing renaming groups."); ContactGroup group = opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName); // first add a listener GroupChangeCollector groupChangeCollector = new GroupChangeCollector(); opSetPersPresence1.addServerStoredGroupChangeListener(groupChangeCollector); // change the name and wait for a confirmation event opSetPersPresence1.renameServerStoredContactGroup(group, testGroupName2); groupChangeCollector.waitForEvent(10000); opSetPersPresence1.removeServerStoredGroupChangeListener(groupChangeCollector); // examine the event assertEquals("Collected Group Change event", 1, groupChangeCollector.collectedEvents.size()); assertEquals( "Group name.", testGroupName2, ((ServerStoredGroupEvent) groupChangeCollector.collectedEvents.get(0)) .getSourceGroup() .getGroupName()); // check whether the group is still on the contact list ContactGroup oldGroup = opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName); assertNull("A group was still findable by its old name after renaming.", oldGroup); // make sure that we could find the group by its new name. ContactGroup newGroup = opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName2); assertNotNull("Could not find a renamed group by its new name.", newGroup); } /** * Create the contact list. Later will be test to be sure that creating is ok * * @throws Exception */ public void prepareContactList() throws Exception { fixture.clearProvidersLists(); Object o = new Object(); synchronized (o) { o.wait(3000); } String contactList = System.getProperty(GibberishProtocolProviderServiceLick.CONTACT_LIST_PROPERTY_NAME, null); logger.debug( "The " + GibberishProtocolProviderServiceLick.CONTACT_LIST_PROPERTY_NAME + " property is set to=" + contactList); if (contactList == null || contactList.trim().length() < 6) // at least 4 for a UIN, 1 for the // dot and 1 for the grp name throw new IllegalArgumentException( "The " + GibberishProtocolProviderServiceLick.CONTACT_LIST_PROPERTY_NAME + " property did not contain a contact list."); StringTokenizer tokenizer = new StringTokenizer(contactList, " \n\t"); logger.debug("tokens contained by the CL tokenized=" + tokenizer.countTokens()); Hashtable<String, List<String>> contactListToCreate = new Hashtable<String, List<String>>(); // go over all group.uin tokens while (tokenizer.hasMoreTokens()) { String groupUinToken = tokenizer.nextToken(); int dotIndex = groupUinToken.indexOf("."); if (dotIndex == -1) { throw new IllegalArgumentException(groupUinToken + " is not a valid Group.UIN token"); } String groupName = groupUinToken.substring(0, dotIndex); String uin = groupUinToken.substring(dotIndex + 1); if (groupName.trim().length() < 1 || uin.trim().length() < 4) { throw new IllegalArgumentException( groupName + " or " + uin + " are not a valid group name or Gibberish user id."); } // check if we've already seen this group and if not - add it List<String> uinInThisGroup = contactListToCreate.get(groupName); if (uinInThisGroup == null) { uinInThisGroup = new ArrayList<String>(); contactListToCreate.put(groupName, uinInThisGroup); } uinInThisGroup.add(uin); } // now init the list Enumeration<String> newGroupsEnum = contactListToCreate.keys(); // go over all groups in the contactsToAdd table while (newGroupsEnum.hasMoreElements()) { String groupName = newGroupsEnum.nextElement(); logger.debug("Will add group " + groupName); opSetPersPresence1.createServerStoredContactGroup( opSetPersPresence1.getServerStoredContactListRoot(), groupName); ContactGroup newlyCreatedGroup = opSetPersPresence1.getServerStoredContactListRoot().getGroup(groupName); Iterator<String> contactsToAddToThisGroup = contactListToCreate.get(groupName).iterator(); while (contactsToAddToThisGroup.hasNext()) { String id = contactsToAddToThisGroup.next(); logger.debug("Will add buddy " + id); opSetPersPresence1.subscribe(newlyCreatedGroup, id); } } // store the created contact list for later reference GibberishSlickFixture.preInstalledBuddyList = contactListToCreate; } /** * The class would listen for and store received events delivered to * <tt>ServerStoredGroupListener</tt>s. */ private class GroupChangeCollector implements ServerStoredGroupListener { public ArrayList<EventObject> collectedEvents = new ArrayList<EventObject>(); /** * Blocks until at least one event is received or until waitFor miliseconds pass (whicever * happens first). * * @param waitFor the number of miliseconds that we should be waiting for an event before simply * bailing out. */ public void waitForEvent(long waitFor) { synchronized (this) { if (collectedEvents.size() > 0) return; try { wait(waitFor); } catch (InterruptedException ex) { logger.debug("Interrupted while waiting for a subscription evt", ex); } } } /** * Called whnever an indication is received that a new server stored group is created. * * @param evt a ServerStoredGroupChangeEvent containing a reference to the newly created group. */ public void groupCreated(ServerStoredGroupEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } /** * Called when an indication is received that the name of a server stored contact group has * changed. * * @param evt a ServerStoredGroupChangeEvent containing the details of the name change. */ public void groupNameChanged(ServerStoredGroupEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } /** * Called whnever an indication is received that an existing server stored group has been * removed. * * @param evt a ServerStoredGroupChangeEvent containing a reference to the newly created group. */ public void groupRemoved(ServerStoredGroupEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } /** * Called whnever an indication is received that an existing server stored group has been * resolved. * * @param evt a ServerStoredGroupChangeEvent containing a reference to the resolved group. */ public void groupResolved(ServerStoredGroupEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } } /** The class would listen for and store received subscription modification events. */ private class SubscriptionEventCollector implements SubscriptionListener { public ArrayList<EventObject> collectedEvents = new ArrayList<EventObject>(); /** * Blocks until at least one event is received or until waitFor milliseconds pass (whichever * happens first). * * @param waitFor the number of milliseconds that we should be waiting for an event before * simply bailing out. */ public void waitForEvent(long waitFor) { logger.trace("Waiting for a persistent subscription event"); synchronized (this) { if (collectedEvents.size() > 0) { logger.trace("SubEvt already received. " + collectedEvents); return; } try { wait(waitFor); if (collectedEvents.size() > 0) logger.trace("Received a SubEvt in provider status."); else logger.trace("No SubEvt received for " + waitFor + "ms."); } catch (InterruptedException ex) { logger.debug("Interrupted while waiting for a subscription evt", ex); } } } /** * Stores the received subsctiption and notifies all waiting on this object * * @param evt the SubscriptionEvent containing the corresponding contact */ public void subscriptionCreated(SubscriptionEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } /** * Stores the received subsctiption and notifies all waiting on this object * * @param evt the SubscriptionEvent containing the corresponding contact */ public void subscriptionRemoved(SubscriptionEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } /** * Stores the received subsctiption and notifies all waiting on this object * * @param evt the SubscriptionEvent containing the corresponding contact */ public void subscriptionFailed(SubscriptionEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } /** * Stores the received subsctiption and notifies all waiting on this object * * @param evt the SubscriptionEvent containing the corresponding contact */ public void subscriptionResolved(SubscriptionEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } /** * Stores the received subsctiption and notifies all waiting on this object * * @param evt the SubscriptionEvent containing the corresponding contact */ public void subscriptionMoved(SubscriptionMovedEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } /** * Stores the received subsctiption and notifies all waiting on this object * * @param evt the SubscriptionEvent containing the corresponding contact */ public void contactModified(ContactPropertyChangeEvent evt) { synchronized (this) { logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt); collectedEvents.add(evt); notifyAll(); } } } }
/** * Activator for the swing notification service. * * @author Symphorien Wanko */ public class SwingNotificationActivator implements BundleActivator { /** The bundle context in which we started */ public static BundleContext bundleContext; /** A reference to the configuration service. */ private static ConfigurationService configService; /** Logger for this class. */ private static final Logger logger = Logger.getLogger(SwingNotificationActivator.class); /** A reference to the resource management service. */ private static ResourceManagementService resourcesService; /** * Start the swing notification service * * @param bc * @throws java.lang.Exception */ public void start(BundleContext bc) throws Exception { if (logger.isInfoEnabled()) logger.info("Swing Notification ...[ STARTING ]"); bundleContext = bc; PopupMessageHandler handler = null; handler = new PopupMessageHandlerSwingImpl(); getConfigurationService(); bc.registerService(PopupMessageHandler.class.getName(), handler, null); if (logger.isInfoEnabled()) logger.info("Swing Notification ...[REGISTERED]"); } public void stop(BundleContext arg0) throws Exception {} /** * Returns the <tt>ConfigurationService</tt> obtained from the bundle context. * * @return the <tt>ConfigurationService</tt> obtained from the bundle context */ public static ConfigurationService getConfigurationService() { if (configService == null) { ServiceReference configReference = bundleContext.getServiceReference(ConfigurationService.class.getName()); configService = (ConfigurationService) bundleContext.getService(configReference); } return configService; } /** * Returns the <tt>ResourceManagementService</tt> obtained from the bundle context. * * @return the <tt>ResourceManagementService</tt> obtained from the bundle context */ public static ResourceManagementService getResources() { if (resourcesService == null) resourcesService = ResourceManagementServiceUtils.getService(bundleContext); return resourcesService; } }
/** * The <tt>OneToOneCallPeerPanel</tt> is the panel containing data for a call peer in a given call. * It contains information like call peer name, photo, call duration, etc. * * @author Yana Stamcheva * @author Lyubomir Marinov * @author Sebastien Vincent * @author Adam Netocny */ public class OneToOneCallPeerPanel extends TransparentPanel implements SwingCallPeerRenderer, PropertyChangeListener, Skinnable { /** * The <tt>Logger</tt> used by the <tt>OneToOneCallPeerPanel</tt> class and its instances for * logging output. */ private static final Logger logger = Logger.getLogger(OneToOneCallPeerPanel.class); /** Serial version UID. */ private static final long serialVersionUID = 0L; /** The <tt>CallPeer</tt>, which is rendered in this panel. */ private final CallPeer callPeer; /** The <tt>Call</tt>, which is rendered in this panel. */ private final Call call; /** * The <tt>CallPeerAdapter</tt> which implements common <tt>CallPeer</tt>-related listeners on * behalf of this instance. */ private final CallPeerAdapter callPeerAdapter; /** The renderer of the call. */ private final SwingCallRenderer callRenderer; /** The component showing the status of the underlying call peer. */ private final JLabel callStatusLabel = new JLabel(); /** The center component. */ private final VideoContainer center; /** * The AWT <tt>Component</tt> which implements a button which allows closing/hiding the visual * <tt>Component</tt> which depicts the video streaming from the local peer/user to the remote * peer(s). */ private Component closeLocalVisualComponentButton; /** * A listener to desktop sharing granted/revoked events and to mouse and keyboard interaction with * the remote video displaying the remote desktop. */ private final DesktopSharingMouseAndKeyboardListener desktopSharingMouseAndKeyboardListener; /** * The indicator which determines whether {@link #dispose()} has already been invoked on this * instance. If <tt>true</tt>, this instance is considered non-functional and is to be left to the * garbage collector. */ private boolean disposed = false; /** The DTMF label. */ private final JLabel dtmfLabel = new JLabel(); /** The component responsible for displaying an error message. */ private JTextComponent errorMessageComponent; /** The label showing whether the call is on or off hold. */ private final JLabel holdStatusLabel = new JLabel(); /** Sound local level label. */ private InputVolumeControlButton localLevel; /** * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the local * peer/user to the remote peer(s). * * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the * current state of the model of this view. */ private Component localVideo; /** The label showing whether the voice has been set to mute. */ private final JLabel muteStatusLabel = new JLabel(); /** The <tt>Icon</tt> which represents the avatar of the associated call peer. */ private ImageIcon peerImage; /** The name of the peer. */ private String peerName; /** The label containing the user photo. */ private final JLabel photoLabel; /** Sound remote level label. */ private Component remoteLevel; /** * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the remote * peer(s) to the local peer/user. * * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the * current state of the model of this view. */ private Component remoteVideo; /** Current id for security image. */ private ImageID securityImageID = ImageLoader.SECURE_BUTTON_OFF; /** The panel containing security related components. */ private SecurityPanel<?> securityPanel; /** The security status of the peer */ private final SecurityStatusLabel securityStatusLabel = new SecurityStatusLabel(); /** The status bar component. */ private final Component statusBar; /** The facility which aids this instance in the dealing with the video-related information. */ private final UIVideoHandler2 uiVideoHandler; /** * The <tt>Observer</tt> which listens to changes in the video-related information detected and * reported by {@link #uiVideoHandler}. */ private final Observer uiVideoHandlerObserver = new Observer() { public void update(Observable o, Object arg) { updateViewFromModel(); } }; /** * The <tt>Runnable</tt> which is scheduled by {@link #updateViewFromModel()} for execution in the * AWT event dispatching thread in order to invoke {@link * #updateViewFromModelInEventDispatchThread()}. */ private final Runnable updateViewFromModelInEventDispatchThread = new Runnable() { public void run() { /* * We receive events/notifications from various threads and we * respond to them in the AWT event dispatching thread. It is * possible to first schedule an event to be brought to the AWT * event dispatching thread, then to have #dispose() invoked on * this instance and, finally, to receive the scheduled event in * the AWT event dispatching thread. In such a case, this * disposed instance should not respond to the event. */ if (!disposed) updateViewFromModelInEventDispatchThread(); } }; /** * Creates a <tt>CallPeerPanel</tt> for the given call peer. * * @param callRenderer the renderer of the call * @param callPeer the <tt>CallPeer</tt> represented in this panel * @param uiVideoHandler the facility which is to aid the new instance in the dealing with the * video-related information */ public OneToOneCallPeerPanel( SwingCallRenderer callRenderer, CallPeer callPeer, UIVideoHandler2 uiVideoHandler) { this.callRenderer = callRenderer; this.callPeer = callPeer; // we need to obtain call as soon as possible // cause if it fails too quickly we may fail to show it this.call = callPeer.getCall(); this.uiVideoHandler = uiVideoHandler; peerName = CallManager.getPeerDisplayName(callPeer); securityPanel = SecurityPanel.create(this, callPeer, null); photoLabel = new JLabel(getPhotoLabelIcon()); center = createCenter(); statusBar = createStatusBar(); setPeerImage(CallManager.getPeerImage(callPeer)); /* Lay out the main Components of the UI. */ setLayout(new GridBagLayout()); GridBagConstraints cnstrnts = new GridBagConstraints(); if (center != null) { cnstrnts.fill = GridBagConstraints.BOTH; cnstrnts.gridx = 0; cnstrnts.gridy = 1; cnstrnts.weightx = 1; cnstrnts.weighty = 1; add(center, cnstrnts); } if (statusBar != null) { cnstrnts.fill = GridBagConstraints.NONE; cnstrnts.gridx = 0; cnstrnts.gridy = 3; cnstrnts.weightx = 0; cnstrnts.weighty = 0; cnstrnts.insets = new Insets(5, 0, 0, 0); add(statusBar, cnstrnts); } createSoundLevelIndicators(); initSecuritySettings(); /* * Add the listeners which will be notified about changes in the model * and which will update this view. */ callPeerAdapter = new CallPeerAdapter(callPeer, this); uiVideoHandler.addObserver(uiVideoHandlerObserver); /* * This view adapts to whether it is displayed in full-screen or * windowed mode. */ if (callRenderer instanceof Component) { ((Component) callRenderer).addPropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this); } OperationSetDesktopSharingClient desktopSharingClient = callPeer.getProtocolProvider().getOperationSet(OperationSetDesktopSharingClient.class); if (desktopSharingClient != null) { desktopSharingMouseAndKeyboardListener = new DesktopSharingMouseAndKeyboardListener(callPeer, desktopSharingClient); } else desktopSharingMouseAndKeyboardListener = null; updateViewFromModel(); } /** * Creates the <tt>Component</tt> hierarchy of the central area of this <tt>CallPeerPanel</tt> * which displays the photo of the <tt>CallPeer</tt> or the video if any. */ private VideoContainer createCenter() { photoLabel.setPreferredSize(new Dimension(90, 90)); return createVideoContainer(photoLabel); } /** Creates sound level related components. */ private void createSoundLevelIndicators() { TransparentPanel localLevelPanel = new TransparentPanel(new BorderLayout(5, 0)); TransparentPanel remoteLevelPanel = new TransparentPanel(new BorderLayout(5, 0)); localLevel = new InputVolumeControlButton( call, ImageLoader.MICROPHONE, ImageLoader.MUTE_BUTTON, false, false); remoteLevel = new OutputVolumeControlButton(call.getConference(), ImageLoader.HEADPHONE, false, false) .getComponent(); final SoundLevelIndicator localLevelIndicator = new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL); final SoundLevelIndicator remoteLevelIndicator = new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL); localLevelPanel.add(localLevel, BorderLayout.WEST); localLevelPanel.add(localLevelIndicator, BorderLayout.CENTER); remoteLevelPanel.add(remoteLevel, BorderLayout.WEST); remoteLevelPanel.add(remoteLevelIndicator, BorderLayout.CENTER); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.NONE; constraints.gridx = 0; constraints.gridy = 5; constraints.weightx = 0; constraints.weighty = 0; constraints.insets = new Insets(10, 0, 0, 0); add(localLevelPanel, constraints); constraints.fill = GridBagConstraints.NONE; constraints.gridx = 0; constraints.gridy = 6; constraints.weightx = 0; constraints.weighty = 0; constraints.insets = new Insets(5, 0, 10, 0); add(remoteLevelPanel, constraints); if (!GuiActivator.getConfigurationService() .getBoolean( "net.java.sip.communicator.impl.gui.main.call." + "DISABLE_SOUND_LEVEL_INDICATORS", false)) { callPeer.addStreamSoundLevelListener( new SoundLevelListener() { public void soundLevelChanged(Object source, int level) { remoteLevelIndicator.updateSoundLevel(level); } }); /* * By the time the UI gets to be initialized, the callPeer may have * been removed from its Call. As far as the UI is concerned, the * callPeer will never have a Call again and there will be no audio * levels to display anyway so there is no point in throwing a * NullPointerException here. */ if (call != null) { call.addLocalUserSoundLevelListener( new SoundLevelListener() { public void soundLevelChanged(Object source, int level) { localLevelIndicator.updateSoundLevel(level); } }); } } } /** * Creates the <tt>Component</tt> hierarchy of the area of status-related information such as * <tt>CallPeer</tt> display name, call duration, security status. * * @return the root of the <tt>Component</tt> hierarchy of the area of status-related information * such as <tt>CallPeer</tt> display name, call duration, security status */ private Component createStatusBar() { // stateLabel callStatusLabel.setForeground(Color.WHITE); dtmfLabel.setForeground(Color.WHITE); callStatusLabel.setText(callPeer.getState().getLocalizedStateString()); PeerStatusPanel statusPanel = new PeerStatusPanel(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 0; statusPanel.add(securityStatusLabel, constraints); initSecurityStatusLabel(); constraints.gridx++; statusPanel.add(holdStatusLabel, constraints); constraints.gridx++; statusPanel.add(muteStatusLabel, constraints); constraints.gridx++; callStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 12)); statusPanel.add(callStatusLabel, constraints); constraints.gridx++; constraints.weightx = 1f; statusPanel.add(dtmfLabel, constraints); return statusPanel; } /** * Creates a new AWT <tt>Container</tt> which can display a single <tt>Component</tt> at a time * (supposedly, one which represents video) and, in the absence of such a <tt>Component</tt>, * displays a predefined default <tt>Component</tt> (in accord with the previous supposition, one * which is the default when there is no video). The returned <tt>Container</tt> will track the * <tt>Components</tt>s added to and removed from it in order to make sure that * <tt>noVideoContainer</tt> is displayed as described. * * @param noVideoComponent the predefined default <tt>Component</tt> to be displayed in the * returned <tt>Container</tt> when there is no other <tt>Component</tt> in it * @return a new <tt>Container</tt> which can display a single <tt>Component</tt> at a time and, * in the absence of such a <tt>Component</tt>, displays <tt>noVideoComponent</tt> */ private VideoContainer createVideoContainer(Component noVideoComponent) { Container oldParent = noVideoComponent.getParent(); if (oldParent != null) oldParent.remove(noVideoComponent); return new VideoContainer(noVideoComponent, false); } /** * Releases the resources acquired by this instance which require explicit disposal (e.g. any * listeners added to the depicted <tt>CallPeer</tt>. Invoked by <tt>OneToOneCallPanel</tt> when * it determines that this <tt>OneToOneCallPeerPanel</tt> is no longer necessary. */ public void dispose() { disposed = true; callPeerAdapter.dispose(); uiVideoHandler.deleteObserver(uiVideoHandlerObserver); if (callRenderer instanceof Component) { ((Component) callRenderer).removePropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this); } } /** * Returns the parent <tt>CallPanel</tt> containing this renderer. * * @return the parent <tt>CallPanel</tt> containing this renderer */ public CallPanel getCallPanel() { return callRenderer.getCallContainer(); } /** * Returns the parent call renderer. * * @return the parent call renderer */ public CallRenderer getCallRenderer() { return callRenderer; } /** * Returns the component associated with this renderer. * * @return the component associated with this renderer */ public Component getComponent() { return this; } /** * Returns the name of the peer, contained in this panel. * * @return the name of the peer, contained in this panel */ public String getPeerName() { return peerName; } /** * Gets the <tt>Icon</tt> to be displayed in {@link #photoLabel}. * * @return the <tt>Icon</tt> to be displayed in {@link #photoLabel} */ private ImageIcon getPhotoLabelIcon() { return (peerImage == null) ? new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO)) : peerImage; } /** Initializes the security settings for this call peer. */ private void initSecuritySettings() { CallPeerSecurityStatusEvent securityEvent = callPeer.getCurrentSecuritySettings(); if (securityEvent instanceof CallPeerSecurityOnEvent) securityOn((CallPeerSecurityOnEvent) securityEvent); } /** Initializes the security status label, shown in the call status bar. */ private void initSecurityStatusLabel() { securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); securityStatusLabel.addMouseListener( new MouseAdapter() { /** Invoked when a mouse button has been pressed on a component. */ @Override public void mousePressed(MouseEvent e) { // Only show the security details if the security is on. SrtpControl ctrl = securityPanel.getSecurityControl(); if (ctrl instanceof ZrtpControl && ctrl.getSecureCommunicationStatus()) { setSecurityPanelVisible( !callRenderer .getCallContainer() .getCallWindow() .getFrame() .getGlassPane() .isVisible()); } } }); } /** * Determines whether the visual <tt>Component</tt> depicting the video streaming from the local * peer/user to the remote peer(s) is currently visible. * * @return <tt>true</tt> if the visual <tt>Component</tt> depicting the video streaming from the * local peer/user to the remote peer(s) is currently visible; otherwise, <tt>false</tt> */ public boolean isLocalVideoVisible() { return uiVideoHandler.isLocalVideoVisible(); } /** Reloads all icons. */ public void loadSkin() { if (localLevel != null) localLevel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MICROPHONE))); if (remoteLevel != null && remoteLevel instanceof Skinnable) ((Skinnable) remoteLevel).loadSkin(); if (muteStatusLabel.getIcon() != null) muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON))); if (holdStatusLabel.getIcon() != null) holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON))); securityStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(securityImageID))); if (peerImage == null) { photoLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO))); } } /** * Prints the given DTMG character through this <tt>CallPeerRenderer</tt>. * * @param dtmfChar the DTMF char to print */ public void printDTMFTone(char dtmfChar) { dtmfLabel.setText(dtmfLabel.getText() + dtmfChar); if (dtmfLabel.getBorder() == null) dtmfLabel.setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 5)); } /** * Notifies this instance about a change in the value of a property of a source which of interest * to this instance. For example, <tt>OneToOneCallPeerPanel</tt> updates its user * interface-related properties upon changes in the value of the {@link * CallContainer#PROP_FULL_SCREEN} property of its associated {@link #callRenderer}. * * @param ev a <tt>PropertyChangeEvent</tt> which identifies the source, the name of the property * and the old and new values */ public void propertyChange(PropertyChangeEvent ev) { if (CallContainer.PROP_FULL_SCREEN.equals(ev.getPropertyName())) updateViewFromModel(); } /** * Re-dispatches glass pane mouse events only in case they occur on the security panel. * * @param glassPane the glass pane * @param e the mouse event in question */ private void redispatchMouseEvent(Component glassPane, MouseEvent e) { Point glassPanePoint = e.getPoint(); Point securityPanelPoint = SwingUtilities.convertPoint(glassPane, glassPanePoint, securityPanel); Component component; Point componentPoint; if (securityPanelPoint.y > 0) { component = securityPanel; componentPoint = securityPanelPoint; } else { Container contentPane = callRenderer.getCallContainer().getCallWindow().getFrame().getContentPane(); Point containerPoint = SwingUtilities.convertPoint(glassPane, glassPanePoint, contentPane); component = SwingUtilities.getDeepestComponentAt(contentPane, containerPoint.x, containerPoint.y); componentPoint = SwingUtilities.convertPoint(contentPane, glassPanePoint, component); } if (component != null) component.dispatchEvent( new MouseEvent( component, e.getID(), e.getWhen(), e.getModifiers(), componentPoint.x, componentPoint.y, e.getClickCount(), e.isPopupTrigger())); e.consume(); } /** * The handler for the security event received. The security event for starting establish a secure * connection. * * @param evt the security started event received */ public void securityNegotiationStarted(CallPeerSecurityNegotiationStartedEvent evt) { if (Boolean.parseBoolean( GuiActivator.getResources().getSettingsString("impl.gui.PARANOIA_UI"))) { SrtpControl srtpControl = null; if (callPeer instanceof MediaAwareCallPeer) srtpControl = evt.getSecurityController(); securityPanel = new ParanoiaTimerSecurityPanel<SrtpControl>(srtpControl); setSecurityPanelVisible(true); } } /** * Indicates that the security has gone off. * * @param evt the <tt>CallPeerSecurityOffEvent</tt> that notified us */ public void securityOff(final CallPeerSecurityOffEvent evt) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { securityOff(evt); } }); return; } if (evt.getSessionType() == CallPeerSecurityOffEvent.AUDIO_SESSION) { securityStatusLabel.setText(""); securityStatusLabel.setSecurityOff(); if (securityStatusLabel.getBorder() == null) securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 3)); } securityPanel.securityOff(evt); } /** * Indicates that the security is turned on. * * <p>Sets the secured status icon to the status panel and initializes/updates the corresponding * security details. * * @param evt Details about the event that caused this message. */ public void securityOn(final CallPeerSecurityOnEvent evt) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { securityOn(evt); } }); return; } // If the securityOn is called without a specific event, we'll just set // the security label status to on. if (evt == null) { securityStatusLabel.setSecurityOn(); return; } SrtpControl srtpControl = evt.getSecurityController(); if (!srtpControl.requiresSecureSignalingTransport() || callPeer.getProtocolProvider().isSignalingTransportSecure()) { if (srtpControl instanceof ZrtpControl) { securityStatusLabel.setText("zrtp"); if (!((ZrtpControl) srtpControl).isSecurityVerified()) securityStatusLabel.setSecurityPending(); else securityStatusLabel.setSecurityOn(); } else securityStatusLabel.setSecurityOn(); } // if we have some other panel, using other control if (!srtpControl.getClass().isInstance(securityPanel.getSecurityControl()) || (securityPanel instanceof ParanoiaTimerSecurityPanel)) { setSecurityPanelVisible(false); securityPanel = SecurityPanel.create(this, callPeer, srtpControl); if (srtpControl instanceof ZrtpControl) ((ZrtpSecurityPanel) securityPanel).setSecurityStatusLabel(securityStatusLabel); } securityPanel.securityOn(evt); boolean isSecurityLowPriority = Boolean.parseBoolean( GuiActivator.getResources() .getSettingsString("impl.gui.I_DONT_CARE_THAT_MUCH_ABOUT_SECURITY")); // Display ZRTP panel in case SAS was not verified or a AOR mismtach // was detected during creation of ZrtpSecurityPanel. // Don't show panel if user does not care about security at all. if (srtpControl instanceof ZrtpControl && !isSecurityLowPriority && (!((ZrtpControl) srtpControl).isSecurityVerified() || ((ZrtpSecurityPanel) securityPanel).isZidAorMismatch())) { setSecurityPanelVisible(true); } this.revalidate(); } /** Indicates that the security status is pending confirmation. */ public void securityPending() { securityStatusLabel.setSecurityPending(); } /** * Indicates that the security is timeouted, is not supported by the other end. * * @param evt Details about the event that caused this message. */ public void securityTimeout(final CallPeerSecurityTimeoutEvent evt) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { securityTimeout(evt); } }); return; } if (securityPanel != null) securityPanel.securityTimeout(evt); } /** * Sets the reason of a call failure if one occurs. The renderer should display this reason to the * user. * * @param reason the reason to display */ public void setErrorReason(final String reason) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setErrorReason(reason); } }); return; } if (errorMessageComponent == null) { errorMessageComponent = new JTextPane(); JTextPane textPane = (JTextPane) errorMessageComponent; textPane.setEditable(false); textPane.setOpaque(false); StyledDocument doc = textPane.getStyledDocument(); MutableAttributeSet standard = new SimpleAttributeSet(); StyleConstants.setAlignment(standard, StyleConstants.ALIGN_CENTER); StyleConstants.setFontFamily(standard, textPane.getFont().getFamily()); StyleConstants.setFontSize(standard, 12); doc.setParagraphAttributes(0, 0, standard, true); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridx = 0; constraints.gridy = 4; constraints.weightx = 1; constraints.weighty = 0; constraints.insets = new Insets(5, 0, 0, 0); add(errorMessageComponent, constraints); this.revalidate(); } errorMessageComponent.setText(reason); if (isVisible()) errorMessageComponent.repaint(); } /** * Shows/hides the visual <tt>Component</tt> depicting the video streaming from the local * peer/user to the remote peer(s). * * @param visible <tt>true</tt> to show the visual <tt>Component</tt> depicting the video * streaming from the local peer/user to the remote peer(s); <tt>false</tt>, otherwise */ public void setLocalVideoVisible(boolean visible) { uiVideoHandler.setLocalVideoVisible(visible); } /** * Sets the mute status icon to the status panel. * * @param isMute indicates if the call with this peer is muted */ public void setMute(final boolean isMute) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setMute(isMute); } }); return; } if (isMute) { muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON))); muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3)); } else { muteStatusLabel.setIcon(null); muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); } // Update input volume control button state to reflect the current // mute status. if (localLevel.isSelected() != isMute) localLevel.setSelected(isMute); this.revalidate(); this.repaint(); } /** * Sets the "on hold" property value. * * @param isOnHold indicates if the call with this peer is put on hold */ public void setOnHold(final boolean isOnHold) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setOnHold(isOnHold); } }); return; } if (isOnHold) { holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON))); holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3)); } else { holdStatusLabel.setIcon(null); holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); } this.revalidate(); this.repaint(); } /** * Set the image of the peer * * @param image new image */ public void setPeerImage(byte[] image) { // If the image is still null we try to obtain it from one of the // available contact sources. if (image == null || image.length <= 0) { GuiActivator.getContactList().setSourceContactImage(peerName, photoLabel, 100, 100); } else { peerImage = ImageUtils.getScaledRoundedIcon(image, 100, 100); if (peerImage == null) peerImage = getPhotoLabelIcon(); if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { photoLabel.setIcon(peerImage); photoLabel.repaint(); } }); } else { photoLabel.setIcon(peerImage); photoLabel.repaint(); } } } /** * Sets the name of the peer. * * @param name the name of the peer */ public void setPeerName(final String name) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setPeerName(name); } }); return; } peerName = name; ((OneToOneCallPanel) callRenderer).setPeerName(name); } /** * Sets the state of the contained call peer by specifying the state name. * * @param oldState the previous state of the peer * @param newState the new state of the peer * @param stateString the state of the contained call peer */ public void setPeerState( final CallPeerState oldState, final CallPeerState newState, final String stateString) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setPeerState(oldState, newState, stateString); } }); return; } this.callStatusLabel.setText(stateString); if (newState == CallPeerState.CONNECTED && !CallPeerState.isOnHold(oldState) && !securityStatusLabel.isSecurityStatusSet()) { securityStatusLabel.setSecurityOff(); } } /** * Shows/hides the security panel. * * @param isVisible <tt>true</tt> to show the security panel, <tt>false</tt> to hide it */ public void setSecurityPanelVisible(final boolean isVisible) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setSecurityPanelVisible(isVisible); } }); return; } final JFrame callFrame = callRenderer.getCallContainer().getCallWindow().getFrame(); final JPanel glassPane = (JPanel) callFrame.getGlassPane(); if (!isVisible) { // Need to hide the security panel explicitly in order to keep the // fade effect. securityPanel.setVisible(false); glassPane.setVisible(false); glassPane.removeAll(); } else { glassPane.setLayout(null); glassPane.addMouseListener( new MouseListener() { public void mouseClicked(MouseEvent e) { redispatchMouseEvent(glassPane, e); } public void mouseEntered(MouseEvent e) { redispatchMouseEvent(glassPane, e); } public void mouseExited(MouseEvent e) { redispatchMouseEvent(glassPane, e); } public void mousePressed(MouseEvent e) { redispatchMouseEvent(glassPane, e); } public void mouseReleased(MouseEvent e) { redispatchMouseEvent(glassPane, e); } }); Point securityLabelPoint = securityStatusLabel.getLocation(); Point newPoint = SwingUtilities.convertPoint( securityStatusLabel.getParent(), securityLabelPoint.x, securityLabelPoint.y, callFrame); securityPanel.setBeginPoint(new Point((int) newPoint.getX() + 15, 0)); securityPanel.setBounds(0, (int) newPoint.getY() - 5, this.getWidth(), 130); glassPane.add(securityPanel); // Need to show the security panel explicitly in order to keep the // fade effect. securityPanel.setVisible(true); glassPane.setVisible(true); glassPane.addComponentListener( new ComponentAdapter() { /** Invoked when the component's size changes. */ @Override public void componentResized(ComponentEvent e) { if (glassPane.isVisible()) { glassPane.setVisible(false); callFrame.removeComponentListener(this); } } }); } } /** * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of * its model i.e. <tt>callPeer</tt>. */ private void updateViewFromModel() { /* * We receive events/notifications from various threads and we respond * to them in the AWT event dispatching thread. It is possible to first * schedule an event to be brought to the AWT event dispatching thread, * then to have #dispose() invoked on this instance and, finally, to * receive the scheduled event in the AWT event dispatching thread. In * such a case, this disposed instance should not respond to the event * because it may, for example, steal a visual Components depicting * video (which cannot belong to more than one parent at a time) from * another non-disposed OneToOneCallPeerPanel. */ if (!disposed) { if (SwingUtilities.isEventDispatchThread()) updateViewFromModelInEventDispatchThread(); else { SwingUtilities.invokeLater(updateViewFromModelInEventDispatchThread); } } } /** * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of * its model i.e. <tt>callPeer</tt>. The update is performed in the AWT event dispatching thread. */ private void updateViewFromModelInEventDispatchThread() { /* * We receive events/notifications from various threads and we respond * to them in the AWT event dispatching thread. It is possible to first * schedule an event to be brought to the AWT event dispatching thread, * then to have #dispose() invoked on this instance and, finally, to * receive the scheduled event in the AWT event dispatching thread. In * such a case, this disposed instance should not respond to the event * because it may, for example, steal a visual Components depicting * video (which cannot belong to more than one parent at a time) from * another non-disposed OneToOneCallPeerPanel. */ if (disposed) return; /* * Update the display of visual <tt>Component</tt>s depicting video * streaming between the local peer/user and the remote peer(s). */ OperationSetVideoTelephony videoTelephony = callPeer.getProtocolProvider().getOperationSet(OperationSetVideoTelephony.class); Component remoteVideo = null; Component localVideo = null; if (videoTelephony != null) { List<Component> remoteVideos = videoTelephony.getVisualComponents(callPeer); if ((remoteVideos != null) && !remoteVideos.isEmpty()) { /* * TODO OneToOneCallPeerPanel displays a one-to-one conversation * between the local peer/user and a specific remote peer. If * the remote peer is the focus of a telephony conference of its * own, it may be sending multiple videos to the local peer. * Switching to a user interface which displays multiple videos * is the responsibility of whoever decided that this * OneToOneCallPeerPanel is to be used to depict the current * state of the CallConference associated with the CallPeer * depicted by this instance. If that switching decides that * this instance is to continue being the user interface, then * we should probably pick up the remote video which is * generated by the remote peer and not one of its * ConferenceMembers. */ remoteVideo = remoteVideos.get(0); } if (uiVideoHandler.isLocalVideoVisible()) { try { localVideo = videoTelephony.getLocalVisualComponent(callPeer); } catch (OperationFailedException ofe) { /* * Well, we cannot do much about the exception. We'll just * not display the local video. */ logger.warn("Failed to retrieve local video to be displayed.", ofe); } } /* * Determine whether there is actually a change in the local and * remote videos which requires an update. */ boolean localVideoChanged = ((localVideo != this.localVideo) || ((localVideo != null) && !UIVideoHandler2.isAncestor(center, localVideo))); boolean remoteVideoChanged = ((remoteVideo != this.remoteVideo) || ((remoteVideo != null) && !UIVideoHandler2.isAncestor(center, remoteVideo))); // If the remote video has changed, maybe the CallPanel can display // the LO/SD/HD button. if (remoteVideoChanged) { // Updates video component which may listen the mouse and key // events. if (desktopSharingMouseAndKeyboardListener != null) { desktopSharingMouseAndKeyboardListener.setVideoComponent(remoteVideo); } CallPanel callPanel = callRenderer.getCallContainer(); // The remote video has been added, then tries to display the // LO/SD/HD button. if (remoteVideo != null) { callPanel.addRemoteVideoSpecificComponents(callPeer); } // The remote video has been removed, then hide the LO/SD/HD // button if it is currently displayed. else { callPanel.removeRemoteVideoSpecificComponents(); } } if (localVideoChanged || remoteVideoChanged) { /* * VideoContainer and JAWTRenderer cannot handle random * additions of Components. Removing the localVideo when the * user has requests its hiding though, should work without * removing all Components from the VideoCotainer and adding * them again. */ if (localVideoChanged && !remoteVideoChanged && (localVideo == null)) { if (this.localVideo != null) { center.remove(this.localVideo); this.localVideo = null; if (closeLocalVisualComponentButton != null) center.remove(closeLocalVisualComponentButton); } } else { center.removeAll(); this.localVideo = null; this.remoteVideo = null; /* * AWT does not make a guarantee about the Z order even * within an operating system i.e. the order of adding the * Components to their Container does not mean that they * will be determinedly painted in that or reverse order. * Anyway, there appears to be an expectation among the * developers less acquainted with AWT that AWT paints the * Components of a Container in an order that is the reverse * of the order of their adding. In order to satisfy that * expectation and thus give at least some idea to the * developers reading the code bellow, do add the Components * according to that expectation. */ if (localVideo != null) { if (closeLocalVisualComponentButton == null) { closeLocalVisualComponentButton = new CloseLocalVisualComponentButton(uiVideoHandler); } center.add(closeLocalVisualComponentButton, VideoLayout.CLOSE_LOCAL_BUTTON, -1); center.add(localVideo, VideoLayout.LOCAL, -1); this.localVideo = localVideo; } if (remoteVideo != null) { center.add(remoteVideo, VideoLayout.CENTER_REMOTE, -1); this.remoteVideo = remoteVideo; } } } } } /** The <tt>TransparentPanel</tt> that will display the peer status. */ private static class PeerStatusPanel extends TransparentPanel { /** * Silence the serial warning. Though there isn't a plan to serialize the instances of the * class, there're no fields so the default serialization routine will work. */ private static final long serialVersionUID = 0L; /** * Constructs a new <tt>PeerStatusPanel</tt>. * * @param layout the <tt>LayoutManager</tt> to use */ public PeerStatusPanel(LayoutManager layout) { super(layout); } /** @{inheritDoc} */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); g = g.create(); try { AntialiasingManager.activateAntialiasing(g); g.setColor(Color.DARK_GRAY); g.fillRoundRect(0, 0, this.getWidth(), this.getHeight(), 10, 10); } finally { g.dispose(); } } } }
public class ShowPreviewDialog extends SIPCommDialog implements ActionListener, ChatLinkClickedListener { /** Serial version UID. */ private static final long serialVersionUID = 1L; /** * The <tt>Logger</tt> used by the <tt>ShowPreviewDialog</tt> class and its instances for logging * output. */ private static final Logger logger = Logger.getLogger(ShowPreviewDialog.class); ConfigurationService cfg = GuiActivator.getConfigurationService(); /** The Ok button. */ private final JButton okButton; /** The cancel button. */ private final JButton cancelButton; /** Checkbox that indicates whether or not to show this dialog next time. */ private final JCheckBox enableReplacementProposal; /** Checkbox that indicates whether or not to show previews automatically */ private final JCheckBox enableReplacement; /** The <tt>ChatConversationPanel</tt> that this dialog is associated with. */ private final ChatConversationPanel chatPanel; /** Mapping between messageID and the string representation of the chat message. */ private Map<String, String> msgIDToChatString = new ConcurrentHashMap<String, String>(); /** * Mapping between the pair (messageID, link position) and the actual link in the string * representation of the chat message. */ private Map<String, String> msgIDandPositionToLink = new ConcurrentHashMap<String, String>(); /** * Mapping between link and replacement for this link that is acquired from it's corresponding * <tt>ReplacementService</tt>. */ private Map<String, String> linkToReplacement = new ConcurrentHashMap<String, String>(); /** The id of the message that is currently associated with this dialog. */ private String currentMessageID = ""; /** The position of the link in the current message. */ private String currentLinkPosition = ""; /** * Creates an instance of <tt>ShowPreviewDialog</tt> * * @param chatPanel The <tt>ChatConversationPanel</tt> that is associated with this dialog. */ ShowPreviewDialog(final ChatConversationPanel chatPanel) { this.chatPanel = chatPanel; this.setTitle( GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_DIALOG_TITLE")); okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK")); cancelButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL")); JPanel mainPanel = new TransparentPanel(); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // mainPanel.setPreferredSize(new Dimension(200, 150)); this.getContentPane().add(mainPanel); JTextPane descriptionMsg = new JTextPane(); descriptionMsg.setEditable(false); descriptionMsg.setOpaque(false); descriptionMsg.setText( GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_WARNING_DESCRIPTION")); Icon warningIcon = null; try { warningIcon = new ImageIcon( ImageIO.read( GuiActivator.getResources().getImageURL("service.gui.icons.WARNING_ICON"))); } catch (IOException e) { logger.debug("failed to load the warning icon"); } JLabel warningSign = new JLabel(warningIcon); JPanel warningPanel = new TransparentPanel(); warningPanel.setLayout(new BoxLayout(warningPanel, BoxLayout.X_AXIS)); warningPanel.add(warningSign); warningPanel.add(Box.createHorizontalStrut(10)); warningPanel.add(descriptionMsg); enableReplacement = new JCheckBox( GuiActivator.getResources() .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_STATUS")); enableReplacement.setOpaque(false); enableReplacement.setSelected(cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true)); enableReplacementProposal = new JCheckBox( GuiActivator.getResources() .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL")); enableReplacementProposal.setOpaque(false); JPanel checkBoxPanel = new TransparentPanel(); checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.Y_AXIS)); checkBoxPanel.add(enableReplacement); checkBoxPanel.add(enableReplacementProposal); JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER)); buttonsPanel.add(okButton); buttonsPanel.add(cancelButton); mainPanel.add(warningPanel); mainPanel.add(Box.createVerticalStrut(10)); mainPanel.add(checkBoxPanel); mainPanel.add(buttonsPanel); okButton.addActionListener(this); cancelButton.addActionListener(this); this.setPreferredSize(new Dimension(390, 230)); } @Override public void actionPerformed(ActionEvent arg0) { if (arg0.getSource().equals(okButton)) { cfg.setProperty(ReplacementProperty.REPLACEMENT_ENABLE, enableReplacement.isSelected()); cfg.setProperty( ReplacementProperty.REPLACEMENT_PROPOSAL, enableReplacementProposal.isSelected()); SwingWorker worker = new SwingWorker() { /** * Called on the event dispatching thread (not on the worker thread) after the <code> * construct</code> method has returned. */ @Override public void finished() { String newChatString = (String) get(); if (newChatString != null) { try { Element elem = chatPanel.document.getElement(currentMessageID); chatPanel.document.setOuterHTML(elem, newChatString); msgIDToChatString.put(currentMessageID, newChatString); } catch (BadLocationException ex) { logger.error("Could not replace chat message", ex); } catch (IOException ex) { logger.error("Could not replace chat message", ex); } } } @Override protected Object construct() throws Exception { String newChatString = msgIDToChatString.get(currentMessageID); try { String originalLink = msgIDandPositionToLink.get(currentMessageID + "#" + currentLinkPosition); String replacementLink = linkToReplacement.get(originalLink); String replacement; DirectImageReplacementService source = GuiActivator.getDirectImageReplacementSource(); if (originalLink.equals(replacementLink) && (!source.isDirectImage(originalLink) || source.getImageSize(originalLink) == -1)) { replacement = originalLink; } else { replacement = "<IMG HEIGHT=\"90\" WIDTH=\"120\" SRC=\"" + replacementLink + "\" BORDER=\"0\" ALT=\"" + originalLink + "\"></IMG>"; } String old = originalLink + "</A> <A href=\"jitsi://" + ShowPreviewDialog.this.getClass().getName() + "/SHOWPREVIEW?" + currentMessageID + "#" + currentLinkPosition + "\">" + GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW"); newChatString = newChatString.replace(old, replacement); } catch (Exception ex) { logger.error("Could not replace chat message", ex); } return newChatString; } }; worker.start(); this.setVisible(false); } else if (arg0.getSource().equals(cancelButton)) { this.setVisible(false); } } @Override public void chatLinkClicked(URI url) { String action = url.getPath(); if (action.equals("/SHOWPREVIEW")) { enableReplacement.setSelected(cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true)); enableReplacementProposal.setSelected( cfg.getBoolean(ReplacementProperty.REPLACEMENT_PROPOSAL, true)); currentMessageID = url.getQuery(); currentLinkPosition = url.getFragment(); this.setVisible(true); this.setLocationRelativeTo(chatPanel); } } /** * Returns mapping between messageID and the string representation of the chat message. * * @return mapping between messageID and chat string. */ Map<String, String> getMsgIDToChatString() { return msgIDToChatString; } /** * Returns mapping between the pair (messageID, link position) and the actual link in the string * representation of the chat message. * * @return mapping between (messageID, linkPosition) and link. */ Map<String, String> getMsgIDandPositionToLink() { return msgIDandPositionToLink; } /** * Returns mapping between link and replacement for this link that was acquired from it's * corresponding <tt>ReplacementService</tt>. * * @return mapping between link and it's corresponding replacement. */ Map<String, String> getLinkToReplacement() { return linkToReplacement; } }
/** * The advanced configuration panel. * * @author Yana Stamcheva */ public class AdvancedConfigurationPanel extends TransparentPanel implements ConfigurationForm, ConfigurationContainer, ServiceListener, ListSelectionListener { /** Serial version UID. */ private static final long serialVersionUID = 0L; /** The <tt>Logger</tt> used by this <tt>AdvancedConfigurationPanel</tt> for logging output. */ private final Logger logger = Logger.getLogger(AdvancedConfigurationPanel.class); /** The configuration list. */ private final JList configList = new JList(); /** The center panel. */ private final JPanel centerPanel = new TransparentPanel(new BorderLayout()); /** Creates an instance of the <tt>AdvancedConfigurationPanel</tt>. */ public AdvancedConfigurationPanel() { super(new BorderLayout(10, 0)); initList(); centerPanel.setPreferredSize(new Dimension(500, 500)); add(centerPanel, BorderLayout.CENTER); } /** Initializes the config list. */ private void initList() { configList.setModel(new DefaultListModel()); configList.setCellRenderer(new ConfigListCellRenderer()); configList.addListSelectionListener(this); configList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane configScrollList = new JScrollPane(); configScrollList.getVerticalScrollBar().setUnitIncrement(30); configScrollList.getViewport().add(configList); add(configScrollList, BorderLayout.WEST); String osgiFilter = "(" + ConfigurationForm.FORM_TYPE + "=" + ConfigurationForm.ADVANCED_TYPE + ")"; ServiceReference[] confFormsRefs = null; try { confFormsRefs = AdvancedConfigActivator.bundleContext.getServiceReferences( ConfigurationForm.class.getName(), osgiFilter); } catch (InvalidSyntaxException ex) { } if (confFormsRefs != null) { for (int i = 0; i < confFormsRefs.length; i++) { ConfigurationForm form = (ConfigurationForm) AdvancedConfigActivator.bundleContext.getService(confFormsRefs[i]); if (form.isAdvanced()) this.addConfigForm(form); } } } /** * Shows on the right the configuration form given by the given <tt>ConfigFormDescriptor</tt>. * * @param configForm the configuration form to show */ private void showFormContent(ConfigurationForm configForm) { this.centerPanel.removeAll(); JComponent configFormPanel = (JComponent) configForm.getForm(); configFormPanel.setOpaque(false); this.centerPanel.add(configFormPanel, BorderLayout.CENTER); this.centerPanel.revalidate(); this.centerPanel.repaint(); } /** * Handles registration of a new configuration form. * * @param event the <tt>ServiceEvent</tt> that notified us */ public void serviceChanged(ServiceEvent event) { Object sService = AdvancedConfigActivator.bundleContext.getService(event.getServiceReference()); // we don't care if the source service is not a configuration form if (!(sService instanceof ConfigurationForm)) return; ConfigurationForm configForm = (ConfigurationForm) sService; /* * This AdvancedConfigurationPanel is an advanced ConfigurationForm so * don't try to add it to itself. */ if ((configForm == this) || !configForm.isAdvanced()) return; switch (event.getType()) { case ServiceEvent.REGISTERED: if (logger.isInfoEnabled()) logger.info("Handling registration of a new Configuration Form."); this.addConfigForm(configForm); break; case ServiceEvent.UNREGISTERING: this.removeConfigForm(configForm); break; } } /** * Adds a new <tt>ConfigurationForm</tt> to this list. * * @param configForm The <tt>ConfigurationForm</tt> to add. */ public void addConfigForm(ConfigurationForm configForm) { if (configForm == null) throw new IllegalArgumentException("configForm"); DefaultListModel listModel = (DefaultListModel) configList.getModel(); int i = 0; int count = listModel.getSize(); int configFormIndex = configForm.getIndex(); for (; i < count; i++) { ConfigurationForm form = (ConfigurationForm) listModel.get(i); if (configFormIndex < form.getIndex()) break; } listModel.add(i, configForm); } /** * Implements <code>ApplicationWindow.show</code> method. * * @param isVisible specifies whether the frame is to be visible or not. */ @Override public void setVisible(boolean isVisible) { if (isVisible && configList.getSelectedIndex() < 0) { this.configList.setSelectedIndex(0); } super.setVisible(isVisible); } /** * Removes a <tt>ConfigurationForm</tt> from this list. * * @param configForm The <tt>ConfigurationForm</tt> to remove. */ public void removeConfigForm(ConfigurationForm configForm) { DefaultListModel listModel = (DefaultListModel) configList.getModel(); for (int count = listModel.getSize(), i = count - 1; i >= 0; i--) { ConfigurationForm form = (ConfigurationForm) listModel.get(i); if (form.equals(configForm)) { listModel.remove(i); /* * TODO We may just consider not allowing duplicates on addition * and then break here. */ } } } /** A custom cell renderer that represents a <tt>ConfigurationForm</tt>. */ private class ConfigListCellRenderer extends DefaultListCellRenderer { /** Serial version UID. */ private static final long serialVersionUID = 0L; private boolean isSelected = false; private final Color selectedColor = new Color( AdvancedConfigActivator.getResources().getColor("service.gui.LIST_SELECTION_COLOR")); /** * Creates an instance of <tt>ConfigListCellRenderer</tt> and specifies that this renderer is * transparent. */ public ConfigListCellRenderer() { this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); this.setOpaque(false); } /** * Returns the component representing the cell given by parameters. * * @param list the parent list * @param value the value of the cell * @param index the index of the cell * @param isSelected indicates if the cell is selected * @param cellHasFocus indicates if the cell has the focus * @return the component representing the cell */ public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { ConfigurationForm configForm = (ConfigurationForm) value; this.isSelected = isSelected; this.setText(configForm.getTitle()); return this; } /** * Paint a background for all groups and a round blue border and background when a cell is * selected. * * @param g the <tt>Graphics</tt> object */ public void paintComponent(Graphics g) { Graphics g2 = g.create(); try { internalPaintComponent(g2); } finally { g2.dispose(); } super.paintComponent(g); } /** * Paint a background for all groups and a round blue border and background when a cell is * selected. * * @param g the <tt>Graphics</tt> object */ private void internalPaintComponent(Graphics g) { AntialiasingManager.activateAntialiasing(g); Graphics2D g2 = (Graphics2D) g; if (isSelected) { g2.setColor(selectedColor); g2.fillRect(0, 0, this.getWidth(), this.getHeight()); } } } /** * Called when user selects a component in the list of configuration forms. * * @param e the <tt>ListSelectionEvent</tt> */ public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { ConfigurationForm configForm = (ConfigurationForm) configList.getSelectedValue(); if (configForm != null) showFormContent(configForm); } } /** * Selects the given <tt>ConfigurationForm</tt>. * * @param configForm the <tt>ConfigurationForm</tt> to select */ public void setSelected(ConfigurationForm configForm) { configList.setSelectedValue(configForm, true); } /** * Returns the title of the form. * * @return the title of the form */ public String getTitle() { return AdvancedConfigActivator.getResources().getI18NString("service.gui.ADVANCED"); } /** * Returns the icon of the form. * * @return a byte array containing the icon of the form */ public byte[] getIcon() { return AdvancedConfigActivator.getResources() .getImageInBytes("plugin.advancedconfig.PLUGIN_ICON"); } /** * Returns the form component. * * @return the form component */ public Object getForm() { return this; } /** * Returns the index of the form in its parent container. * * @return the index of the form in its parent container */ public int getIndex() { return 300; } /** * Indicates if the form is an advanced form. * * @return <tt>true</tt> to indicate that this is an advanced form, otherwise returns * <tt>false</tt> */ public boolean isAdvanced() { return false; } /** * Validates the currently selected configuration form. This method is meant to be used by * configuration forms the re-validate when a new component has been added or size has changed. */ public void validateCurrentForm() {} }
/** * The class listens for "focus joined room" and "conference created" events and adds the info about * all conference components versions to Jicofo's MUC presence. * * @author Pawel Domas */ public class VersionBroadcaster extends EventHandlerActivator { /** The logger */ private static final Logger logger = Logger.getLogger(VersionBroadcaster.class); /** <tt>FocusManager</tt> instance used to access <tt>JitsiMeetConference</tt>. */ private FocusManager focusManager; /** <tt>VersionService</tt> which provides Jicofo version. */ private VersionService versionService; /** Jitsi Meet tools used to add packet extension to Jicofo presence. */ private OperationSetJitsiMeetTools meetTools; /** Creates new instance of <tt>VersionBroadcaster</tt>. */ public VersionBroadcaster() { super(new String[] {EventFactory.FOCUS_JOINED_ROOM_TOPIC, EventFactory.CONFERENCE_ROOM_TOPIC}); } /** {@inheritDoc} */ @Override public void start(BundleContext bundleContext) throws Exception { focusManager = ServiceUtils.getService(bundleContext, FocusManager.class); Assert.notNull(focusManager, "focusManager"); versionService = ServiceUtils.getService(bundleContext, VersionService.class); Assert.notNull(versionService, "versionService"); meetTools = focusManager.getOperationSet(OperationSetJitsiMeetTools.class); Assert.notNull(meetTools, "meetTools"); super.start(bundleContext); } /** {@inheritDoc} */ @Override public void stop(BundleContext bundleContext) throws Exception { super.stop(bundleContext); focusManager = null; versionService = null; meetTools = null; } /** * Handles {@link EventFactory#FOCUS_JOINED_ROOM_TOPIC} and {@link * EventFactory#CONFERENCE_ROOM_TOPIC}. * * <p>{@inheritDoc} */ @Override public void handleEvent(Event event) { String topic = event.getTopic(); if (!topic.equals(EventFactory.FOCUS_JOINED_ROOM_TOPIC) && !topic.equals(EventFactory.CONFERENCE_ROOM_TOPIC)) { logger.error("Unexpected event topic: " + topic); return; } String roomJid = (String) event.getProperty(EventFactory.ROOM_JID_KEY); JitsiMeetConference conference = focusManager.getConference(roomJid); if (conference == null) { logger.error("Conference is null"); return; } ChatRoom chatRoom = conference.getChatRoom(); if (chatRoom == null) { logger.error("Chat room is null"); return; } JitsiMeetServices meetServices = focusManager.getJitsiMeetServices(); ComponentVersionsExtension versionsExtension = new ComponentVersionsExtension(); // XMPP Version xmppServerVersion = meetServices.getXMPPServerVersion(); if (xmppServerVersion != null) { versionsExtension.addComponentVersion( ComponentVersionsExtension.COMPONENT_XMPP_SERVER, xmppServerVersion.getNameVersionOsString()); } // Conference focus org.jitsi.service.version.Version jicofoVersion = versionService.getCurrentVersion(); versionsExtension.addComponentVersion( ComponentVersionsExtension.COMPONENT_FOCUS, jicofoVersion.getApplicationName() + "(" + jicofoVersion.toString() + "," + System.getProperty("os.name") + ")"); // Videobridge // It is not be reported for FOCUS_JOINED_ROOM_TOPIC String bridgeJid = (String) event.getProperty(EventFactory.BRIDGE_JID_KEY); Version jvbVersion = bridgeJid == null ? null : meetServices.getBridgeVersion(bridgeJid); if (jvbVersion != null) { versionsExtension.addComponentVersion( ComponentVersionsExtension.COMPONENT_VIDEOBRIDGE, jvbVersion.getNameVersionOsString()); } meetTools.sendPresenceExtension(chatRoom, versionsExtension); if (logger.isDebugEnabled()) logger.debug("Sending versions: " + versionsExtension.toXML()); } }
/** * The <tt>GibberishAccountRegistrationWizard</tt> is an implementation of the * <tt>AccountRegistrationWizard</tt> for the Gibberish protocol. It allows the user to create and * configure a new Gibberish account. * * @author Emil Ivov */ public class GibberishAccountRegistrationWizard extends DesktopAccountRegistrationWizard { private final Logger logger = Logger.getLogger(GibberishAccountRegistrationWizard.class); /** The first page of the gibberish account registration wizard. */ private FirstWizardPage firstWizardPage; /** The object that we use to store details on an account that we will be creating. */ private GibberishAccountRegistration registration = new GibberishAccountRegistration(); private ProtocolProviderService protocolProvider; /** * Creates an instance of <tt>GibberishAccountRegistrationWizard</tt>. * * @param wizardContainer the wizard container, where this wizard is added */ public GibberishAccountRegistrationWizard(WizardContainer wizardContainer) { setWizardContainer(wizardContainer); wizardContainer.setFinishButtonText(Resources.getString("service.gui.SIGN_IN")); } /** * Implements the <code>AccountRegistrationWizard.getIcon</code> method. Returns the icon to be * used for this wizard. * * @return byte[] */ public byte[] getIcon() { return Resources.getImage(Resources.GIBBERISH_LOGO); } /** * Implements the <code>AccountRegistrationWizard.getPageImage</code> method. Returns the image * used to decorate the wizard page * * @return byte[] the image used to decorate the wizard page */ public byte[] getPageImage() { return Resources.getImage(Resources.PAGE_IMAGE); } /** * Implements the <code>AccountRegistrationWizard.getProtocolName</code> method. Returns the * protocol name for this wizard. * * @return String */ public String getProtocolName() { return Resources.getString("plugin.gibberishaccregwizz.PROTOCOL_NAME"); } /** * Implements the <code>AccountRegistrationWizard.getProtocolDescription * </code> method. Returns the description of the protocol for this wizard. * * @return String */ public String getProtocolDescription() { return Resources.getString("plugin.gibberishaccregwizz.PROTOCOL_DESCRIPTION"); } /** * Returns the set of pages contained in this wizard. * * @return Iterator */ public Iterator<WizardPage> getPages() { java.util.List<WizardPage> pages = new ArrayList<WizardPage>(); firstWizardPage = new FirstWizardPage(this); pages.add(firstWizardPage); return pages.iterator(); } /** * Returns the set of data that user has entered through this wizard. * * @return Iterator */ public Iterator<Map.Entry<String, String>> getSummary() { Map<String, String> summaryTable = new Hashtable<String, String>(); summaryTable.put("User ID", registration.getUserID()); return summaryTable.entrySet().iterator(); } /** * Defines the operations that will be executed when the user clicks on the wizard "Signin" * button. * * @return the created <tt>ProtocolProviderService</tt> corresponding to the new account * @throws OperationFailedException if the operation didn't succeed */ public ProtocolProviderService signin() throws OperationFailedException { firstWizardPage.commitPage(); return signin(registration.getUserID(), null); } /** * Defines the operations that will be executed when the user clicks on the wizard "Signin" * button. * * @param userName the user name to sign in with * @param password the password to sign in with * @return the created <tt>ProtocolProviderService</tt> corresponding to the new account * @throws OperationFailedException if the operation didn't succeed */ public ProtocolProviderService signin(String userName, String password) throws OperationFailedException { ProtocolProviderFactory factory = GibberishAccRegWizzActivator.getGibberishProtocolProviderFactory(); return this.installAccount(factory, userName); } /** * Creates an account for the given user and password. * * @param providerFactory the ProtocolProviderFactory which will create the account * @param user the user identifier * @return the <tt>ProtocolProviderService</tt> for the new account. */ public ProtocolProviderService installAccount( ProtocolProviderFactory providerFactory, String user) throws OperationFailedException { Hashtable<String, String> accountProperties = new Hashtable<String, String>(); accountProperties.put( ProtocolProviderFactory.ACCOUNT_ICON_PATH, "resources/images/protocol/gibberish/gibberish32x32.png"); if (registration.isRememberPassword()) { accountProperties.put(ProtocolProviderFactory.PASSWORD, registration.getPassword()); } if (isModification()) { providerFactory.uninstallAccount(protocolProvider.getAccountID()); this.protocolProvider = null; setModification(false); } try { AccountID accountID = providerFactory.installAccount(user, accountProperties); ServiceReference serRef = providerFactory.getProviderForAccount(accountID); protocolProvider = (ProtocolProviderService) GibberishAccRegWizzActivator.bundleContext.getService(serRef); } catch (IllegalStateException exc) { logger.warn(exc.getMessage()); throw new OperationFailedException( "Account already exists.", OperationFailedException.IDENTIFICATION_CONFLICT); } catch (Exception exc) { logger.warn(exc.getMessage()); throw new OperationFailedException( "Failed to add account", OperationFailedException.GENERAL_ERROR); } return protocolProvider; } /** * Fills the UserID and Password fields in this panel with the data comming from the given * protocolProvider. * * @param protocolProvider The <tt>ProtocolProviderService</tt> to load the data from. */ public void loadAccount(ProtocolProviderService protocolProvider) { setModification(true); this.protocolProvider = protocolProvider; this.registration = new GibberishAccountRegistration(); this.firstWizardPage.loadAccount(protocolProvider); } /** * Returns the registration object, which will store all the data through the wizard. * * @return the registration object, which will store all the data through the wizard */ public GibberishAccountRegistration getRegistration() { return registration; } /** * Returns the size of this wizard. * * @return the size of this wizard */ public Dimension getSize() { return new Dimension(600, 500); } /** * Returns the identifier of the page to show first in the wizard. * * @return the identifier of the page to show first in the wizard. */ public Object getFirstPageIdentifier() { return firstWizardPage.getIdentifier(); } /** * Returns the identifier of the page to show last in the wizard. * * @return the identifier of the page to show last in the wizard. */ public Object getLastPageIdentifier() { return firstWizardPage.getIdentifier(); } /** * Returns an example string, which should indicate to the user how the user name should look * like. * * @return an example string, which should indicate to the user how the user name should look * like. */ public String getUserNameExample() { return FirstWizardPage.USER_NAME_EXAMPLE; } /** * Indicates whether this wizard enables the simple "sign in" form shown when the user opens the * application for the first time. The simple "sign in" form allows user to configure her account * in one click, just specifying her username and password and leaving any other configuration as * by default. * * @return <code>true</code> if the simple "Sign in" form is enabled or <code>false</code> * otherwise. */ public boolean isSimpleFormEnabled() { return false; } /** * Returns a simple account registration form that would be the first form shown to the user. Only * if the user needs more settings she'll choose to open the advanced wizard, consisted by all * pages. * * @param isCreateAccount indicates if the simple form should be opened as a create account form * or as a login form * @return a simple account registration form */ public Object getSimpleForm(boolean isCreateAccount) { firstWizardPage = new FirstWizardPage(this); return firstWizardPage.getSimpleForm(); } }
/** * @author Yana Stamcheva * @author Pawel Domas */ public class AndroidCallUtil { /** The logger for this class. */ private static final Logger logger = Logger.getLogger(AndroidCallUtil.class); /** Field used to track the thread used to create outgoing calls. */ private static Thread createCallThread; /** * Creates an android call. * * @param context the android context * @param callButtonView the button view that generated the call * @param contact the contact address to call */ public static void createAndroidCall(Context context, View callButtonView, String contact) { if (AccountUtils.getRegisteredProviders().size() > 1) showCallViaMenu(context, callButtonView, contact); else createCall(context, contact); } /** * Creates new call to target <tt>destination</tt>. * * @param context the android context * @param destination the target callee name that will be used. */ private static void createCall(Context context, String destination) { Iterator<ProtocolProviderService> allProviders = AccountUtils.getRegisteredProviders().iterator(); if (!allProviders.hasNext()) { logger.error("No registered providers found"); return; } createCall(context, destination, allProviders.next()); } /** * Creates new call to given <tt>destination</tt> using selected <tt>provider</tt>. * * @param context the android context * @param destination target callee name. * @param provider the provider that will be used to make a call. */ public static void createCall( final Context context, final String destination, final ProtocolProviderService provider) { if (createCallThread != null) { logger.warn("Another call is already being created"); return; } else if (CallManager.getActiveCallsCount() > 0) { logger.warn("Another call is in progress"); return; } final long dialogId = ProgressDialogFragment.showProgressDialog( JitsiApplication.getResString(R.string.service_gui_OUTGOING_CALL), JitsiApplication.getResString(R.string.service_gui_OUTGOING_CALL_MSG, destination)); createCallThread = new Thread("Create call thread") { public void run() { try { CallManager.createCall(provider, destination); } catch (Throwable t) { logger.error("Error creating the call: " + t.getMessage(), t); AndroidUtils.showAlertDialog( context, context.getString(R.string.service_gui_ERROR), t.getMessage()); } finally { if (DialogActivity.waitForDialogOpened(dialogId)) { DialogActivity.closeDialog(JitsiApplication.getGlobalContext(), dialogId); } else { logger.error("Failed to wait for the dialog: " + dialogId); } createCallThread = null; } } }; createCallThread.start(); } /** * Shows "call via" menu allowing user to selected from multiple providers. * * @param context the android context * @param v the View that will contain the popup menu. * @param destination target callee name. */ private static void showCallViaMenu(final Context context, View v, final String destination) { PopupMenu popup = new PopupMenu(context, v); Menu menu = popup.getMenu(); Iterator<ProtocolProviderService> registeredProviders = AccountUtils.getRegisteredProviders().iterator(); while (registeredProviders.hasNext()) { final ProtocolProviderService provider = registeredProviders.next(); String accountAddress = provider.getAccountID().getAccountAddress(); MenuItem menuItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, accountAddress); menuItem.setOnMenuItemClickListener( new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { createCall(context, destination, provider); return false; } }); } popup.show(); } /** * Checks if there is a call in progress. If true then shows a warning toast and finishes the * activity. * * @param activity activity doing a check. * @return <tt>true</tt> if there is call in progress and <tt>Activity</tt> was finished. */ public static boolean checkCallInProgress(Activity activity) { if (CallManager.getActiveCallsCount() > 0) { logger.warn("Call is in progress"); Toast t = Toast.makeText(activity, R.string.service_gui_WARN_CALL_IN_PROGRESS, Toast.LENGTH_SHORT); t.show(); activity.finish(); return true; } else { return false; } } }
/** * A SSH implementation of the ProtocolProviderService. * * @author Shobhit Jindal */ public class ProtocolProviderServiceSSHImpl implements ProtocolProviderService { private static final Logger logger = Logger.getLogger(ProtocolProviderServiceSSHImpl.class); /** The name of this protocol. */ public static final String SSH_PROTOCOL_NAME = ProtocolNames.SSH; // /** // * The identifier for SSH Stack // * Java Secure Channel JSch // */ // JSch jsch = new JSch(); /** The test command given after each command to determine the reply length of the command */ private final String testCommand = Resources.getString("testCommand"); /** A reference to the protocol provider of UIService */ private static ServiceReference ppUIServiceRef; /** Connection timeout to a remote server in milliseconds */ private static int connectionTimeout = 30000; /** A reference to UI Service */ private static UIService uiService; /** The id of the account that this protocol provider represents. */ private AccountID accountID = null; /** We use this to lock access to initialization. */ private Object initializationLock = new Object(); /** The hashtable with the operation sets that we support locally. */ private final Hashtable supportedOperationSets = new Hashtable(); private OperationSetBasicInstantMessagingSSHImpl basicInstantMessaging; private OperationSetFileTransferSSHImpl fileTranfer; /** A list of listeners interested in changes in our registration state. */ private Vector registrationStateListeners = new Vector(); /** Indicates whether or not the provider is initialized and ready for use. */ private boolean isInitialized = false; /** The logo corresponding to the ssh protocol. */ private ProtocolIconSSHImpl sshIcon = new ProtocolIconSSHImpl(); /** * The registration state of SSH Provider is taken to be registered by default as it doesn't * correspond to the state on remote server */ private RegistrationState currentRegistrationState = RegistrationState.REGISTERED; /** The default constructor for the SSH protocol provider. */ public ProtocolProviderServiceSSHImpl() { logger.trace("Creating a ssh provider."); try { // converting to milliseconds connectionTimeout = Integer.parseInt(Resources.getString("connectionTimeout")) * 1000; } catch (NumberFormatException ex) { logger.error("Connection Timeout set to 30 seconds"); } } /** * Initializes the service implementation, and puts it in a sate where it could interoperate with * other services. It is strongly recomended that properties in this Map be mapped to property * names as specified by <tt>AccountProperties</tt>. * * @param userID the user id of the ssh account we're currently initializing * @param accountID the identifier of the account that this protocol provider represents. * @see net.java.sip.communicator.service.protocol.AccountID */ protected void initialize(String userID, AccountID accountID) { synchronized (initializationLock) { this.accountID = accountID; // initialize the presence operationset OperationSetPersistentPresenceSSHImpl persistentPresence = new OperationSetPersistentPresenceSSHImpl(this); supportedOperationSets.put( OperationSetPersistentPresence.class.getName(), persistentPresence); // register it once again for those that simply need presence and // won't be smart enough to check for a persistent presence // alternative supportedOperationSets.put(OperationSetPresence.class.getName(), persistentPresence); // initialize the IM operation set basicInstantMessaging = new OperationSetBasicInstantMessagingSSHImpl(this); supportedOperationSets.put( OperationSetBasicInstantMessaging.class.getName(), basicInstantMessaging); // initialze the file transfer operation set fileTranfer = new OperationSetFileTransferSSHImpl(this); supportedOperationSets.put(OperationSetFileTransfer.class.getName(), fileTranfer); isInitialized = true; } } /** * Determines whether a vaild session exists for the contact of remote machine. * * @param sshContact ID of SSH Contact * @return <tt>true</tt> if the session is connected <tt>false</tt> otherwise */ public boolean isSessionValid(ContactSSH sshContact) { Session sshSession = sshContact.getSSHSession(); if (sshSession != null) if (sshSession.isConnected()) return true; // remove reference to an unconnected SSH Session, if any sshContact.setSSHSession(null); return false; } /** * Determines whether the contact is connected to shell of remote machine as a precheck for any * further operation * * @param sshContact ID of SSH Contact * @return <tt>true</tt> if the contact is connected <tt>false</tt> if the contact is not * connected */ public boolean isShellConnected(ContactSSH sshContact) { // a test command may also be run here if (isSessionValid(sshContact)) { return (sshContact.getShellChannel() != null); } /* * Above should be return(sshContact.getShellChannel() != null * && sshContact.getShellChannel().isConnected()); * * but incorrect reply from stack for isConnected() */ return false; } /** * Creates a shell channel to the remote machine a new jsch session is also created if the current * one is invalid * * @param sshContact the contact of the remote machine * @param firstMessage the first message */ public void connectShell(final ContactSSH sshContact, final Message firstMessage) { sshContact.setConnectionInProgress(true); final UIService uiService = this.uiService; final Thread newConnection = new Thread( (new Runnable() { public void run() { OperationSetPersistentPresenceSSHImpl persistentPresence = (OperationSetPersistentPresenceSSHImpl) sshContact.getParentPresenceOperationSet(); persistentPresence.changeContactPresenceStatus( sshContact, SSHStatusEnum.CONNECTING); try { if (!isSessionValid(sshContact)) createSSHSessionAndLogin(sshContact); createShellChannel(sshContact); // initalizing the reader and writers of ssh contact persistentPresence.changeContactPresenceStatus( sshContact, SSHStatusEnum.CONNECTED); showWelcomeMessage(sshContact); sshContact.setMessageType(sshContact.CONVERSATION_MESSAGE_RECEIVED); sshContact.setConnectionInProgress(false); Thread.sleep(1500); sshContact.setCommandSent(true); basicInstantMessaging.sendInstantMessage(sshContact, firstMessage); } // rigoruos Exception Checking in future catch (Exception ex) { persistentPresence.changeContactPresenceStatus( sshContact, SSHStatusEnum.NOT_AVAILABLE); ex.printStackTrace(); } finally { sshContact.setConnectionInProgress(false); } } })); newConnection.start(); } /** * Creates a channel for shell type in the current session channel types = shell, sftp, exec(X * forwarding), direct-tcpip(stream forwarding) etc * * @param sshContact ID of SSH Contact */ public void createShellChannel(ContactSSH sshContact) throws IOException { try { Channel shellChannel = sshContact.getSSHSession().openChannel("shell"); // initalizing the reader and writers of ssh contact sshContact.initializeShellIO(shellChannel.getInputStream(), shellChannel.getOutputStream()); ((ChannelShell) shellChannel) .setPtyType(sshContact.getSSHConfigurationForm().getTerminalType()); // initializing the shell shellChannel.connect(1000); sshContact.setShellChannel(shellChannel); sshContact.sendLine("export PS1="); } catch (JSchException ex) { sshContact.setSSHSession(null); throw new IOException("Unable to create shell channel to remote" + " server"); } } /** * Closes the Shell channel are associated IO Streams * * @param sshContact ID of SSH Contact */ public void closeShellChannel(ContactSSH sshContact) throws JSchException, IOException { sshContact.closeShellIO(); sshContact.getShellChannel().disconnect(); sshContact.setShellChannel(null); } /** * Creates a SSH Session with a remote machine and tries to login according to the details * specified by Contact An appropriate message is shown to the end user in case the login fails * * @param sshContact ID of SSH Contact * @throws JSchException if a JSch is unable to create a SSH Session with the remote machine * @throws InterruptedException if the thread is interrupted before session connected or is timed * out * @throws OperationFailedException if not of above reasons :-) */ public void createSSHSessionAndLogin(ContactSSH sshContact) throws JSchException, OperationFailedException, InterruptedException { logger.info("Creating a new SSH Session to " + sshContact.getHostName()); // creating a new JSch Stack identifier for contact JSch jsch = new JSch(); String knownHosts = (String) accountID.getAccountProperties().get("KNOWN_HOSTS_FILE"); if (!knownHosts.equals("Optional")) jsch.setKnownHosts(knownHosts); String identitiyKey = (String) accountID.getAccountProperties().get("IDENTITY_FILE"); String userName = sshContact.getUserName(); // use the name of system user if the contact has not supplied SSH // details if (userName.equals("")) userName = System.getProperty("user.name"); if (!identitiyKey.equals("Optional")) jsch.addIdentity(identitiyKey); // creating a new session for the contact Session session = jsch.getSession( userName, sshContact.getHostName(), sshContact.getSSHConfigurationForm().getPort()); /** * Creating and associating User Info with the session User Info passes authentication from * sshContact to SSH Stack */ SSHUserInfo sshUserInfo = new SSHUserInfo(sshContact); session.setUserInfo(sshUserInfo); /** initializing the session */ session.connect(connectionTimeout); int count = 0; // wait for session to get connected while (!session.isConnected() && count <= 30000) { Thread.sleep(1000); count += 1000; logger.trace("SSH:" + sshContact.getHostName() + ": Sleep zzz .. "); } // if timeout have exceeded if (count > 30000) { sshContact.setSSHSession(null); JOptionPane.showMessageDialog( null, "SSH Connection attempt to " + sshContact.getHostName() + " timed out"); // error codes are not defined yet throw new OperationFailedException( "SSH Connection attempt to " + sshContact.getHostName() + " timed out", 2); } sshContact.setJSch(jsch); sshContact.setSSHSession(session); logger.info("A new SSH Session to " + sshContact.getHostName() + " Created"); } /** * Closes the SSH Session associated with the contact * * @param sshContact ID of SSH Contact */ void closeSSHSession(ContactSSH sshContact) { sshContact.getSSHSession().disconnect(); sshContact.setSSHSession(null); } /** * Presents the login welcome message to user * * @param sshContact ID of SSH Contact */ public void showWelcomeMessage(ContactSSH sshContact) throws IOException { /* //sending the command sshContact.sendLine(testCommand); String reply = "", line = ""; // message is extracted until the test Command ie echoed back while(line.indexOf(testCommand) == -1) { reply += line + "\n"; line = sshContact.getLine(); } uiService.getPopupDialog().showMessagePopupDialog (reply,"Message from " + sshContact.getDisplayName(), uiService.getPopupDialog().INFORMATION_MESSAGE); if(line.startsWith(testCommand)) while(!sshContact.getLine().contains(testCommand)); //one line output of testCommand sshContact.getLine(); */ logger.debug("SSH: Welcome message shown"); } /** * Returns a reference to UIServce for accessing UI related services * * @return uiService a reference to UIService */ public static UIService getUIService() { return uiService; } /** * Registers the specified listener with this provider so that it would receive notifications on * changes of its state or other properties such as its local address and display name. * * @param listener the listener to register. */ public void addRegistrationStateChangeListener(RegistrationStateChangeListener listener) { synchronized (registrationStateListeners) { if (!registrationStateListeners.contains(listener)) registrationStateListeners.add(listener); } } /** * Removes the specified registration listener so that it won't receive further notifications when * our registration state changes. * * @param listener the listener to remove. */ public void removeRegistrationStateChangeListener(RegistrationStateChangeListener listener) { synchronized (registrationStateListeners) { registrationStateListeners.remove(listener); } } /** * Creates a <tt>RegistrationStateChangeEvent</tt> corresponding to the specified old and new * states and notifies all currently registered listeners. * * @param oldState the state that the provider had before the change occurred * @param newState the state that the provider is currently in. * @param reasonCode a value corresponding to one of the REASON_XXX fields of the * RegistrationStateChangeEvent class, indicating the reason for this state transition. * @param reason a String further explaining the reason code or null if no such explanation is * necessary. */ private void fireRegistrationStateChanged( RegistrationState oldState, RegistrationState newState, int reasonCode, String reason) { RegistrationStateChangeEvent event = new RegistrationStateChangeEvent(this, oldState, newState, reasonCode, reason); logger.debug( "Dispatching " + event + " to " + registrationStateListeners.size() + " listeners."); Iterator listeners = null; synchronized (registrationStateListeners) { listeners = new ArrayList(registrationStateListeners).iterator(); } while (listeners.hasNext()) { RegistrationStateChangeListener listener = (RegistrationStateChangeListener) listeners.next(); listener.registrationStateChanged(event); } logger.trace("Done."); } /** * Returns the AccountID that uniquely identifies the account represented by this instance of the * ProtocolProviderService. * * @return the id of the account represented by this provider. */ public AccountID getAccountID() { return accountID; } /** * Returns the operation set corresponding to the specified class or null if this operation set is * not supported by the provider implementation. * * @param opsetClass the <tt>Class</tt> of the operation set that we're looking for. * @return returns an OperationSet of the specified <tt>Class</tt> if the undelying implementation * supports it or null otherwise. */ public OperationSet getOperationSet(Class opsetClass) { return (OperationSet) getSupportedOperationSets().get(opsetClass.getName()); } /** * Returns the short name of the protocol that the implementation of this provider is based upon * (like SIP, Jabber, ICQ/AIM, or others for example). * * @return a String containing the short name of the protocol this service is implementing (most * often that would be a name in ProtocolNames). */ public String getProtocolName() { return SSH_PROTOCOL_NAME; } /** * Returns the protocol display name. This is the name that would be used by the GUI to display * the protocol name. * * @return a String containing the display name of the protocol this service is implementing */ public String getProtocolDisplayName() { return SSH_PROTOCOL_NAME; } /** * Returns the state of the registration of this protocol provider with the corresponding * registration service. * * @return ProviderRegistrationState */ public RegistrationState getRegistrationState() { return currentRegistrationState; } /** * Returns an array containing all operation sets supported by the current implementation. * * @return a java.util.Map containing instance of all supported operation sets mapped against * their class names (e.g. OperationSetPresence.class.getName()) . */ public Map getSupportedOperationSets() { // Copy the map so that the caller is not able to modify it. return (Map) supportedOperationSets.clone(); } /** * Indicates whether or not this provider is registered * * @return true if the provider is currently registered and false otherwise. */ public boolean isRegistered() { return currentRegistrationState.equals(RegistrationState.REGISTERED); } /** * Starts the registration process. * * @param authority the security authority that will be used for resolving any security challenges * that may be returned during the registration or at any moment while wer're registered. * @throws OperationFailedException with the corresponding code it the registration fails for some * reason (e.g. a networking error or an implementation problem). */ public void register(SecurityAuthority authority) throws OperationFailedException { RegistrationState oldState = currentRegistrationState; currentRegistrationState = RegistrationState.REGISTERED; // get a reference to UI Service via its Service Reference ppUIServiceRef = SSHActivator.getBundleContext().getServiceReference(UIService.class.getName()); uiService = (UIService) SSHActivator.getBundleContext().getService(ppUIServiceRef); fireRegistrationStateChanged( oldState, currentRegistrationState, RegistrationStateChangeEvent.REASON_USER_REQUEST, null); } /** * Makes the service implementation close all open sockets and release any resources that it might * have taken and prepare for shutdown/garbage collection. */ public void shutdown() { if (!isInitialized) { return; } logger.trace("Killing the SSH Protocol Provider."); if (isRegistered()) { try { // do the unregistration unregister(); } catch (OperationFailedException ex) { // we're shutting down so we need to silence the exception here logger.error("Failed to properly unregister before shutting down. " + getAccountID(), ex); } } isInitialized = false; } /** * Ends the registration of this protocol provider with the current registration service. * * @throws OperationFailedException with the corresponding code it the registration fails for some * reason (e.g. a networking error or an implementation problem). */ public void unregister() throws OperationFailedException { RegistrationState oldState = currentRegistrationState; currentRegistrationState = RegistrationState.UNREGISTERED; fireRegistrationStateChanged( oldState, currentRegistrationState, RegistrationStateChangeEvent.REASON_USER_REQUEST, null); } /** * Returns the ssh protocol icon. * * @return the ssh protocol icon */ public ProtocolIcon getProtocolIcon() { return sshIcon; } }
/** * The class represents the recurring pattern structure of calendar item. * * @author Hristo Terezov */ public class RecurringPattern { /** * The <tt>Logger</tt> used by the <tt>RecurringPattern</tt> class and its instances for logging * output. */ private static final Logger logger = Logger.getLogger(RecurringPattern.class); /** Enum for the type of the pattern. */ public enum PatternType { /** Daily recurrence. */ Day((short) 0x0000), /** Weekly recurrence. */ Week((short) 0x0001), /** Monthly recurrence. */ Month((short) 0x0002), /** Monthly recurrence. */ MonthNth((short) 0x0003), /** Monthly recurrence. */ MonthEnd((short) 0x004), /** Monthly recurrence. */ HjMonth((short) 0x000A), /** Monthly recurrence. */ HjMonthNth((short) 0x000B), /** Monthly recurrence. */ HjMonthEnd((short) 0x000C); /** The value of the type. */ private short value = 0; /** * Constructs new <tt>PatternType</tt> instance. * * @param value the value. */ PatternType(short value) { this.value = value; } /** * Returns the value of the <tt>PatternType</tt> instance. * * @return the value */ public short getValue() { return value; } /** * Finds the <tt>PatternType</tt> by given value. * * @param value the value * @return the found <tt>PatternType</tt> instance or null if no type is found. */ public static PatternType getFromShort(short value) { for (PatternType type : values()) { if (type.getValue() == value) { return type; } } return null; } }; /** The value of recurFrequency field. */ private short recurFrequency; /** The value of patternType field. */ private PatternType patternType; /** The value of calendarType field. */ private short calendarType; /** The value of firstDateTime field. */ private int firstDateTime; /** The value of period field. */ private int period; /** The value of slidingFlag field. */ private int slidingFlag; /** The value of patternSpecific1 field. */ private int patternSpecific1; /** The value of patternSpecific2 field. */ private int patternSpecific2; /** The value of endType field. */ private int endType; /** The value of occurenceCount field. */ private int occurenceCount; /** The value of firstDow field. */ private int firstDow; /** The value of deletedInstanceCount field. */ private int deletedInstanceCount; /** The value of modifiedInstanceCount field. */ private int modifiedInstanceCount; /** The value of startDate field. */ private int startDate; /** The value of endDate field. */ private int endDate; /** List with the start dates of deleted instances. */ private List<Date> deletedInstances = new ArrayList<Date>(); /** Array with the start dates of modified instances. */ private int[] modifiedInstances; /** List of exception info structures included in the pattern. */ private List<ExceptionInfo> exceptionInfo; /** The source calendar item of the recurrent series. */ private CalendarItemTimerTask sourceTask; /** List of days of week when the calendar item occurred. */ private List<Integer> allowedDaysOfWeek = new LinkedList<Integer>(); /** The binary data of the pattern. */ private ByteBuffer dataBuffer; /** Array with masks for days of week when the calendar item occurs. */ public static int[] weekOfDayMask = { 0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, 0x00000040 }; /** * Parses the binary data that describes the recurrent pattern. * * @param data the binary data. * @param sourceTask the calendar item. */ public RecurringPattern(byte[] data, CalendarItemTimerTask sourceTask) { this.sourceTask = sourceTask; dataBuffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); int offset = 4; recurFrequency = dataBuffer.getShort(offset); offset += 2; patternType = PatternType.getFromShort(dataBuffer.getShort(offset)); offset += 2; calendarType = dataBuffer.getShort(offset); offset += 2; firstDateTime = dataBuffer.getInt(offset); offset += 4; period = dataBuffer.getInt(offset); offset += 4; slidingFlag = dataBuffer.getInt(offset); offset += 4; switch (patternType) { case Week: case Month: case MonthEnd: case HjMonth: case HjMonthEnd: patternSpecific1 = dataBuffer.getInt(offset); patternSpecific2 = 0; offset += 4; if (patternType == PatternType.Week) { for (int day = firstDow; day < firstDow + 7; day++) { if ((patternSpecific1 & (weekOfDayMask[day % 7])) != 0) { allowedDaysOfWeek.add((day % 7) + 1); } } } break; case MonthNth: case HjMonthNth: patternSpecific1 = dataBuffer.getInt(offset); patternSpecific2 = dataBuffer.getInt(offset + 4); if (patternSpecific1 == 0x7f && patternSpecific2 != 0x5) { patternType = PatternType.Month; } for (int day = 0; day < 7; day++) { if ((patternSpecific1 & (weekOfDayMask[day])) != 0) { allowedDaysOfWeek.add((day) + 1); } } offset += 8; break; default: break; } // endType endType = dataBuffer.getInt(offset); offset += 4; occurenceCount = dataBuffer.getInt(offset); offset += 4; firstDow = dataBuffer.getInt(offset); offset += 4; deletedInstanceCount = dataBuffer.getInt(offset); offset += 4; // deleted instances for (int i = 0; i < deletedInstanceCount; i++) { deletedInstances.add(windowsTimeToDateObject(dataBuffer.getInt(offset))); offset += 4; } modifiedInstanceCount = dataBuffer.getInt(offset); offset += 4; // modified instances modifiedInstances = new int[modifiedInstanceCount]; for (int i = 0; i < modifiedInstanceCount; i++) { modifiedInstances[i] = dataBuffer.getInt(offset); offset += 4; } startDate = dataBuffer.getInt(offset); offset += 4; endDate = dataBuffer.getInt(offset); offset += 4; offset += 16; short exceptionCount = dataBuffer.getShort(offset); offset += 2; exceptionInfo = new ArrayList<ExceptionInfo>(exceptionCount); for (int i = 0; i < exceptionCount; i++) { ExceptionInfo tmpExceptionInfo = new ExceptionInfo(offset); exceptionInfo.add(tmpExceptionInfo); offset += tmpExceptionInfo.sizeInBytes(); CalendarService.BusyStatusEnum status = tmpExceptionInfo.getBusyStatus(); Date startTime = tmpExceptionInfo.getStartDate(); Date endTime = tmpExceptionInfo.getEndDate(); if (status == CalendarService.BusyStatusEnum.FREE || startTime == null || endTime == null) continue; Date currentTime = new Date(); if (endTime.before(currentTime) || endTime.equals(currentTime)) return; boolean executeNow = false; if (startTime.before(currentTime) || startTime.equals(currentTime)) executeNow = true; CalendarItemTimerTask task = new CalendarItemTimerTask( status, startTime, endTime, sourceTask.getId(), executeNow, this); task.scheduleTasks(); } } /** * Converts windows time in minutes from 1/1/1601 to <tt>Date</tt> object. * * @param time the number of minutes from 1/1/1601 * @return the <tt>Date</tt> object */ public static Date windowsTimeToDateObject(long time) { // Date.parse("1/1/1601") == 11644473600000L long date = time * 60000 - 11644473600000L; date -= TimeZone.getDefault().getOffset(date); return new Date(date); } /** Prints the properties of the class for debugging purpose. */ @Override public String toString() { String result = ""; result += "recurFrequency: " + String.format("%#02x", this.recurFrequency) + "\n"; result += "patternType: " + String.format("%#02x", this.patternType.getValue()) + "\n"; result += "calendarType: " + String.format("%#02x", this.calendarType) + "\n"; result += "endType: " + String.format("%#04x", this.endType) + "\n"; result += "period: " + this.period + "\n"; result += "occurenceCount: " + String.format("%#04x", this.occurenceCount) + "\n"; result += "patternSpecific1: " + String.format("%#04x", this.patternSpecific1) + "\n"; result += "patternSpecific2: " + String.format("%#04x", this.patternSpecific2) + "\n"; result += "startDate hex: " + String.format("%#04x", this.startDate) + "\n"; result += "endDate hex: " + String.format("%#04x", this.endDate) + "\n"; result += "startDate: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(windowsTimeToDateObject(this.startDate)) + "\n"; result += "endDate: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(windowsTimeToDateObject(this.endDate)) + "\n"; for (int i = 0; i < modifiedInstanceCount; i++) { result += "modified Instance date hex: " + String.format("%#04x", this.modifiedInstances[i]) + "\n"; result += "modified Instance date: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z") .format(windowsTimeToDateObject(this.modifiedInstances[i])) + "\n"; } for (int i = 0; i < deletedInstanceCount; i++) { result += "deleted Instance date: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(deletedInstances.get(i)) + "\n"; } result += "patternSpecific2: " + String.format("%#04x", this.patternSpecific2) + "\n"; result += "\n\n =====================Exeptions====================\n\n"; for (ExceptionInfo info : exceptionInfo) { result += info.toString() + "\n\n"; } return result; } /** * Checks whether the given date is in the recurrent pattern range or not * * @param date the date * @return <tt>true</tt> if the date is in the pattern range. */ private boolean dateOutOfRange(Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); if ((endType != 0x00002023) && (endType != 0xFFFFFFFF) && cal.getTime().after(windowsTimeToDateObject(this.endDate))) { return true; // the series are finished } return false; } /** * Calculates and creates the next calendar item. * * @param previousStartDate the start date of the previous occurrence. * @param previousEndDate the end date of the previous occurrence. * @return the new calendar item or null if there are no more calendar items from that recurrent * series. */ public CalendarItemTimerTask next(Date previousStartDate, Date previousEndDate) { if (dateOutOfRange(new Date())) { return null; } Date startDate = previousStartDate; Date endDate = null; boolean executeNow = false; long duration = sourceTask.getEndDate().getTime() - sourceTask.getStartDate().getTime(); switch (patternType) { case Day: { startDate = new Date(startDate.getTime() + period * 60000); endDate = new Date(previousEndDate.getTime() + period * 60000); Date currentDate = new Date(); if (endDate.before(currentDate)) { long offset = currentDate.getTime() - endDate.getTime(); offset -= offset % (period * 60000); if (endDate.getTime() + offset < currentDate.getTime()) { offset += period * 60000; } startDate = new Date(startDate.getTime() + offset); } Calendar cal = Calendar.getInstance(); cal.setTime(startDate); Calendar cal2 = (Calendar) cal.clone(); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); while (deletedInstances.contains(cal.getTime())) { cal.add(Calendar.MINUTE, period); cal2.add(Calendar.MINUTE, period); } if (dateOutOfRange(cal.getTime())) { return null; } startDate = cal2.getTime(); endDate = new Date(startDate.getTime() + duration); if (startDate.before(currentDate)) { executeNow = true; } return new CalendarItemTimerTask( sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this); } case Week: { Calendar cal = Calendar.getInstance(); /** The enum for the firstDow field is the same as Calendar day of week enum + 1 day */ cal.setFirstDayOfWeek(firstDow + 1); cal.setTime(startDate); int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); int index = allowedDaysOfWeek.indexOf(dayOfWeek); if (++index < allowedDaysOfWeek.size()) { cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(index)); startDate = cal.getTime(); endDate = new Date(startDate.getTime() + duration); } else { cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(0)); cal.add(Calendar.WEEK_OF_YEAR, period); startDate = cal.getTime(); endDate = new Date(startDate.getTime() + duration); } Date currentDate = new Date(); if (endDate.before(currentDate)) { cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(0)); endDate = new Date(cal.getTimeInMillis() + duration); long offset = (currentDate.getTime() - endDate.getTime()); // 1 week = 604800000 is milliseconds offset -= offset % (period * 604800000); if (endDate.getTime() + offset < currentDate.getTime()) { cal.add(Calendar.WEEK_OF_YEAR, (int) (offset / (period * 604800000))); int i = 1; while (((cal.getTimeInMillis() + duration) < (currentDate.getTime()))) { if (i == allowedDaysOfWeek.size()) { cal.add(Calendar.WEEK_OF_YEAR, period); i = 0; } cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(i)); i++; } startDate = cal.getTime(); } else { startDate = new Date(cal.getTimeInMillis() + offset); } } cal.setTime(startDate); Calendar cal2 = (Calendar) cal.clone(); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); index = allowedDaysOfWeek.indexOf(dayOfWeek) + 1; while (deletedInstances.contains(cal.getTime())) { if (index >= allowedDaysOfWeek.size()) { index = 0; cal.add(Calendar.WEEK_OF_YEAR, period); cal2.add(Calendar.WEEK_OF_YEAR, period); } cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(index)); cal2.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(index)); index++; } startDate = cal2.getTime(); endDate = new Date(startDate.getTime() + duration); if (dateOutOfRange(endDate)) return null; if (startDate.before(currentDate)) { executeNow = true; } return new CalendarItemTimerTask( sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this); } case Month: case MonthEnd: case HjMonth: case HjMonthEnd: { return nextMonth(startDate, endDate, false); } case MonthNth: case HjMonthNth: { if (patternSpecific1 == 0x7f && patternSpecific2 == 0x05) { return nextMonth(startDate, endDate, true); } return nextMonthN(startDate, endDate); } } return null; } /** * Finds the occurrence of the events in the next months * * @param cal the calendar object * @param lastDay if <tt>true</tt> it will return the last day of the month * @param period the number of months to add * @return the calendar object with set date */ private Calendar incrementMonths(Calendar cal, boolean lastDay, int period) { int dayOfMonth = patternSpecific1; cal.set(Calendar.DAY_OF_MONTH, 1); cal.add(Calendar.MONTH, period); if (lastDay || (cal.getActualMaximum(Calendar.DAY_OF_MONTH) < dayOfMonth)) dayOfMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH); cal.set(Calendar.DAY_OF_MONTH, dayOfMonth); return cal; } /** * Finds the next occurrence for monthly recurrence. * * @param startDate the start date of the previous calendar item. * @param endDate the end date of the previous calendar item. * @param lastDay if <tt>true</tt> we are interested in last day of the month * @return the next item */ public CalendarItemTimerTask nextMonth(Date startDate, Date endDate, boolean lastDay) { long duration = sourceTask.getEndDate().getTime() - sourceTask.getStartDate().getTime(); Calendar cal = Calendar.getInstance(); cal.setTime(startDate); cal = incrementMonths(cal, lastDay, period); Date currentDate = new Date(); if (cal.getTimeInMillis() + duration < currentDate.getTime()) { Calendar cal2 = Calendar.getInstance(); cal2.setTime(currentDate); int years = cal2.get(Calendar.YEAR) - cal.get(Calendar.YEAR); int months = (years * 12) + (cal2.get(Calendar.MONTH) - cal.get(Calendar.MONTH)); int monthsToAdd = months; monthsToAdd -= months % period; cal = incrementMonths(cal, lastDay, monthsToAdd); if (cal.getTimeInMillis() + duration < currentDate.getTime()) { cal = incrementMonths(cal, lastDay, period); } } Calendar cal2 = (Calendar) cal.clone(); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); while (deletedInstances.contains(cal.getTime())) { cal = incrementMonths(cal, lastDay, period); cal2 = incrementMonths(cal2, lastDay, period); } startDate = cal2.getTime(); endDate = new Date(startDate.getTime() + duration); if (dateOutOfRange(endDate)) { return null; } boolean executeNow = false; if (startDate.before(currentDate)) { executeNow = true; } return new CalendarItemTimerTask( sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this); } /** * Finds the occurrence of the events in the next months * * @param startDate the start date if the calendar item * @param dayOfWeekInMonth the number of week days occurrences * @return the date of the next occurrence */ private Date getMonthNStartDate(Date startDate, int dayOfWeekInMonth) { Calendar cal = Calendar.getInstance(); cal.setTime(startDate); if (dayOfWeekInMonth == -1) { Date result = null; cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, dayOfWeekInMonth); for (int day : allowedDaysOfWeek) { cal.set(Calendar.DAY_OF_WEEK, day); if (result == null || result.before(cal.getTime())) result = cal.getTime(); } return result; } else while (dayOfWeekInMonth > 0) { int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); if (allowedDaysOfWeek.contains(dayOfWeek)) dayOfWeekInMonth--; if (dayOfWeekInMonth > 0) cal.add(Calendar.DAY_OF_MONTH, 1); } return cal.getTime(); } /** * Finds the next occurrence for monthly Nth recurrence. * * @param startDate the start date of the previous calendar item. * @param endDate the end date of the previous calendar item. * @return the next item */ public CalendarItemTimerTask nextMonthN(Date startDate, Date endDate) { int dayOfWeekInMonth = (patternSpecific2 == 5 ? -1 : patternSpecific2); long duration = sourceTask.getEndDate().getTime() - sourceTask.getStartDate().getTime(); Calendar cal = Calendar.getInstance(); cal.setTime(startDate); cal.set(Calendar.DAY_OF_MONTH, 1); cal.add(Calendar.MONTH, period); cal.setTime(getMonthNStartDate(cal.getTime(), dayOfWeekInMonth)); Date currentDate = new Date(); if (cal.getTimeInMillis() + duration < currentDate.getTime()) { Calendar cal2 = Calendar.getInstance(); cal2.setTime(currentDate); int years = cal2.get(Calendar.YEAR) - cal.get(Calendar.YEAR); int months = (years * 12) + (cal2.get(Calendar.MONTH) - cal.get(Calendar.MONTH)); int monthsToAdd = months; monthsToAdd -= months % period; cal.set(Calendar.DAY_OF_MONTH, 1); cal.add(Calendar.MONTH, monthsToAdd); cal.setTime(getMonthNStartDate(cal.getTime(), dayOfWeekInMonth)); if (cal.getTimeInMillis() + duration < currentDate.getTime()) { cal.set(Calendar.DAY_OF_MONTH, 1); cal.add(Calendar.MONTH, monthsToAdd); cal.setTime(getMonthNStartDate(cal.getTime(), dayOfWeekInMonth)); } } Calendar cal2 = (Calendar) cal.clone(); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); while (deletedInstances.contains(cal.getTime())) { cal.set(Calendar.DAY_OF_MONTH, 1); cal.add(Calendar.MONTH, period); startDate = null; for (int dayOfWeek : allowedDaysOfWeek) { cal.set(Calendar.DAY_OF_WEEK, dayOfWeek); cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, dayOfWeekInMonth); if ((cal.after(startDate) && dayOfWeekInMonth == -1) || (cal.before(startDate) && dayOfWeekInMonth != -1) || startDate == null) { startDate = cal.getTime(); cal2.set(Calendar.YEAR, cal.get(Calendar.YEAR)); cal2.set(Calendar.MONTH, cal.get(Calendar.MONTH)); cal2.set(Calendar.DATE, cal.get(Calendar.DATE)); } } } startDate = cal2.getTime(); endDate = new Date(startDate.getTime() + duration); if (dateOutOfRange(endDate)) return null; boolean executeNow = false; if (startDate.before(currentDate)) { executeNow = true; } return new CalendarItemTimerTask( sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this); } /** Represents the exception info structure. */ public class ExceptionInfo { /** The start date of the exception. */ private final Date startDate; /** The end date of the exception. */ private final Date endDate; /** The original start date of the exception. */ private final Date originalStartDate; /** The modified flags of the exception. */ private final short overrideFlags; /** The new busy status of the exception. */ private CalendarService.BusyStatusEnum busyStatus; /** The size of the fixed fields. */ private int size = 22; /** * Parses the data of the exception. * * @param offset the position where the exception starts in the binary data */ public ExceptionInfo(int offset) { startDate = windowsTimeToDateObject(dataBuffer.getInt(offset)); offset += 4; endDate = windowsTimeToDateObject(dataBuffer.getInt(offset)); offset += 4; originalStartDate = windowsTimeToDateObject(dataBuffer.getInt(offset)); offset += 4; overrideFlags = dataBuffer.getShort(offset); offset += 2; int[] fieldMasks = {0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080}; for (int mask : fieldMasks) { if (mask == 0x0020) { if ((overrideFlags & mask) != 0) { busyStatus = CalendarService.BusyStatusEnum.getFromLong((long) dataBuffer.getInt(offset)); } if (busyStatus == null) { busyStatus = sourceTask.getStatus(); } } if ((overrideFlags & mask) != 0) { if (mask == 0x0010 || mask == 0x0001) { short size = dataBuffer.getShort(offset + 2); offset += size; size += size; } offset += 4; size += 4; } } offset += 4; int reservedBlockSize = dataBuffer.getShort(offset); size += reservedBlockSize; } /** * Returns the size of the exception * * @return the size of the exception */ public int sizeInBytes() { return size; } /** * Returns the start date * * @return the start date */ public Date getStartDate() { return startDate; } /** * Returns the end date * * @return the end date */ public Date getEndDate() { return endDate; } /** * Returns the busy status * * @return the busy status */ public CalendarService.BusyStatusEnum getBusyStatus() { return busyStatus; } /** Prints the properties of the class for debugging purpose. */ @Override public String toString() { String result = ""; result += "startDate: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(startDate) + "\n"; result += "endDate: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(endDate) + "\n"; result += "originalStartDate: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(originalStartDate) + "\n"; return result; } } }
/** * Represents the Gibberish protocol icon. Implements the <tt>ProtocolIcon</tt> interface in order * to provide a gibberish logo image in two different sizes. * * @author Yana Stamcheva */ public class ProtocolIconGibberishImpl implements ProtocolIcon { private static Logger logger = Logger.getLogger(ProtocolIconGibberishImpl.class); private static ResourceManagementService resourcesService; /** A hash table containing the protocol icon in different sizes. */ private static Hashtable<String, byte[]> iconsTable = new Hashtable<String, byte[]>(); static { iconsTable.put( ProtocolIcon.ICON_SIZE_16x16, getImageInBytes("service.protocol.gibberish.GIBBERISH_16x16")); iconsTable.put( ProtocolIcon.ICON_SIZE_32x32, getImageInBytes("service.protocol.gibberish.GIBBERISH_32x32")); iconsTable.put( ProtocolIcon.ICON_SIZE_48x48, getImageInBytes("service.protocol.gibberish.GIBBERISH_48x48")); iconsTable.put( ProtocolIcon.ICON_SIZE_64x64, getImageInBytes("service.protocol.gibberish.GIBBERISH_64x64")); } /** A hash table containing the path to the protocol icon in different sizes. */ private static Hashtable<String, String> iconPathsTable = new Hashtable<String, String>(); static { iconPathsTable.put( ProtocolIcon.ICON_SIZE_16x16, getResources().getImagePath("service.protocol.gibberish.GIBBERISH_16x16")); iconPathsTable.put( ProtocolIcon.ICON_SIZE_32x32, getResources().getImagePath("service.protocol.gibberish.GIBBERISH_32x32")); iconPathsTable.put( ProtocolIcon.ICON_SIZE_48x48, getResources().getImagePath("service.protocol.gibberish.GIBBERISH_48x48")); iconPathsTable.put( ProtocolIcon.ICON_SIZE_64x64, getResources().getImagePath("service.protocol.gibberish.GIBBERISH_64x64")); } /** * Implements the <tt>ProtocolIcon.getSupportedSizes()</tt> method. Returns an iterator to a set * containing the supported icon sizes. * * @return an iterator to a set containing the supported icon sizes */ public Iterator<String> getSupportedSizes() { return iconsTable.keySet().iterator(); } /** Returne TRUE if a icon with the given size is supported, FALSE-otherwise. */ public boolean isSizeSupported(String iconSize) { return iconsTable.containsKey(iconSize); } /** * Returns the icon image in the given size. * * @param iconSize the icon size; one of ICON_SIZE_XXX constants */ public byte[] getIcon(String iconSize) { return iconsTable.get(iconSize); } /** * Returns a path to the icon with the given size. * * @param iconSize the size of the icon we're looking for * @return the path to the icon with the given size */ public String getIconPath(String iconSize) { return iconPathsTable.get(iconSize); } /** * Returns the icon image used to represent the protocol connecting state. * * @return the icon image used to represent the protocol connecting state */ public byte[] getConnectingIcon() { return getImageInBytes("gibberishOnlineIcon"); } /** * Returns the byte representation of the image corresponding to the given identifier. * * @param imageID the identifier of the image * @return the byte representation of the image corresponding to the given identifier. */ private static byte[] getImageInBytes(String imageID) { InputStream in = getResources().getImageInputStream(imageID); if (in == null) return null; byte[] image = null; try { image = new byte[in.available()]; in.read(image); } catch (IOException e) { logger.error("Failed to load image:" + imageID, e); } return image; } public static ResourceManagementService getResources() { if (resourcesService == null) { ServiceReference serviceReference = GibberishActivator.bundleContext.getServiceReference( ResourceManagementService.class.getName()); if (serviceReference == null) return null; resourcesService = (ResourceManagementService) GibberishActivator.bundleContext.getService(serviceReference); } return resourcesService; } }
/** * The ICQ protocol filetransfer OperationSet. * * @author Anthony Schmitt * @author Damian Minkov */ public class OperationSetFileTransferIcqImpl implements OperationSetFileTransfer, RvConnectionManagerListener { private static final Logger logger = Logger.getLogger(OperationSetFileTransferIcqImpl.class); /** A call back to the ICQ provider that created us. */ private ProtocolProviderServiceIcqImpl icqProvider = null; /** A list of listeners registered for file transfer events. */ private ArrayList<FileTransferListener> fileTransferListeners = new ArrayList<FileTransferListener>(); /** * Create a new FileTransfer OperationSet over the specified Icq provider * * @param icqProvider ICQ protocol provider service */ public OperationSetFileTransferIcqImpl(ProtocolProviderServiceIcqImpl icqProvider) { this.icqProvider = icqProvider; icqProvider.addRegistrationStateChangeListener(new RegistrationStateListener()); } /** * Sends a file transfer request to the given <tt>toContact</tt> by specifying the local and * remote file path and the <tt>fromContact</tt>, sending the file. * * @param toContact the contact that should receive the file * @param file the file to send * @return the transfer object * @throws IllegalStateException if the protocol provider is not registered or connected * @throws IllegalArgumentException if some of the arguments doesn't fit the protocol requirements */ public FileTransfer sendFile(Contact toContact, File file) throws IllegalStateException, IllegalArgumentException { assertConnected(); if (file.length() > getMaximumFileLength()) throw new IllegalArgumentException("File length exceeds the allowed one for this protocol"); // Get the aim connection AimConnection aimConnection = icqProvider.getAimConnection(); // Create an outgoing file transfer instance OutgoingFileTransfer outgoingFileTransfer = aimConnection .getIcbmService() .getRvConnectionManager() .createOutgoingFileTransfer(new Screenname(toContact.getAddress())); String id = String.valueOf(outgoingFileTransfer.getRvSessionInfo().getRvSession().getRvSessionId()); FileTransferImpl outFileTransfer = new FileTransferImpl(outgoingFileTransfer, id, toContact, file, FileTransfer.OUT); // Adding the file to the outgoing file transfer try { outgoingFileTransfer.setSingleFile(new File(file.getPath())); } catch (IOException e) { if (logger.isDebugEnabled()) logger.debug("Error sending file", e); return null; } // Notify all interested listeners that a file transfer has been // created. FileTransferCreatedEvent event = new FileTransferCreatedEvent(outFileTransfer, new Date()); fireFileTransferCreated(event); // Sending the file outgoingFileTransfer.sendRequest(new InvitationMessage("")); outFileTransfer.fireStatusChangeEvent(FileTransferStatusChangeEvent.PREPARING); return outFileTransfer; } /** * Sends a file transfer request to the given <tt>toContact</tt> by specifying the local and * remote file path and the <tt>fromContact</tt>, sending the file. * * @param toContact the contact that should receive the file * @param fromContact the contact sending the file * @param remotePath the remote file path * @param localPath the local file path * @return the transfer object * @throws IllegalStateException if the protocol provider is not registered or connected * @throws IllegalArgumentException if some of the arguments doesn't fit the protocol requirements */ public FileTransfer sendFile( Contact toContact, Contact fromContact, String remotePath, String localPath) throws IllegalStateException, IllegalArgumentException { return this.sendFile(toContact, new File(localPath)); } /** * Adds the given <tt>FileTransferListener</tt> that would listen for file transfer requests and * created file transfers. * * @param listener the <tt>FileTransferListener</tt> to add */ public void addFileTransferListener(FileTransferListener listener) { synchronized (fileTransferListeners) { if (!fileTransferListeners.contains(listener)) { this.fileTransferListeners.add(listener); } } } /** * Removes the given <tt>FileTransferListener</tt> that listens for file transfer requests and * created file transfers. * * @param listener the <tt>FileTransferListener</tt> to remove */ public void removeFileTransferListener(FileTransferListener listener) { synchronized (fileTransferListeners) { this.fileTransferListeners.remove(listener); } } /** * Utility method throwing an exception if the stack is not properly initialized. * * @throws java.lang.IllegalStateException if the underlying stack is not registered and * initialized. */ private void assertConnected() throws IllegalStateException { if (icqProvider == null) throw new IllegalStateException( "The provider must be non-null and signed on the " + "service before being able to send a file."); else if (!icqProvider.isRegistered()) throw new IllegalStateException( "The provider must be signed on the service before " + "being able to send a file."); } /** * Function called when a icq file transfer request arrive * * @param manager the joustsim manager * @param transfer the incoming transfer */ public void handleNewIncomingConnection( RvConnectionManager manager, IncomingRvConnection transfer) { if (transfer instanceof IncomingFileTransfer) { if (logger.isTraceEnabled()) logger.trace("Incoming Icq file transfer request " + transfer.getClass()); if (!(transfer instanceof IncomingFileTransfer)) { logger.warn("Wrong file transfer."); return; } OperationSetPersistentPresenceIcqImpl opSetPersPresence = (OperationSetPersistentPresenceIcqImpl) icqProvider.getOperationSet(OperationSetPersistentPresence.class); Contact sender = opSetPersPresence.findContactByID(transfer.getBuddyScreenname().getFormatted()); IncomingFileTransfer incomingFileTransfer = (IncomingFileTransfer) transfer; final Date newDate = new Date(); final IncomingFileTransferRequest req = new IncomingFileTransferRequestIcqImpl( icqProvider, this, incomingFileTransfer, sender, newDate); // this handels when we receive request and before accept or decline // it we receive cancel transfer.addEventListener( new RvConnectionEventListener() { public void handleEventWithStateChange( RvConnection transfer, RvConnectionState state, RvConnectionEvent event) { if (state == FileTransferState.FAILED && event instanceof BuddyCancelledEvent) { fireFileTransferRequestCanceled( new FileTransferRequestEvent( OperationSetFileTransferIcqImpl.this, req, newDate)); } } public void handleEvent(RvConnection arg0, RvConnectionEvent arg1) {} }); fireFileTransferRequest(new FileTransferRequestEvent(this, req, newDate)); } } /** * Delivers the specified event to all registered file transfer listeners. * * @param event the <tt>EventObject</tt> that we'd like delivered to all registered file transfer * listeners. */ private void fireFileTransferRequest(FileTransferRequestEvent event) { Iterator<FileTransferListener> listeners = null; synchronized (fileTransferListeners) { listeners = new ArrayList<FileTransferListener>(fileTransferListeners).iterator(); } while (listeners.hasNext()) { FileTransferListener listener = listeners.next(); listener.fileTransferRequestReceived(event); } } /** * Delivers the specified event to all registered file transfer listeners. * * @param event the <tt>EventObject</tt> that we'd like delivered to all registered file transfer * listeners. */ void fireFileTransferRequestRejected(FileTransferRequestEvent event) { Iterator<FileTransferListener> listeners = null; synchronized (fileTransferListeners) { listeners = new ArrayList<FileTransferListener>(fileTransferListeners).iterator(); } while (listeners.hasNext()) { FileTransferListener listener = listeners.next(); listener.fileTransferRequestRejected(event); } } /** * Delivers the specified event to all registered file transfer listeners. * * @param event the <tt>EventObject</tt> that we'd like delivered to all registered file transfer * listeners. */ void fireFileTransferRequestCanceled(FileTransferRequestEvent event) { Iterator<FileTransferListener> listeners = null; synchronized (fileTransferListeners) { listeners = new ArrayList<FileTransferListener>(fileTransferListeners).iterator(); } while (listeners.hasNext()) { FileTransferListener listener = listeners.next(); listener.fileTransferRequestCanceled(event); } } /** * Delivers the file transfer to all registered listeners. * * @param event the <tt>FileTransferEvent</tt> that we'd like delivered to all registered file * transfer listeners. */ void fireFileTransferCreated(FileTransferCreatedEvent event) { Iterator<FileTransferListener> listeners = null; synchronized (fileTransferListeners) { listeners = new ArrayList<FileTransferListener>(fileTransferListeners).iterator(); } while (listeners.hasNext()) { FileTransferListener listener = listeners.next(); listener.fileTransferCreated(event); } } /** * Returns the maximum file length supported by the protocol in bytes. Supports up to 2GB. * * @return the file length that is supported. */ public long getMaximumFileLength() { return 2147483648l; // = 2048*1024*1024; } /** Our listener that will tell us when we're registered to */ private class RegistrationStateListener implements RegistrationStateChangeListener { /** * The method is called by a ProtocolProvider implementation whenever a change in the * registration state of the corresponding provider had occurred. * * @param evt ProviderStatusChangeEvent the event describing the status change. */ public void registrationStateChanged(RegistrationStateChangeEvent evt) { if (logger.isDebugEnabled()) logger.debug( "The provider changed state from: " + evt.getOldState() + " to: " + evt.getNewState()); if (evt.getNewState() == RegistrationState.REGISTERED) { AimConnection aimConnection = icqProvider.getAimConnection(); aimConnection .getIcbmService() .getRvConnectionManager() .addConnectionManagerListener(OperationSetFileTransferIcqImpl.this); } } } }
/** * The <tt>MUCServiceImpl</tt> class implements the service for the chat rooms. * * @author Hristo Terezov */ public class MUCServiceImpl extends MUCService { /** The list of persistent chat rooms. */ private final ChatRoomListImpl chatRoomList = new ChatRoomListImpl(); /** * The <tt>Logger</tt> used by the <tt>MUCServiceImpl</tt> class and its instances for logging * output. */ private static Logger logger = Logger.getLogger(MUCServiceImpl.class); /** * Called to accept an incoming invitation. Adds the invitation chat room to the list of chat * rooms and joins it. * * @param invitation the invitation to accept. */ public void acceptInvitation(ChatRoomInvitation invitation) { ChatRoom chatRoom = invitation.getTargetChatRoom(); byte[] password = invitation.getChatRoomPassword(); String nickName = chatRoom.getParentProvider().getAccountID().getUserID(); joinChatRoom(chatRoom, nickName, password); } /** * Adds a change listener to the <tt>ChatRoomList</tt>. * * @param l the listener. */ public void addChatRoomListChangeListener(ChatRoomListChangeListener l) { chatRoomList.addChatRoomListChangeListener(l); } /** * Removes a change listener to the <tt>ChatRoomList</tt>. * * @param l the listener. */ public void removeChatRoomListChangeListener(ChatRoomListChangeListener l) { chatRoomList.removeChatRoomListChangeListener(l); } /** * Fires a <tt>ChatRoomListChangedEvent</tt> event. * * @param chatRoomWrapper the chat room. * @param eventID the id of the event. */ public void fireChatRoomListChangedEvent(ChatRoomWrapper chatRoomWrapper, int eventID) { chatRoomList.fireChatRoomListChangedEvent(chatRoomWrapper, eventID); } /** * Joins the given chat room with the given password and manages all the exceptions that could * occur during the join process. * * @param chatRoomWrapper the chat room to join. * @param nickName the nickname we choose for the given chat room. * @param password the password. * @param rememberPassword if true the password should be saved. * @param isFirstAttempt is this the first attempt to join room, used to check whether to show * some error messages * @param subject the subject which will be set to the room after the user join successful. */ private void joinChatRoom( ChatRoomWrapper chatRoomWrapper, String nickName, byte[] password, boolean rememberPassword, boolean isFirstAttempt, String subject) { ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); if (chatRoom == null) { MUCActivator.getAlertUIService() .showAlertDialog( MUCActivator.getResources().getI18NString("service.gui.WARNING"), MUCActivator.getResources() .getI18NString( "service.gui.CHAT_ROOM_NOT_CONNECTED", new String[] {chatRoomWrapper.getChatRoomName()})); return; } new JoinChatRoomTask( (ChatRoomWrapperImpl) chatRoomWrapper, nickName, password, rememberPassword, isFirstAttempt, subject) .start(); } /** * Joins the given chat room with the given password and manages all the exceptions that could * occur during the join process. * * @param chatRoomWrapper the chat room to join. * @param nickName the nickname we choose for the given chat room. * @param password the password. */ public void joinChatRoom(ChatRoomWrapper chatRoomWrapper, String nickName, byte[] password) { ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); if (chatRoom == null) { MUCActivator.getAlertUIService() .showAlertDialog( MUCActivator.getResources().getI18NString("service.gui.WARNING"), MUCActivator.getResources() .getI18NString( "service.gui.CHAT_ROOM_NOT_CONNECTED", new String[] {chatRoomWrapper.getChatRoomName()})); return; } new JoinChatRoomTask((ChatRoomWrapperImpl) chatRoomWrapper, nickName, password).start(); } /** * Joins the given chat room with the given password and manages all the exceptions that could * occur during the join process. * * @param chatRoomWrapper the chat room to join. * @param nickName the nickname we choose for the given chat room. * @param password the password. * @param subject the subject which will be set to the room after the user join successful. */ public void joinChatRoom( ChatRoomWrapper chatRoomWrapper, String nickName, byte[] password, String subject) { ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); if (chatRoom == null) { MUCActivator.getAlertUIService() .showAlertDialog( MUCActivator.getResources().getI18NString("service.gui.WARNING"), MUCActivator.getResources() .getI18NString( "service.gui.CHAT_ROOM_NOT_CONNECTED", new String[] {chatRoomWrapper.getChatRoomName()})); return; } // join from add chat room dialog new JoinChatRoomTask((ChatRoomWrapperImpl) chatRoomWrapper, nickName, password, subject) .start(); } /** * Join chat room. * * @param chatRoomWrapper */ public void joinChatRoom(ChatRoomWrapper chatRoomWrapper) { ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); if (chatRoom == null) { MUCActivator.getAlertUIService() .showAlertDialog( MUCActivator.getResources().getI18NString("service.gui.WARNING"), MUCActivator.getResources() .getI18NString( "service.gui.CHAT_ROOM_NOT_CONNECTED", new String[] {chatRoomWrapper.getChatRoomName()})); return; } new JoinChatRoomTask((ChatRoomWrapperImpl) chatRoomWrapper, null, null).start(); } /** * Joins the given chat room and manages all the exceptions that could occur during the join * process. * * @param chatRoom the chat room to join * @param nickname the nickname we're using to join * @param password the password we're using to join */ public void joinChatRoom(ChatRoom chatRoom, String nickname, byte[] password) { ChatRoomWrapper chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom); if (chatRoomWrapper == null) { ChatRoomProviderWrapper parentProvider = chatRoomList.findServerWrapperFromProvider(chatRoom.getParentProvider()); chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom); chatRoomList.addChatRoom(chatRoomWrapper); fireChatRoomListChangedEvent(chatRoomWrapper, ChatRoomListChangeEvent.CHAT_ROOM_ADDED); } this.joinChatRoom(chatRoomWrapper, nickname, password); } /** * Joins the room with the given name though the given chat room provider. * * @param chatRoomName the name of the room to join. * @param chatRoomProvider the chat room provider to join through. */ public void joinChatRoom(String chatRoomName, ChatRoomProviderWrapper chatRoomProvider) { OperationSetMultiUserChat groupChatOpSet = chatRoomProvider.getProtocolProvider().getOperationSet(OperationSetMultiUserChat.class); ChatRoom chatRoom = null; try { chatRoom = groupChatOpSet.findRoom(chatRoomName); } catch (Exception e) { if (logger.isTraceEnabled()) logger.trace("Un exception occurred while searching for room:" + chatRoomName, e); } if (chatRoom != null) { ChatRoomWrapper chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom); if (chatRoomWrapper == null) { ChatRoomProviderWrapper parentProvider = chatRoomList.findServerWrapperFromProvider(chatRoom.getParentProvider()); chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom); chatRoomList.addChatRoom(chatRoomWrapper); fireChatRoomListChangedEvent(chatRoomWrapper, ChatRoomListChangeEvent.CHAT_ROOM_ADDED); } joinChatRoom(chatRoomWrapper); } else MUCActivator.getAlertUIService() .showAlertDialog( MUCActivator.getResources().getI18NString("service.gui.ERROR"), MUCActivator.getResources() .getI18NString( "service.gui.CHAT_ROOM_NOT_EXIST", new String[] { chatRoomName, chatRoomProvider.getProtocolProvider().getAccountID().getService() })); } /** * Creates a chat room, by specifying the chat room name, the parent protocol provider and * eventually, the contacts invited to participate in this chat room. * * @param roomName the name of the room * @param protocolProvider the parent protocol provider. * @param contacts the contacts invited when creating the chat room. * @param reason * @param persistent is the room persistent * @param isPrivate whether the room will be private or public. * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room */ public ChatRoomWrapper createChatRoom( String roomName, ProtocolProviderService protocolProvider, Collection<String> contacts, String reason, boolean persistent, boolean isPrivate) { return createChatRoom( roomName, protocolProvider, contacts, reason, true, persistent, isPrivate); } /** * Creates a chat room, by specifying the chat room name, the parent protocol provider and * eventually, the contacts invited to participate in this chat room. * * @param roomName the name of the room * @param protocolProvider the parent protocol provider. * @param contacts the contacts invited when creating the chat room. * @param reason * @param persistent is the room persistent * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room */ public ChatRoomWrapper createChatRoom( String roomName, ProtocolProviderService protocolProvider, Collection<String> contacts, String reason, boolean persistent) { return createChatRoom(roomName, protocolProvider, contacts, reason, true, persistent, false); } /** * Creates a chat room, by specifying the chat room name, the parent protocol provider and * eventually, the contacts invited to participate in this chat room. * * @param roomName the name of the room * @param protocolProvider the parent protocol provider. * @param contacts the contacts invited when creating the chat room. * @param reason * @param join whether we should join the room after creating it. * @param persistent whether the newly created room will be persistent. * @param isPrivate whether the room will be private or public. * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room */ public ChatRoomWrapper createChatRoom( String roomName, ProtocolProviderService protocolProvider, Collection<String> contacts, String reason, boolean join, boolean persistent, boolean isPrivate) { ChatRoomWrapper chatRoomWrapper = null; OperationSetMultiUserChat groupChatOpSet = protocolProvider.getOperationSet(OperationSetMultiUserChat.class); // If there's no group chat operation set we have nothing to do here. if (groupChatOpSet == null) return null; ChatRoom chatRoom = null; try { HashMap<String, Object> roomProperties = new HashMap<String, Object>(); roomProperties.put("isPrivate", isPrivate); chatRoom = groupChatOpSet.createChatRoom(roomName, roomProperties); if (join) { chatRoom.join(); for (String contact : contacts) chatRoom.invite(contact, reason); } } catch (OperationFailedException ex) { logger.error("Failed to create chat room.", ex); MUCActivator.getAlertUIService() .showAlertDialog( MUCActivator.getResources().getI18NString("service.gui.ERROR"), MUCActivator.getResources() .getI18NString( "service.gui.CREATE_CHAT_ROOM_ERROR", new String[] {protocolProvider.getProtocolDisplayName()}), ex); } catch (OperationNotSupportedException ex) { logger.error("Failed to create chat room.", ex); MUCActivator.getAlertUIService() .showAlertDialog( MUCActivator.getResources().getI18NString("service.gui.ERROR"), MUCActivator.getResources() .getI18NString( "service.gui.CREATE_CHAT_ROOM_ERROR", new String[] {protocolProvider.getProtocolDisplayName()}), ex); } if (chatRoom != null) { ChatRoomProviderWrapper parentProvider = chatRoomList.findServerWrapperFromProvider(protocolProvider); // if there is the same room ids don't add new wrapper as old one // maybe already created chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom); if (chatRoomWrapper == null) { chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom); chatRoomWrapper.setPersistent(persistent); chatRoomList.addChatRoom(chatRoomWrapper); } } return chatRoomWrapper; } /** * Creates a private chat room, by specifying the parent protocol provider and eventually, the * contacts invited to participate in this chat room. * * @param protocolProvider the parent protocol provider. * @param contacts the contacts invited when creating the chat room. * @param reason * @param persistent is the room persistent * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room */ public ChatRoomWrapper createPrivateChatRoom( ProtocolProviderService protocolProvider, Collection<String> contacts, String reason, boolean persistent) { return this.createChatRoom(null, protocolProvider, contacts, reason, persistent, true); } /** * Returns existing chat rooms for the given <tt>chatRoomProvider</tt>. * * @param chatRoomProvider the <tt>ChatRoomProviderWrapper</tt>, which chat rooms we're looking * for * @return existing chat rooms for the given <tt>chatRoomProvider</tt> */ public List<String> getExistingChatRooms(ChatRoomProviderWrapper chatRoomProvider) { if (chatRoomProvider == null) return null; ProtocolProviderService protocolProvider = chatRoomProvider.getProtocolProvider(); if (protocolProvider == null) return null; OperationSetMultiUserChat groupChatOpSet = protocolProvider.getOperationSet(OperationSetMultiUserChat.class); if (groupChatOpSet == null) return null; List<String> chatRooms = null; try { chatRooms = groupChatOpSet.getExistingChatRooms(); } catch (OperationFailedException e) { if (logger.isTraceEnabled()) logger.trace( "Failed to obtain existing chat rooms for server: " + protocolProvider.getAccountID().getService(), e); } catch (OperationNotSupportedException e) { if (logger.isTraceEnabled()) logger.trace( "Failed to obtain existing chat rooms for server: " + protocolProvider.getAccountID().getService(), e); } return chatRooms; } /** * Rejects the given invitation with the specified reason. * * @param multiUserChatOpSet the operation set to use for rejecting the invitation * @param invitation the invitation to reject * @param reason the reason for the rejection */ public void rejectInvitation( OperationSetMultiUserChat multiUserChatOpSet, ChatRoomInvitation invitation, String reason) { multiUserChatOpSet.rejectInvitation(invitation, reason); } /** * Leaves the given chat room. * * @param chatRoomWrapper the chat room to leave. * @return <tt>ChatRoomWrapper</tt> instance associated with the chat room. */ public ChatRoomWrapper leaveChatRoom(ChatRoomWrapper chatRoomWrapper) { ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); if (chatRoom == null) { ResourceManagementService resources = MUCActivator.getResources(); MUCActivator.getAlertUIService() .showAlertDialog( resources.getI18NString("service.gui.WARNING"), resources.getI18NString("service.gui.CHAT_ROOM_LEAVE_NOT_CONNECTED")); return null; } if (chatRoom.isJoined()) chatRoom.leave(); ChatRoomWrapper existChatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom); if (existChatRoomWrapper == null) return null; // We save the choice of the user, before the chat room is really // joined, because even the join fails we want the next time when // we login to join this chat room automatically. ConfigurationUtils.updateChatRoomStatus( chatRoomWrapper.getParentProvider().getProtocolProvider(), chatRoomWrapper.getChatRoomID(), GlobalStatusEnum.OFFLINE_STATUS); return existChatRoomWrapper; } /** Joins a chat room in an asynchronous way. */ private class JoinChatRoomTask extends Thread { private final ChatRoomWrapperImpl chatRoomWrapper; private final String nickName; private final byte[] password; private final boolean rememberPassword; private final boolean isFirstAttempt; private final String subject; private ResourceManagementService resources = MUCActivator.getResources(); JoinChatRoomTask( ChatRoomWrapperImpl chatRoomWrapper, String nickName, byte[] password, boolean rememberPassword, boolean isFirstAttempt, String subject) { this.chatRoomWrapper = chatRoomWrapper; this.nickName = nickName; this.isFirstAttempt = isFirstAttempt; this.subject = subject; if (password == null) { String passString = chatRoomWrapper.loadPassword(); if (passString != null) { this.password = passString.getBytes(); } else { this.password = null; } } else { this.password = password; } this.rememberPassword = rememberPassword; } JoinChatRoomTask(ChatRoomWrapperImpl chatRoomWrapper, String nickName, byte[] password) { this(chatRoomWrapper, nickName, password, false, true, null); } JoinChatRoomTask( ChatRoomWrapperImpl chatRoomWrapper, String nickName, byte[] password, String subject) { this(chatRoomWrapper, nickName, password, false, true, subject); } /** @override {@link Thread}{@link #run()} to perform all asynchronous tasks. */ @Override public void run() { ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); try { if (password != null && password.length > 0) chatRoom.joinAs(nickName, password); else if (nickName != null) chatRoom.joinAs(nickName); else chatRoom.join(); done(JOIN_SUCCESS_PROP); } catch (OperationFailedException e) { if (logger.isTraceEnabled()) logger.trace("Failed to join chat room: " + chatRoom.getName(), e); switch (e.getErrorCode()) { case OperationFailedException.AUTHENTICATION_FAILED: done(JOIN_AUTHENTICATION_FAILED_PROP); break; case OperationFailedException.REGISTRATION_REQUIRED: done(JOIN_REGISTRATION_REQUIRED_PROP); break; case OperationFailedException.PROVIDER_NOT_REGISTERED: done(JOIN_PROVIDER_NOT_REGISTERED_PROP); break; case OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS: done(JOIN_SUBSCRIPTION_ALREADY_EXISTS_PROP); break; default: done(JOIN_UNKNOWN_ERROR_PROP); } } } /** * Performs UI changes after the chat room join task has finished. * * @param returnCode the result code from the chat room join task. */ private void done(String returnCode) { ConfigurationUtils.updateChatRoomStatus( chatRoomWrapper.getParentProvider().getProtocolProvider(), chatRoomWrapper.getChatRoomID(), GlobalStatusEnum.ONLINE_STATUS); String errorMessage = null; if (JOIN_AUTHENTICATION_FAILED_PROP.equals(returnCode)) { chatRoomWrapper.removePassword(); AuthenticationWindowService authWindowsService = ServiceUtils.getService(MUCActivator.bundleContext, AuthenticationWindowService.class); AuthenticationWindowService.AuthenticationWindow authWindow = authWindowsService.create( null, null, null, false, chatRoomWrapper.isPersistent(), AuthenticationWindow.getAuthenticationWindowIcon( chatRoomWrapper.getParentProvider().getProtocolProvider()), resources.getI18NString( "service.gui.AUTHENTICATION_WINDOW_TITLE", new String[] {chatRoomWrapper.getParentProvider().getName()}), resources.getI18NString( "service.gui.CHAT_ROOM_REQUIRES_PASSWORD", new String[] {chatRoomWrapper.getChatRoomName()}), "", null, isFirstAttempt ? null : resources.getI18NString( "service.gui.AUTHENTICATION_FAILED", new String[] {chatRoomWrapper.getChatRoomName()}), null); authWindow.setVisible(true); if (!authWindow.isCanceled()) { joinChatRoom( chatRoomWrapper, nickName, new String(authWindow.getPassword()).getBytes(), authWindow.isRememberPassword(), false, subject); } } else if (JOIN_REGISTRATION_REQUIRED_PROP.equals(returnCode)) { errorMessage = resources.getI18NString( "service.gui.CHAT_ROOM_REGISTRATION_REQUIRED", new String[] {chatRoomWrapper.getChatRoomName()}); } else if (JOIN_PROVIDER_NOT_REGISTERED_PROP.equals(returnCode)) { errorMessage = resources.getI18NString( "service.gui.CHAT_ROOM_NOT_CONNECTED", new String[] {chatRoomWrapper.getChatRoomName()}); } else if (JOIN_SUBSCRIPTION_ALREADY_EXISTS_PROP.equals(returnCode)) { errorMessage = resources.getI18NString( "service.gui.CHAT_ROOM_ALREADY_JOINED", new String[] {chatRoomWrapper.getChatRoomName()}); } else { errorMessage = resources.getI18NString( "service.gui.FAILED_TO_JOIN_CHAT_ROOM", new String[] {chatRoomWrapper.getChatRoomName()}); } if (!JOIN_SUCCESS_PROP.equals(returnCode) && !JOIN_AUTHENTICATION_FAILED_PROP.equals(returnCode)) { MUCActivator.getAlertUIService() .showAlertPopup(resources.getI18NString("service.gui.ERROR"), errorMessage); } if (JOIN_SUCCESS_PROP.equals(returnCode)) { if (rememberPassword) { chatRoomWrapper.savePassword(new String(password)); } if (subject != null) { try { chatRoomWrapper.getChatRoom().setSubject(subject); } catch (OperationFailedException ex) { logger.warn("Failed to set subject."); } } } chatRoomWrapper.firePropertyChange(returnCode); } } /** * Finds the <tt>ChatRoomWrapper</tt> instance associated with the source contact. * * @param contact the source contact. * @return the <tt>ChatRoomWrapper</tt> instance. */ public ChatRoomWrapper findChatRoomWrapperFromSourceContact(SourceContact contact) { if (!(contact instanceof ChatRoomSourceContact)) return null; ChatRoomSourceContact chatRoomContact = (ChatRoomSourceContact) contact; return chatRoomList.findChatRoomWrapperFromChatRoomID( chatRoomContact.getChatRoomID(), chatRoomContact.getProvider()); } /** * Finds the <tt>ChatRoomWrapper</tt> instance associated with the chat room. * * @param chatRoomID the id of the chat room. * @param pps the provider of the chat room. * @return the <tt>ChatRoomWrapper</tt> instance. */ public ChatRoomWrapper findChatRoomWrapperFromChatRoomID( String chatRoomID, ProtocolProviderService pps) { return chatRoomList.findChatRoomWrapperFromChatRoomID(chatRoomID, pps); } /** * Searches for chat room wrapper in chat room list by chat room. * * @param chatRoom the chat room. * @param create if <tt>true</tt> and the chat room wrapper is not found new chatRoomWrapper is * created. * @return found chat room wrapper or the created chat room wrapper. */ @Override public ChatRoomWrapper getChatRoomWrapperByChatRoom(ChatRoom chatRoom, boolean create) { ChatRoomWrapper chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom); if ((chatRoomWrapper == null) && create) { ChatRoomProviderWrapper parentProvider = chatRoomList.findServerWrapperFromProvider(chatRoom.getParentProvider()); chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom); chatRoomList.addChatRoom(chatRoomWrapper); } return chatRoomWrapper; } /** * Goes through the locally stored chat rooms list and for each {@link ChatRoomWrapper} tries to * find the corresponding server stored {@link ChatRoom} in the specified operation set. Joins * automatically all found chat rooms. * * @param protocolProvider the protocol provider for the account to synchronize * @param opSet the multi user chat operation set, which give us access to chat room server */ public void synchronizeOpSetWithLocalContactList( ProtocolProviderService protocolProvider, final OperationSetMultiUserChat opSet) { ChatRoomProviderWrapper chatRoomProvider = findServerWrapperFromProvider(protocolProvider); if (chatRoomProvider == null) { chatRoomProvider = chatRoomList.addRegisteredChatProvider(protocolProvider); } if (chatRoomProvider != null) { chatRoomProvider.synchronizeProvider(); } } /** * Returns an iterator to the list of chat room providers. * * @return an iterator to the list of chat room providers. */ public Iterator<ChatRoomProviderWrapper> getChatRoomProviders() { return chatRoomList.getChatRoomProviders(); } /** * Removes the given <tt>ChatRoom</tt> from the list of all chat rooms. * * @param chatRoomWrapper the <tt>ChatRoomWrapper</tt> to remove */ public void removeChatRoom(ChatRoomWrapper chatRoomWrapper) { chatRoomList.removeChatRoom(chatRoomWrapper); } /** * Destroys the given <tt>ChatRoom</tt> from the list of all chat rooms. * * @param chatRoomWrapper the <tt>ChatRoomWrapper</tt> to be destroyed. * @param reason the reason for destroying. * @param alternateAddress the alternate address. */ public void destroyChatRoom( ChatRoomWrapper chatRoomWrapper, String reason, String alternateAddress) { if (chatRoomWrapper.getChatRoom().destroy(reason, alternateAddress)) { MUCActivator.getUIService().closeChatRoomWindow(chatRoomWrapper); chatRoomList.removeChatRoom(chatRoomWrapper); } else { // if we leave a chat room which is not persistent // the room can be destroyed on the server, and error is returned // when we try to destroy it not-authorized(401) if (!chatRoomWrapper.getChatRoom().isPersistent() && !chatRoomWrapper.getChatRoom().isJoined()) { chatRoomList.removeChatRoom(chatRoomWrapper); } } } /** * Adds a ChatRoomProviderWrapperListener to the listener list. * * @param listener the ChatRoomProviderWrapperListener to be added */ public void addChatRoomProviderWrapperListener(ChatRoomProviderWrapperListener listener) { chatRoomList.addChatRoomProviderWrapperListener(listener); } /** * Removes the ChatRoomProviderWrapperListener to the listener list. * * @param listener the ChatRoomProviderWrapperListener to be removed */ public void removeChatRoomProviderWrapperListener(ChatRoomProviderWrapperListener listener) { chatRoomList.removeChatRoomProviderWrapperListener(listener); } /** * Returns the <tt>ChatRoomProviderWrapper</tt> that correspond to the given * <tt>ProtocolProviderService</tt>. If the list doesn't contain a corresponding wrapper - returns * null. * * @param protocolProvider the protocol provider that we're looking for * @return the <tt>ChatRoomProvider</tt> object corresponding to the given * <tt>ProtocolProviderService</tt> */ public ChatRoomProviderWrapper findServerWrapperFromProvider( ProtocolProviderService protocolProvider) { return chatRoomList.findServerWrapperFromProvider(protocolProvider); } /** * Returns the <tt>ChatRoomWrapper</tt> that correspond to the given <tt>ChatRoom</tt>. If the * list of chat rooms doesn't contain a corresponding wrapper - returns null. * * @param chatRoom the <tt>ChatRoom</tt> that we're looking for * @return the <tt>ChatRoomWrapper</tt> object corresponding to the given <tt>ChatRoom</tt> */ public ChatRoomWrapper findChatRoomWrapperFromChatRoom(ChatRoom chatRoom) { return chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom); } /** * Opens a chat window for the chat room. * * @param room the chat room. */ public void openChatRoom(ChatRoomWrapper room) { if (room.getChatRoom() == null) { room = createChatRoom( room.getChatRoomName(), room.getParentProvider().getProtocolProvider(), new ArrayList<String>(), "", false, false, true); // leave the chatroom because getChatRoom().isJoined() returns true // otherwise if (room.getChatRoom().isJoined()) room.getChatRoom().leave(); } if (!room.getChatRoom().isJoined()) { String savedNick = ConfigurationUtils.getChatRoomProperty( room.getParentProvider().getProtocolProvider(), room.getChatRoomID(), "userNickName"); String subject = null; if (savedNick == null) { String[] joinOptions = ChatRoomJoinOptionsDialog.getJoinOptions( room.getParentProvider().getProtocolProvider(), room.getChatRoomID(), getDefaultNickname(room.getParentProvider().getProtocolProvider())); savedNick = joinOptions[0]; subject = joinOptions[1]; } if (savedNick != null) { joinChatRoom(room, savedNick, null, subject); } else return; } MUCActivator.getUIService().openChatRoomWindow(room); } /** * Returns default nickname for chat room based on the given provider. * * @param pps the given protocol provider service * @return default nickname for chat room based on the given provider. */ public String getDefaultNickname(ProtocolProviderService pps) { final OperationSetServerStoredAccountInfo accountInfoOpSet = pps.getOperationSet(OperationSetServerStoredAccountInfo.class); String displayName = ""; if (accountInfoOpSet != null) { displayName = AccountInfoUtils.getDisplayName(accountInfoOpSet); } if (displayName == null || displayName.length() == 0) { displayName = MUCActivator.getGlobalDisplayDetailsService().getGlobalDisplayName(); if (displayName == null || displayName.length() == 0) { displayName = pps.getAccountID().getUserID(); if (displayName != null) { int atIndex = displayName.lastIndexOf("@"); if (atIndex > 0) displayName = displayName.substring(0, atIndex); } } } return displayName; } /** * Returns instance of the <tt>ServerChatRoomContactSourceService</tt> contact source. * * @return instance of the <tt>ServerChatRoomContactSourceService</tt> contact source. */ public ContactSourceService getServerChatRoomsContactSourceForProvider( ChatRoomProviderWrapper pps) { return new ServerChatRoomContactSourceService(pps); } /** * Returns <tt>true</tt> if the contact is <tt>ChatRoomSourceContact</tt> * * @param contact the contact * @return <tt>true</tt> if the contact is <tt>ChatRoomSourceContact</tt> */ public boolean isMUCSourceContact(SourceContact contact) { return (contact instanceof ChatRoomSourceContact); } }
/** * The ProtocolProviderFactory is what actually creates instances of a ProtocolProviderService * implementation. A provider factory would register, persistently store, and remove when necessary, * ProtocolProviders. The way things are in the SIP Communicator, a user account is represented (in * a 1:1 relationship) by an AccountID and a ProtocolProvider. In other words - one would have as * many protocol providers installed in a given moment as they would user account registered through * the various services. * * @author Emil Ivov * @author Lubomir Marinov */ public abstract class ProtocolProviderFactory { /** * The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class and its instances for * logging output. */ private static final Logger logger = Logger.getLogger(ProtocolProviderFactory.class); /** Then name of a property which represents a password. */ public static final String PASSWORD = "******"; /** * The name of a property representing the name of the protocol for an ProtocolProviderFactory. */ public static final String PROTOCOL = "PROTOCOL_NAME"; /** The name of a property representing the path to protocol icons. */ public static final String PROTOCOL_ICON_PATH = "PROTOCOL_ICON_PATH"; /** * The name of a property representing the path to the account icon to be used in the user * interface, when the protocol provider service is not available. */ public static final String ACCOUNT_ICON_PATH = "ACCOUNT_ICON_PATH"; /** * The name of a property which represents the AccountID of a ProtocolProvider and that, together * with a password is used to login on the protocol network.. */ public static final String USER_ID = "USER_ID"; /** The name that should be displayed to others when we are calling or writing them. */ public static final String DISPLAY_NAME = "DISPLAY_NAME"; /** The name that should be displayed to the user on call via and chat via lists. */ public static final String ACCOUNT_DISPLAY_NAME = "ACCOUNT_DISPLAY_NAME"; /** The name of the property under which we store protocol AccountID-s. */ public static final String ACCOUNT_UID = "ACCOUNT_UID"; /** * The name of the property under which we store protocol the address of a protocol centric entity * (any protocol server). */ public static final String SERVER_ADDRESS = "SERVER_ADDRESS"; /** * The name of the property under which we store the number of the port where the server stored * against the SERVER_ADDRESS property is expecting connections to be made via this protocol. */ public static final String SERVER_PORT = "SERVER_PORT"; /** * The name of the property under which we store the name of the transport protocol that needs to * be used to access the server. */ public static final String SERVER_TRANSPORT = "SERVER_TRANSPORT"; /** The name of the property under which we store protocol the address of a protocol proxy. */ public static final String PROXY_ADDRESS = "PROXY_ADDRESS"; /** * The name of the property under which we store the number of the port where the proxy stored * against the PROXY_ADDRESS property is expecting connections to be made via this protocol. */ public static final String PROXY_PORT = "PROXY_PORT"; /** * The name of the property which defines whether proxy is auto configured by the protocol by * using known methods such as specific DNS queries. */ public static final String PROXY_AUTO_CONFIG = "PROXY_AUTO_CONFIG"; /** The property indicating the preferred UDP and TCP port to bind to for clear communications. */ public static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME = "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT"; /** The property indicating the preferred TLS (TCP) port to bind to for secure communications. */ public static final String PREFERRED_SECURE_PORT_PROPERTY_NAME = "net.java.sip.communicator.SIP_PREFERRED_SECURE_PORT"; /** * The name of the property under which we store the the type of the proxy stored against the * PROXY_ADDRESS property. Exact type values depend on protocols and among them are socks4, * socks5, http and possibly others. */ public static final String PROXY_TYPE = "PROXY_TYPE"; /** * The name of the property under which we store the the username for the proxy stored against the * PROXY_ADDRESS property. */ public static final String PROXY_USERNAME = "******"; /** * The name of the property under which we store the the authorization name for the proxy stored * against the PROXY_ADDRESS property. */ public static final String AUTHORIZATION_NAME = "AUTHORIZATION_NAME"; /** * The name of the property under which we store the password for the proxy stored against the * PROXY_ADDRESS property. */ public static final String PROXY_PASSWORD = "******"; /** * The name of the property under which we store the name of the transport protocol that needs to * be used to access the proxy. */ public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT"; /** * The name of the property that indicates whether loose routing should be forced for all traffic * in an account, rather than routing through an outbound proxy which is the default for Jitsi. */ public static final String FORCE_PROXY_BYPASS = "******"; /** * The name of the property under which we store the user preference for a transport protocol to * use (i.e. tcp or udp). */ public static final String PREFERRED_TRANSPORT = "PREFERRED_TRANSPORT"; /** * The name of the property under which we store whether we generate resource values or we just * use the stored one. */ public static final String AUTO_GENERATE_RESOURCE = "AUTO_GENERATE_RESOURCE"; /** * The name of the property under which we store resources such as the jabber resource property. */ public static final String RESOURCE = "RESOURCE"; /** The name of the property under which we store resource priority. */ public static final String RESOURCE_PRIORITY = "RESOURCE_PRIORITY"; /** The name of the property which defines that the call is encrypted by default */ public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION"; /** The name of the property that indicates the encryption protocols for this account. */ public static final String ENCRYPTION_PROTOCOL = "ENCRYPTION_PROTOCOL"; /** * The name of the property that indicates the status (enabed or disabled) encryption protocols * for this account. */ public static final String ENCRYPTION_PROTOCOL_STATUS = "ENCRYPTION_PROTOCOL_STATUS"; /** The name of the property which defines if to include the ZRTP attribute to SIP/SDP */ public static final String DEFAULT_SIPZRTP_ATTRIBUTE = "DEFAULT_SIPZRTP_ATTRIBUTE"; /** * The name of the property which defines the ID of the client TLS certificate configuration * entry. */ public static final String CLIENT_TLS_CERTIFICATE = "CLIENT_TLS_CERTIFICATE"; /** * The name of the property under which we store the boolean value indicating if the user name * should be automatically changed if the specified name already exists. This property is meant to * be used by IRC implementations. */ public static final String AUTO_CHANGE_USER_NAME = "AUTO_CHANGE_USER_NAME"; /** * The name of the property under which we store the boolean value indicating if a password is * required. Initially this property is meant to be used by IRC implementations. */ public static final String NO_PASSWORD_REQUIRED = "NO_PASSWORD_REQUIRED"; /** The name of the property under which we store if the presence is enabled. */ public static final String IS_PRESENCE_ENABLED = "IS_PRESENCE_ENABLED"; /** The name of the property under which we store if the p2p mode for SIMPLE should be forced. */ public static final String FORCE_P2P_MODE = "FORCE_P2P_MODE"; /** * The name of the property under which we store the offline contact polling period for SIMPLE. */ public static final String POLLING_PERIOD = "POLLING_PERIOD"; /** * The name of the property under which we store the chosen default subscription expiration value * for SIMPLE. */ public static final String SUBSCRIPTION_EXPIRATION = "SUBSCRIPTION_EXPIRATION"; /** Indicates if the server address has been validated. */ public static final String SERVER_ADDRESS_VALIDATED = "SERVER_ADDRESS_VALIDATED"; /** Indicates if the server settings are over */ public static final String IS_SERVER_OVERRIDDEN = "IS_SERVER_OVERRIDDEN"; /** Indicates if the proxy address has been validated. */ public static final String PROXY_ADDRESS_VALIDATED = "PROXY_ADDRESS_VALIDATED"; /** Indicates the search strategy chosen for the DICT protocole. */ public static final String STRATEGY = "STRATEGY"; /** Indicates a protocol that would not be shown in the user interface as an account. */ public static final String IS_PROTOCOL_HIDDEN = "IS_PROTOCOL_HIDDEN"; /** Indicates if the given account is the preferred account. */ public static final String IS_PREFERRED_PROTOCOL = "IS_PREFERRED_PROTOCOL"; /** * The name of the property that would indicate if a given account is currently enabled or * disabled. */ public static final String IS_ACCOUNT_DISABLED = "IS_ACCOUNT_DISABLED"; /** Indicates if ICE should be used. */ public static final String IS_USE_ICE = "ICE_ENABLED"; /** Indicates if Google ICE should be used. */ public static final String IS_USE_GOOGLE_ICE = "GTALK_ICE_ENABLED"; /** Indicates if STUN server should be automatically discovered. */ public static final String AUTO_DISCOVER_STUN = "AUTO_DISCOVER_STUN"; /** Indicates if default STUN server would be used if no other STUN/TURN server are available. */ public static final String USE_DEFAULT_STUN_SERVER = "USE_DEFAULT_STUN_SERVER"; /** * The name of the boolean account property which indicates whether Jitsi VideoBridge is to be * used, if available and supported, for conference calls. */ public static final String USE_JITSI_VIDEO_BRIDGE = "USE_JITSI_VIDEO_BRIDGE"; /** * The property name prefix for all stun server properties. We generally use this prefix in * conjunction with an index which is how we store multiple servers. */ public static final String STUN_PREFIX = "STUN"; /** The base property name for address of additional STUN servers specified. */ public static final String STUN_ADDRESS = "ADDRESS"; /** The base property name for port of additional STUN servers specified. */ public static final String STUN_PORT = "PORT"; /** The base property name for username of additional STUN servers specified. */ public static final String STUN_USERNAME = "******"; /** The base property name for password of additional STUN servers specified. */ public static final String STUN_PASSWORD = "******"; /** * The base property name for the turn supported property of additional STUN servers specified. */ public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED"; /** Indicates if JingleNodes should be used with ICE. */ public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED"; /** Indicates if JingleNodes should be used with ICE. */ public static final String AUTO_DISCOVER_JINGLE_NODES = "AUTO_DISCOVER_JINGLE_NODES"; /** Indicates if JingleNodes should use buddies to search for nodes. */ public static final String JINGLE_NODES_SEARCH_BUDDIES = "JINGLE_NODES_SEARCH_BUDDIES"; /** Indicates if UPnP should be used with ICE. */ public static final String IS_USE_UPNP = "UPNP_ENABLED"; /** Indicates if we allow non-TLS connection. */ public static final String IS_ALLOW_NON_SECURE = "ALLOW_NON_SECURE"; /** Enable notifications for new voicemail messages. */ public static final String VOICEMAIL_ENABLED = "VOICEMAIL_ENABLED"; /** * Address used to reach voicemail box, by services able to subscribe for voicemail new messages * notifications. */ public static final String VOICEMAIL_URI = "VOICEMAIL_URI"; /** Address used to call to hear your messages stored on the server for your voicemail. */ public static final String VOICEMAIL_CHECK_URI = "VOICEMAIL_CHECK_URI"; /** Indicates if calling is disabled for a certain account. */ public static final String IS_CALLING_DISABLED_FOR_ACCOUNT = "CALLING_DISABLED"; /** Indicates if desktop streaming/sharing is disabled for a certain account. */ public static final String IS_DESKTOP_STREAMING_DISABLED = "DESKTOP_STREAMING_DISABLED"; /** The sms default server address. */ public static final String SMS_SERVER_ADDRESS = "SMS_SERVER_ADDRESS"; /** Keep-alive method used by the protocol. */ public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD"; /** The interval for keep-alives if any. */ public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL"; /** The minimal DTMF tone duration. */ public static final String DTMF_MINIMAL_TONE_DURATION = "DTMF_MINIMAL_TONE_DURATION"; /** Paranoia mode when turned on requires all calls to be secure and indicated as such. */ public static final String MODE_PARANOIA = "MODE_PARANOIA"; /** The name of the "override encodings" property */ public static final String OVERRIDE_ENCODINGS = "OVERRIDE_ENCODINGS"; /** The prefix used to store account encoding properties */ public static final String ENCODING_PROP_PREFIX = "Encodings"; /** * The <code>BundleContext</code> containing (or to contain) the service registration of this * factory. */ private final BundleContext bundleContext; /** * The name of the protocol this factory registers its <code>ProtocolProviderService</code>s with * and to be placed in the properties of the accounts created by this factory. */ private final String protocolName; /** * The table that we store our accounts in. * * <p>TODO Synchronize the access to the field which may in turn be better achieved by also hiding * it from protected into private access. */ protected final Hashtable<AccountID, ServiceRegistration> registeredAccounts = new Hashtable<AccountID, ServiceRegistration>(); /** * The name of the property that indicates the AVP type. * * <ul> * <li>{@link #SAVP_OFF} * <li>{@link #SAVP_MANDATORY} * <li>{@link #SAVP_OPTIONAL} * </ul> */ public static final String SAVP_OPTION = "SAVP_OPTION"; /** Always use RTP/AVP */ public static final int SAVP_OFF = 0; /** Always use RTP/SAVP */ public static final int SAVP_MANDATORY = 1; /** Sends two media description, with RTP/SAVP being first. */ public static final int SAVP_OPTIONAL = 2; /** * The name of the property that defines the enabled SDES cipher suites. Enabled suites are listed * as CSV by their RFC name. */ public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES"; /** * Creates a new <tt>ProtocolProviderFactory</tt>. * * @param bundleContext the bundle context reference of the service * @param protocolName the name of the protocol */ protected ProtocolProviderFactory(BundleContext bundleContext, String protocolName) { this.bundleContext = bundleContext; this.protocolName = protocolName; } /** * Gets the <code>BundleContext</code> containing (or to contain) the service registration of this * factory. * * @return the <code>BundleContext</code> containing (or to contain) the service registration of * this factory */ public BundleContext getBundleContext() { return bundleContext; } /** * Initializes and creates an account corresponding to the specified accountProperties and * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter. Note * that account registration is persistent and accounts that are registered during a particular * sip-communicator session would be automatically reloaded during all following sessions until * they are removed through the removeAccount method. * * @param userID the user identifier uniquely representing the newly created account within the * protocol namespace. * @param accountProperties a set of protocol (or implementation) specific properties defining the * new account. * @return the AccountID of the newly created account. * @throws java.lang.IllegalArgumentException if userID does not correspond to an identifier in * the context of the underlying protocol or if accountProperties does not contain a complete * set of account installation properties. * @throws java.lang.IllegalStateException if the account has already been installed. * @throws java.lang.NullPointerException if any of the arguments is null. */ public abstract AccountID installAccount(String userID, Map<String, String> accountProperties) throws IllegalArgumentException, IllegalStateException, NullPointerException; /** * Modifies the account corresponding to the specified accountID. This method is meant to be used * to change properties of already existing accounts. Note that if the given accountID doesn't * correspond to any registered account this method would do nothing. * * @param protocolProvider the protocol provider service corresponding to the modified account. * @param accountProperties a set of protocol (or implementation) specific properties defining the * new account. * @throws java.lang.NullPointerException if any of the arguments is null. */ public abstract void modifyAccount( ProtocolProviderService protocolProvider, Map<String, String> accountProperties) throws NullPointerException; /** * Returns a copy of the list containing the <tt>AccountID</tt>s of all accounts currently * registered in this protocol provider. * * @return a copy of the list containing the <tt>AccountID</tt>s of all accounts currently * registered in this protocol provider. */ public ArrayList<AccountID> getRegisteredAccounts() { synchronized (registeredAccounts) { return new ArrayList<AccountID>(registeredAccounts.keySet()); } } /** * Returns the ServiceReference for the protocol provider corresponding to the specified accountID * or null if the accountID is unknown. * * @param accountID the accountID of the protocol provider we'd like to get * @return a ServiceReference object to the protocol provider with the specified account id and * null if the account id is unknown to the provider factory. */ public ServiceReference getProviderForAccount(AccountID accountID) { ServiceRegistration registration; synchronized (registeredAccounts) { registration = registeredAccounts.get(accountID); } return (registration == null) ? null : registration.getReference(); } /** * Removes the specified account from the list of accounts that this provider factory is handling. * If the specified accountID is unknown to the ProtocolProviderFactory, the call has no effect * and false is returned. This method is persistent in nature and once called the account * corresponding to the specified ID will not be loaded during future runs of the project. * * @param accountID the ID of the account to remove. * @return true if an account with the specified ID existed and was removed and false otherwise. */ public boolean uninstallAccount(AccountID accountID) { // Unregister the protocol provider. ServiceReference serRef = getProviderForAccount(accountID); boolean wasAccountExisting = false; // If the protocol provider service is registered, first unregister the // service. if (serRef != null) { BundleContext bundleContext = getBundleContext(); ProtocolProviderService protocolProvider = (ProtocolProviderService) bundleContext.getService(serRef); try { protocolProvider.unregister(); } catch (OperationFailedException ex) { logger.error( "Failed to unregister protocol provider for account: " + accountID + " caused by: " + ex); } } ServiceRegistration registration; synchronized (registeredAccounts) { registration = registeredAccounts.remove(accountID); } // first remove the stored account so when PP is unregistered we can // distinguish between deleted or just disabled account wasAccountExisting = removeStoredAccount(accountID); if (registration != null) { // Kill the service. registration.unregister(); } return wasAccountExisting; } /** * The method stores the specified account in the configuration service under the package name of * the source factory. The restore and remove account methods are to be used to obtain access to * and control the stored accounts. * * <p>In order to store all account properties, the method would create an entry in the * configuration service corresponding (beginning with) the <tt>sourceFactory</tt>'s package name * and add to it a unique identifier (e.g. the current miliseconds.) * * @param accountID the AccountID corresponding to the account that we would like to store. */ protected void storeAccount(AccountID accountID) { this.storeAccount(accountID, true); } /** * The method stores the specified account in the configuration service under the package name of * the source factory. The restore and remove account methods are to be used to obtain access to * and control the stored accounts. * * <p>In order to store all account properties, the method would create an entry in the * configuration service corresponding (beginning with) the <tt>sourceFactory</tt>'s package name * and add to it a unique identifier (e.g. the current miliseconds.) * * @param accountID the AccountID corresponding to the account that we would like to store. * @param isModification if <tt>false</tt> there must be no such already loaded account, it * <tt>true</tt> ist modification of an existing account. Usually we use this method with * <tt>false</tt> in method installAccount and with <tt>true</tt> or the overridden method in * method modifyAccount. */ protected void storeAccount(AccountID accountID, boolean isModification) { if (!isModification && getAccountManager().getStoredAccounts().contains(accountID)) { throw new IllegalStateException( "An account for id " + accountID.getUserID() + " was already loaded!"); } try { getAccountManager().storeAccount(this, accountID); } catch (OperationFailedException ofex) { throw new UndeclaredThrowableException(ofex); } } /** * Saves the password for the specified account after scrambling it a bit so that it is not * visible from first sight. (The method remains highly insecure). * * @param accountID the AccountID for the account whose password we're storing * @param password the password itself * @throws IllegalArgumentException if no account corresponding to <code>accountID</code> has been * previously stored */ public void storePassword(AccountID accountID, String password) throws IllegalArgumentException { try { storePassword(getBundleContext(), accountID, password); } catch (OperationFailedException ofex) { throw new UndeclaredThrowableException(ofex); } } /** * Saves the password for the specified account after scrambling it a bit so that it is not * visible from first sight (Method remains highly insecure). * * <p>TODO Delegate the implementation to {@link AccountManager} because it knows the format in * which the password (among the other account properties) is to be saved. * * @param bundleContext a currently valid bundle context. * @param accountID the <tt>AccountID</tt> of the account whose password is to be stored * @param password the password to be stored * @throws IllegalArgumentException if no account corresponding to <tt>accountID</tt> has been * previously stored. * @throws OperationFailedException if anything goes wrong while storing the specified * <tt>password</tt> */ protected void storePassword(BundleContext bundleContext, AccountID accountID, String password) throws IllegalArgumentException, OperationFailedException { String accountPrefix = findAccountPrefix(bundleContext, accountID, getFactoryImplPackageName()); if (accountPrefix == null) { throw new IllegalArgumentException( "No previous records found for account ID: " + accountID.getAccountUniqueID() + " in package" + getFactoryImplPackageName()); } CredentialsStorageService credentialsStorage = ServiceUtils.getService(bundleContext, CredentialsStorageService.class); if (!credentialsStorage.storePassword(accountPrefix, password)) { throw new OperationFailedException( "CredentialsStorageService failed to storePassword", OperationFailedException.GENERAL_ERROR); } } /** * Returns the password last saved for the specified account. * * @param accountID the AccountID for the account whose password we're looking for * @return a String containing the password for the specified accountID */ public String loadPassword(AccountID accountID) { return loadPassword(getBundleContext(), accountID); } /** * Returns the password last saved for the specified account. * * <p>TODO Delegate the implementation to {@link AccountManager} because it knows the format in * which the password (among the other account properties) was saved. * * @param bundleContext a currently valid bundle context. * @param accountID the AccountID for the account whose password we're looking for.. * @return a String containing the password for the specified accountID. */ protected String loadPassword(BundleContext bundleContext, AccountID accountID) { String accountPrefix = findAccountPrefix(bundleContext, accountID, getFactoryImplPackageName()); if (accountPrefix == null) return null; CredentialsStorageService credentialsStorage = ServiceUtils.getService(bundleContext, CredentialsStorageService.class); return credentialsStorage.loadPassword(accountPrefix); } /** * Initializes and creates an account corresponding to the specified accountProperties and * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter. This * method has a persistent effect. Once created the resulting account will remain installed until * removed through the uninstallAccount method. * * @param accountProperties a set of protocol (or implementation) specific properties defining the * new account. * @return the AccountID of the newly loaded account */ public AccountID loadAccount(Map<String, String> accountProperties) { AccountID accountID = createAccount(accountProperties); loadAccount(accountID); return accountID; } /** * Creates a protocol provider for the given <tt>accountID</tt> and registers it in the bundle * context. This method has a persistent effect. Once created the resulting account will remain * installed until removed through the uninstallAccount method. * * @param accountID the account identifier * @return <tt>true</tt> if the account with the given <tt>accountID</tt> is successfully loaded, * otherwise returns <tt>false</tt> */ public boolean loadAccount(AccountID accountID) { // Need to obtain the original user id property, instead of calling // accountID.getUserID(), because this method could return a modified // version of the user id property. String userID = accountID.getAccountPropertyString(ProtocolProviderFactory.USER_ID); ProtocolProviderService service = createService(userID, accountID); Dictionary<String, String> properties = new Hashtable<String, String>(); properties.put(PROTOCOL, protocolName); properties.put(USER_ID, userID); ServiceRegistration serviceRegistration = bundleContext.registerService(ProtocolProviderService.class.getName(), service, properties); if (serviceRegistration == null) return false; else { synchronized (registeredAccounts) { registeredAccounts.put(accountID, serviceRegistration); } return true; } } /** * Unloads the account corresponding to the given <tt>accountID</tt>. Unregisters the * corresponding protocol provider, but keeps the account in contrast to the uninstallAccount * method. * * @param accountID the account identifier * @return true if an account with the specified ID existed and was unloaded and false otherwise. */ public boolean unloadAccount(AccountID accountID) { // Unregister the protocol provider. ServiceReference serRef = getProviderForAccount(accountID); if (serRef == null) { return false; } BundleContext bundleContext = getBundleContext(); ProtocolProviderService protocolProvider = (ProtocolProviderService) bundleContext.getService(serRef); try { protocolProvider.unregister(); } catch (OperationFailedException ex) { logger.error( "Failed to unregister protocol provider for account : " + accountID + " caused by: " + ex); } ServiceRegistration registration; synchronized (registeredAccounts) { registration = registeredAccounts.remove(accountID); } if (registration == null) { return false; } // Kill the service. registration.unregister(); return true; } /** * Initializes and creates an account corresponding to the specified accountProperties. * * @param accountProperties a set of protocol (or implementation) specific properties defining the * new account. * @return the AccountID of the newly created account */ public AccountID createAccount(Map<String, String> accountProperties) { BundleContext bundleContext = getBundleContext(); if (bundleContext == null) throw new NullPointerException("The specified BundleContext was null"); if (accountProperties == null) throw new NullPointerException("The specified property map was null"); String userID = accountProperties.get(USER_ID); if (userID == null) throw new NullPointerException("The account properties contained no user id."); String protocolName = getProtocolName(); if (!accountProperties.containsKey(PROTOCOL)) accountProperties.put(PROTOCOL, protocolName); return createAccountID(userID, accountProperties); } /** * Creates a new <code>AccountID</code> instance with a specific user ID to represent a given set * of account properties. * * <p>The method is a pure factory allowing implementers to specify the runtime type of the * created <code>AccountID</code> and customize the instance. The returned <code>AccountID</code> * will later be associated with a <code>ProtocolProviderService</code> by the caller (e.g. using * {@link #createService(String, AccountID)}). * * @param userID the user ID of the new instance * @param accountProperties the set of properties to be represented by the new instance * @return a new <code>AccountID</code> instance with the specified user ID representing the given * set of account properties */ protected abstract AccountID createAccountID( String userID, Map<String, String> accountProperties); /** * Gets the name of the protocol this factory registers its <code>ProtocolProviderService</code>s * with and to be placed in the properties of the accounts created by this factory. * * @return the name of the protocol this factory registers its <code>ProtocolProviderService * </code>s with and to be placed in the properties of the accounts created by this factory */ public String getProtocolName() { return protocolName; } /** * Initializes a new <code>ProtocolProviderService</code> instance with a specific user ID to * represent a specific <code>AccountID</code>. * * <p>The method is a pure factory allowing implementers to specify the runtime type of the * created <code>ProtocolProviderService</code> and customize the instance. The caller will later * register the returned service with the <code>BundleContext</code> of this factory. * * @param userID the user ID to initialize the new instance with * @param accountID the <code>AccountID</code> to be represented by the new instance * @return a new <code>ProtocolProviderService</code> instance with the specific user ID * representing the specified <code>AccountID</code> */ protected abstract ProtocolProviderService createService(String userID, AccountID accountID); /** * Removes the account with <tt>accountID</tt> from the set of accounts that are persistently * stored inside the configuration service. * * @param accountID the AccountID of the account to remove. * @return true if an account has been removed and false otherwise. */ protected boolean removeStoredAccount(AccountID accountID) { return getAccountManager().removeStoredAccount(this, accountID); } /** * Returns the prefix for all persistently stored properties of the account with the specified id. * * @param bundleContext a currently valid bundle context. * @param accountID the AccountID of the account whose properties we're looking for. * @param sourcePackageName a String containing the package name of the concrete factory class * that extends us. * @return a String indicating the ConfigurationService property name prefix under which all * account properties are stored or null if no account corresponding to the specified id was * found. */ public static String findAccountPrefix( BundleContext bundleContext, AccountID accountID, String sourcePackageName) { ServiceReference confReference = bundleContext.getServiceReference(ConfigurationService.class.getName()); ConfigurationService configurationService = (ConfigurationService) bundleContext.getService(confReference); // first retrieve all accounts that we've registered List<String> storedAccounts = configurationService.getPropertyNamesByPrefix(sourcePackageName, true); // find an account with the corresponding id. for (String accountRootPropertyName : storedAccounts) { // unregister the account in the configuration service. // all the properties must have been registered in the following // hierarchy: // net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME String accountUID = configurationService.getString( accountRootPropertyName // node id + "." + ACCOUNT_UID); // propname if (accountID.getAccountUniqueID().equals(accountUID)) { return accountRootPropertyName; } } return null; } /** * Returns the name of the package that we're currently running in (i.e. the name of the package * containing the proto factory that extends us). * * @return a String containing the package name of the concrete factory class that extends us. */ private String getFactoryImplPackageName() { String className = getClass().getName(); return className.substring(0, className.lastIndexOf('.')); } /** Prepares the factory for bundle shutdown. */ public void stop() { if (logger.isTraceEnabled()) logger.trace("Preparing to stop all protocol providers of" + this); synchronized (registeredAccounts) { for (Enumeration<ServiceRegistration> registrations = registeredAccounts.elements(); registrations.hasMoreElements(); ) { ServiceRegistration reg = registrations.nextElement(); stop(reg); reg.unregister(); } registeredAccounts.clear(); } } /** * Shuts down the <code>ProtocolProviderService</code> representing an account registered with * this factory. * * @param registeredAccount the <code>ServiceRegistration</code> of the <code> * ProtocolProviderService</code> representing an account registered with this factory */ protected void stop(ServiceRegistration registeredAccount) { ProtocolProviderService protocolProviderService = (ProtocolProviderService) getBundleContext().getService(registeredAccount.getReference()); protocolProviderService.shutdown(); } /** * Get the <tt>AccountManager</tt> of the protocol. * * @return <tt>AccountManager</tt> of the protocol */ private AccountManager getAccountManager() { BundleContext bundleContext = getBundleContext(); ServiceReference serviceReference = bundleContext.getServiceReference(AccountManager.class.getName()); return (AccountManager) bundleContext.getService(serviceReference); } }
/** * The <tt>AuthenticationWindow</tt> is the window where the user should type his user identifier * and password to login. * * @author Yana Stamcheva */ public class AuthenticationWindow extends SIPCommDialog implements ActionListener { private static final long serialVersionUID = 1L; /** Used for logging. */ private static Logger logger = Logger.getLogger(AuthenticationWindow.class); /** Info text area. */ private final JTextArea infoTextArea = new JTextArea(); /** The uin component. */ private JComponent uinValue; /** The password field. */ private final JPasswordField passwdField = new JPasswordField(15); /** The login button. */ private final JButton loginButton = new JButton(DesktopUtilActivator.getResources().getI18NString("service.gui.OK")); /** The cancel button. */ private final JButton cancelButton = new JButton(DesktopUtilActivator.getResources().getI18NString("service.gui.CANCEL")); /** The check box indicating if the password should be remembered. */ private final JCheckBox rememberPassCheckBox = new SIPCommCheckBox( DesktopUtilActivator.getResources().getI18NString("service.gui.REMEMBER_PASSWORD"), DesktopUtilActivator.getConfigurationService() .getBoolean(PNAME_SAVE_PASSWORD_TICKED, false)); /** * Property to disable/enable allow save password option in authentication window. By default it * is enabled. */ private static final String PNAME_ALLOW_SAVE_PASSWORD = "******"; /** * Property to set whether the save password option in the authentication window is ticked by * default or not. By default it is not ticked */ private static final String PNAME_SAVE_PASSWORD_TICKED = "net.java.sip.communicator.util.swing.auth.SAVE_PASSWORD_TICKED"; /** The name of the server, for which this authentication window is about. */ private String server; /** The user name. */ private String userName; /** The password. */ private char[] password; /** Indicates if the password should be remembered. */ private boolean isRememberPassword = false; /** Indicates if the window has been canceled. */ private boolean isCanceled = false; /** A lock used to synchronize data setting. */ private final Object lock = new Object(); /** The condition that decides whether to continue waiting for data. */ private boolean buttonClicked = false; /** Used to override default Authentication window title. */ private String windowTitle = null; /** Used to override default window text. */ private String windowText = null; /** Used to override username label text. */ private String usernameLabelText = null; /** Used to override password label text. */ private String passwordLabelText = null; /** The sign up link if specified. */ private String signupLink = null; /** * Creates an instance of the <tt>LoginWindow</tt>. * * @param server the server name * @param isUserNameEditable indicates if the user name is editable * @param icon the icon to display on the left of the authentication window */ public AuthenticationWindow(String server, boolean isUserNameEditable, ImageIcon icon) { this(null, null, server, isUserNameEditable, false, icon, null, null, null, null, null, null); } /** * Creates an instance of the <tt>LoginWindow</tt>. * * @param server the server name * @param isUserNameEditable indicates if the user name is editable * @param icon the icon to display on the left of the authentication window * @param windowTitle customized window title * @param windowText customized window text * @param usernameLabelText customized username field label text * @param passwordLabelText customized password field label text * @param errorMessage an error message if this dialog is shown to indicate the user that * something went wrong * @param signupLink an URL that allows the user to sign up */ private AuthenticationWindow( String userName, char[] password, String server, boolean isUserNameEditable, boolean isRememberPassword, ImageIcon icon, String windowTitle, String windowText, String usernameLabelText, String passwordLabelText, String errorMessage, String signupLink) { super(false); this.windowTitle = windowTitle; this.windowText = windowText; this.usernameLabelText = usernameLabelText; this.passwordLabelText = passwordLabelText; this.isRememberPassword = isRememberPassword; this.signupLink = signupLink; init(userName, password, server, isUserNameEditable, icon, errorMessage); } /** * Initializes this authentication window. * * @param server the server * @param isUserNameEditable indicates if the user name is editable * @param icon the icon to show on the authentication window */ private void init( String userName, char[] password, String server, boolean isUserNameEditable, ImageIcon icon, String errorMessage) { this.server = server; initIcon(icon); if (!isUserNameEditable) this.uinValue = new JLabel(); else this.uinValue = new JTextField(); this.init(); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.enableKeyActions(); this.setResizable(false); /* * Workaround for the following bug: * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4446522 * Need to pack() the window after it's opened in order to obtain the * correct size of our infoTextArea, otherwise window size is wrong and * buttons on the south are cut. */ this.addWindowListener( new WindowAdapter() { public void windowOpened(WindowEvent e) { pack(); removeWindowListener(this); } }); if (OSUtils.IS_MAC) getRootPane().putClientProperty("apple.awt.brushMetalLook", Boolean.TRUE); if (userName != null) { if (uinValue instanceof JLabel) ((JLabel) uinValue).setText(userName); else if (uinValue instanceof JTextField) ((JTextField) uinValue).setText(userName); } if (password != null) passwdField.setText(new String(password)); if (errorMessage != null) { this.infoTextArea.setForeground(Color.RED); this.infoTextArea.setText(errorMessage); } } /** * Creates an instance of the <tt>LoginWindow</tt>. * * @param userName the user name to set by default * @param password the password to set by default * @param server the server name this authentication window is about * @param isUserNameEditable indicates if the user name should be editable by the user or not * @param icon the icon displayed on the left of the authentication window * @param errorMessage an error message explaining a reason for opening the authentication dialog * (when a wrong password was provided, etc.) */ public AuthenticationWindow( String userName, char[] password, String server, boolean isUserNameEditable, ImageIcon icon, String errorMessage) { this( userName, password, server, isUserNameEditable, false, icon, null, null, null, null, errorMessage, null); } /** * Creates an instance of the <tt>LoginWindow</tt>. * * @param userName the user name to set by default * @param password the password to set by default * @param server the server name this authentication window is about * @param isUserNameEditable indicates if the user name should be editable by the user or not * @param icon the icon displayed on the left of the authentication window * @param errorMessage an error message explaining a reason for opening the authentication dialog * (when a wrong password was provided, etc.) * @param signupLink an URL that allows the user to sign up */ public AuthenticationWindow( String userName, char[] password, String server, boolean isUserNameEditable, ImageIcon icon, String errorMessage, String signupLink) { this( userName, password, server, isUserNameEditable, false, icon, null, null, null, null, errorMessage, signupLink); } /** * Creates an instance of the <tt>LoginWindow</tt>. * * @param userName the user name to set by default * @param password the password to set by default * @param server the server name this authentication window is about * @param isUserNameEditable indicates if the user name should be editable by the user or not * @param icon the icon displayed on the left of the authentication window */ public AuthenticationWindow( String userName, char[] password, String server, boolean isUserNameEditable, ImageIcon icon) { this(userName, password, server, isUserNameEditable, icon, null, null); } /** * Creates an instance of the <tt>LoginWindow</tt>. * * @param owner the owner of this dialog * @param userName the user name to set by default * @param password the password to set by default * @param server the server name this authentication window is about * @param isUserNameEditable indicates if the user name should be editable by the user or not * @param icon the icon displayed on the left of the authentication window */ public AuthenticationWindow( Dialog owner, String userName, char[] password, String server, boolean isUserNameEditable, ImageIcon icon) { super(owner, false); init(userName, password, server, isUserNameEditable, icon, null); } /** * Shows or hides the "save password" checkbox. * * @param allow the checkbox is shown when allow is <tt>true</tt> */ public void setAllowSavePassword(boolean allow) { rememberPassCheckBox.setVisible(allow); } /** * Initializes the icon image. * * @param icon the icon to show on the left of the window */ private void initIcon(ImageIcon icon) { // If an icon isn't provided set the application logo icon by default. if (icon == null) icon = DesktopUtilActivator.getResources().getImage("service.gui.SIP_COMMUNICATOR_LOGO_64x64"); JLabel iconLabel = new JLabel(icon); iconLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); iconLabel.setAlignmentY(Component.TOP_ALIGNMENT); JPanel iconPanel = new TransparentPanel(new BorderLayout()); iconPanel.add(iconLabel, BorderLayout.NORTH); getContentPane().add(iconPanel, BorderLayout.WEST); } /** Constructs the <tt>LoginWindow</tt>. */ private void init() { String title; if (windowTitle != null) title = windowTitle; else title = DesktopUtilActivator.getResources() .getI18NString("service.gui.AUTHENTICATION_WINDOW_TITLE", new String[] {server}); String text; if (windowText != null) text = windowText; else text = DesktopUtilActivator.getResources() .getI18NString("service.gui.AUTHENTICATION_REQUESTED_SERVER", new String[] {server}); String uinText; if (usernameLabelText != null) uinText = usernameLabelText; else uinText = DesktopUtilActivator.getResources().getI18NString("service.gui.IDENTIFIER"); String passText; if (passwordLabelText != null) passText = passwordLabelText; else passText = DesktopUtilActivator.getResources().getI18NString("service.gui.PASSWORD"); setTitle(title); infoTextArea.setEditable(false); infoTextArea.setOpaque(false); infoTextArea.setLineWrap(true); infoTextArea.setWrapStyleWord(true); infoTextArea.setFont(infoTextArea.getFont().deriveFont(Font.BOLD)); infoTextArea.setText(text); infoTextArea.setAlignmentX(0.5f); JLabel uinLabel = new JLabel(uinText); uinLabel.setFont(uinLabel.getFont().deriveFont(Font.BOLD)); JLabel passwdLabel = new JLabel(passText); passwdLabel.setFont(passwdLabel.getFont().deriveFont(Font.BOLD)); TransparentPanel labelsPanel = new TransparentPanel(new GridLayout(0, 1, 8, 8)); labelsPanel.add(uinLabel); labelsPanel.add(passwdLabel); TransparentPanel textFieldsPanel = new TransparentPanel(new GridLayout(0, 1, 8, 8)); textFieldsPanel.add(uinValue); textFieldsPanel.add(passwdField); JPanel southFieldsPanel = new TransparentPanel(new GridLayout(1, 2)); this.rememberPassCheckBox.setOpaque(false); this.rememberPassCheckBox.setBorder(null); southFieldsPanel.add(rememberPassCheckBox); if (signupLink != null && signupLink.length() > 0) southFieldsPanel.add( createWebSignupLabel( DesktopUtilActivator.getResources().getI18NString("plugin.simpleaccregwizz.SIGNUP"), signupLink)); else southFieldsPanel.add(new JLabel()); boolean allowRememberPassword = true; String allowRemPassStr = DesktopUtilActivator.getResources().getSettingsString(PNAME_ALLOW_SAVE_PASSWORD); if (allowRemPassStr != null) { allowRememberPassword = Boolean.parseBoolean(allowRemPassStr); } allowRememberPassword = DesktopUtilActivator.getConfigurationService() .getBoolean(PNAME_ALLOW_SAVE_PASSWORD, allowRememberPassword); setAllowSavePassword(allowRememberPassword); JPanel buttonPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER)); buttonPanel.add(loginButton); buttonPanel.add(cancelButton); JPanel southEastPanel = new TransparentPanel(new BorderLayout()); southEastPanel.add(buttonPanel, BorderLayout.EAST); TransparentPanel mainPanel = new TransparentPanel(new BorderLayout(10, 10)); mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 20, 20)); JPanel mainFieldsPanel = new TransparentPanel(new BorderLayout(0, 10)); mainFieldsPanel.add(labelsPanel, BorderLayout.WEST); mainFieldsPanel.add(textFieldsPanel, BorderLayout.CENTER); mainFieldsPanel.add(southFieldsPanel, BorderLayout.SOUTH); mainPanel.add(infoTextArea, BorderLayout.NORTH); mainPanel.add(mainFieldsPanel, BorderLayout.CENTER); mainPanel.add(southEastPanel, BorderLayout.SOUTH); this.getContentPane().add(mainPanel, BorderLayout.EAST); this.loginButton.setName("ok"); this.cancelButton.setName("cancel"); if (loginButton.getPreferredSize().width > cancelButton.getPreferredSize().width) cancelButton.setPreferredSize(loginButton.getPreferredSize()); else loginButton.setPreferredSize(cancelButton.getPreferredSize()); this.loginButton.setMnemonic( DesktopUtilActivator.getResources().getI18nMnemonic("service.gui.OK")); this.cancelButton.setMnemonic( DesktopUtilActivator.getResources().getI18nMnemonic("service.gui.CANCEL")); this.loginButton.addActionListener(this); this.cancelButton.addActionListener(this); this.getRootPane().setDefaultButton(loginButton); } /** * Handles the <tt>ActionEvent</tt> triggered when one of the buttons is clicked. When "Login" * button is chosen installs a new account from the user input and logs in. * * @param evt the action event that has just occurred. */ public void actionPerformed(ActionEvent evt) { JButton button = (JButton) evt.getSource(); String buttonName = button.getName(); if ("ok".equals(buttonName)) { if (uinValue instanceof JLabel) userName = ((JLabel) uinValue).getText(); else if (uinValue instanceof JTextField) userName = ((JTextField) uinValue).getText(); password = passwdField.getPassword(); isRememberPassword = rememberPassCheckBox.isSelected(); } else { isCanceled = true; } // release the caller that opened the window buttonClicked = true; synchronized (lock) { lock.notify(); } this.dispose(); } /** Enables the actions when a key is pressed, for now closes the window when esc is pressed. */ private void enableKeyActions() { @SuppressWarnings("serial") UIAction act = new UIAction() { public void actionPerformed(ActionEvent e) { close(true); } }; getRootPane().getActionMap().put("close", act); InputMap imap = this.getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close"); } /** * Automatically clicks the cancel button, when this window is closed. * * @param isEscaped indicates if the window has been closed by pressing the Esc key */ @Override protected void close(boolean isEscaped) { this.cancelButton.doClick(); } /** * Shows this modal dialog. * * @param isVisible specifies whether we should be showing or hiding the window. */ @Override public void setVisible(final boolean isVisible) { this.setName("AUTHENTICATION"); if (getOwner() != null) setModal(true); if (isVisible) { addWindowFocusListener( new WindowAdapter() { public void windowGainedFocus(WindowEvent e) { removeWindowFocusListener(this); if (uinValue instanceof JTextField && "".equals(((JTextField) uinValue).getText())) { uinValue.requestFocusInWindow(); } else passwdField.requestFocusInWindow(); } }); } super.setVisible(isVisible); if (isVisible) { if (getOwner() != null) return; synchronized (lock) { while (!buttonClicked) { try { lock.wait(); } catch (InterruptedException e) { } // we don't care, just retry } } } } /** * Indicates if this window has been canceled. * * @return <tt>true</tt> if this window has been canceled, <tt>false</tt> - otherwise */ public boolean isCanceled() { return isCanceled; } /** * Returns the user name entered by the user or previously set if the user name is not editable. * * @return the user name */ public String getUserName() { return userName; } /** * Returns the password entered by the user. * * @return the password */ public char[] getPassword() { return password; } /** * Indicates if the password should be remembered. * * @return <tt>true</tt> if the password should be remembered, <tt>false</tt> - otherwise */ public boolean isRememberPassword() { return isRememberPassword; } /** * Creates the subscribe label. * * @param linkName the link name * @return the newly created subscribe label */ private Component createWebSignupLabel(String linkName, final String linkURL) { JLabel subscribeLabel = new JLabel("<html><a href=''>" + linkName + "</a></html>", JLabel.RIGHT); subscribeLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); subscribeLabel.setToolTipText( DesktopUtilActivator.getResources() .getI18NString("plugin.simpleaccregwizz.SPECIAL_SIGNUP")); subscribeLabel.addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent e) { try { DesktopUtilActivator.getBrowserLauncher().openURL(linkURL); } catch (UnsupportedOperationException ex) { // This should not happen, because we check if the // operation is supported, before adding the sign // up. logger.error("The web sign up is not supported.", ex); } } }); return subscribeLabel; } }
/** * Implements the {@link ReplacementService} to provide previews for direct image links. * * @author Purvesh Sahoo * @author Marin Dzhigarov */ public class ReplacementServiceDirectImageImpl implements DirectImageReplacementService { /** The logger for this class. */ private static final Logger logger = Logger.getLogger(ReplacementServiceDirectImageImpl.class); /** The regex used to match the link in the message. */ public static final String URL_PATTERN = "https?\\:\\/\\/(www\\.)*.*\\.(?:jpg|png|gif)"; /** Configuration label shown in the config form. */ public static final String DIRECT_IMAGE_CONFIG_LABEL = "Direct Image Link"; /** Source name; also used as property label. */ public static final String SOURCE_NAME = "DIRECTIMAGE"; /** Maximum allowed size of the image in bytes. The default size is 2MB. */ private long imgMaxSize = 2097152; /** Configuration property name for maximum allowed size of the image in bytes. */ private static final String MAX_IMG_SIZE = "net.java.sip.communicator.impl.replacement.directimage.MAX_IMG_SIZE"; /** Constructor for <tt>ReplacementServiceDirectImageImpl</tt>. */ public ReplacementServiceDirectImageImpl() { setMaxImgSizeFromConf(); logger.trace("Creating a Direct Image Link Source."); } /** * Gets the max allowed size value in bytes from Configuration service and sets the value to * <tt>imgMaxSize</tt> if succeed. If the configuration property isn't available or the value * can't be parsed correctly the value of <tt>imgMaxSize</tt> isn't changed. */ private void setMaxImgSizeFromConf() { ConfigurationService configService = DirectImageActivator.getConfigService(); if (configService != null) { String confImgSizeStr = (String) configService.getProperty(MAX_IMG_SIZE); try { if (confImgSizeStr != null) { imgMaxSize = Long.parseLong(confImgSizeStr); } else { configService.setProperty(MAX_IMG_SIZE, imgMaxSize); } } catch (NumberFormatException e) { if (logger.isDebugEnabled()) logger.debug( "Failed to parse max image size: " + confImgSizeStr + ". Going for default."); } } } /** * Returns the thumbnail URL of the image link provided. * * @param sourceString the original image link. * @return the thumbnail image link; the original link in case of no match. */ public String getReplacement(String sourceString) { return sourceString; } /** * Returns the source name * * @return the source name */ public String getSourceName() { return SOURCE_NAME; } /** * Returns the pattern of the source * * @return the source pattern */ public String getPattern() { return URL_PATTERN; } @Override /** * Returns the size of the image in bytes. * * @param sourceString the image link. * @return the file size in bytes of the image link provided; -1 if the size isn't available or * exceeds the max allowed image size. */ public int getImageSize(String sourceString) { int length = -1; try { URL url = new URL(sourceString); String protocol = url.getProtocol(); if (protocol.equals("http") || protocol.equals("https")) { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); length = connection.getContentLength(); connection.disconnect(); } else if (protocol.equals("ftp")) { FTPUtils ftp = new FTPUtils(sourceString); length = ftp.getSize(); ftp.disconnect(); } if (length > imgMaxSize) { length = -1; } } catch (Exception e) { logger.debug("Failed to get the length of the image in bytes", e); } return length; } /** * Returns true if the content type of the resource pointed by sourceString is an image. * * @param sourceString the original image link. * @return true if the content type of the resource pointed by sourceString is an image. */ @Override public boolean isDirectImage(String sourceString) { boolean isDirectImage = false; try { URL url = new URL(sourceString); String protocol = url.getProtocol(); if (protocol.equals("http") || protocol.equals("https")) { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); isDirectImage = connection.getContentType().contains("image"); connection.disconnect(); } else if (protocol.equals("ftp")) { if (sourceString.endsWith(".png") || sourceString.endsWith(".jpg") || sourceString.endsWith(".gif")) { isDirectImage = true; } } } catch (Exception e) { logger.debug("Failed to retrieve content type information for" + sourceString, e); } return isDirectImage; } }
/** * @author Lyubomir Marinov * @author Damian Minkov * @author Yana Stamcheva */ public class MediaConfiguration { /** The <tt>Logger</tt> used by the <tt>MediaConfiguration</tt> class for logging output. */ private static final Logger logger = Logger.getLogger(MediaConfiguration.class); /** The <tt>MediaService</tt> implementation used by <tt>MediaConfiguration</tt>. */ private static final MediaServiceImpl mediaService = NeomediaActivator.getMediaServiceImpl(); /** The preferred width of all panels. */ private static final int WIDTH = 350; /** * Indicates if the Devices settings configuration tab should be disabled, i.e. not visible to the * user. */ private static final String DEVICES_DISABLED_PROP = "net.java.sip.communicator.impl.neomedia.devicesconfig.DISABLED"; /** * Indicates if the Audio/Video encodings configuration tab should be disabled, i.e. not visible * to the user. */ private static final String ENCODINGS_DISABLED_PROP = "net.java.sip.communicator.impl.neomedia.encodingsconfig.DISABLED"; /** * Indicates if the Video/More Settings configuration tab should be disabled, i.e. not visible to * the user. */ private static final String VIDEO_MORE_SETTINGS_DISABLED_PROP = "net.java.sip.communicator.impl.neomedia.videomoresettingsconfig.DISABLED"; /** * Returns the audio configuration panel. * * @return the audio configuration panel */ public static Component createAudioConfigPanel() { return createControls(DeviceConfigurationComboBoxModel.AUDIO); } /** * Returns the video configuration panel. * * @return the video configuration panel */ public static Component createVideoConfigPanel() { return createControls(DeviceConfigurationComboBoxModel.VIDEO); } private static void createAudioPreview( final AudioSystem audioSystem, final JComboBox comboBox, final SoundLevelIndicator soundLevelIndicator) { final ActionListener captureComboActionListener = new ActionListener() { private final SimpleAudioLevelListener audioLevelListener = new SimpleAudioLevelListener() { public void audioLevelChanged(int level) { soundLevelIndicator.updateSoundLevel(level); } }; private AudioMediaDeviceSession deviceSession; private final BufferTransferHandler transferHandler = new BufferTransferHandler() { public void transferData(PushBufferStream stream) { try { stream.read(transferHandlerBuffer); } catch (IOException ioe) { } } }; private final Buffer transferHandlerBuffer = new Buffer(); public void actionPerformed(ActionEvent event) { setDeviceSession(null); CaptureDeviceInfo cdi; if (comboBox == null) { cdi = soundLevelIndicator.isShowing() ? audioSystem.getCaptureDevice() : null; } else { Object selectedItem = soundLevelIndicator.isShowing() ? comboBox.getSelectedItem() : null; cdi = (selectedItem instanceof DeviceConfigurationComboBoxModel.CaptureDevice) ? ((DeviceConfigurationComboBoxModel.CaptureDevice) selectedItem).info : null; } if (cdi != null) { for (MediaDevice md : mediaService.getDevices(MediaType.AUDIO, MediaUseCase.ANY)) { if (md instanceof AudioMediaDeviceImpl) { AudioMediaDeviceImpl amd = (AudioMediaDeviceImpl) md; if (cdi.equals(amd.getCaptureDeviceInfo())) { try { MediaDeviceSession deviceSession = amd.createSession(); boolean setDeviceSession = false; try { if (deviceSession instanceof AudioMediaDeviceSession) { setDeviceSession((AudioMediaDeviceSession) deviceSession); setDeviceSession = true; } } finally { if (!setDeviceSession) deviceSession.close(); } } catch (Throwable t) { if (t instanceof ThreadDeath) throw (ThreadDeath) t; } break; } } } } } private void setDeviceSession(AudioMediaDeviceSession deviceSession) { if (this.deviceSession == deviceSession) return; if (this.deviceSession != null) { try { this.deviceSession.close(); } finally { this.deviceSession.setLocalUserAudioLevelListener(null); soundLevelIndicator.resetSoundLevel(); } } this.deviceSession = deviceSession; if (this.deviceSession != null) { this.deviceSession.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW)); this.deviceSession.setLocalUserAudioLevelListener(audioLevelListener); this.deviceSession.start(MediaDirection.SENDONLY); try { DataSource dataSource = this.deviceSession.getOutputDataSource(); dataSource.connect(); PushBufferStream[] streams = ((PushBufferDataSource) dataSource).getStreams(); for (PushBufferStream stream : streams) stream.setTransferHandler(transferHandler); dataSource.start(); } catch (Throwable t) { if (t instanceof ThreadDeath) throw (ThreadDeath) t; else setDeviceSession(null); } } } }; if (comboBox != null) comboBox.addActionListener(captureComboActionListener); soundLevelIndicator.addHierarchyListener( new HierarchyListener() { public void hierarchyChanged(HierarchyEvent event) { if ((event.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { SwingUtilities.invokeLater( new Runnable() { public void run() { captureComboActionListener.actionPerformed(null); } }); } } }); } /** * Creates the UI controls which are to control the details of a specific <tt>AudioSystem</tt>. * * @param audioSystem the <tt>AudioSystem</tt> for which the UI controls to control its details * are to be created * @param container the <tt>JComponent</tt> into which the UI controls which are to control the * details of the specified <tt>audioSystem</tt> are to be added */ public static void createAudioSystemControls(AudioSystem audioSystem, JComponent container) { GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.NORTHWEST; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.weighty = 0; int audioSystemFeatures = audioSystem.getFeatures(); boolean featureNotifyAndPlaybackDevices = ((audioSystemFeatures & AudioSystem.FEATURE_NOTIFY_AND_PLAYBACK_DEVICES) != 0); constraints.gridx = 0; constraints.insets = new Insets(3, 0, 3, 3); constraints.weightx = 0; constraints.gridy = 0; container.add( new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)), constraints); if (featureNotifyAndPlaybackDevices) { constraints.gridy = 2; container.add( new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)), constraints); constraints.gridy = 3; container.add( new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)), constraints); } constraints.gridx = 1; constraints.insets = new Insets(3, 3, 3, 0); constraints.weightx = 1; JComboBox captureCombo = null; if (featureNotifyAndPlaybackDevices) { captureCombo = new JComboBox(); captureCombo.setEditable(false); captureCombo.setModel( new DeviceConfigurationComboBoxModel( captureCombo, mediaService.getDeviceConfiguration(), DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)); constraints.gridy = 0; container.add(captureCombo, constraints); } int anchor = constraints.anchor; SoundLevelIndicator capturePreview = new SoundLevelIndicator( SimpleAudioLevelListener.MIN_LEVEL, SimpleAudioLevelListener.MAX_LEVEL); constraints.anchor = GridBagConstraints.CENTER; constraints.gridy = (captureCombo == null) ? 0 : 1; container.add(capturePreview, constraints); constraints.anchor = anchor; constraints.gridy = GridBagConstraints.RELATIVE; if (featureNotifyAndPlaybackDevices) { JComboBox playbackCombo = new JComboBox(); playbackCombo.setEditable(false); playbackCombo.setModel( new DeviceConfigurationComboBoxModel( captureCombo, mediaService.getDeviceConfiguration(), DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)); container.add(playbackCombo, constraints); JComboBox notifyCombo = new JComboBox(); notifyCombo.setEditable(false); notifyCombo.setModel( new DeviceConfigurationComboBoxModel( captureCombo, mediaService.getDeviceConfiguration(), DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)); container.add(notifyCombo, constraints); } if ((AudioSystem.FEATURE_ECHO_CANCELLATION & audioSystemFeatures) != 0) { final SIPCommCheckBox echoCancelCheckBox = new SIPCommCheckBox( NeomediaActivator.getResources().getI18NString("impl.media.configform.ECHOCANCEL")); /* * First set the selected one, then add the listener in order to * avoid saving the value when using the default one and only * showing to user without modification. */ echoCancelCheckBox.setSelected(mediaService.getDeviceConfiguration().isEchoCancel()); echoCancelCheckBox.addItemListener( new ItemListener() { public void itemStateChanged(ItemEvent e) { mediaService.getDeviceConfiguration().setEchoCancel(echoCancelCheckBox.isSelected()); } }); container.add(echoCancelCheckBox, constraints); } if ((AudioSystem.FEATURE_DENOISE & audioSystemFeatures) != 0) { final SIPCommCheckBox denoiseCheckBox = new SIPCommCheckBox( NeomediaActivator.getResources().getI18NString("impl.media.configform.DENOISE")); /* * First set the selected one, then add the listener in order to * avoid saving the value when using the default one and only * showing to user without modification. */ denoiseCheckBox.setSelected(mediaService.getDeviceConfiguration().isDenoise()); denoiseCheckBox.addItemListener( new ItemListener() { public void itemStateChanged(ItemEvent e) { mediaService.getDeviceConfiguration().setDenoise(denoiseCheckBox.isSelected()); } }); container.add(denoiseCheckBox, constraints); } createAudioPreview(audioSystem, captureCombo, capturePreview); } /** * Creates basic controls for a type (AUDIO or VIDEO). * * @param type the type. * @return the build Component. */ public static Component createBasicControls(final int type) { final JComboBox deviceComboBox = new JComboBox(); deviceComboBox.setEditable(false); deviceComboBox.setModel( new DeviceConfigurationComboBoxModel( deviceComboBox, mediaService.getDeviceConfiguration(), type)); JLabel deviceLabel = new JLabel(getLabelText(type)); deviceLabel.setDisplayedMnemonic(getDisplayedMnemonic(type)); deviceLabel.setLabelFor(deviceComboBox); final Container devicePanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER)); devicePanel.setMaximumSize(new Dimension(WIDTH, 25)); devicePanel.add(deviceLabel); devicePanel.add(deviceComboBox); final JPanel deviceAndPreviewPanel = new TransparentPanel(new BorderLayout()); int preferredDeviceAndPreviewPanelHeight; switch (type) { case DeviceConfigurationComboBoxModel.AUDIO: preferredDeviceAndPreviewPanelHeight = 225; break; case DeviceConfigurationComboBoxModel.VIDEO: preferredDeviceAndPreviewPanelHeight = 305; break; default: preferredDeviceAndPreviewPanelHeight = 0; break; } if (preferredDeviceAndPreviewPanelHeight > 0) deviceAndPreviewPanel.setPreferredSize( new Dimension(WIDTH, preferredDeviceAndPreviewPanelHeight)); deviceAndPreviewPanel.add(devicePanel, BorderLayout.NORTH); final ActionListener deviceComboBoxActionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { boolean revalidateAndRepaint = false; for (int i = deviceAndPreviewPanel.getComponentCount() - 1; i >= 0; i--) { Component c = deviceAndPreviewPanel.getComponent(i); if (c != devicePanel) { deviceAndPreviewPanel.remove(i); revalidateAndRepaint = true; } } Component preview = null; if ((deviceComboBox.getSelectedItem() != null) && deviceComboBox.isShowing()) { preview = createPreview(type, deviceComboBox, deviceAndPreviewPanel.getPreferredSize()); } if (preview != null) { deviceAndPreviewPanel.add(preview, BorderLayout.CENTER); revalidateAndRepaint = true; } if (revalidateAndRepaint) { deviceAndPreviewPanel.revalidate(); deviceAndPreviewPanel.repaint(); } } }; deviceComboBox.addActionListener(deviceComboBoxActionListener); /* * We have to initialize the controls to reflect the configuration * at the time of creating this instance. Additionally, because the * video preview will stop when it and its associated controls * become unnecessary, we have to restart it when the mentioned * controls become necessary again. We'll address the two goals * described by pretending there's a selection in the video combo * box when the combo box in question becomes displayable. */ deviceComboBox.addHierarchyListener( new HierarchyListener() { public void hierarchyChanged(HierarchyEvent event) { if ((event.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { SwingUtilities.invokeLater( new Runnable() { public void run() { deviceComboBoxActionListener.actionPerformed(null); } }); } } }); return deviceAndPreviewPanel; } /** * Creates all the controls (including encoding) for a type(AUDIO or VIDEO) * * @param type the type. * @return the build Component. */ private static Component createControls(int type) { ConfigurationService cfg = NeomediaActivator.getConfigurationService(); SIPCommTabbedPane container = new SIPCommTabbedPane(); ResourceManagementService res = NeomediaActivator.getResources(); if ((cfg == null) || !cfg.getBoolean(DEVICES_DISABLED_PROP, false)) { container.insertTab( res.getI18NString("impl.media.configform.DEVICES"), null, createBasicControls(type), null, 0); } if ((cfg == null) || !cfg.getBoolean(ENCODINGS_DISABLED_PROP, false)) { container.insertTab( res.getI18NString("impl.media.configform.ENCODINGS"), null, new PriorityTable( new EncodingConfigurationTableModel(mediaService.getEncodingConfiguration(), type), 100), null, 1); } if ((type == DeviceConfigurationComboBoxModel.VIDEO) && ((cfg == null) || !cfg.getBoolean(VIDEO_MORE_SETTINGS_DISABLED_PROP, false))) { container.insertTab( res.getI18NString("impl.media.configform.VIDEO_MORE_SETTINGS"), null, createVideoAdvancedSettings(), null, 2); } return container; } /** * Creates preview for the (video) device in the video container. * * @param device the device * @param videoContainer the video container * @throws IOException a problem accessing the device * @throws MediaException a problem getting preview */ private static void createVideoPreview(CaptureDeviceInfo device, JComponent videoContainer) throws IOException, MediaException { videoContainer.removeAll(); videoContainer.revalidate(); videoContainer.repaint(); if (device == null) return; for (MediaDevice mediaDevice : mediaService.getDevices(MediaType.VIDEO, MediaUseCase.ANY)) { if (((MediaDeviceImpl) mediaDevice).getCaptureDeviceInfo().equals(device)) { Dimension videoContainerSize = videoContainer.getPreferredSize(); Component preview = (Component) mediaService.getVideoPreviewComponent( mediaDevice, videoContainerSize.width, videoContainerSize.height); if (preview != null) videoContainer.add(preview); break; } } } /** * Create preview component. * * @param type type * @param comboBox the options. * @param prefSize the preferred size * @return the component. */ private static Component createPreview(int type, final JComboBox comboBox, Dimension prefSize) { JComponent preview = null; if (type == DeviceConfigurationComboBoxModel.AUDIO) { Object selectedItem = comboBox.getSelectedItem(); if (selectedItem instanceof AudioSystem) { AudioSystem audioSystem = (AudioSystem) selectedItem; if (!NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase(audioSystem.getLocatorProtocol())) { preview = new TransparentPanel(new GridBagLayout()); createAudioSystemControls(audioSystem, preview); } } } else if (type == DeviceConfigurationComboBoxModel.VIDEO) { JLabel noPreview = new JLabel( NeomediaActivator.getResources().getI18NString("impl.media.configform.NO_PREVIEW")); noPreview.setHorizontalAlignment(SwingConstants.CENTER); noPreview.setVerticalAlignment(SwingConstants.CENTER); preview = createVideoContainer(noPreview); preview.setPreferredSize(prefSize); Object selectedItem = comboBox.getSelectedItem(); CaptureDeviceInfo device = null; if (selectedItem instanceof DeviceConfigurationComboBoxModel.CaptureDevice) device = ((DeviceConfigurationComboBoxModel.CaptureDevice) selectedItem).info; Exception exception; try { createVideoPreview(device, preview); exception = null; } catch (IOException ex) { exception = ex; } catch (MediaException ex) { exception = ex; } if (exception != null) { logger.error("Failed to create preview for device " + device, exception); device = null; } } return preview; } /** * Creates the video container. * * @param noVideoComponent the container component. * @return the video container. */ private static JComponent createVideoContainer(Component noVideoComponent) { return new VideoContainer(noVideoComponent, false); } /** * The mnemonic for a type. * * @param type audio or video type. * @return the mnemonic. */ private static char getDisplayedMnemonic(int type) { switch (type) { case DeviceConfigurationComboBoxModel.AUDIO: return NeomediaActivator.getResources().getI18nMnemonic("impl.media.configform.AUDIO"); case DeviceConfigurationComboBoxModel.VIDEO: return NeomediaActivator.getResources().getI18nMnemonic("impl.media.configform.VIDEO"); default: throw new IllegalArgumentException("type"); } } /** * A label for a type. * * @param type the type. * @return the label. */ private static String getLabelText(int type) { switch (type) { case DeviceConfigurationComboBoxModel.AUDIO: return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO"); case DeviceConfigurationComboBoxModel.AUDIO_CAPTURE: return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO_IN"); case DeviceConfigurationComboBoxModel.AUDIO_NOTIFY: return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO_NOTIFY"); case DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK: return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO_OUT"); case DeviceConfigurationComboBoxModel.VIDEO: return NeomediaActivator.getResources().getI18NString("impl.media.configform.VIDEO"); default: throw new IllegalArgumentException("type"); } } /** * Creates the video advanced settings. * * @return video advanced settings panel. */ private static Component createVideoAdvancedSettings() { ResourceManagementService resources = NeomediaActivator.getResources(); final DeviceConfiguration deviceConfig = mediaService.getDeviceConfiguration(); TransparentPanel centerPanel = new TransparentPanel(new GridBagLayout()); centerPanel.setMaximumSize(new Dimension(WIDTH, 150)); JButton resetDefaultsButton = new JButton(resources.getI18NString("impl.media.configform.VIDEO_RESET")); JPanel resetButtonPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); resetButtonPanel.add(resetDefaultsButton); final JPanel centerAdvancedPanel = new TransparentPanel(new BorderLayout()); centerAdvancedPanel.add(centerPanel, BorderLayout.NORTH); centerAdvancedPanel.add(resetButtonPanel, BorderLayout.SOUTH); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.anchor = GridBagConstraints.NORTHWEST; constraints.insets = new Insets(5, 5, 0, 0); constraints.gridx = 0; constraints.weightx = 0; constraints.weighty = 0; constraints.gridy = 0; centerPanel.add( new JLabel(resources.getI18NString("impl.media.configform.VIDEO_RESOLUTION")), constraints); constraints.gridy = 1; constraints.insets = new Insets(0, 0, 0, 0); final JCheckBox frameRateCheck = new SIPCommCheckBox(resources.getI18NString("impl.media.configform.VIDEO_FRAME_RATE")); centerPanel.add(frameRateCheck, constraints); constraints.gridy = 2; constraints.insets = new Insets(5, 5, 0, 0); centerPanel.add( new JLabel(resources.getI18NString("impl.media.configform.VIDEO_PACKETS_POLICY")), constraints); constraints.weightx = 1; constraints.gridx = 1; constraints.gridy = 0; constraints.insets = new Insets(5, 0, 0, 5); Object[] resolutionValues = new Object[DeviceConfiguration.SUPPORTED_RESOLUTIONS.length + 1]; System.arraycopy( DeviceConfiguration.SUPPORTED_RESOLUTIONS, 0, resolutionValues, 1, DeviceConfiguration.SUPPORTED_RESOLUTIONS.length); final JComboBox sizeCombo = new JComboBox(resolutionValues); sizeCombo.setRenderer(new ResolutionCellRenderer()); sizeCombo.setEditable(false); centerPanel.add(sizeCombo, constraints); // default value is 20 final JSpinner frameRate = new JSpinner(new SpinnerNumberModel(20, 5, 30, 1)); frameRate.addChangeListener( new ChangeListener() { public void stateChanged(ChangeEvent e) { deviceConfig.setFrameRate( ((SpinnerNumberModel) frameRate.getModel()).getNumber().intValue()); } }); constraints.gridy = 1; constraints.insets = new Insets(0, 0, 0, 5); centerPanel.add(frameRate, constraints); frameRateCheck.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { if (frameRateCheck.isSelected()) { deviceConfig.setFrameRate( ((SpinnerNumberModel) frameRate.getModel()).getNumber().intValue()); } else // unlimited framerate deviceConfig.setFrameRate(-1); frameRate.setEnabled(frameRateCheck.isSelected()); } }); final JSpinner videoMaxBandwidth = new JSpinner( new SpinnerNumberModel(deviceConfig.getVideoMaxBandwidth(), 1, Integer.MAX_VALUE, 1)); videoMaxBandwidth.addChangeListener( new ChangeListener() { public void stateChanged(ChangeEvent e) { deviceConfig.setVideoMaxBandwidth( ((SpinnerNumberModel) videoMaxBandwidth.getModel()).getNumber().intValue()); } }); constraints.gridx = 1; constraints.gridy = 2; constraints.insets = new Insets(0, 0, 5, 5); centerPanel.add(videoMaxBandwidth, constraints); resetDefaultsButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { // reset to defaults sizeCombo.setSelectedIndex(0); frameRateCheck.setSelected(false); frameRate.setEnabled(false); frameRate.setValue(20); // unlimited framerate deviceConfig.setFrameRate(-1); videoMaxBandwidth.setValue(DeviceConfiguration.DEFAULT_VIDEO_MAX_BANDWIDTH); } }); // load selected value or auto Dimension videoSize = deviceConfig.getVideoSize(); if ((videoSize.getHeight() != DeviceConfiguration.DEFAULT_VIDEO_HEIGHT) && (videoSize.getWidth() != DeviceConfiguration.DEFAULT_VIDEO_WIDTH)) sizeCombo.setSelectedItem(deviceConfig.getVideoSize()); else sizeCombo.setSelectedIndex(0); sizeCombo.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { Dimension selectedVideoSize = (Dimension) sizeCombo.getSelectedItem(); if (selectedVideoSize == null) { // the auto value, default one selectedVideoSize = new Dimension( DeviceConfiguration.DEFAULT_VIDEO_WIDTH, DeviceConfiguration.DEFAULT_VIDEO_HEIGHT); } deviceConfig.setVideoSize(selectedVideoSize); } }); frameRateCheck.setSelected( deviceConfig.getFrameRate() != DeviceConfiguration.DEFAULT_VIDEO_FRAMERATE); frameRate.setEnabled(frameRateCheck.isSelected()); if (frameRate.isEnabled()) frameRate.setValue(deviceConfig.getFrameRate()); return centerAdvancedPanel; } /** Renders the available resolutions in the combo box. */ private static class ResolutionCellRenderer extends DefaultListCellRenderer { /** * The serialization version number of the <tt>ResolutionCellRenderer</tt> class. Defined to the * value of <tt>0</tt> because the <tt>ResolutionCellRenderer</tt> instances do not have state * of their own. */ private static final long serialVersionUID = 0L; /** * Sets readable text describing the resolution if the selected value is null we return the * string "Auto". * * @param list * @param value * @param index * @param isSelected * @param cellHasFocus * @return Component */ @Override public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { // call super to set backgrounds and fonts super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); // now just change the text if (value == null) setText("Auto"); else if (value instanceof Dimension) { Dimension d = (Dimension) value; setText(((int) d.getWidth()) + "x" + ((int) d.getHeight())); } return this; } } }