diff options
author | Wolfgang Wiedmeyer <wolfgit@wiedmeyer.de> | 2017-03-11 22:15:03 +0100 |
---|---|---|
committer | Wolfgang Wiedmeyer <wolfgit@wiedmeyer.de> | 2017-03-11 22:15:03 +0100 |
commit | 85901329b0794b136b96bf745f4ab1572806fc89 (patch) | |
tree | f23da7e97cae727f39d825f0fef8348cffb238e4 /src/net/java/sip/communicator/impl | |
parent | 3db2e44f186c59429901b2c899e139ea60117a55 (diff) | |
parent | cf5da997da8820b4050f5b87ee9440a0ede36d1f (diff) | |
download | jitsi-master.zip jitsi-master.tar.gz jitsi-master.tar.bz2 |
Signed-off-by: Wolfgang Wiedmeyer <wolfgit@wiedmeyer.de>
Diffstat (limited to 'src/net/java/sip/communicator/impl')
264 files changed, 23175 insertions, 48359 deletions
diff --git a/src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java b/src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java index 7bf29d1..bb888ad 100644 --- a/src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java +++ b/src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java @@ -211,49 +211,6 @@ public class CertificateServiceImpl System.getProperties().remove("javax.net.ssl.trustStorePassword"); } - /** - * Appends an index number to the alias of each entry in the KeyStore. - * - * The Windows TrustStore might contain multiple entries with the same - * "Friendly Name", which is directly used as the "Alias" for the KeyStore. - * As all operations of the KeyStore operate with these non-unique names, - * PKIX path building could fail and in the end lead to certificate warnings - * for perfectly valid certificates. - * - * @throws Exception when the aliases could not be renamed. - */ - private static int keyStoreAppendIndex(KeyStore ks) throws Exception - { - Field keyStoreSpiField = ks.getClass().getDeclaredField("keyStoreSpi"); - keyStoreSpiField.setAccessible(true); - KeyStoreSpi keyStoreSpi = (KeyStoreSpi) keyStoreSpiField.get(ks); - - if ("sun.security.mscapi.KeyStore$ROOT".equals(keyStoreSpi.getClass() - .getName())) - { - Field entriesField = - keyStoreSpi.getClass().getEnclosingClass() - .getDeclaredField("entries"); - entriesField.setAccessible(true); - Collection<?> entries = - (Collection<?>) entriesField.get(keyStoreSpi); - - int i = 0; - for (Object entry : entries) - { - Field aliasField = entry.getClass().getDeclaredField("alias"); - aliasField.setAccessible(true); - String alias = (String) aliasField.get(entry); - aliasField.set(entry, - alias.concat("_").concat(Integer.toString(i++))); - } - - return i; - } - - return -1; - } - // ------------------------------------------------------------------------ // Client authentication configuration // ------------------------------------------------------------------------ @@ -678,10 +635,6 @@ public class CertificateServiceImpl { ks = KeyStore.getInstance(tsType); ks.load(null, null); - int numEntries = keyStoreAppendIndex(ks); - logger.info( - "Using Windows-ROOT. Aliases sucessfully renamed on " - + numEntries + " root certificates."); } catch (Exception e) { @@ -784,8 +737,7 @@ public class CertificateServiceImpl propNames.add(propName); message = - R.getI18NString("service.gui." - + "CERT_DIALOG_DESCRIPTION_TXT_NOHOST", + R.getI18NString("service.gui.CERT_DIALOG_DESCRIPTION_TXT_NOHOST", new String[] { appName } @@ -809,8 +761,7 @@ public class CertificateServiceImpl { message = R.getI18NString( - "service.gui." - + "CERT_DIALOG_DESCRIPTION_TXT", + "service.gui.CERT_DIALOG_DESCRIPTION_TXT", new String[] { appName, identitiesToTest.toString() @@ -821,8 +772,7 @@ public class CertificateServiceImpl { message = R.getI18NString( - "service.gui." - + "CERT_DIALOG_PEER_DESCRIPTION_TXT", + "service.gui.CERT_DIALOG_PEER_DESCRIPTION_TXT", new String[] { appName, identitiesToTest.toString() diff --git a/src/net/java/sip/communicator/impl/configuration/ConfigurationActivator.java b/src/net/java/sip/communicator/impl/configuration/ConfigurationActivator.java index 1553658..a77b2f5 100644 --- a/src/net/java/sip/communicator/impl/configuration/ConfigurationActivator.java +++ b/src/net/java/sip/communicator/impl/configuration/ConfigurationActivator.java @@ -28,6 +28,9 @@ import org.jitsi.util.*; import org.osgi.framework.*; import java.io.*; +import java.nio.file.*; +import java.nio.file.attribute.*; +import java.util.*; /** * @@ -64,39 +67,38 @@ public class ConfigurationActivator if (fas != null) { - File useDatabaseConfig; - + File usePropFileConfig; try { - useDatabaseConfig + usePropFileConfig = fas.getPrivatePersistentFile( - ".usedatabaseconfig", + ".usepropfileconfig", FileCategory.PROFILE); } catch (Exception ise) { - // There is somewhat of a chicken-and-egg dependency between // FileConfigurationServiceImpl and ConfigurationServiceImpl: // FileConfigurationServiceImpl throws IllegalStateException if // certain System properties are not set, // ConfigurationServiceImpl will make sure that these properties - //are set but it will do that later. + // are set but it will do that later. // A SecurityException is thrown when the destination // is not writable or we do not have access to that folder - useDatabaseConfig = null; + usePropFileConfig = null; } - // BETA: if the marker file exists, use the database configuration - if ((useDatabaseConfig != null) && useDatabaseConfig.exists()) + if (usePropFileConfig != null && usePropFileConfig.exists()) { - logger.info("Using database configuration store."); - this.cs = new JdbcConfigService(fas); + logger.info("Using properties file configuration store."); + this.cs = LibJitsi.getConfigurationService(); } } if (this.cs == null) - this.cs = LibJitsi.getConfigurationService(); + { + this.cs = new JdbcConfigService(fas); + } bundleContext.registerService( ConfigurationService.class.getName(), @@ -139,17 +141,30 @@ public class ConfigurationActivator // let's check config file and config folder File homeFolder = new File(cs.getScHomeDirLocation(), cs.getScHomeDirName()); - CLibrary libc = (CLibrary) Native.loadLibrary("c", CLibrary.class); - - libc.chmod(homeFolder.getAbsolutePath(), 0700); + Set<PosixFilePermission> perms = + new HashSet<PosixFilePermission>() + {{ + add(PosixFilePermission.OWNER_READ); + add(PosixFilePermission.OWNER_WRITE); + add(PosixFilePermission.OWNER_EXECUTE); + }}; + Files.setPosixFilePermissions( + Paths.get(homeFolder.getAbsolutePath()), perms); String fileName = cs.getConfigurationFilename(); - if(fileName != null) { File cf = new File(homeFolder, fileName); if(cf.exists()) - libc.chmod(cf.getAbsolutePath(), 0600); + { + perms = new HashSet<PosixFilePermission>() + {{ + add(PosixFilePermission.OWNER_READ); + add(PosixFilePermission.OWNER_WRITE); + }}; + Files.setPosixFilePermissions( + Paths.get(cf.getAbsolutePath()), perms); + } } } catch(Throwable t) @@ -164,24 +179,4 @@ public class ConfigurationActivator throw (ThreadDeath) t; } } - - /** - * The JNA interface to the <tt>c</tt> library and the <tt>chmod</tt> - * function we use to fix permissions of user files and folders. - */ - public interface CLibrary - extends Library - { - /** - * Changes file permissions. - * - * @param path the path to the file or folder the permissions of which - * are to be changed. - * @param mode the mode operand - * @return <tt>0</tt> upon successful completion; otherwise, - * <tt>-1</tt>. If <tt>-1</tt> is returned, no change to the file mode - * occurs. - */ - public int chmod(String path, int mode); - } } diff --git a/src/net/java/sip/communicator/impl/configuration/JdbcConfigService.java b/src/net/java/sip/communicator/impl/configuration/JdbcConfigService.java index 7ba362e..c9c9c3c 100644 --- a/src/net/java/sip/communicator/impl/configuration/JdbcConfigService.java +++ b/src/net/java/sip/communicator/impl/configuration/JdbcConfigService.java @@ -595,6 +595,35 @@ public final class JdbcConfigService * (non-Javadoc) * * @see + * org.jitsi.service.configuration.ConfigurationService#getDouble(java.lang + * .String, double) + */ + @Override + public double getDouble(String propertyName, double defaultValue) + { + Object value = this.getProperty(propertyName); + if (value == null || "".equals(value.toString())) + { + return defaultValue; + } + + try + { + return Double.parseDouble(value.toString()); + } + catch (NumberFormatException ex) + { + logger.error(String.format( + "'%s' for property %s not a double, returning default (%s)", + value, propertyName, defaultValue), ex); + return defaultValue; + } + } + + /* + * (non-Javadoc) + * + * @see * org.jitsi.service.configuration.ConfigurationService#getLong(java.lang * .String, long) */ diff --git a/src/net/java/sip/communicator/impl/dns/ConfigurableDnssecResolver.java b/src/net/java/sip/communicator/impl/dns/ConfigurableDnssecResolver.java index af8d465..1b23af4 100644 --- a/src/net/java/sip/communicator/impl/dns/ConfigurableDnssecResolver.java +++ b/src/net/java/sip/communicator/impl/dns/ConfigurableDnssecResolver.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,379 +15,457 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.dns;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.util.*;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.service.dns.*;
-import net.java.sip.communicator.service.notification.*;
-import net.java.sip.communicator.util.Logger;
-import net.java.sip.communicator.plugin.desktoputil.*;
-
-import org.jitsi.service.configuration.*;
-import org.jitsi.service.resources.*;
-import org.jitsi.util.*;
-import org.xbill.DNS.*;
-
-/**
- * Resolver that wraps a DNSSEC capable resolver and handles validation
- * failures according to the user's settings.
- *
- * @author Ingo Bauersachs
- */
-public class ConfigurableDnssecResolver
- extends UnboundResolver
-{
- private final static Logger logger
- = Logger.getLogger(ConfigurableDnssecResolver.class);
-
- /**
- * Name of the property that defines the default DNSSEC validation
- * behavior.
- */
- public final static String PNAME_DNSSEC_VALIDATION_MODE
- = "net.java.sip.communicator.util.dns.DNSSEC_VALIDATION_MODE";
-
- /**
- * Default value of {@link #PNAME_DNSSEC_VALIDATION_MODE}
- */
- public final static String PNAME_BASE_DNSSEC_PIN
- = "net.java.sip.communicator.util.dns.pin";
-
- final static String EVENT_TYPE = "DNSSEC_NOTIFICATION";
-
- private ConfigurationService config
- = DnsUtilActivator.getConfigurationService();
- private ResourceManagementService R
- = DnsUtilActivator.getResources();
- private Map<String, Date> lastNotifications
- = new HashMap<String, Date>();
-
- /**
- * Creates a new instance of this class. Tries to use the system's
- * default forwarders.
- */
- public ConfigurableDnssecResolver()
- {
- super();
- reset();
- Lookup.setDefaultResolver(this);
-
- DnsUtilActivator.getNotificationService().
- registerDefaultNotificationForEvent(
- ConfigurableDnssecResolver.EVENT_TYPE,
- NotificationAction.ACTION_POPUP_MESSAGE,
- null, null);
- }
-
- /**
- * Inspects a DNS answer message and handles validation results according to
- * the user's preferences.
- *
- * @throws DnssecRuntimeException when the validation failed and the user
- * did not choose to ignore it.
- */
- @Override
- protected void validateMessage(SecureMessage msg)
- throws DnssecRuntimeException
- {
- //---------------------------------------------------------------------
- // || 1 | 2 | 3 | 4 | 5
- //---------------------------------------------------------------------
- // Sec. | Bog. || Ign. | Sec.Only | Sec.Or.Unsig | Warn.Bog | WarnAll
- //---------------------------------------------------------------------
- //a) 1 | 0 || ok | ok | ok | ok | ok
- //b) 0 | 1 || ok | nok | nok | ask | ask
- //c) 0 | 0 || ok | nok | ok | ok | ask
- //---------------------------------------------------------------------
-
- String fqdn = msg.getQuestion().getName().toString();
- String type = Type.string(msg.getQuestion().getType());
- String propName = createPropNameUnsigned(fqdn, type);
- SecureResolveMode defaultAction = Enum.valueOf(SecureResolveMode.class,
- config.getString(
- PNAME_DNSSEC_VALIDATION_MODE,
- SecureResolveMode.WarnIfBogus.name()
- )
- );
- SecureResolveMode pinned = Enum.valueOf(SecureResolveMode.class,
- config.getString(
- propName,
- defaultAction.name()
- )
- );
-
- //create default entry
- if(pinned == defaultAction)
- config.setProperty(propName, pinned.name());
-
- //check domain policy
-
- //[abc]1, a[2-5]
- if(pinned == SecureResolveMode.IgnoreDnssec || msg.isSecure())
- return;
-
- if(
- //b2, c2
- (pinned == SecureResolveMode.SecureOnly && !msg.isSecure())
- ||
- //b3
- (pinned == SecureResolveMode.SecureOrUnsigned && msg.isBogus())
- )
- {
- String text = getExceptionMessage(msg);
- Date last = lastNotifications.get(text);
- if(last == null
- //wait at least 5 minutes before showing the same info again
- || last.before(new Date(new Date().getTime() - 1000*60*5)))
- {
- DnsUtilActivator.getNotificationService().fireNotification(
- EVENT_TYPE,
- R.getI18NString("util.dns.INSECURE_ANSWER_TITLE"),
- text,
- null);
- lastNotifications.put(text, new Date());
- }
- throw new DnssecRuntimeException(text);
- }
-
- //c3
- if(pinned == SecureResolveMode.SecureOrUnsigned && !msg.isBogus())
- return;
-
- //c4
- if(pinned == SecureResolveMode.WarnIfBogus && !msg.isBogus())
- return;
-
- //b4, b5, c5
- String reason = msg.isBogus()
- ? R.getI18NString("util.dns.DNSSEC_ADVANCED_REASON_BOGUS",
- new String[]{fqdn, msg.getBogusReason()})
- : R.getI18NString("util.dns.DNSSEC_ADVANCED_REASON_UNSIGNED",
- new String[]{type, fqdn});
- DnssecDialog dlg = new DnssecDialog(fqdn, reason);
- dlg.setVisible(true);
- DnssecDialogResult result = dlg.getResult();
- switch(result)
- {
- case Accept:
- break;
- case Deny:
- throw new DnssecRuntimeException(getExceptionMessage(msg));
- case AlwaysAccept:
- if(msg.isBogus())
- config.setProperty(propName,
- SecureResolveMode.IgnoreDnssec.name());
- else
- config.setProperty(propName,
- SecureResolveMode.WarnIfBogus.name());
- break;
- case AlwaysDeny:
- config.setProperty(propName, SecureResolveMode.SecureOnly);
- throw new DnssecRuntimeException(getExceptionMessage(msg));
- }
- }
-
- /**
- * Defines the return code from the DNSSEC verification dialog.
- */
- private enum DnssecDialogResult
- {
- /** The DNS result shall be accepted. */
- Accept,
- /** The result shall be rejected. */
- Deny,
- /** The result shall be accepted permanently. */
- AlwaysAccept,
- /**
- * The result shall be rejected automatically unless it is valid
- * according to DNSSEC.
- */
- AlwaysDeny
- }
-
- /**
- * Dialog to ask and warn the user if he wants to continue to accept an
- * invalid dnssec result.
- */
- private class DnssecDialog extends SIPCommDialog implements ActionListener
- {
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- //UI controls
- private JPanel pnlAdvanced;
- private JPanel pnlStandard;
- private final String domain;
- private final String reason;
- private JButton cmdAck;
- private JButton cmdShowDetails;
-
- //state
- private DnssecDialogResult result = DnssecDialogResult.Deny;
-
- /**
- * Creates a new instance of this class.
- * @param domain The FQDN of the domain that failed.
- * @param reason String describing why the validation failed.
- */
- public DnssecDialog(String domain, String reason)
- {
- super(false);
- setModal(true);
- this.domain = domain;
- this.reason = reason;
- initComponents();
- }
-
- /**
- * Creates the UI controls
- */
- private void initComponents()
- {
- setLayout(new BorderLayout(15, 15));
- setTitle(R.getI18NString("util.dns.INSECURE_ANSWER_TITLE"));
-
- // warning text
- JLabel imgWarning =
- new JLabel(R.getImage("service.gui.icons.WARNING_ICON"));
- imgWarning.setBorder(BorderFactory
- .createEmptyBorder(10, 10, 10, 10));
- add(imgWarning, BorderLayout.WEST);
- JLabel lblWarning = new JLabel(
- R.getI18NString("util.dns.DNSSEC_WARNING", new String[]{
- R.getSettingsString("service.gui.APPLICATION_NAME"),
- domain
- })
- );
- add(lblWarning, BorderLayout.CENTER);
-
- //standard panel (deny option)
- cmdAck = new JButton(R.getI18NString("service.gui.OK"));
- cmdAck.addActionListener(this);
-
- cmdShowDetails = new JButton(
- R.getI18NString("util.dns.DNSSEC_ADVANCED_OPTIONS"));
- cmdShowDetails.addActionListener(this);
-
- pnlStandard = new TransparentPanel(new BorderLayout());
- pnlStandard.setBorder(BorderFactory
- .createEmptyBorder(10, 10, 10, 10));
- pnlStandard.add(cmdShowDetails, BorderLayout.WEST);
- pnlStandard.add(cmdAck, BorderLayout.EAST);
- add(pnlStandard, BorderLayout.SOUTH);
-
- //advanced panel
- pnlAdvanced = new TransparentPanel(new BorderLayout());
- JPanel pnlAdvancedButtons = new TransparentPanel(
- new FlowLayout(FlowLayout.RIGHT));
- pnlAdvancedButtons.setBorder(BorderFactory
- .createEmptyBorder(10, 10, 10, 10));
- pnlAdvanced.add(pnlAdvancedButtons, BorderLayout.EAST);
- for(DnssecDialogResult r : DnssecDialogResult.values())
- {
- JButton cmd = new JButton(R.getI18NString(
- "net.java.sip.communicator.util.dns."
- + "ConfigurableDnssecResolver$DnssecDialogResult."
- + r.name()));
- cmd.setActionCommand(r.name());
- cmd.addActionListener(this);
- pnlAdvancedButtons.add(cmd);
- }
- JLabel lblReason = new JLabel(reason);
- lblReason.setBorder(BorderFactory
- .createEmptyBorder(10, 10, 10, 10));
- pnlAdvanced.add(lblReason, BorderLayout.NORTH);
- }
-
- /**
- * Handles the events coming from the buttons.
- */
- public void actionPerformed(ActionEvent e)
- {
- if(e.getSource() == cmdAck)
- {
- result = DnssecDialogResult.Deny;
- dispose();
- }
- else if(e.getSource() == cmdShowDetails)
- {
- getContentPane().remove(pnlStandard);
- add(pnlAdvanced, BorderLayout.SOUTH);
- pack();
- }
- else
- {
- result = Enum.valueOf(DnssecDialogResult.class,
- e.getActionCommand());
- dispose();
- }
- }
-
- /**
- * Gets the option that user has chosen.
- * @return the option that user has chosen.
- */
- public DnssecDialogResult getResult()
- {
- return result;
- }
- }
-
- private String getExceptionMessage(SecureMessage msg)
- {
- return msg.getBogusReason() == null
- ? R.getI18NString(
- "util.dns.INSECURE_ANSWER_MESSAGE_NO_REASON",
- new String[]{msg.getQuestion().getName().toString()}
- )
- : R.getI18NString(
- "util.dns.INSECURE_ANSWER_MESSAGE_REASON",
- new String[]{msg.getQuestion().getName().toString(),
- //TODO parse bogus reason text and translate
- msg.getBogusReason()}
- );
- }
-
- private String createPropNameUnsigned(String fqdn, String type)
- {
- return PNAME_BASE_DNSSEC_PIN + "." + fqdn.replace(".", "__");
- }
-
- /**
- * Reloads the configuration of forwarders and trust anchors.
- */
- @Override
- public void reset()
- {
- String forwarders = DnsUtilActivator.getConfigurationService()
- .getString(DnsUtilActivator.PNAME_DNSSEC_NAMESERVERS);
- if(!StringUtils.isNullOrEmpty(forwarders, true))
- {
- if(logger.isTraceEnabled())
- {
- logger.trace("Setting DNSSEC forwarders to: "
- + Arrays.toString(forwarders.split(",")));
- }
- super.setForwarders(forwarders.split(","));
- }
-
- for(int i = 1;;i++)
- {
- String anchor = DnsUtilActivator.getResources().getSettingsString(
- "net.java.sip.communicator.util.dns.DS_ROOT." + i);
- if(anchor == null)
- break;
- clearTrustAnchors();
- addTrustAnchor(anchor);
- if(logger.isTraceEnabled())
- logger.trace("Loaded trust anchor " + anchor);
- }
- }
-}
+package net.java.sip.communicator.impl.dns; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.List; + +import javax.swing.*; + +import net.java.sip.communicator.service.dns.*; +import net.java.sip.communicator.service.notification.*; +import net.java.sip.communicator.util.Logger; +import net.java.sip.communicator.plugin.desktoputil.*; + +import org.jitsi.dnssec.validator.ValidatingResolver; +import org.jitsi.service.configuration.*; +import org.jitsi.service.resources.*; +import org.jitsi.util.*; +import org.xbill.DNS.*; + +/** + * Resolver that wraps a DNSSEC capable resolver and handles validation + * failures according to the user's settings. + * + * @author Ingo Bauersachs + */ +public class ConfigurableDnssecResolver + extends ValidatingResolver + implements CustomResolver +{ + private final static Logger logger + = Logger.getLogger(ConfigurableDnssecResolver.class); + + /** + * Name of the property that defines the default DNSSEC validation + * behavior. + */ + public final static String PNAME_DNSSEC_VALIDATION_MODE + = "net.java.sip.communicator.util.dns.DNSSEC_VALIDATION_MODE"; + + /** + * Default value of {@link #PNAME_DNSSEC_VALIDATION_MODE} + */ + public final static String PNAME_BASE_DNSSEC_PIN + = "net.java.sip.communicator.util.dns.pin"; + + final static String EVENT_TYPE = "DNSSEC_NOTIFICATION"; + + private ConfigurationService config + = DnsUtilActivator.getConfigurationService(); + private ResourceManagementService R + = DnsUtilActivator.getResources(); + private Map<String, Date> lastNotifications + = new HashMap<String, Date>(); + + private ExtendedResolver headResolver; + + /** + * Creates a new instance of this class. Tries to use the system's + * default forwarders. + */ + public ConfigurableDnssecResolver(ExtendedResolver headResolver) + { + super(headResolver); + + List<String> propNames + = config.getPropertyNamesByPrefix("org.jitsi.dnssec", false); + Properties config = new Properties(); + for (String propName : propNames) + { + String value = config.getProperty(propName); + if (!StringUtils.isNullOrEmpty(value)) + { + config.put(propName, value); + } + } + + try + { + super.init(config); + } + catch (IOException e) + { + logger.error("Extended dnssec properties contained an error", e); + } + + this.headResolver = headResolver; + reset(); + Lookup.setDefaultResolver(this); + + DnsUtilActivator.getNotificationService(). + registerDefaultNotificationForEvent( + ConfigurableDnssecResolver.EVENT_TYPE, + NotificationAction.ACTION_POPUP_MESSAGE, + null, null); + } + + /** + * Inspects a DNS answer message and handles validation results according to + * the user's preferences. + * + * @throws DnssecRuntimeException when the validation failed and the user + * did not choose to ignore it. + */ + @Override + public Message send(Message query) + throws DnssecRuntimeException, IOException + { + //--------------------------------------------------------------------- + // || 1 | 2 | 3 | 4 | 5 + //--------------------------------------------------------------------- + // Sec. | Bog. || Ign. | Sec.Only | Sec.Or.Unsig | Warn.Bog | WarnAll + //--------------------------------------------------------------------- + //a) 1 | 0 || ok | ok | ok | ok | ok + //b) 0 | 1 || ok | nok | nok | ask | ask + //c) 0 | 0 || ok | nok | ok | ok | ask + //--------------------------------------------------------------------- + + SecureMessage msg = new SecureMessage(super.send(query)); + String fqdn = msg.getQuestion().getName().toString(); + String type = Type.string(msg.getQuestion().getType()); + String propName = createPropNameUnsigned(fqdn, type); + SecureResolveMode defaultAction = Enum.valueOf(SecureResolveMode.class, + config.getString( + PNAME_DNSSEC_VALIDATION_MODE, + SecureResolveMode.WarnIfBogus.name() + ) + ); + SecureResolveMode pinned = Enum.valueOf(SecureResolveMode.class, + config.getString( + propName, + defaultAction.name() + ) + ); + + //create default entry + if(pinned == defaultAction) + config.setProperty(propName, pinned.name()); + + //check domain policy + + //[abc]1, a[2-5] + if(pinned == SecureResolveMode.IgnoreDnssec || msg.isSecure()) + return msg; + + if( + //b2, c2 + (pinned == SecureResolveMode.SecureOnly && !msg.isSecure()) + || + //b3 + (pinned == SecureResolveMode.SecureOrUnsigned && msg.isBogus()) + ) + { + String text = getExceptionMessage(msg); + Date last = lastNotifications.get(text); + if(last == null + //wait at least 5 minutes before showing the same info again + || last.before(new Date(new Date().getTime() - 1000*60*5))) + { + DnsUtilActivator.getNotificationService().fireNotification( + EVENT_TYPE, + R.getI18NString("util.dns.INSECURE_ANSWER_TITLE"), + text, + null); + lastNotifications.put(text, new Date()); + } + + throw new DnssecRuntimeException(text); + } + + //c3 + if(pinned == SecureResolveMode.SecureOrUnsigned && !msg.isBogus()) + return msg; + + //c4 + if(pinned == SecureResolveMode.WarnIfBogus && !msg.isBogus()) + return msg; + + //b4, b5, c5 + String reason = msg.isBogus() + ? R.getI18NString("util.dns.DNSSEC_ADVANCED_REASON_BOGUS", + new String[]{fqdn, msg.getBogusReason()}) + : R.getI18NString("util.dns.DNSSEC_ADVANCED_REASON_UNSIGNED", + new String[]{type, fqdn}); + DnssecDialog dlg = new DnssecDialog(fqdn, reason); + dlg.setVisible(true); + DnssecDialogResult result = dlg.getResult(); + switch(result) + { + case Accept: + break; + case Deny: + throw new DnssecRuntimeException(getExceptionMessage(msg)); + case AlwaysAccept: + if(msg.isBogus()) + config.setProperty(propName, + SecureResolveMode.IgnoreDnssec.name()); + else + config.setProperty(propName, + SecureResolveMode.WarnIfBogus.name()); + break; + case AlwaysDeny: + config.setProperty(propName, SecureResolveMode.SecureOnly); + throw new DnssecRuntimeException(getExceptionMessage(msg)); + } + + return msg; + } + + /** + * Defines the return code from the DNSSEC verification dialog. + */ + private enum DnssecDialogResult + { + /** The DNS result shall be accepted. */ + Accept, + /** The result shall be rejected. */ + Deny, + /** The result shall be accepted permanently. */ + AlwaysAccept, + /** + * The result shall be rejected automatically unless it is valid + * according to DNSSEC. + */ + AlwaysDeny + } + + /** + * Dialog to ask and warn the user if he wants to continue to accept an + * invalid dnssec result. + */ + private class DnssecDialog extends SIPCommDialog implements ActionListener + { + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + //UI controls + private JPanel pnlAdvanced; + private JPanel pnlStandard; + private final String domain; + private final String reason; + private JButton cmdAck; + private JButton cmdShowDetails; + + //state + private DnssecDialogResult result = DnssecDialogResult.Deny; + + /** + * Creates a new instance of this class. + * @param domain The FQDN of the domain that failed. + * @param reason String describing why the validation failed. + */ + public DnssecDialog(String domain, String reason) + { + super(false); + setModal(true); + this.domain = domain; + this.reason = reason; + initComponents(); + } + + /** + * Creates the UI controls + */ + private void initComponents() + { + setLayout(new BorderLayout(15, 15)); + setTitle(R.getI18NString("util.dns.INSECURE_ANSWER_TITLE")); + + // warning text + JLabel imgWarning = + new JLabel(R.getImage("service.gui.icons.WARNING_ICON")); + imgWarning.setBorder(BorderFactory + .createEmptyBorder(10, 10, 10, 10)); + add(imgWarning, BorderLayout.WEST); + JLabel lblWarning = new JLabel( + R.getI18NString("util.dns.DNSSEC_WARNING", new String[]{ + R.getSettingsString("service.gui.APPLICATION_NAME"), + domain + }) + ); + add(lblWarning, BorderLayout.CENTER); + + //standard panel (deny option) + cmdAck = new JButton(R.getI18NString("service.gui.OK")); + cmdAck.addActionListener(this); + + cmdShowDetails = new JButton( + R.getI18NString("util.dns.DNSSEC_ADVANCED_OPTIONS")); + cmdShowDetails.addActionListener(this); + + pnlStandard = new TransparentPanel(new BorderLayout()); + pnlStandard.setBorder(BorderFactory + .createEmptyBorder(10, 10, 10, 10)); + pnlStandard.add(cmdShowDetails, BorderLayout.WEST); + pnlStandard.add(cmdAck, BorderLayout.EAST); + add(pnlStandard, BorderLayout.SOUTH); + + //advanced panel + pnlAdvanced = new TransparentPanel(new BorderLayout()); + JPanel pnlAdvancedButtons = new TransparentPanel( + new FlowLayout(FlowLayout.RIGHT)); + pnlAdvancedButtons.setBorder(BorderFactory + .createEmptyBorder(10, 10, 10, 10)); + pnlAdvanced.add(pnlAdvancedButtons, BorderLayout.EAST); + for(DnssecDialogResult r : DnssecDialogResult.values()) + { + JButton cmd = new JButton(R.getI18NString( + "net.java.sip.communicator.util.dns." + + "ConfigurableDnssecResolver$DnssecDialogResult." + + r.name())); + cmd.setActionCommand(r.name()); + cmd.addActionListener(this); + pnlAdvancedButtons.add(cmd); + } + JLabel lblReason = new JLabel(reason); + lblReason.setBorder(BorderFactory + .createEmptyBorder(10, 10, 10, 10)); + pnlAdvanced.add(lblReason, BorderLayout.NORTH); + } + + /** + * Handles the events coming from the buttons. + */ + public void actionPerformed(ActionEvent e) + { + if(e.getSource() == cmdAck) + { + result = DnssecDialogResult.Deny; + dispose(); + } + else if(e.getSource() == cmdShowDetails) + { + getContentPane().remove(pnlStandard); + add(pnlAdvanced, BorderLayout.SOUTH); + pack(); + } + else + { + result = Enum.valueOf(DnssecDialogResult.class, + e.getActionCommand()); + dispose(); + } + } + + /** + * Gets the option that user has chosen. + * @return the option that user has chosen. + */ + public DnssecDialogResult getResult() + { + return result; + } + } + + private String getExceptionMessage(SecureMessage msg) + { + return msg.getBogusReason() == null + ? R.getI18NString( + "util.dns.INSECURE_ANSWER_MESSAGE_NO_REASON", + new String[]{msg.getQuestion().getName().toString()} + ) + : R.getI18NString( + "util.dns.INSECURE_ANSWER_MESSAGE_REASON", + new String[]{msg.getQuestion().getName().toString(), + //TODO parse bogus reason text and translate + msg.getBogusReason()} + ); + } + + private String createPropNameUnsigned(String fqdn, String type) + { + return PNAME_BASE_DNSSEC_PIN + "." + fqdn.replace(".", "__"); + } + + /** + * Reloads the configuration of forwarders and trust anchors. + */ + @Override + public void reset() + { + String forwarders = DnsUtilActivator.getConfigurationService() + .getString(DnsUtilActivator.PNAME_DNSSEC_NAMESERVERS); + if(!StringUtils.isNullOrEmpty(forwarders, true)) + { + if(logger.isTraceEnabled()) + { + logger.trace("Setting DNSSEC forwarders to: " + forwarders); + } + + synchronized (Lookup.class) + { + Lookup.refreshDefault(); + String[] fwds = forwarders.split(","); + Resolver[] rs = headResolver.getResolvers(); + for (Resolver r : rs) + { + headResolver.deleteResolver(r); + } + + for (String fwd : fwds) + { + try + { + SimpleResolver sr = new SimpleResolver(fwd); + + // these properties are normally set by the + // ValidatingResolver in the constructor + sr.setEDNS(0, 0, ExtendedFlags.DO, null); + sr.setIgnoreTruncation(false); + headResolver.addResolver(sr); + } + catch (UnknownHostException e) + { + logger.error("Invalid forwarder, ignoring", e); + } + } + + Lookup.setDefaultResolver(this); + } + } + + StringBuilder sb = new StringBuilder(); + for(int i = 1;;i++) + { + String anchor = DnsUtilActivator.getResources().getSettingsString( + "net.java.sip.communicator.util.dns.DS_ROOT." + i); + if(anchor == null) + { + break; + } + + sb.append(anchor); + sb.append('\n'); + } + + try + { + super.loadTrustAnchors(new ByteArrayInputStream( + sb.toString().getBytes("ASCII"))); + } + catch (IOException e) + { + logger.error("Could not load the trust anchors", e); + } + + if(logger.isTraceEnabled()) + logger.trace("Loaded trust anchors " + sb.toString()); + } +} diff --git a/src/net/java/sip/communicator/impl/dns/DnsUtilActivator.java b/src/net/java/sip/communicator/impl/dns/DnsUtilActivator.java index e8b2e7d..4fc828c 100644 --- a/src/net/java/sip/communicator/impl/dns/DnsUtilActivator.java +++ b/src/net/java/sip/communicator/impl/dns/DnsUtilActivator.java @@ -150,7 +150,7 @@ public class DnsUtilActivator { bundleContext.registerService( CustomResolver.class.getName(), - new ConfigurableDnssecResolver(), + new ConfigurableDnssecResolver(new ExtendedResolver()), null); logger.info("DnssecResolver ... [REGISTERED]"); } diff --git a/src/net/java/sip/communicator/impl/dns/SecureMessage.java b/src/net/java/sip/communicator/impl/dns/SecureMessage.java index 7fe9b68..15f77b0 100644 --- a/src/net/java/sip/communicator/impl/dns/SecureMessage.java +++ b/src/net/java/sip/communicator/impl/dns/SecureMessage.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,90 +15,97 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.dns;
-
-import java.io.*;
-
-import org.xbill.DNS.*;
-
-/**
- * DNS Message that adds DNSSEC validation information.
- *
- * @author Ingo Bauersachs
- */
-public class SecureMessage
- extends Message
-{
- private boolean secure;
- private boolean bogus;
- private String bogusReason;
-
- /**
- * Creates a new instance of this class based on data received from an
- * Unbound resolve.
- *
- * @param msg The answer of the Unbound resolver.
- * @throws IOException
- */
- public SecureMessage(UnboundResult msg) throws IOException
- {
- super(msg.answerPacket);
- secure = msg.secure;
- bogus = msg.bogus;
- bogusReason = msg.whyBogus;
- }
-
- /**
- * Indicates whether the answer is secure.
- * @return True, if the result is validated securely.
- */
- public boolean isSecure()
- {
- return secure;
- }
-
- /**
- * Indicates if there was a validation failure.
- *
- * @return If the result was not secure (secure == false), and this result
- * is due to a security failure, bogus is true.
- */
- public boolean isBogus()
- {
- return bogus;
- }
-
- /**
- * If the result is bogus this contains a string that describes the failure.
- *
- * @return string that describes the failure.
- */
- public String getBogusReason()
- {
- return bogusReason;
- }
-
- /**
- * Converts the Message to a String. The fields secure, bogus and whyBogus
- * are append as a comment.
- */
- @Override
- public String toString()
- {
- StringBuilder s = new StringBuilder(super.toString());
- s.append('\n');
- s.append(";; Secure: ");
- s.append(secure);
- s.append('\n');
- s.append(";; Bogus: ");
- s.append(bogus);
- s.append('\n');
- if(bogus)
- {
- s.append(";; Reason: ");
- s.append(bogusReason);
- s.append('\n');
- }
- return s.toString();
- }
-}
+package net.java.sip.communicator.impl.dns; + +import java.io.*; + +import org.jitsi.dnssec.validator.*; +import org.xbill.DNS.*; + +/** + * DNS Message that adds DNSSEC validation information. + * + * @author Ingo Bauersachs + */ +public class SecureMessage + extends Message +{ + private boolean secure; + private boolean bogus; + private String bogusReason; + + /** + * Creates a new instance of this class based on data received from a + * dnssecjava resolve. + * + * @param msg The answer of the dnssecjava resolver. + * @throws IOException + */ + public SecureMessage(Message msg) throws IOException + { + super(msg.toWire()); + secure = msg.getHeader().getFlag(Flags.AD); + bogus = !secure && msg.getRcode() == Rcode.SERVFAIL; + for (RRset set : msg.getSectionRRsets(Section.ADDITIONAL)) { + if (set.getName().equals(Name.root) && set.getType() == Type.TXT + && set.getDClass() == ValidatingResolver.VALIDATION_REASON_QCLASS) + { + bogusReason = ((TXTRecord)set.first()).getStrings().get(0).toString(); + } + } + } + + /** + * Indicates whether the answer is secure. + * @return True, if the result is validated securely. + */ + public boolean isSecure() + { + return secure; + } + + /** + * Indicates if there was a validation failure. + * + * @return If the result was not secure (secure == false), and this result + * is due to a security failure, bogus is true. + */ + public boolean isBogus() + { + return bogus; + } + + /** + * If the result is bogus this contains a string that describes the failure. + * + * @return string that describes the failure. + */ + public String getBogusReason() + { + return bogusReason; + } + + /** + * Converts the Message to a String. The fields secure, bogus and whyBogus + * are append as a comment. + */ + @Override + public String toString() + { + StringBuilder s = new StringBuilder(super.toString()); + s.append('\n'); + s.append(";; Secure: "); + s.append(secure); + s.append('\n'); + s.append(";; Bogus: "); + s.append(bogus); + s.append('\n'); + if(bogus) + { + s.append(";; Reason: "); + s.append(bogusReason); + s.append('\n'); + } + return s.toString(); + } +} diff --git a/src/net/java/sip/communicator/impl/dns/SecureResolveMode.java b/src/net/java/sip/communicator/impl/dns/SecureResolveMode.java index 8c99836..b74f1c6 100644 --- a/src/net/java/sip/communicator/impl/dns/SecureResolveMode.java +++ b/src/net/java/sip/communicator/impl/dns/SecureResolveMode.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,40 +15,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.dns;
-
-/**
- * Defines how DNSSEC validation errors should be handled.
- *
- * @author Ingo Bauersachs
- */
-public enum SecureResolveMode
-{
- /**
- * Any DNSSEC data is completely ignored.
- */
- IgnoreDnssec,
-
- /**
- * The result of a query is only returned if it validated successfully.
- */
- SecureOnly,
-
- /**
- * The result of a query is returned if it validated successfully or when
- * the zone is unsigned.
- */
- SecureOrUnsigned,
-
- /**
- * If the result of a query is bogus (manipulated, incorrect), the user is
- * to be asked how to proceed.
- */
- WarnIfBogus,
-
- /**
- * If the result of a query is bogus (manipulated, incorrect) or if the zone
- * is unsigned, the user is to be asked how to proceed.
- */
- WarnIfBogusOrUnsigned
-}
+package net.java.sip.communicator.impl.dns; + +/** + * Defines how DNSSEC validation errors should be handled. + * + * @author Ingo Bauersachs + */ +public enum SecureResolveMode +{ + /** + * Any DNSSEC data is completely ignored. + */ + IgnoreDnssec, + + /** + * The result of a query is only returned if it validated successfully. + */ + SecureOnly, + + /** + * The result of a query is returned if it validated successfully or when + * the zone is unsigned. + */ + SecureOrUnsigned, + + /** + * If the result of a query is bogus (manipulated, incorrect), the user is + * to be asked how to proceed. + */ + WarnIfBogus, + + /** + * If the result of a query is bogus (manipulated, incorrect) or if the zone + * is unsigned, the user is to be asked how to proceed. + */ + WarnIfBogusOrUnsigned +} diff --git a/src/net/java/sip/communicator/impl/dns/UnboundApi.java b/src/net/java/sip/communicator/impl/dns/UnboundApi.java deleted file mode 100644 index 6f35a8b..0000000 --- a/src/net/java/sip/communicator/impl/dns/UnboundApi.java +++ /dev/null @@ -1,239 +0,0 @@ -/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.dns;
-
-/**
- * Wrapper for the JUnbound JNI wrapper.
- * <p>
- * The JavaDoc of these methods is directly copied from libunbound, licensed as
- * follows:
- * <p>
- * Copyright (c) 2007, NLnet Labs. All rights reserved.
- *
- * This software is open source.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * Neither the name of the NLNET LABS nor the names of its contributors may be
- * used to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * @author Ingo Bauersachs
- */
-public class UnboundApi
-{
- private static boolean isAvailable;
- private static final Object syncRoot = new Object();
-
- static
- {
- tryLoadUnbound();
- }
-
- /**
- * Attempts to load the Unbound native library. When successful,
- * {@link #isAvailable()} returns true.
- */
- public static void tryLoadUnbound()
- {
- synchronized(syncRoot)
- {
- try
- {
- System.loadLibrary("junbound");
- isAvailable = true;
- }
- catch(UnsatisfiedLinkError e)
- {
- isAvailable = false;
- }
- }
- }
-
- /**
- * Indicates whether the Unbound library is loaded.
- * @return True when the JNI wrapper could be loaded, false otherwise.
- */
- public static boolean isAvailable()
- {
- return isAvailable;
- }
-
- /**
- * Set debug verbosity for the context. Output is directed to stderr. Higher
- * debug level gives more output.
- *
- * @param context context.
- * @param level The debug level.
- */
- public static native void setDebugLevel(long context, int level);
-
- /**
- * Create a resolving and validation context.
- * @return a new context. default initialization. returns NULL on error.
- */
- public static native long createContext();
-
- /**
- * Destroy a validation context and free all its resources. Outstanding
- * async queries are killed and callbacks are not called for them.
- *
- * @param context context to delete
- */
- public static native void deleteContext(long context);
-
- /**
- * Set machine to forward DNS queries to, the caching resolver to use.
- * <p>
- * IP4 or IP6 address. Forwards all DNS requests to that machine, which is
- * expected to run a recursive resolver. If the proxy is not DNSSEC-capable,
- * validation may fail. Can be called several times, in that case the
- * addresses are used as backup servers.
- *
- * @param context context. At this time it is only possible to set
- * configuration before the first resolve is done.
- * @param server address, IP4 or IP6 in string format. If the server is
- * NULL, forwarding is disabled.
- */
- public static native void setForwarder(long context, String server);
-
- /**
- * Add a trust anchor to the given context.
- * <p>
- * The trust anchor is a string, on one line, that holds a valid DNSKEY or
- * DS RR.
- *
- * @param context context. At this time it is only possible to add trusted
- * keys before the first resolve is done.
- * @param anchor string, with zone-format RR on one line. [domainname] [TTL
- * optional] [type] [class optional] [rdata contents]
- */
- public static native void addTrustAnchor(long context, String anchor);
-
- /**
- * Perform resolution and validation of the target name.
- *
- * @param context context. The context is finalized, and can no longer
- * accept config changes.
- * @param name domain name in text format (a zero terminated text string).
- * @param rrtype type of RR in host order, 1 is A (address).
- * @param rrclass class of RR in host order, 1 is IN (for internet).
- * @return the result data is returned in a newly allocated result
- * structure. May be NULL on return, return value is set to an error
- * in that case (out of memory).
- * @throws UnboundException when an error occurred.
- */
- public static native UnboundResult resolve(long context, String name,
- int rrtype, int rrclass) throws UnboundException;
-
- /**
- * Perform resolution and validation of the target name.
- * <p>
- * Asynchronous, after a while, the callback will be called with your data
- * and the result.
- *
- * @param context context. If no thread or process has been created yet to
- * perform the work in the background, it is created now. The
- * context is finalized, and can no longer accept config changes.
- * @param name domain name in text format (a string).
- * @param rrtype type of RR in host order, 1 is A.
- * @param rrclass class of RR in host order, 1 is IN (for internet).
- * @param data this data is your own data (you can pass null), and is passed
- * on to the callback function.
- * @param cb this is called on completion of the resolution.
- * @return an identifier number is returned for the query as it is in
- * progress. It can be used to cancel the query.
- * @throws UnboundException when an error occurred.
- */
- public static native int resolveAsync(long context, String name,
- int rrtype, int rrclass, Object data, UnboundCallback cb)
- throws UnboundException;
-
- /**
- * Cancel an async query in progress. Its callback will not be called.
- *
- * @param context context.
- * @param asyncId which query to cancel.
- * @throws UnboundException This routine can error if the async_id passed
- * does not exist or has already been delivered. If another
- * thread is processing results at the same time, the result may
- * be delivered at the same time and the cancel fails with an
- * error. Also the cancel can fail due to a system error, no
- * memory or socket failures.
- */
- public static native void cancelAsync(long context, int asyncId)
- throws UnboundException;
-
- /**
- * Convert error value to a human readable string.
- *
- * @param code error code from one of the Unbound functions.
- * @return text string of the error code.
- */
- public static native String errorCodeToString(int code);
-
- /**
- * Wait for a context to finish with results. Call this routine to continue
- * processing results from the validating resolver. After the wait, there
- * are no more outstanding asynchronous queries.
- *
- * @param context context.
- * @throws UnboundException when an error occurred.
- */
- public static native void processAsync(long context)
- throws UnboundException;
-
- /**
- * Interface for the async resolve callback.
- */
- public interface UnboundCallback
- {
- /**
- * Called on completion of the async resolution.
- *
- * @param data the same object as passed to
- * {@link UnboundApi#resolveAsync(long, String, int, int,
- * Object, UnboundCallback)}
- * @param err 0 when a result has been found, an Unbound error code
- * otherwise
- * @param result a newly allocated result structure. The result may be
- * null, in that case err is set.
- */
- public void UnboundResolveCallback(Object data, int err,
- UnboundResult result);
- }
-}
diff --git a/src/net/java/sip/communicator/impl/dns/UnboundResolver.java b/src/net/java/sip/communicator/impl/dns/UnboundResolver.java deleted file mode 100644 index 309e58e..0000000 --- a/src/net/java/sip/communicator/impl/dns/UnboundResolver.java +++ /dev/null @@ -1,410 +0,0 @@ -/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.dns;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import java.util.concurrent.*;
-
-import net.java.sip.communicator.service.dns.*;
-import net.java.sip.communicator.util.*;
-
-import org.xbill.DNS.*;
-
-/**
- * Implementation of the {@link Resolver} interface, wrapping the native NLnet
- * Labs Unbound resolver. Only the basic methods for queries are supported.
- *
- * @author Ingo Bauersachs
- */
-public class UnboundResolver
- implements CustomResolver
-{
- private final static Logger logger =
- Logger.getLogger(UnboundResolver.class);
-
- /**
- * Helper class to synchronize on asynchronous queries.
- */
- private static class CallbackData
- {
- /**
- * The resolver consumer that wishes to be informed when the request
- * completed.
- */
- ResolverListener listener;
-
- /**
- * The unbound session context.
- */
- long context;
-
- /**
- * The ID of the unbound async query.
- */
- int asyncId;
-
- /**
- * Java synchronization on top of unbound.
- */
- CountDownLatch sync = new CountDownLatch(1);
- }
-
- /**
- * Timeout for DNS queries.
- */
- private int timeout = 10000;
-
- /**
- * The recursive DNS servers answering our queries.
- */
- private String[] forwarders;
-
- /**
- * DNSSEC trust anchors for signed zones (usually for the root zone).
- */
- private List<String> trustAnchors = new LinkedList<String>();
-
- /**
- * Pool that executes our queries.
- */
- private ExecutorService threadPool;
-
- /**
- * Creates a new instance of this class.
- */
- public UnboundResolver()
- {
- threadPool = Executors.newCachedThreadPool();
- }
-
- /**
- * Sets a list of forwarders to use instead of the system default.
- *
- * @param forwarders list of servers to use for our queries.
- */
- public void setForwarders(String[] forwarders)
- {
- this.forwarders = forwarders;
- }
-
- /**
- * Clears any existing trust anchors previously added.
- */
- public void clearTrustAnchors()
- {
- trustAnchors.clear();
- }
-
- /**
- * Adds a DNSSEC trust anchor validation of the DNSKEYs.
- *
- * @param anchor trust anchor in the form of
- * "'zone' IN DS 'key tag' 'algorithm' 'digest type' 'digest'"
- */
- public void addTrustAnchor(String anchor)
- {
- trustAnchors.add(anchor);
- }
-
- /**
- * {@inheritDoc}
- */
- public SecureMessage send(final Message query) throws IOException
- {
- Future<SecureMessage> future = threadPool.submit(
- new Callable<SecureMessage>()
- {
- public SecureMessage call() throws Exception
- {
- if(logger.isDebugEnabled())
- logger.debug(query);
-
- SecureMessage secureMessage = null;
- final long context = prepareContext();
- try
- {
- UnboundResult result = UnboundApi.resolve(
- context,
- query.getQuestion().getName().toString(),
- query.getQuestion().getType(),
- query.getQuestion().getDClass()
- );
- secureMessage = new SecureMessage(result);
- validateMessage(secureMessage);
- }
- finally
- {
- UnboundApi.deleteContext(context);
- if(logger.isDebugEnabled() && secureMessage != null)
- logger.debug(secureMessage);
- }
-
- return secureMessage;
- }
- });
- try
- {
- return future.get(timeout, TimeUnit.SECONDS);
- }
- catch (InterruptedException e)
- {
- logger.error(e);
- throw new IOException(e.getMessage());
- }
- catch (ExecutionException e)
- {
- if(e.getCause() instanceof DnssecRuntimeException)
- throw new DnssecRuntimeException(e.getCause().getMessage());
- logger.error(e);
- throw new IOException(e.getMessage());
- }
- catch (TimeoutException e)
- {
- throw new SocketTimeoutException(e.getMessage());
- }
- }
-
- /**
- * Method to allow overriders to inspect the message. This class'
- * implementation does nothing.
- *
- * @param msg The message to inspect.
- * @throws DnssecRuntimeException if the inspector does not want the code to
- * continue normal processing of the answer.
- */
- protected void validateMessage(SecureMessage msg)
- throws DnssecRuntimeException
- {
- }
-
- /**
- * Prepares a unbound session context initialized with forwarders and trust
- * anchors.
- *
- * @return The context id
- */
- private long prepareContext()
- {
- final long context = UnboundApi.createContext();
- if(logger.isTraceEnabled())
- UnboundApi.setDebugLevel(context, 100);
- for(String fwd : forwarders == null
- ? ResolverConfig.getCurrentConfig().servers()
- : forwarders)
- {
- fwd = fwd.trim();
- if(NetworkUtils.isValidIPAddress(fwd))
- {
- if(fwd.startsWith("["))
- fwd = fwd.substring(1, fwd.length() - 1);
- UnboundApi.setForwarder(context, fwd);
- }
- }
- for(String anchor : trustAnchors)
- {
- UnboundApi.addTrustAnchor(context, anchor);
- }
- return context;
- }
-
- /**
- * Cleans up an Unbound session context.
- *
- * @param cbData The helper object of the asynchronous call.
- * @param cancelAsync Whether an outstanding asynchronous unbound query
- * should be canceled.
- */
- private static synchronized void deleteContext(CallbackData cbData,
- boolean cancelAsync)
- {
- if(cbData.context == 0)
- return;
-
- if(cancelAsync)
- {
- try
- {
- UnboundApi.cancelAsync(cbData.context, cbData.asyncId);
- }
- catch (UnboundException ignore)
- {}
- }
- UnboundApi.deleteContext(cbData.context);
- cbData.context = 0;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see org.xbill.DNS.Resolver#sendAsync(org.xbill.DNS.Message,
- * org.xbill.DNS.ResolverListener)
- */
- public CallbackData sendAsync(Message query, ResolverListener listener)
- {
- if(listener == null)
- throw new IllegalArgumentException("listener cannot be null");
-
- final long context = prepareContext();
- final CallbackData cbData = new CallbackData();
- cbData.listener = listener;
- cbData.context = context;
- int asyncId;
- try
- {
- asyncId = UnboundApi.resolveAsync(
- context,
- query.getQuestion().getName().toString(),
- query.getQuestion().getType(),
- query.getQuestion().getDClass(),
- cbData,
- new UnboundApi.UnboundCallback()
- {
- public void UnboundResolveCallback(Object data, int err,
- UnboundResult result)
- {
- CallbackData cbData = (CallbackData)data;
- deleteContext(cbData, false);
-
- ResolverListener l = cbData.listener;
- if(err == 0)
- {
- try
- {
- l.receiveMessage(data,
- new SecureMessage(result));
- }
- catch (IOException e)
- {
- l.handleException(data, e);
- }
- }
- else
- l.handleException(data,
- new Exception(
- UnboundApi.errorCodeToString(err)));
-
- cbData.sync.countDown();
- }
- }
- );
- }
- catch (UnboundException e)
- {
- listener.handleException(null, e);
- return null;
- }
- cbData.asyncId = asyncId;
- threadPool.execute(new Runnable()
- {
- public void run()
- {
- try
- {
- UnboundApi.processAsync(context);
- }
- catch(UnboundException ex)
- {
- cbData.listener.handleException(this, ex);
- deleteContext(cbData, false);
- cbData.sync.countDown();
- }
- }
- });
- return cbData;
- }
-
- /**
- * Not supported.
- * @throws UnsupportedOperationException
- */
- public void setEDNS(int level)
- {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Not supported.
- * @throws UnsupportedOperationException
- */
- @SuppressWarnings("rawtypes")
- public void setEDNS(int level, int payloadSize, int flags, List options)
- {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Not supported.
- * @throws UnsupportedOperationException
- */
- public void setIgnoreTruncation(boolean flag)
- {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Not supported.
- * @throws UnsupportedOperationException
- */
- public void setPort(int port)
- {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Not supported.
- * @throws UnsupportedOperationException
- */
- public void setTCP(boolean flag)
- {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Not supported.
- * @throws UnsupportedOperationException
- */
- public void setTSIGKey(TSIG key)
- {
- throw new UnsupportedOperationException();
- }
-
- /* (non-Javadoc)
- * @see org.xbill.DNS.Resolver#setTimeout(int)
- */
- public void setTimeout(int secs)
- {
- timeout = secs * 1000;
- }
-
- /* (non-Javadoc)
- * @see org.xbill.DNS.Resolver#setTimeout(int, int)
- */
- public void setTimeout(int secs, int msecs)
- {
- timeout = secs * 1000 + msecs;
- }
-
- /**
- * Does nothing.
- */
- public void reset()
- {
- }
-}
diff --git a/src/net/java/sip/communicator/impl/dns/UnboundResult.java b/src/net/java/sip/communicator/impl/dns/UnboundResult.java deleted file mode 100644 index 85167d0..0000000 --- a/src/net/java/sip/communicator/impl/dns/UnboundResult.java +++ /dev/null @@ -1,128 +0,0 @@ -/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.dns;
-
-/**
- * Class that contains the answer to query processed by the native Unbound
- * resolver. Corresponds to the <a
- * href="http://unbound.net/documentation/doxygen/structub__result.html"
- * >ub_result</a> data structure.
- *
- * The fields {@link #data} and {@link #canonname} are not filled.
- * <p>
- * The JavaDoc of these fields is directly copied from libunbound, licensed as
- * follows:
- * <p>
- * Copyright (c) 2007, NLnet Labs. All rights reserved.
- *
- * This software is open source.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * Neither the name of the NLNET LABS nor the names of its contributors may be
- * used to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- * @author Ingo Bauersachs
- */
-public class UnboundResult
-{
- /**
- * The original question, name text string.
- */
- String qname;
-
- /**
- * the type asked for
- */
- int qtype;
-
- /**
- * the type asked for
- */
- int qclass;
-
-
- /**
- * a list of network order DNS rdata items, terminated with a NULL pointer,
- * so that data[0] is the first result entry, data[1] the second, and the
- * last entry is NULL.
- */
- byte[][] data;
-
- /**
- * canonical name for the result (the final cname).
- */
- String canonname;
-
- /**
- * DNS RCODE for the result.
- */
- int rcode;
-
- /**
- * The DNS answer packet.
- */
- byte[] answerPacket;
-
-
- /**
- * If there is any data, this is true.
- */
- boolean haveData;
-
- /**
- * If there was no data, and the domain did not exist, this is true.
- */
- boolean nxDomain;
-
- /**
- * True, if the result is validated securely.
- */
- boolean secure;
-
- /**
- * If the result was not secure ({@link #secure} == false), and this result
- * is due to a security failure, bogus is true.
- */
- boolean bogus;
-
- /**
- * If the result is bogus this contains a string (zero terminated) that
- * describes the failure.
- */
- String whyBogus;
-}
diff --git a/src/net/java/sip/communicator/impl/dns/dns.manifest.mf b/src/net/java/sip/communicator/impl/dns/dns.manifest.mf index 331eba1..33174b5 100644 --- a/src/net/java/sip/communicator/impl/dns/dns.manifest.mf +++ b/src/net/java/sip/communicator/impl/dns/dns.manifest.mf @@ -8,6 +8,8 @@ Import-Package: org.jitsi.util, org.osgi.framework,
net.java.sip.communicator.util,
net.java.sip.communicator.plugin.desktoputil,
+ org.jitsi.dnssec,
+ org.jitsi.dnssec.validator,
org.jitsi.service.resources,
org.jitsi.service.fileaccess,
net.java.sip.communicator.service.resources,
diff --git a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsEntryImpl.java b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsEntryImpl.java index d4d2ed5..cf42720 100644 --- a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsEntryImpl.java +++ b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsEntryImpl.java @@ -43,20 +43,14 @@ public class GoogleContactsEntryImpl /** * Google Talk protocol type. */ - private static final String YAHOO_PROTOCOL = - "http://schemas.google.com/g/2005#YAHOO"; - - /** - * Google Talk protocol type. - */ private static final String AIM_PROTOCOL = "http://schemas.google.com/g/2005#AIM"; /** * Google Talk protocol type. */ - private static final String MSN_PROTOCOL = - "http://schemas.google.com/g/2005#MSN"; + private static final String SKYPE_PROTOCOL = + "http://schemas.google.com/g/2005#SKYPE"; /** * Google Talk protocol type. @@ -432,17 +426,13 @@ public class GoogleContactsEntryImpl { proto = GoogleContactsEntry.IMProtocol.GOOGLETALK; } - else if(protocol.equals(YAHOO_PROTOCOL)) - { - proto = GoogleContactsEntry.IMProtocol.YAHOO; - } else if(protocol.equals(AIM_PROTOCOL)) { proto = GoogleContactsEntry.IMProtocol.AIM; } - else if(protocol.equals(MSN_PROTOCOL)) + else if(protocol.equals(SKYPE_PROTOCOL)) { - proto = GoogleContactsEntry.IMProtocol.MSN; + proto = GoogleContactsEntry.IMProtocol.SKYPE; } else if(protocol.equals(ICQ_PROTOCOL)) { diff --git a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsQuery.java b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsQuery.java index d33e4da..66ea7ad 100644 --- a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsQuery.java +++ b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsQuery.java @@ -279,14 +279,11 @@ public class GoogleContactsQuery case ICQ: imSubCat = ContactDetail.SubCategory.ICQ; break; - case YAHOO: - imSubCat = ContactDetail.SubCategory.Yahoo; - break; case JABBER: imSubCat = ContactDetail.SubCategory.Jabber; break; - case MSN: - imSubCat = ContactDetail.SubCategory.MSN; + case SKYPE: + imSubCat = ContactDetail.SubCategory.Skype; break; case GOOGLETALK: imSubCat = ContactDetail.SubCategory.GoogleTalk; @@ -357,12 +354,6 @@ public class GoogleContactsQuery OperationSetBasicTelephony.class, ProtocolNames.JABBER); break; - case YAHOO: - supportedOpSets.add(OperationSetBasicInstantMessaging.class); - preferredProtocols.put( - OperationSetBasicInstantMessaging.class, - ProtocolNames.YAHOO); - break; default: break; } diff --git a/src/net/java/sip/communicator/impl/gui/UIServiceImpl.java b/src/net/java/sip/communicator/impl/gui/UIServiceImpl.java index 2e8735f..db2eac9 100644 --- a/src/net/java/sip/communicator/impl/gui/UIServiceImpl.java +++ b/src/net/java/sip/communicator/impl/gui/UIServiceImpl.java @@ -74,7 +74,8 @@ public class UIServiceImpl implements UIService, ShutdownService, ServiceListener, - PropertyChangeListener + PropertyChangeListener, + UINotificationListener { /** * The <tt>Logger</tt> used by the <tt>UIServiceImpl</tt> class and its @@ -140,6 +141,7 @@ public class UIServiceImpl */ public UIServiceImpl() { + UINotificationManager.addNotificationListener(this); } /** @@ -197,8 +199,12 @@ public class UIServiceImpl } } - if(ConfigurationUtils.isApplicationVisible()) + if(ConfigurationUtils.isApplicationVisible() + || Boolean.getBoolean("disable-tray") + || ConfigurationUtils.isMinimizeInsteadOfHide()) + { mainFrame.setFrameVisible(true); + } SwingUtilities.invokeLater(new RunLoginGui()); @@ -428,36 +434,16 @@ public class UIServiceImpl } /** - * Implements {@link UIService#setExitOnMainWindowClose}. Sets the boolean - * property which indicates whether the application should be exited when - * the main application window is closed. - * - * @param exitOnMainWindowClose <tt>true</tt> if closing the main - * application window should also be exiting the application; otherwise, - * <tt>false</tt> - */ - public void setExitOnMainWindowClose(boolean exitOnMainWindowClose) - { - mainFrame.setDefaultCloseOperation( - exitOnMainWindowClose - ? JFrame.DISPOSE_ON_CLOSE - : JFrame.HIDE_ON_CLOSE); - } - - /** - * Implements {@link UIService#getExitOnMainWindowClose()}. Gets the boolean - * property which indicates whether the application should be exited when - * the main application window is closed. - * - * @return determines whether the UI impl would exit the application when - * the main application window is closed. + * Called from the systray service when a tray has been initialized and + * hiding (instead of minimizing or exiting) is possible). If hiding is + * possible and the option to minimize is not selected, the application + * gets hidden on clicking 'X'. + * + * @param true if a tray icon was loaded. */ - public boolean getExitOnMainWindowClose() + public void setMainWindowCanHide(boolean canHide) { - return - (mainFrame != null) - && (mainFrame.getDefaultCloseOperation() - == JFrame.DISPOSE_ON_CLOSE); + mainFrame.updateCloseAction(canHide); } /** @@ -1642,4 +1628,41 @@ public class UIServiceImpl ChatRoomAutoOpenConfigDialog.showChatRoomAutoOpenConfigDialog( pps, chatRoomId); } + + /** + * Counts the number of unread notifications and forwards the sum to the + * systray service. + */ + @Override + public void notificationReceived(UINotification notification) + { + forwardNotificationCount(); + } + + /** + * Counts the number of unread notifications and forwards the sum to the + * systray service. + */ + @Override + public void notificationCleared(UINotification notification) + { + forwardNotificationCount(); + } + + private void forwardNotificationCount() + { + int count = 0; + for (UINotificationGroup g : UINotificationManager + .getNotificationGroups()) + { + Iterator<UINotification> it = + UINotificationManager.getUnreadNotifications(g); + while (it.hasNext()) + { + count += it.next().getUnreadObjects(); + } + } + + GuiActivator.getSystrayService().setNotificationCount(count); + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/MainFrame.java b/src/net/java/sip/communicator/impl/gui/main/MainFrame.java index a8ee80a..b450efe 100644 --- a/src/net/java/sip/communicator/impl/gui/main/MainFrame.java +++ b/src/net/java/sip/communicator/impl/gui/main/MainFrame.java @@ -334,13 +334,9 @@ public class MainFrame */ private void init() { - setDefaultCloseOperation( - GuiActivator.getUIService().getExitOnMainWindowClose() - ? JFrame.DISPOSE_ON_CLOSE - : JFrame.HIDE_ON_CLOSE); - + // at startup, we cannot hide yet + updateCloseAction(false); registerKeyActions(); - JComponent northPanel = createTopComponent(); this.setJMenuBar(menu); @@ -394,6 +390,30 @@ public class MainFrame } } + /** + * If hiding is possible and the option to minimize is not selected, the + * application gets hidden on clicking 'X'. + * + * @param true if hiding is possible, i.e. a tray icon is loaded + */ + public void updateCloseAction(boolean canHide) + { + if (ConfigurationUtils.isMinimizeInsteadOfHide()) + { + logger.info("Updating close action: DO_NOTHING_ON_CLOSE"); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + } + else + { + logger.info("Updating close action: " + (canHide + ? "HIDE_ON_CLOSE" + : "DISPOSE_ON_CLOSE")); + setDefaultCloseOperation(canHide + ? JFrame.HIDE_ON_CLOSE + : JFrame.DISPOSE_ON_CLOSE); + } + } + private Component createButtonPanel() { boolean isCallButtonEnabled = false; @@ -1885,7 +1905,8 @@ public class MainFrame */ protected void windowClosed(WindowEvent event) { - if(GuiActivator.getUIService().getExitOnMainWindowClose()) + if(getDefaultCloseOperation() == JFrame.EXIT_ON_CLOSE || + getDefaultCloseOperation() == JFrame.DISPOSE_ON_CLOSE) { try { @@ -1919,9 +1940,17 @@ public class MainFrame // On Mac systems the application is not quited on window close, so we // don't need to warn the user. - if (!GuiActivator.getUIService().getExitOnMainWindowClose() - && !OSUtils.IS_MAC) + if (OSUtils.IS_MAC) { + return; + } + + switch (getDefaultCloseOperation()) + { + case JFrame.EXIT_ON_CLOSE: + case JFrame.DISPOSE_ON_CLOSE: + return; + case JFrame.HIDE_ON_CLOSE: SwingUtilities.invokeLater(new Runnable() { public void run() @@ -1940,8 +1969,11 @@ public class MainFrame } } }); - ConfigurationUtils.setApplicationVisible(false); + break; + case JFrame.DO_NOTHING_ON_CLOSE: + this.minimize(); + break; } } diff --git a/src/net/java/sip/communicator/impl/gui/main/UINotification.java b/src/net/java/sip/communicator/impl/gui/main/UINotification.java index fbfb6da..4304907 100644 --- a/src/net/java/sip/communicator/impl/gui/main/UINotification.java +++ b/src/net/java/sip/communicator/impl/gui/main/UINotification.java @@ -17,6 +17,8 @@ */ package net.java.sip.communicator.impl.gui.main; +import java.util.Objects; + /** * The <tt>UINotification</tt> class represents a notification received in the * user interface. This could be a missed call, voicemail, email notification or @@ -172,4 +174,10 @@ public class UINotification return true; } + + @Override + public int hashCode() + { + return Objects.hash(notificationName, parentGroup); + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/UINotificationGroup.java b/src/net/java/sip/communicator/impl/gui/main/UINotificationGroup.java index a2e3673..fb14645 100644 --- a/src/net/java/sip/communicator/impl/gui/main/UINotificationGroup.java +++ b/src/net/java/sip/communicator/impl/gui/main/UINotificationGroup.java @@ -99,7 +99,12 @@ public class UINotificationGroup { synchronized (unreadNotifications) { + List<UINotification> copy = new ArrayList<>(unreadNotifications); unreadNotifications.clear(); + for (UINotification n : copy) + { + UINotificationManager.fireClearedEvent(n); + } } } diff --git a/src/net/java/sip/communicator/impl/gui/main/UINotificationListener.java b/src/net/java/sip/communicator/impl/gui/main/UINotificationListener.java index fc5568d..932d96c 100644 --- a/src/net/java/sip/communicator/impl/gui/main/UINotificationListener.java +++ b/src/net/java/sip/communicator/impl/gui/main/UINotificationListener.java @@ -32,4 +32,11 @@ public interface UINotificationListener * @param notification the notification that was received */ public void notificationReceived(UINotification notification); + + /** + * Indicates that a notification has been cleared. + * + * @param notification the notification that was cleared. + */ + public void notificationCleared(UINotification notification); } diff --git a/src/net/java/sip/communicator/impl/gui/main/UINotificationManager.java b/src/net/java/sip/communicator/impl/gui/main/UINotificationManager.java index b6001f7..2d42233 100644 --- a/src/net/java/sip/communicator/impl/gui/main/UINotificationManager.java +++ b/src/net/java/sip/communicator/impl/gui/main/UINotificationManager.java @@ -150,4 +150,23 @@ public class UINotificationManager listeners.next().notificationReceived(notification); } } + + + /** + * Notifies interested <tt>UINotificationListener</tt> that a + * notification has been cleared. + * + * @param notification the cleared notification + */ + static void fireClearedEvent(UINotification notification) + { + synchronized (notificationListeners) + { + Iterator<UINotificationListener> listeners + = notificationListeners.iterator(); + + while (listeners.hasNext()) + listeners.next().notificationCleared(notification); + } + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/account/AccountsConfigurationPanel.java b/src/net/java/sip/communicator/impl/gui/main/account/AccountsConfigurationPanel.java index 9511310..a85ffc3 100644 --- a/src/net/java/sip/communicator/impl/gui/main/account/AccountsConfigurationPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/account/AccountsConfigurationPanel.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,278 +15,278 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.account;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.beans.*;
-import java.util.List;
-
-import javax.swing.*;
-import javax.swing.event.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.account.*;
-
-import org.jitsi.service.configuration.*;
-import org.jitsi.service.resources.*;
-
-/**
- * The <tt>AccountsConfigurationPanel</tt> is the panel containing the accounts
- * list and according buttons shown in the options form.
- *
- * @author Yana Stamcheva
- * @author Lubomir Marinov
- */
-public class AccountsConfigurationPanel
- extends TransparentPanel
- implements ActionListener,
- ListSelectionListener,
- PropertyChangeListener
-{
- private final AccountList accountList;
-
- private final JButton newButton =
- new JButton(GuiActivator.getResources().getI18NString(
- "service.gui.ADD"));
-
- private final JButton editButton =
- new JButton(GuiActivator.getResources().getI18NString(
- "service.gui.EDIT"));
-
- private final JButton removeButton =
- new JButton(GuiActivator.getResources().getI18NString(
- "service.gui.DELETE"));
-
- /**
- * Creates and initializes this account configuration panel.
- */
- public AccountsConfigurationPanel()
- {
- super(new BorderLayout());
-
- accountList = new AccountList(this);
-
- /*
- * It seems that we can only delete one account at a time because our
- * confirmation dialog asks for one account.
- */
- accountList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-
- this.setPreferredSize(new Dimension(500, 400));
-
- JScrollPane accountListPane = new JScrollPane();
-
- accountListPane.getViewport().add(accountList);
- accountListPane.getVerticalScrollBar().setUnitIncrement(30);
-
- this.add(accountListPane, BorderLayout.CENTER);
-
- JPanel buttonsPanel =
- new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));
-
- newButton.addActionListener(this);
- editButton.addActionListener(this);
- removeButton.addActionListener(this);
-
- this.newButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic(
- "service.gui.ADD"));
- this.editButton
- .setMnemonic(GuiActivator.getResources().getI18nMnemonic(
- "service.gui.EDIT"));
- this.removeButton
- .setMnemonic(GuiActivator.getResources().getI18nMnemonic(
- "service.gui.DELETE"));
-
- buttonsPanel.add(newButton);
-
- buttonsPanel.add(editButton);
-
- buttonsPanel.add(removeButton);
-
- this.add(buttonsPanel, BorderLayout.SOUTH);
-
- accountList.addListSelectionListener(this);
- accountList.addPropertyChangeListener(
- AccountList.ACCOUNT_STATE_CHANGED, this);
- updateButtons();
- }
-
- /**
- * Handles the <tt>ActionEvent</tt> triggered when user clicks on on the
- * buttons. Shows the account registration wizard when user clicks on "New".
- *
- * @param evt the action event that has just occurred.
- */
- public void actionPerformed(ActionEvent evt)
- {
- Object sourceButton = evt.getSource();
-
- if (sourceButton.equals(newButton))
- {
- NewAccountDialog.showNewAccountDialog();
- }
- else if (sourceButton.equals(removeButton))
- {
- Account account = accountList.getSelectedAccount();
-
- if (account == null)
- return;
-
- AccountID accountID = account.getAccountID();
-
- ProtocolProviderFactory providerFactory =
- AccountUtils.getProtocolProviderFactory(
- accountID.getProtocolName());
-
- if (providerFactory != null)
- {
- int result = JOptionPane.showConfirmDialog(
- this,
- GuiActivator.getResources()
- .getI18NString("service.gui.REMOVE_ACCOUNT_MESSAGE"),
- GuiActivator.getResources().getI18NString(
- "service.gui.REMOVE_ACCOUNT"),
- JOptionPane.YES_NO_OPTION);
-
- if (result == JOptionPane.YES_OPTION)
- {
- ConfigurationService configService
- = GuiActivator.getConfigurationService();
- String prefix
- = "net.java.sip.communicator.impl.gui.accounts";
- List<String> accounts
- = configService.getPropertyNamesByPrefix(prefix, true);
-
- for (String accountRootPropName : accounts)
- {
- String accountUID
- = configService.getString(accountRootPropName);
-
- if (accountUID.equals(accountID.getAccountUniqueID()))
- {
- configService.setProperty(accountRootPropName, null);
- break;
- }
- }
- boolean isUninstalled
- = providerFactory.uninstallAccount(accountID);
-
- if (isUninstalled)
- {
- accountList.ensureAccountRemoved(accountID);
-
- // Notify the corresponding wizard that the account
- // would be removed.
- AccountRegWizardContainerImpl wizardContainer
- = (AccountRegWizardContainerImpl) GuiActivator
- .getUIService().getAccountRegWizardContainer();
-
- ProtocolProviderService protocolProvider =
- account.getProtocolProvider();
- AccountRegistrationWizard wizard =
- wizardContainer.getProtocolWizard(protocolProvider);
-
- if (wizard != null)
- wizard.accountRemoved(protocolProvider);
- }
- }
- }
- }
- else if (sourceButton.equals(editButton))
- {
- Account account = accountList.getSelectedAccount();
-
- if (account == null)
- return;
-
- AccountRegWizardContainerImpl wizard =
- (AccountRegWizardContainerImpl) GuiActivator.getUIService()
- .getAccountRegWizardContainer();
-
- AccountRegistrationWizard protocolWizard =
- wizard.getProtocolWizard(account.getProtocolProvider());
-
- ResourceManagementService resources = GuiActivator.getResources();
- if (protocolWizard != null)
- {
- wizard.setTitle(resources.getI18NString(
- "service.gui.ACCOUNT_REGISTRATION_WIZARD"));
-
- wizard.modifyAccount(account.getProtocolProvider());
- wizard.showDialog(false);
- }
- else
- {
- // There is no wizard for this account - just show an error
- // dialog:
- String title = resources.getI18NString("service.gui.ERROR");
- String message =
- resources.getI18NString("service.gui.EDIT_NOT_SUPPORTED");
- ErrorDialog dialog = new ErrorDialog(null, title, message);
- dialog.setVisible(true);
- }
- }
- }
-
- /**
- * Returns the edit button.
- *
- * @return the edit button
- */
- public JButton getEditButton()
- {
- return editButton;
- }
-
- /**
- * Updates enabled states of the buttons of this
- * <tt>AccountsConfigurationPanel</tt> to reflect their applicability to the
- * current selection in <tt>accountList</tt>.
- */
- private void updateButtons()
- {
- if(!SwingUtilities.isEventDispatchThread())
- {
- SwingUtilities.invokeLater(new Runnable()
- {
- public void run()
- {
- updateButtons();
- }
- });
- return;
- }
-
- Account account = accountList.getSelectedAccount();
- boolean enabled = (account != null);
-
- editButton.setEnabled(enabled && account.isEnabled());
- removeButton.setEnabled(enabled);
- }
-
- /**
- * Implements ListSelectionListener#valueChanged(ListSelectionEvent).
- * @param e the <tt>ListSelectionEvent</tt> that notified us
- */
- public void valueChanged(ListSelectionEvent e)
- {
- if (!e.getValueIsAdjusting())
- updateButtons();
- }
-
- /**
- * This method gets called when a property is changed.
- *
- * @param evt A PropertyChangeEvent object describing the event source
- * and the property that has changed.
- */
- public void propertyChange(PropertyChangeEvent evt)
- {
- // update buttons whenever an account changes its state
- updateButtons();
- }
-}
+package net.java.sip.communicator.impl.gui.main.account; + +import java.awt.*; +import java.awt.event.*; +import java.beans.*; +import java.util.List; + +import javax.swing.*; +import javax.swing.event.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.account.*; + +import org.jitsi.service.configuration.*; +import org.jitsi.service.resources.*; + +/** + * The <tt>AccountsConfigurationPanel</tt> is the panel containing the accounts + * list and according buttons shown in the options form. + * + * @author Yana Stamcheva + * @author Lubomir Marinov + */ +public class AccountsConfigurationPanel + extends TransparentPanel + implements ActionListener, + ListSelectionListener, + PropertyChangeListener +{ + private final AccountList accountList; + + private final JButton newButton = + new JButton(GuiActivator.getResources().getI18NString( + "service.gui.ADD")); + + private final JButton editButton = + new JButton(GuiActivator.getResources().getI18NString( + "service.gui.EDIT")); + + private final JButton removeButton = + new JButton(GuiActivator.getResources().getI18NString( + "service.gui.DELETE")); + + /** + * Creates and initializes this account configuration panel. + */ + public AccountsConfigurationPanel() + { + super(new BorderLayout()); + + accountList = new AccountList(this); + + /* + * It seems that we can only delete one account at a time because our + * confirmation dialog asks for one account. + */ + accountList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + this.setPreferredSize(new Dimension(500, 400)); + + JScrollPane accountListPane = new JScrollPane(); + + accountListPane.getViewport().add(accountList); + accountListPane.getVerticalScrollBar().setUnitIncrement(30); + + this.add(accountListPane, BorderLayout.CENTER); + + JPanel buttonsPanel = + new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); + + newButton.addActionListener(this); + editButton.addActionListener(this); + removeButton.addActionListener(this); + + this.newButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic( + "service.gui.ADD")); + this.editButton + .setMnemonic(GuiActivator.getResources().getI18nMnemonic( + "service.gui.EDIT")); + this.removeButton + .setMnemonic(GuiActivator.getResources().getI18nMnemonic( + "service.gui.DELETE")); + + buttonsPanel.add(newButton); + + buttonsPanel.add(editButton); + + buttonsPanel.add(removeButton); + + this.add(buttonsPanel, BorderLayout.SOUTH); + + accountList.addListSelectionListener(this); + accountList.addPropertyChangeListener( + AccountList.ACCOUNT_STATE_CHANGED, this); + updateButtons(); + } + + /** + * Handles the <tt>ActionEvent</tt> triggered when user clicks on on the + * buttons. Shows the account registration wizard when user clicks on "New". + * + * @param evt the action event that has just occurred. + */ + public void actionPerformed(ActionEvent evt) + { + Object sourceButton = evt.getSource(); + + if (sourceButton.equals(newButton)) + { + NewAccountDialog.showNewAccountDialog(); + } + else if (sourceButton.equals(removeButton)) + { + Account account = accountList.getSelectedAccount(); + + if (account == null) + return; + + AccountID accountID = account.getAccountID(); + + ProtocolProviderFactory providerFactory = + AccountUtils.getProtocolProviderFactory( + accountID.getProtocolName()); + + if (providerFactory != null) + { + int result = JOptionPane.showConfirmDialog( + this, + GuiActivator.getResources() + .getI18NString("service.gui.REMOVE_ACCOUNT_MESSAGE"), + GuiActivator.getResources().getI18NString( + "service.gui.REMOVE_ACCOUNT"), + JOptionPane.YES_NO_OPTION); + + if (result == JOptionPane.YES_OPTION) + { + ConfigurationService configService + = GuiActivator.getConfigurationService(); + String prefix + = "net.java.sip.communicator.impl.gui.accounts"; + List<String> accounts + = configService.getPropertyNamesByPrefix(prefix, true); + + for (String accountRootPropName : accounts) + { + String accountUID + = configService.getString(accountRootPropName); + + if (accountUID.equals(accountID.getAccountUniqueID())) + { + configService.setProperty(accountRootPropName, null); + break; + } + } + boolean isUninstalled + = providerFactory.uninstallAccount(accountID); + + if (isUninstalled) + { + accountList.ensureAccountRemoved(accountID); + + // Notify the corresponding wizard that the account + // would be removed. + AccountRegWizardContainerImpl wizardContainer + = (AccountRegWizardContainerImpl) GuiActivator + .getUIService().getAccountRegWizardContainer(); + + ProtocolProviderService protocolProvider = + account.getProtocolProvider(); + AccountRegistrationWizard wizard = + wizardContainer.getProtocolWizard(protocolProvider); + + if (wizard != null) + wizard.accountRemoved(protocolProvider); + } + } + } + } + else if (sourceButton.equals(editButton)) + { + Account account = accountList.getSelectedAccount(); + + if (account == null) + return; + + AccountRegWizardContainerImpl wizard = + (AccountRegWizardContainerImpl) GuiActivator.getUIService() + .getAccountRegWizardContainer(); + + AccountRegistrationWizard protocolWizard = + wizard.getProtocolWizard(account.getProtocolProvider()); + + ResourceManagementService resources = GuiActivator.getResources(); + if (protocolWizard != null) + { + wizard.setTitle(resources.getI18NString( + "service.gui.ACCOUNT_REGISTRATION_WIZARD")); + + wizard.modifyAccount(account.getProtocolProvider()); + wizard.showDialog(false); + } + else + { + // There is no wizard for this account - just show an error + // dialog: + String title = resources.getI18NString("service.gui.ERROR"); + String message = + resources.getI18NString("service.gui.EDIT_NOT_SUPPORTED"); + ErrorDialog dialog = new ErrorDialog(null, title, message); + dialog.setVisible(true); + } + } + } + + /** + * Returns the edit button. + * + * @return the edit button + */ + public JButton getEditButton() + { + return editButton; + } + + /** + * Updates enabled states of the buttons of this + * <tt>AccountsConfigurationPanel</tt> to reflect their applicability to the + * current selection in <tt>accountList</tt>. + */ + private void updateButtons() + { + if(!SwingUtilities.isEventDispatchThread()) + { + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + updateButtons(); + } + }); + return; + } + + Account account = accountList.getSelectedAccount(); + boolean enabled = (account != null); + + editButton.setEnabled(enabled && account.isEnabled()); + removeButton.setEnabled(enabled); + } + + /** + * Implements ListSelectionListener#valueChanged(ListSelectionEvent). + * @param e the <tt>ListSelectionEvent</tt> that notified us + */ + public void valueChanged(ListSelectionEvent e) + { + if (!e.getValueIsAdjusting()) + updateButtons(); + } + + /** + * This method gets called when a property is changed. + * + * @param evt A PropertyChangeEvent object describing the event source + * and the property that has changed. + */ + public void propertyChange(PropertyChangeEvent evt) + { + // update buttons whenever an account changes its state + updateButtons(); + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java b/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java index 697a3fc..de63f79 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,619 +15,619 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.call;
-
-import java.awt.*;
-import java.awt.event.*;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-
-/**
- * The dialog created for a given call.
- *
- * @author Yana Stamcheva
- * @author Adam Netocny
- * @author Lyubomir Marinov
- */
-public class CallDialog
- extends SIPCommFrame
- implements CallContainer,
- CallTitleListener
-{
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- /**
- * Enabling force minimized mode will always open call dialog minimized.
- * The call dialog still can be shown, but by default it will be minimized.
- */
- private static final String FORCE_MINIMIZED_MODE
- = "net.java.sip.communicator.impl.gui.main.call.FORCE_MINIMIZED_MODE";
-
- /**
- * Finds a <tt>Container</tt> which is an ancestor of a specific
- * <tt>Component</tt>, has a set <tt>preferredSize</tt> and is closest to
- * the specified <tt>Component</tt> up the ancestor hierarchy.
- *
- * @param component the <tt>Component</tt> whose ancestor hierarchy is to be
- * searched upwards
- * @return a <tt>Container</tt>, if any, which is an ancestor of the
- * specified <tt>component</tt>, has a set <tt>preferredSize</tt> and is
- * closest to the specified <tt>component</tt> up the ancestor hierarchy
- */
- private static Container findClosestAncestorWithSetPreferredSize(
- Component component)
- {
- if ((component instanceof Container) && component.isPreferredSizeSet())
- return (Container) component;
- else
- {
- Container parent;
-
- while ((parent = component.getParent()) != null)
- {
- if (parent.isPreferredSizeSet())
- return parent;
- else
- component = parent;
- }
- return null;
- }
- }
-
- /**
- * The panel, where all call components are added.
- */
- private CallPanel callPanel;
-
- private final WindowStateListener windowStateListener
- = new WindowStateListener()
- {
- public void windowStateChanged(WindowEvent ev)
- {
- switch (ev.getID())
- {
- case WindowEvent.WINDOW_DEACTIVATED:
- case WindowEvent.WINDOW_ICONIFIED:
- case WindowEvent.WINDOW_LOST_FOCUS:
- setFullScreen(false);
- break;
- }
- }
- };
-
- /**
- * Creates a <tt>CallDialog</tt> by specifying the underlying call panel.
- */
- public CallDialog()
- {
- super(true, false);
-
- setMinimumSize(new Dimension(360, 300));
- }
-
- /**
- * Adds a call panel.
- *
- * @param callPanel the call panel to add to this dialog
- */
- public void addCallPanel(CallPanel callPanel)
- {
- this.callPanel = callPanel;
-
- getContentPane().add(callPanel);
-
- callPanel.addCallTitleListener(this);
- setTitle(callPanel.getCallTitle());
-
- if (!isVisible())
- {
- pack();
-
- // checks whether we need to open the call dialog in minimized mode
- if(GuiActivator.getConfigurationService()
- .getBoolean(FORCE_MINIMIZED_MODE, false))
- {
- setState(ICONIFIED);
- }
- setVisible(true);
- }
- }
-
- /**
- * Called when the title of the given <tt>CallPanel</tt> changes.
- *
- * @param callPanel the <tt>CallPanel</tt>, which title has changed
- */
- public void callTitleChanged(CallPanel callPanel)
- {
- if (this.callPanel.equals(callPanel))
- setTitle(callPanel.getCallTitle());
- }
-
- /**
- * {@inheritDoc}
- *
- * Hang ups the call/telephony conference depicted by this
- * <tt>CallDialog</tt> on close.
- */
- @Override
- protected void close(boolean escape)
- {
- if (escape)
- {
- /*
- * In full-screen mode, ESC does not close this CallDialog but exits
- * from full-screen to windowed mode.
- */
- if (isFullScreen())
- {
- setFullScreen(false);
- return;
- }
- }
- else
- {
- /*
- * If the window has been closed by clicking the X button or
- * pressing the key combination corresponding to the same button we
- * close the window first and then perform all hang up operations.
- */
-
- callPanel.disposeCallInfoFrame();
- // We hide the window here. It will be disposed when the call has
- // been ended.
- setVisible(false);
- }
-
- // Then perform hang up operations.
- callPanel.actionPerformedOnHangupButton(escape);
- }
-
- /**
- * {@inheritDoc}
- *
- * The delay implemented by <tt>CallDialog</tt> is 5 seconds.
- */
- public void close(final CallPanel callPanel, boolean delay)
- {
- if (this.callPanel.equals(callPanel))
- {
- if (delay)
- {
- Timer timer
- = new Timer(
- 5000,
- new ActionListener()
- {
- public void actionPerformed(ActionEvent ev)
- {
- dispose();
- }
- });
-
- timer.setRepeats(false);
- timer.start();
- }
- else
- {
- dispose();
- }
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * <tt>CallDialog</tt> prepares the <tt>CallPanel</tt> it contains for
- * garbage collection.
- */
- @Override
- public void dispose()
- {
- super.dispose();
-
- /*
- * Technically, CallManager adds/removes the callPanel to/from this
- * instance. It may want to just move it to another CallContainer so it
- * does not sound right that we are disposing of it. But we do not have
- * such a case at this time so try to reduce the risk of memory leaks.
- */
- if (this.callPanel != null)
- {
- callPanel.disposeCallInfoFrame();
- callPanel.dispose();
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * Attempts to adjust the size of this <tt>Frame</tt> as requested in the
- * AWT event dispatching thread.
- * <p>
- * The method may be executed on the AWT event dispatching thread only
- * because whoever is making the decision to request an adjustment of the
- * Frame size in relation to a AWT Component should be analyzing that same
- * Component in the AWT event dispatching thread only.
- * </p>
- *
- * @throws RuntimeException if the method is not called on the AWT event
- * dispatching thread
- */
- public void ensureSize(Component component, int width, int height)
- {
- CallManager.assertIsEventDispatchingThread();
-
- Frame frame = CallPeerRendererUtils.getFrame(component);
-
- if (frame == null)
- return;
- else if ((frame.getExtendedState() & Frame.MAXIMIZED_BOTH)
- == Frame.MAXIMIZED_BOTH)
- {
- /*
- * Forcing the size of a Component which is displayed in a maximized
- * window does not sound like anything we want to do.
- */
- return;
- }
- else if (frame.equals(
- frame.getGraphicsConfiguration().getDevice()
- .getFullScreenWindow()))
- {
- /*
- * Forcing the size of a Component which is displayed in a
- * full-screen window does not sound like anything we want to do.
- */
- return;
- }
- else if (!frame.equals(this))
- {
- /* This Frame will try to adjust only its own size. */
- return;
- }
- else if ((component.getHeight() >= height)
- && (component.getWidth() >= width))
- {
- /*
- * We will only enlarge the frame size. If the component has already
- * been given at least what it is requesting, do not enlarge the
- * frame size because the whole calculation is prone to inaccuracy.
- */
- return;
- }
- else
- {
- /*
- * If there is no callPanel, it is unlikely that this CallDialog
- * will be asked to ensureSize. Anyway, support the scenario just in
- * case. In light of the absence of a callPanel to guide this
- * CallDialog about the preferred size, we do not have much of a
- * choice but to trust the method arguments.
- */
- if (callPanel != null)
- {
- /*
- * If there is a callPanel, we are likely to get a much better
- * estimation about the preferred size by asking the callPanel
- * rather than by trusting the method arguments. For example,
- * the visual Component displaying the video streaming from the
- * local user/peer to the remote peer(s) will think that its
- * preferred size is the one to base this Frame's size on but
- * that may be misleading because the local video may not be
- * displayed with its preferred size even if this Frame's size
- * will accommodate it.
- */
- /*
- * Just asking the callPanel about its preferredSize would've
- * been terrificly great. Unfortunately, that is presently
- * futile because the callPanel may have a preferredSize while
- * we are still required to display visual Components displaying
- * video in their non-scaled size. The same goes for any
- * Container which is an ancestor of the specified component.
- */
- Container ancestor
- = findClosestAncestorWithSetPreferredSize(component);
-
- if (ancestor == null)
- ancestor = callPanel;
- /*
- * If the ancestor has a forced preferredSize, its LayoutManager
- * may be able to give a good enough estimation.
- */
- if (ancestor.isPreferredSizeSet())
- {
- LayoutManager ancestorLayout = ancestor.getLayout();
-
- if (ancestorLayout != null)
- {
- Dimension preferredLayoutSize
- = ancestorLayout.preferredLayoutSize(ancestor);
-
- if (preferredLayoutSize != null)
- {
- component = ancestor;
- width = preferredLayoutSize.width;
- height = preferredLayoutSize.height;
- }
- }
- }
- else
- {
- /*
- * If the ancestor doesn't have a preferredSize forced, then
- * we may think that it will calculate an appropriate
- * preferredSize itself.
- */
- Dimension prefSize = ancestor.getPreferredSize();
-
- if (prefSize != null)
- {
- component = ancestor;
- width = prefSize.width;
- height = prefSize.height;
- }
- }
- }
-
- /*
- * If the component (which may be an ancestor of the Component
- * specified as an argument to the ensureSize method at this point)
- * has not been given a size, we will make a mistake if we try to
- * use it for the purposes of determining how much this Frame is to
- * be enlarged.
- */
- Dimension componentSize = component.getSize();
-
- if ((componentSize.width < 1) || (componentSize.height < 1))
- return;
-
- Dimension frameSize = frame.getSize();
- int newFrameWidth = frameSize.width + width - componentSize.width;
- int newFrameHeight
- = frameSize.height + height - componentSize.height;
-
- // Respect the minimum size.
- Dimension minSize = frame.getMinimumSize();
-
- if (newFrameWidth < minSize.width)
- newFrameWidth = minSize.width;
- if (newFrameHeight < minSize.height)
- newFrameHeight = minSize.height;
-
- // Don't get bigger than the screen.
- Rectangle screenBounds
- = frame.getGraphicsConfiguration().getBounds();
-
- if (newFrameWidth > screenBounds.width)
- newFrameWidth = screenBounds.width;
- if (newFrameHeight > screenBounds.height)
- newFrameHeight = screenBounds.height;
-
- /*
- * If we're going to make too small a change, don't even bother.
- * Besides, we don't want some weird recursive resizing.
- * Additionally, do not reduce the Frame size.
- */
- boolean changeWidth = ((newFrameWidth - frameSize.width) > 1);
- boolean changeHeight = ((newFrameHeight - frameSize.height) > 1);
-
- if (changeWidth || changeHeight)
- {
- if (!changeWidth)
- newFrameWidth = frameSize.width;
- else if (!changeHeight)
- newFrameHeight = frameSize.height;
-
- /*
- * The latest requirement with respect to the behavior upon
- * resizing is to center the Frame.
- */
- int newFrameX
- = screenBounds.x
- + (screenBounds.width - newFrameWidth) / 2;
- int newFrameY
- = screenBounds.y
- + (screenBounds.height - newFrameHeight) / 2;
-
- // Do not let the top left go out of the screen.
- if (newFrameX < screenBounds.x)
- newFrameX = screenBounds.x;
- if (newFrameY < screenBounds.y)
- newFrameY = screenBounds.y;
-
- frame.setBounds(
- newFrameX, newFrameY,
- newFrameWidth, newFrameHeight);
-
- /*
- * Make sure that the component which originally requested the
- * update to the size of the frame realizes the change as soon
- * as possible; otherwise, it may request yet another update.
- */
- if (frame.isDisplayable())
- {
- if (frame.isValid())
- frame.doLayout();
- else
- frame.validate();
- frame.repaint();
- }
- else
- frame.doLayout();
- }
- }
- }
-
- /**
- * Returns the frame of the call window.
- *
- * @return the frame of the call window
- */
- public JFrame getFrame()
- {
- return this;
- }
-
- /**
- * Overrides getMinimumSize and checks the minimum size that
- * is needed to display buttons and use it for minimum size if
- * needed.
- * @return minimum size.
- */
- @Override
- public Dimension getMinimumSize()
- {
- Dimension minSize = super.getMinimumSize();
-
- if(callPanel != null)
- {
- int minButtonWidth = callPanel.getMinimumButtonWidth();
-
- if(minButtonWidth > minSize.getWidth())
- minSize = new Dimension(minButtonWidth, 300);
- }
-
- return minSize;
- }
-
- /**
- * Indicates if the given <tt>callPanel</tt> is currently visible.
- *
- * @param callPanel the <tt>CallPanel</tt>, for which we verify
- * @return <tt>true</tt> if the given call container is visible in this
- * call window, otherwise - <tt>false</tt>
- */
- public boolean isCallVisible(CallPanel callPanel)
- {
- return this.callPanel.equals(callPanel) ? isVisible() : false;
- }
-
- /**
- * {@inheritDoc}
- */
- public boolean isFullScreen()
- {
- return isFullScreen(getFrame());
- }
-
- /**
- * Determines whether a specific <tt>Window</tt> is displayed in full-screen
- * mode.
- *
- * @param window the <tt>Window</tt> to be checked whether it is displayed
- * in full-screen mode
- * @return <tt>true</tt> if the specified <tt>window</tt> is displayed in
- * full-screen mode; otherwise, <tt>false</tt>
- */
- public static boolean isFullScreen(Window window)
- {
- GraphicsConfiguration graphicsConfiguration
- = window.getGraphicsConfiguration();
-
- if (graphicsConfiguration != null)
- {
- GraphicsDevice device = graphicsConfiguration.getDevice();
-
- if (device != null)
- return window.equals(device.getFullScreenWindow());
- }
- return false;
- }
-
- /**
- * {@inheritDoc}
- */
- public void setFullScreen(boolean fullScreen)
- {
- GraphicsConfiguration graphicsConfiguration
- = getGraphicsConfiguration();
-
- if (graphicsConfiguration != null)
- {
- GraphicsDevice device = graphicsConfiguration.getDevice();
-
- if (device != null)
- {
- boolean thisIsFullScreen = equals(device.getFullScreenWindow());
- boolean firePropertyChange = false;
- boolean setVisible = isVisible();
-
- try
- {
- if (fullScreen)
- {
- if (!thisIsFullScreen)
- {
- /*
- * XXX The setUndecorated method will only work if
- * this Window is not displayable.
- */
- windowDispose();
- setUndecorated(true);
-
- device.setFullScreenWindow(this);
- firePropertyChange = true;
- }
- }
- else if (thisIsFullScreen)
- {
- /*
- * XXX The setUndecorated method will only work if this
- * Window is not displayable.
- */
- windowDispose();
- setUndecorated(false);
-
- device.setFullScreenWindow(null);
- firePropertyChange = true;
- }
-
- if (firePropertyChange)
- {
- if (fullScreen)
- {
- addWindowStateListener(windowStateListener);
-
- /*
- * If full-screen mode, a black background is the
- * most common.
- */
- getContentPane().setBackground(Color.BLACK);
- }
- else
- {
- removeWindowStateListener(windowStateListener);
-
- /*
- * In windowed mode, a system-defined background is
- * the most common.
- */
- getContentPane().setBackground(null);
- }
-
- firePropertyChange(
- PROP_FULL_SCREEN,
- thisIsFullScreen,
- fullScreen);
- }
- }
- finally
- {
- /*
- * Regardless of whether this Window successfully entered or
- * exited full-screen mode, make sure that remains visible.
- */
- if (setVisible)
- setVisible(true);
- }
- }
- }
- }
-}
+package net.java.sip.communicator.impl.gui.main.call; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.plugin.desktoputil.*; + +/** + * The dialog created for a given call. + * + * @author Yana Stamcheva + * @author Adam Netocny + * @author Lyubomir Marinov + */ +public class CallDialog + extends SIPCommFrame + implements CallContainer, + CallTitleListener +{ + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * Enabling force minimized mode will always open call dialog minimized. + * The call dialog still can be shown, but by default it will be minimized. + */ + private static final String FORCE_MINIMIZED_MODE + = "net.java.sip.communicator.impl.gui.main.call.FORCE_MINIMIZED_MODE"; + + /** + * Finds a <tt>Container</tt> which is an ancestor of a specific + * <tt>Component</tt>, has a set <tt>preferredSize</tt> and is closest to + * the specified <tt>Component</tt> up the ancestor hierarchy. + * + * @param component the <tt>Component</tt> whose ancestor hierarchy is to be + * searched upwards + * @return a <tt>Container</tt>, if any, which is an ancestor of the + * specified <tt>component</tt>, has a set <tt>preferredSize</tt> and is + * closest to the specified <tt>component</tt> up the ancestor hierarchy + */ + private static Container findClosestAncestorWithSetPreferredSize( + Component component) + { + if ((component instanceof Container) && component.isPreferredSizeSet()) + return (Container) component; + else + { + Container parent; + + while ((parent = component.getParent()) != null) + { + if (parent.isPreferredSizeSet()) + return parent; + else + component = parent; + } + return null; + } + } + + /** + * The panel, where all call components are added. + */ + private CallPanel callPanel; + + private final WindowStateListener windowStateListener + = new WindowStateListener() + { + public void windowStateChanged(WindowEvent ev) + { + switch (ev.getID()) + { + case WindowEvent.WINDOW_DEACTIVATED: + case WindowEvent.WINDOW_ICONIFIED: + case WindowEvent.WINDOW_LOST_FOCUS: + setFullScreen(false); + break; + } + } + }; + + /** + * Creates a <tt>CallDialog</tt> by specifying the underlying call panel. + */ + public CallDialog() + { + super(true, false); + + setMinimumSize(new Dimension(360, 300)); + } + + /** + * Adds a call panel. + * + * @param callPanel the call panel to add to this dialog + */ + public void addCallPanel(CallPanel callPanel) + { + this.callPanel = callPanel; + + getContentPane().add(callPanel); + + callPanel.addCallTitleListener(this); + setTitle(callPanel.getCallTitle()); + + if (!isVisible()) + { + pack(); + + // checks whether we need to open the call dialog in minimized mode + if(GuiActivator.getConfigurationService() + .getBoolean(FORCE_MINIMIZED_MODE, false)) + { + setState(ICONIFIED); + } + setVisible(true); + } + } + + /** + * Called when the title of the given <tt>CallPanel</tt> changes. + * + * @param callPanel the <tt>CallPanel</tt>, which title has changed + */ + public void callTitleChanged(CallPanel callPanel) + { + if (this.callPanel.equals(callPanel)) + setTitle(callPanel.getCallTitle()); + } + + /** + * {@inheritDoc} + * + * Hang ups the call/telephony conference depicted by this + * <tt>CallDialog</tt> on close. + */ + @Override + protected void close(boolean escape) + { + if (escape) + { + /* + * In full-screen mode, ESC does not close this CallDialog but exits + * from full-screen to windowed mode. + */ + if (isFullScreen()) + { + setFullScreen(false); + return; + } + } + else + { + /* + * If the window has been closed by clicking the X button or + * pressing the key combination corresponding to the same button we + * close the window first and then perform all hang up operations. + */ + + callPanel.disposeCallInfoFrame(); + // We hide the window here. It will be disposed when the call has + // been ended. + setVisible(false); + } + + // Then perform hang up operations. + callPanel.actionPerformedOnHangupButton(escape); + } + + /** + * {@inheritDoc} + * + * The delay implemented by <tt>CallDialog</tt> is 5 seconds. + */ + public void close(final CallPanel callPanel, boolean delay) + { + if (this.callPanel.equals(callPanel)) + { + if (delay) + { + Timer timer + = new Timer( + 5000, + new ActionListener() + { + public void actionPerformed(ActionEvent ev) + { + dispose(); + } + }); + + timer.setRepeats(false); + timer.start(); + } + else + { + dispose(); + } + } + } + + /** + * {@inheritDoc} + * + * <tt>CallDialog</tt> prepares the <tt>CallPanel</tt> it contains for + * garbage collection. + */ + @Override + public void dispose() + { + super.dispose(); + + /* + * Technically, CallManager adds/removes the callPanel to/from this + * instance. It may want to just move it to another CallContainer so it + * does not sound right that we are disposing of it. But we do not have + * such a case at this time so try to reduce the risk of memory leaks. + */ + if (this.callPanel != null) + { + callPanel.disposeCallInfoFrame(); + callPanel.dispose(); + } + } + + /** + * {@inheritDoc} + * + * Attempts to adjust the size of this <tt>Frame</tt> as requested in the + * AWT event dispatching thread. + * <p> + * The method may be executed on the AWT event dispatching thread only + * because whoever is making the decision to request an adjustment of the + * Frame size in relation to a AWT Component should be analyzing that same + * Component in the AWT event dispatching thread only. + * </p> + * + * @throws RuntimeException if the method is not called on the AWT event + * dispatching thread + */ + public void ensureSize(Component component, int width, int height) + { + CallManager.assertIsEventDispatchingThread(); + + Frame frame = CallPeerRendererUtils.getFrame(component); + + if (frame == null) + return; + else if ((frame.getExtendedState() & Frame.MAXIMIZED_BOTH) + == Frame.MAXIMIZED_BOTH) + { + /* + * Forcing the size of a Component which is displayed in a maximized + * window does not sound like anything we want to do. + */ + return; + } + else if (frame.equals( + frame.getGraphicsConfiguration().getDevice() + .getFullScreenWindow())) + { + /* + * Forcing the size of a Component which is displayed in a + * full-screen window does not sound like anything we want to do. + */ + return; + } + else if (!frame.equals(this)) + { + /* This Frame will try to adjust only its own size. */ + return; + } + else if ((component.getHeight() >= height) + && (component.getWidth() >= width)) + { + /* + * We will only enlarge the frame size. If the component has already + * been given at least what it is requesting, do not enlarge the + * frame size because the whole calculation is prone to inaccuracy. + */ + return; + } + else + { + /* + * If there is no callPanel, it is unlikely that this CallDialog + * will be asked to ensureSize. Anyway, support the scenario just in + * case. In light of the absence of a callPanel to guide this + * CallDialog about the preferred size, we do not have much of a + * choice but to trust the method arguments. + */ + if (callPanel != null) + { + /* + * If there is a callPanel, we are likely to get a much better + * estimation about the preferred size by asking the callPanel + * rather than by trusting the method arguments. For example, + * the visual Component displaying the video streaming from the + * local user/peer to the remote peer(s) will think that its + * preferred size is the one to base this Frame's size on but + * that may be misleading because the local video may not be + * displayed with its preferred size even if this Frame's size + * will accommodate it. + */ + /* + * Just asking the callPanel about its preferredSize would've + * been terrificly great. Unfortunately, that is presently + * futile because the callPanel may have a preferredSize while + * we are still required to display visual Components displaying + * video in their non-scaled size. The same goes for any + * Container which is an ancestor of the specified component. + */ + Container ancestor + = findClosestAncestorWithSetPreferredSize(component); + + if (ancestor == null) + ancestor = callPanel; + /* + * If the ancestor has a forced preferredSize, its LayoutManager + * may be able to give a good enough estimation. + */ + if (ancestor.isPreferredSizeSet()) + { + LayoutManager ancestorLayout = ancestor.getLayout(); + + if (ancestorLayout != null) + { + Dimension preferredLayoutSize + = ancestorLayout.preferredLayoutSize(ancestor); + + if (preferredLayoutSize != null) + { + component = ancestor; + width = preferredLayoutSize.width; + height = preferredLayoutSize.height; + } + } + } + else + { + /* + * If the ancestor doesn't have a preferredSize forced, then + * we may think that it will calculate an appropriate + * preferredSize itself. + */ + Dimension prefSize = ancestor.getPreferredSize(); + + if (prefSize != null) + { + component = ancestor; + width = prefSize.width; + height = prefSize.height; + } + } + } + + /* + * If the component (which may be an ancestor of the Component + * specified as an argument to the ensureSize method at this point) + * has not been given a size, we will make a mistake if we try to + * use it for the purposes of determining how much this Frame is to + * be enlarged. + */ + Dimension componentSize = component.getSize(); + + if ((componentSize.width < 1) || (componentSize.height < 1)) + return; + + Dimension frameSize = frame.getSize(); + int newFrameWidth = frameSize.width + width - componentSize.width; + int newFrameHeight + = frameSize.height + height - componentSize.height; + + // Respect the minimum size. + Dimension minSize = frame.getMinimumSize(); + + if (newFrameWidth < minSize.width) + newFrameWidth = minSize.width; + if (newFrameHeight < minSize.height) + newFrameHeight = minSize.height; + + // Don't get bigger than the screen. + Rectangle screenBounds + = frame.getGraphicsConfiguration().getBounds(); + + if (newFrameWidth > screenBounds.width) + newFrameWidth = screenBounds.width; + if (newFrameHeight > screenBounds.height) + newFrameHeight = screenBounds.height; + + /* + * If we're going to make too small a change, don't even bother. + * Besides, we don't want some weird recursive resizing. + * Additionally, do not reduce the Frame size. + */ + boolean changeWidth = ((newFrameWidth - frameSize.width) > 1); + boolean changeHeight = ((newFrameHeight - frameSize.height) > 1); + + if (changeWidth || changeHeight) + { + if (!changeWidth) + newFrameWidth = frameSize.width; + else if (!changeHeight) + newFrameHeight = frameSize.height; + + /* + * The latest requirement with respect to the behavior upon + * resizing is to center the Frame. + */ + int newFrameX + = screenBounds.x + + (screenBounds.width - newFrameWidth) / 2; + int newFrameY + = screenBounds.y + + (screenBounds.height - newFrameHeight) / 2; + + // Do not let the top left go out of the screen. + if (newFrameX < screenBounds.x) + newFrameX = screenBounds.x; + if (newFrameY < screenBounds.y) + newFrameY = screenBounds.y; + + frame.setBounds( + newFrameX, newFrameY, + newFrameWidth, newFrameHeight); + + /* + * Make sure that the component which originally requested the + * update to the size of the frame realizes the change as soon + * as possible; otherwise, it may request yet another update. + */ + if (frame.isDisplayable()) + { + if (frame.isValid()) + frame.doLayout(); + else + frame.validate(); + frame.repaint(); + } + else + frame.doLayout(); + } + } + } + + /** + * Returns the frame of the call window. + * + * @return the frame of the call window + */ + public JFrame getFrame() + { + return this; + } + + /** + * Overrides getMinimumSize and checks the minimum size that + * is needed to display buttons and use it for minimum size if + * needed. + * @return minimum size. + */ + @Override + public Dimension getMinimumSize() + { + Dimension minSize = super.getMinimumSize(); + + if(callPanel != null) + { + int minButtonWidth = callPanel.getMinimumButtonWidth(); + + if(minButtonWidth > minSize.getWidth()) + minSize = new Dimension(minButtonWidth, 300); + } + + return minSize; + } + + /** + * Indicates if the given <tt>callPanel</tt> is currently visible. + * + * @param callPanel the <tt>CallPanel</tt>, for which we verify + * @return <tt>true</tt> if the given call container is visible in this + * call window, otherwise - <tt>false</tt> + */ + public boolean isCallVisible(CallPanel callPanel) + { + return this.callPanel.equals(callPanel) ? isVisible() : false; + } + + /** + * {@inheritDoc} + */ + public boolean isFullScreen() + { + return isFullScreen(getFrame()); + } + + /** + * Determines whether a specific <tt>Window</tt> is displayed in full-screen + * mode. + * + * @param window the <tt>Window</tt> to be checked whether it is displayed + * in full-screen mode + * @return <tt>true</tt> if the specified <tt>window</tt> is displayed in + * full-screen mode; otherwise, <tt>false</tt> + */ + public static boolean isFullScreen(Window window) + { + GraphicsConfiguration graphicsConfiguration + = window.getGraphicsConfiguration(); + + if (graphicsConfiguration != null) + { + GraphicsDevice device = graphicsConfiguration.getDevice(); + + if (device != null) + return window.equals(device.getFullScreenWindow()); + } + return false; + } + + /** + * {@inheritDoc} + */ + public void setFullScreen(boolean fullScreen) + { + GraphicsConfiguration graphicsConfiguration + = getGraphicsConfiguration(); + + if (graphicsConfiguration != null) + { + GraphicsDevice device = graphicsConfiguration.getDevice(); + + if (device != null) + { + boolean thisIsFullScreen = equals(device.getFullScreenWindow()); + boolean firePropertyChange = false; + boolean setVisible = isVisible(); + + try + { + if (fullScreen) + { + if (!thisIsFullScreen) + { + /* + * XXX The setUndecorated method will only work if + * this Window is not displayable. + */ + windowDispose(); + setUndecorated(true); + + device.setFullScreenWindow(this); + firePropertyChange = true; + } + } + else if (thisIsFullScreen) + { + /* + * XXX The setUndecorated method will only work if this + * Window is not displayable. + */ + windowDispose(); + setUndecorated(false); + + device.setFullScreenWindow(null); + firePropertyChange = true; + } + + if (firePropertyChange) + { + if (fullScreen) + { + addWindowStateListener(windowStateListener); + + /* + * If full-screen mode, a black background is the + * most common. + */ + getContentPane().setBackground(Color.BLACK); + } + else + { + removeWindowStateListener(windowStateListener); + + /* + * In windowed mode, a system-defined background is + * the most common. + */ + getContentPane().setBackground(null); + } + + firePropertyChange( + PROP_FULL_SCREEN, + thisIsFullScreen, + fullScreen); + } + } + finally + { + /* + * Regardless of whether this Window successfully entered or + * exited full-screen mode, make sure that remains visible. + */ + if (setVisible) + setVisible(true); + } + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java b/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java index 095b3dd..d1c0a42 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java @@ -130,6 +130,14 @@ public class CallHistoryButton } /** + * Does nothing because this class causes the clearing. + */ + @Override + public void notificationCleared(UINotification notification) + { + } + + /** * Sets the history view. */ private void setHistoryView() diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java b/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java index ad8f6fd..872e861 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java @@ -34,6 +34,7 @@ import net.java.sip.communicator.util.*; import org.ice4j.ice.*; import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.stats.*; import org.jitsi.service.resources.*; import org.jitsi.util.*; @@ -469,7 +470,7 @@ public class CallInfoFrame StringBuffer stringBuffer, MediaType mediaType) { - MediaStreamStats mediaStreamStats + MediaStreamStats2 mediaStreamStats = mediaStream.getMediaStreamStats(); if(mediaStreamStats == null) @@ -624,18 +625,19 @@ public class CallInfoFrame resources.getI18NString( "service.gui.callinfo.BANDWITH"), "↓ " - + (int) mediaStreamStats.getDownloadRateKiloBitPerSec() + + (int) mediaStreamStats.getReceiveStats().getBitrate()/1024 + " Kbps " + " ↑ " - + (int) mediaStreamStats.getUploadRateKiloBitPerSec() + + (int) mediaStreamStats.getSendStats().getBitrate()/1024 + " Kbps")); stringBuffer.append( getLineString( resources.getI18NString("service.gui.callinfo.LOSS_RATE"), - "↓ " + (int) mediaStreamStats.getDownloadPercentLoss() + "↓" + + (int) (mediaStreamStats.getReceiveStats().getLossRate() * 100) + "% ↑ " - + (int) mediaStreamStats.getUploadPercentLoss() + + (int) (mediaStreamStats.getSendStats().getLossRate() * 100) + "%")); stringBuffer.append( getLineString( @@ -668,21 +670,23 @@ public class CallInfoFrame + mediaStreamStats.getPacketQueueCountPackets() + "/" + mediaStreamStats.getPacketQueueSize() + " packets")); - long rttMs = mediaStreamStats.getRttMs(); - if(rttMs != -1) + long sendRttMs = mediaStreamStats.getSendStats().getRtt(); + long recvRttMs = mediaStreamStats.getReceiveStats().getRtt(); + if(recvRttMs != -1 || sendRttMs != -1) { stringBuffer.append( getLineString(resources.getI18NString( "service.gui.callinfo.RTT"), - rttMs + " ms")); + (recvRttMs != -1 ? "↓ " + recvRttMs + " ms" : "") + + (sendRttMs != -1 ? "↑ " + sendRttMs + " ms" : ""))); } stringBuffer.append( getLineString(resources.getI18NString( "service.gui.callinfo.JITTER"), - "↓ " + (int) mediaStreamStats.getDownloadJitterMs() + "↓ " + (int) mediaStreamStats.getReceiveStats().getJitter() + " ms ↑ " - + (int) mediaStreamStats.getUploadJitterMs() + " ms")); + + (int) mediaStreamStats.getSendStats().getJitter() + " ms")); } /** diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java b/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java index 54f48ee..6235e1c 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,276 +15,276 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.call;
-
-import java.awt.datatransfer.*;
-import java.awt.im.*;
-import java.io.*;
-import java.util.*;
-
-import javax.swing.*;
-
-import org.jitsi.service.resources.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.main.contactlist.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-/**
- * A <tt>TransferHandler</tt> that handles dropping of <tt>UIContact</tt>s or
- * <tt>String</tt> addresses on a <tt>CallConference</tt>. Dropping such data on
- * the <tt>CallDialog</tt> will turn a one-to-one <tt>Call</tt> into a telephony
- * conference.
- *
- * @author Yana Stamcheva
- */
-public class CallTransferHandler
- extends ExtendedTransferHandler
-{
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- /**
- * The data flavor used when transferring <tt>UIContact</tt>s.
- */
- protected static final DataFlavor uiContactDataFlavor
- = new DataFlavor(UIContact.class, "UIContact");
-
- /**
- * The logger.
- */
- private static final Logger logger
- = Logger.getLogger(CallTransferHandler.class);
-
- /**
- * The <tt>CallConference</tt> into which the dropped callees are to be
- * invited.
- */
- private final CallConference callConference;
-
- /**
- * Initializes a new <tt>CallTransferHandler</tt> instance which is to
- * invite dropped callees to a telephony conference specified by a specific
- * <tt>Call</tt> which participates in it.
- *
- * @param call the <tt>Call</tt> which specifies the telephony conference to
- * which dropped callees are to be invited
- */
- public CallTransferHandler(Call call)
- {
- this(call.getConference());
- }
-
- /**
- * Initializes a new <tt>CallTransferHandler</tt> instance which is to
- * invite dropped callees to a specific <tt>CallConference</tt>.
- *
- * @param callConference the <tt>CallConference</tt> to which dropped
- * callees are to be invited
- */
- public CallTransferHandler(CallConference callConference)
- {
- this.callConference = callConference;
- }
-
- /**
- * Indicates whether a component will accept an import of the given
- * set of data flavors prior to actually attempting to import it. We return
- * <tt>true</tt> to indicate that the transfer with at least one of the
- * given flavors would work and <tt>false</tt> to reject the transfer.
- * <p>
- * @param comp component
- * @param flavor the data formats available
- * @return true if the data can be inserted into the component, false
- * otherwise
- * @throws NullPointerException if <code>support</code> is {@code null}
- */
- @Override
- public boolean canImport(JComponent comp, DataFlavor[] flavor)
- {
- for (DataFlavor f : flavor)
- {
- if (f.equals(DataFlavor.stringFlavor)
- || f.equals(uiContactDataFlavor))
- {
- return (comp instanceof JPanel);
- }
- }
- return false;
- }
-
- /**
- * Handles transfers to the chat panel from the clip board or a
- * DND drop operation. The <tt>Transferable</tt> parameter contains the
- * data that needs to be imported.
- * <p>
- * @param comp the component to receive the transfer;
- * @param t the data to import
- * @return true if the data was inserted into the component and false
- * otherwise
- */
- @Override
- public boolean importData(JComponent comp, Transferable t)
- {
- String callee = null;
- ProtocolProviderService provider = null;
-
- if (t.isDataFlavorSupported(uiContactDataFlavor))
- {
- Object o = null;
-
- try
- {
- o = t.getTransferData(uiContactDataFlavor);
- }
- catch (UnsupportedFlavorException e)
- {
- if (logger.isDebugEnabled())
- logger.debug("Failed to drop meta contact.", e);
- }
- catch (IOException e)
- {
- if (logger.isDebugEnabled())
- logger.debug("Failed to drop meta contact.", e);
- }
-
- if (o instanceof ContactNode)
- {
- UIContact uiContact = ((ContactNode) o).getContactDescriptor();
- Iterator<UIContactDetail> contactDetails
- = uiContact
- .getContactDetailsForOperationSet(
- OperationSetBasicTelephony.class)
- .iterator();
-
- while (contactDetails.hasNext())
- {
- UIContactDetail detail = contactDetails.next();
- ProtocolProviderService detailProvider
- = detail.getPreferredProtocolProvider(
- OperationSetBasicTelephony.class);
-
- if (detailProvider != null)
- {
- /*
- * Currently for videobridge conferences we only support
- * adding contacts via the account with the videobridge
- */
- if (callConference.isJitsiVideobridge())
- {
- for (Call call : callConference.getCalls())
- {
- if (detailProvider == call.getProtocolProvider())
- {
- callee = detail.getAddress();
- provider = detailProvider;
- break;
- }
- }
- }
- else
- {
- callee = detail.getAddress();
- provider = detailProvider;
- break;
- }
- }
- }
-
- if (callee == null)
- {
- /*
- * It turns out that the error message to be reported would
- * like to display information about the account which could
- * not add the dropped callee to the telephony conference.
- * Unfortunately, a telephony conference may have multiple
- * accounts involved. Anyway, choose the first account
- * involved in the telephony conference.
- */
- ProtocolProviderService callProvider
- = callConference.getCalls().get(0)
- .getProtocolProvider();
-
- ResourceManagementService resources
- = GuiActivator.getResources();
- AccountID accountID = callProvider.getAccountID();
-
- new ErrorDialog(null,
- resources.getI18NString("service.gui.ERROR"),
- resources.getI18NString(
- "service.gui.CALL_NOT_SUPPORTING_PARTICIPANT",
- new String[]
- {
- accountID.getService(),
- accountID.getUserID(),
- uiContact.getDisplayName()
- }))
- .showDialog();
- }
- }
- }
- else if (t.isDataFlavorSupported(DataFlavor.stringFlavor))
- {
- InputContext inputContext = comp.getInputContext();
-
- if (inputContext != null)
- inputContext.endComposition();
-
- try
- {
- BufferedReader reader
- = new BufferedReader(
- DataFlavor.stringFlavor.getReaderForText(t));
-
- try
- {
- String line;
- StringBuilder calleeBuilder = new StringBuilder();
-
- while ((line = reader.readLine()) != null)
- calleeBuilder.append(line);
-
- callee = calleeBuilder.toString();
- /*
- * The value of the local variable provider will be null
- * because we have a String only and hence we have no
- * associated ProtocolProviderService.
- * CallManager.inviteToConferenceCall will accept it.
- */
- }
- finally
- {
- reader.close();
- }
- }
- catch (UnsupportedFlavorException e)
- {
- if (logger.isDebugEnabled())
- logger.debug("Failed to drop string.", e);
- }
- catch (IOException e)
- {
- if (logger.isDebugEnabled())
- logger.debug("Failed to drop string.", e);
- }
- }
-
- if (callee == null)
- return false;
- else
- {
- Map<ProtocolProviderService, List<String>> callees
- = new HashMap<ProtocolProviderService, List<String>>();
-
- callees.put(provider, Arrays.asList(callee));
- CallManager.inviteToConferenceCall(callees, callConference);
-
- return true;
- }
- }
-}
+package net.java.sip.communicator.impl.gui.main.call; + +import java.awt.datatransfer.*; +import java.awt.im.*; +import java.io.*; +import java.util.*; + +import javax.swing.*; + +import org.jitsi.service.resources.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.*; +import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * A <tt>TransferHandler</tt> that handles dropping of <tt>UIContact</tt>s or + * <tt>String</tt> addresses on a <tt>CallConference</tt>. Dropping such data on + * the <tt>CallDialog</tt> will turn a one-to-one <tt>Call</tt> into a telephony + * conference. + * + * @author Yana Stamcheva + */ +public class CallTransferHandler + extends ExtendedTransferHandler +{ + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * The data flavor used when transferring <tt>UIContact</tt>s. + */ + protected static final DataFlavor uiContactDataFlavor + = new DataFlavor(UIContact.class, "UIContact"); + + /** + * The logger. + */ + private static final Logger logger + = Logger.getLogger(CallTransferHandler.class); + + /** + * The <tt>CallConference</tt> into which the dropped callees are to be + * invited. + */ + private final CallConference callConference; + + /** + * Initializes a new <tt>CallTransferHandler</tt> instance which is to + * invite dropped callees to a telephony conference specified by a specific + * <tt>Call</tt> which participates in it. + * + * @param call the <tt>Call</tt> which specifies the telephony conference to + * which dropped callees are to be invited + */ + public CallTransferHandler(Call call) + { + this(call.getConference()); + } + + /** + * Initializes a new <tt>CallTransferHandler</tt> instance which is to + * invite dropped callees to a specific <tt>CallConference</tt>. + * + * @param callConference the <tt>CallConference</tt> to which dropped + * callees are to be invited + */ + public CallTransferHandler(CallConference callConference) + { + this.callConference = callConference; + } + + /** + * Indicates whether a component will accept an import of the given + * set of data flavors prior to actually attempting to import it. We return + * <tt>true</tt> to indicate that the transfer with at least one of the + * given flavors would work and <tt>false</tt> to reject the transfer. + * <p> + * @param comp component + * @param flavor the data formats available + * @return true if the data can be inserted into the component, false + * otherwise + * @throws NullPointerException if <code>support</code> is {@code null} + */ + @Override + public boolean canImport(JComponent comp, DataFlavor[] flavor) + { + for (DataFlavor f : flavor) + { + if (f.equals(DataFlavor.stringFlavor) + || f.equals(uiContactDataFlavor)) + { + return (comp instanceof JPanel); + } + } + return false; + } + + /** + * Handles transfers to the chat panel from the clip board or a + * DND drop operation. The <tt>Transferable</tt> parameter contains the + * data that needs to be imported. + * <p> + * @param comp the component to receive the transfer; + * @param t the data to import + * @return true if the data was inserted into the component and false + * otherwise + */ + @Override + public boolean importData(JComponent comp, Transferable t) + { + String callee = null; + ProtocolProviderService provider = null; + + if (t.isDataFlavorSupported(uiContactDataFlavor)) + { + Object o = null; + + try + { + o = t.getTransferData(uiContactDataFlavor); + } + catch (UnsupportedFlavorException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop meta contact.", e); + } + catch (IOException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop meta contact.", e); + } + + if (o instanceof ContactNode) + { + UIContact uiContact = ((ContactNode) o).getContactDescriptor(); + Iterator<UIContactDetail> contactDetails + = uiContact + .getContactDetailsForOperationSet( + OperationSetBasicTelephony.class) + .iterator(); + + while (contactDetails.hasNext()) + { + UIContactDetail detail = contactDetails.next(); + ProtocolProviderService detailProvider + = detail.getPreferredProtocolProvider( + OperationSetBasicTelephony.class); + + if (detailProvider != null) + { + /* + * Currently for videobridge conferences we only support + * adding contacts via the account with the videobridge + */ + if (callConference.isJitsiVideobridge()) + { + for (Call call : callConference.getCalls()) + { + if (detailProvider == call.getProtocolProvider()) + { + callee = detail.getAddress(); + provider = detailProvider; + break; + } + } + } + else + { + callee = detail.getAddress(); + provider = detailProvider; + break; + } + } + } + + if (callee == null) + { + /* + * It turns out that the error message to be reported would + * like to display information about the account which could + * not add the dropped callee to the telephony conference. + * Unfortunately, a telephony conference may have multiple + * accounts involved. Anyway, choose the first account + * involved in the telephony conference. + */ + ProtocolProviderService callProvider + = callConference.getCalls().get(0) + .getProtocolProvider(); + + ResourceManagementService resources + = GuiActivator.getResources(); + AccountID accountID = callProvider.getAccountID(); + + new ErrorDialog(null, + resources.getI18NString("service.gui.ERROR"), + resources.getI18NString( + "service.gui.CALL_NOT_SUPPORTING_PARTICIPANT", + new String[] + { + accountID.getService(), + accountID.getUserID(), + uiContact.getDisplayName() + })) + .showDialog(); + } + } + } + else if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) + { + InputContext inputContext = comp.getInputContext(); + + if (inputContext != null) + inputContext.endComposition(); + + try + { + BufferedReader reader + = new BufferedReader( + DataFlavor.stringFlavor.getReaderForText(t)); + + try + { + String line; + StringBuilder calleeBuilder = new StringBuilder(); + + while ((line = reader.readLine()) != null) + calleeBuilder.append(line); + + callee = calleeBuilder.toString(); + /* + * The value of the local variable provider will be null + * because we have a String only and hence we have no + * associated ProtocolProviderService. + * CallManager.inviteToConferenceCall will accept it. + */ + } + finally + { + reader.close(); + } + } + catch (UnsupportedFlavorException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop string.", e); + } + catch (IOException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop string.", e); + } + } + + if (callee == null) + return false; + else + { + Map<ProtocolProviderService, List<String>> callees + = new HashMap<ProtocolProviderService, List<String>>(); + + callees.put(provider, Arrays.asList(callee)); + CallManager.inviteToConferenceCall(callees, callConference); + + return true; + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/call/FullScreenLayout.java b/src/net/java/sip/communicator/impl/gui/main/call/FullScreenLayout.java index 3e823ba..c56a415 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/FullScreenLayout.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/FullScreenLayout.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,176 +15,176 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.call;
-
-import java.awt.*;
-import java.util.*;
-import java.util.List;
-
-/**
- * Implements a <tt>LayoutManager</tt> for the full-screen <tt>Call</tt>
- * display.
- *
- * @author Lyubomir Marinov
- */
-public class FullScreenLayout
- implements LayoutManager
-{
- public static final String CENTER = "CENTER";
-
- public static final String SOUTH = "SOUTH";
-
- private Component center;
-
- /**
- * The indicator which determines whether {@link #south} is to be laid out
- * on top of {@link #center} i.e. as an overlay.
- */
- private final boolean overlay;
-
- private Component south;
-
- /**
- * The vertical gap between the center and the south components.
- */
- private int yGap = 0;
-
- /**
- * Initializes a new <tt>FullScreenLayout</tt> instance.
- *
- * @param overlay <tt>true</tt> to lay out the <tt>Component</tt> at
- * {@link #SOUTH} on top of the <tt>Component</tt> at {@link #CENTER} i.e as
- * an overlay; otherwise, <tt>false</tt>
- * @oaram yGap the gap betwen the center and the south component
- */
- public FullScreenLayout(boolean overlay, int yGap)
- {
- this.overlay = overlay;
- this.yGap = yGap;
- }
-
- /**
- * Adds the given component to this layout.
- *
- * @param name the name of the constraint (CENTER or SOUTH)
- * @param comp the component to add to this layout
- */
- public void addLayoutComponent(String name, Component comp)
- {
- if (CENTER.equals(name))
- center = comp;
- else if (SOUTH.equals(name))
- south = comp;
- }
-
- /**
- * Gets a <tt>List</tt> of the <tt>Component</tt>s to be laid out by this
- * <tt>LayoutManager</tt> i.e. the non-<tt>null</tt> of {@link #center}
- * and {@link #south}.
- *
- * @return a <tt>List</tt> of the <tt>Component</tt>s to be laid out by this
- * <tt>LayoutManager</tt>
- */
- private List<Component> getLayoutComponents()
- {
- List<Component> layoutComponents = new ArrayList<Component>(2);
-
- if (center != null)
- layoutComponents.add(center);
- if (south != null)
- layoutComponents.add(south);
- return layoutComponents;
- }
-
- /**
- * Lays out the components added in the given parent container
- *
- * @param parent the parent container to lay out
- */
- public void layoutContainer(Container parent)
- {
- int southWidth;
- int southHeight;
-
- if (south == null)
- {
- southWidth = southHeight = 0;
- }
- else
- {
- Dimension southSize = south.getPreferredSize();
-
- southWidth = southSize.width;
- southHeight = southSize.height;
- }
-
- Dimension parentSize = parent.getSize();
-
- if (center != null)
- {
- /*
- * If the Component at the SOUTH is not to be shown as an overlay,
- * make room for it bellow the Component at the CENTER.
- */
- int yOffset = overlay ? 0 : southHeight + yGap;
-
- center.setBounds(
- 0,
- 0,
- parentSize.width,
- parentSize.height - yOffset);
- }
- if (south != null)
- {
- south.setBounds(
- (parentSize.width - southWidth) / 2,
- parentSize.height - southHeight,
- southWidth,
- southHeight);
- }
- }
-
- public Dimension minimumLayoutSize(Container parent)
- {
- List<Component> components = getLayoutComponents();
- Dimension size = new Dimension(0, 0);
-
- for (Component component : components)
- {
- Dimension componentSize = component.getMinimumSize();
-
- size.width = Math.max(size.width, componentSize.width);
- if (overlay)
- size.height = Math.max(size.height, componentSize.height);
- else
- size.height += componentSize.height;
- }
- return size;
- }
-
- public Dimension preferredLayoutSize(Container parent)
- {
- List<Component> components = getLayoutComponents();
- Dimension size = new Dimension(0, 0);
-
- for (Component component : components)
- {
- Dimension componentSize = component.getPreferredSize();
-
- size.width = Math.max(size.width, componentSize.width);
- if (overlay)
- size.height = Math.max(size.height, componentSize.height);
- else
- size.height += componentSize.height;
- }
- return size;
- }
-
- public void removeLayoutComponent(Component comp)
- {
- if (comp.equals(center))
- center = null;
- else if (comp.equals(south))
- south = null;
- }
-}
+package net.java.sip.communicator.impl.gui.main.call; + +import java.awt.*; +import java.util.*; +import java.util.List; + +/** + * Implements a <tt>LayoutManager</tt> for the full-screen <tt>Call</tt> + * display. + * + * @author Lyubomir Marinov + */ +public class FullScreenLayout + implements LayoutManager +{ + public static final String CENTER = "CENTER"; + + public static final String SOUTH = "SOUTH"; + + private Component center; + + /** + * The indicator which determines whether {@link #south} is to be laid out + * on top of {@link #center} i.e. as an overlay. + */ + private final boolean overlay; + + private Component south; + + /** + * The vertical gap between the center and the south components. + */ + private int yGap = 0; + + /** + * Initializes a new <tt>FullScreenLayout</tt> instance. + * + * @param overlay <tt>true</tt> to lay out the <tt>Component</tt> at + * {@link #SOUTH} on top of the <tt>Component</tt> at {@link #CENTER} i.e as + * an overlay; otherwise, <tt>false</tt> + * @oaram yGap the gap betwen the center and the south component + */ + public FullScreenLayout(boolean overlay, int yGap) + { + this.overlay = overlay; + this.yGap = yGap; + } + + /** + * Adds the given component to this layout. + * + * @param name the name of the constraint (CENTER or SOUTH) + * @param comp the component to add to this layout + */ + public void addLayoutComponent(String name, Component comp) + { + if (CENTER.equals(name)) + center = comp; + else if (SOUTH.equals(name)) + south = comp; + } + + /** + * Gets a <tt>List</tt> of the <tt>Component</tt>s to be laid out by this + * <tt>LayoutManager</tt> i.e. the non-<tt>null</tt> of {@link #center} + * and {@link #south}. + * + * @return a <tt>List</tt> of the <tt>Component</tt>s to be laid out by this + * <tt>LayoutManager</tt> + */ + private List<Component> getLayoutComponents() + { + List<Component> layoutComponents = new ArrayList<Component>(2); + + if (center != null) + layoutComponents.add(center); + if (south != null) + layoutComponents.add(south); + return layoutComponents; + } + + /** + * Lays out the components added in the given parent container + * + * @param parent the parent container to lay out + */ + public void layoutContainer(Container parent) + { + int southWidth; + int southHeight; + + if (south == null) + { + southWidth = southHeight = 0; + } + else + { + Dimension southSize = south.getPreferredSize(); + + southWidth = southSize.width; + southHeight = southSize.height; + } + + Dimension parentSize = parent.getSize(); + + if (center != null) + { + /* + * If the Component at the SOUTH is not to be shown as an overlay, + * make room for it bellow the Component at the CENTER. + */ + int yOffset = overlay ? 0 : southHeight + yGap; + + center.setBounds( + 0, + 0, + parentSize.width, + parentSize.height - yOffset); + } + if (south != null) + { + south.setBounds( + (parentSize.width - southWidth) / 2, + parentSize.height - southHeight, + southWidth, + southHeight); + } + } + + public Dimension minimumLayoutSize(Container parent) + { + List<Component> components = getLayoutComponents(); + Dimension size = new Dimension(0, 0); + + for (Component component : components) + { + Dimension componentSize = component.getMinimumSize(); + + size.width = Math.max(size.width, componentSize.width); + if (overlay) + size.height = Math.max(size.height, componentSize.height); + else + size.height += componentSize.height; + } + return size; + } + + public Dimension preferredLayoutSize(Container parent) + { + List<Component> components = getLayoutComponents(); + Dimension size = new Dimension(0, 0); + + for (Component component : components) + { + Dimension componentSize = component.getPreferredSize(); + + size.width = Math.max(size.width, componentSize.width); + if (overlay) + size.height = Math.max(size.height, componentSize.height); + else + size.height += componentSize.height; + } + return size; + } + + public void removeLayoutComponent(Component comp) + { + if (comp.equals(center)) + center = null; + else if (comp.equals(south)) + south = null; + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/call/PreCallDialog.java b/src/net/java/sip/communicator/impl/gui/main/call/PreCallDialog.java index 76c4139..0d6206a 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/PreCallDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/PreCallDialog.java @@ -256,8 +256,12 @@ public abstract class PreCallDialog receivedCallWindow.setAlwaysOnTop(true); + // Register the JFrame so the dialog window is able to get dragged + // across the screen + ComponentMover.registerComponent(receivedCallWindow); + // prevents dialog window to get unwanted key events and when going - // on top on linux, it steals focus and if we are accidently + // on top on linux, it steals focus and if we are accidentally // writing something and pressing enter a call get answered receivedCallWindow.setFocusableWindowState(false); diff --git a/src/net/java/sip/communicator/impl/gui/main/call/SecurityPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/SecurityPanel.java index 2e2ed5b..b49e0a3 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/SecurityPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/SecurityPanel.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,117 +15,117 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.call;
-
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.util.skin.*;
-
-import org.jitsi.service.neomedia.*;
-
-/**
- * Base class for security panels that show encryption specific UI controls.
- *
- * @author Ingo Bauersachs
- */
-public abstract class SecurityPanel<T extends SrtpControl>
- extends FadeInBalloonPanel
- implements Skinnable
-{
- /**
- * The currently used security control.
- */
- protected T securityControl;
-
- /**
- * Create security panel using the security control.
- * @param securityControl
- */
- public SecurityPanel(T securityControl)
- {
- this.securityControl = securityControl;
- }
-
- /**
- * The currently used security control.
- * @return
- */
- public T getSecurityControl()
- {
- return securityControl;
- }
-
- /**
- * The currently used security control.
- * @return
- */
- public void setSecurityControl(T securityControl)
- {
- this.securityControl = securityControl;
- }
-
- /**
- * Creates the security panel depending on the concrete implementation of
- * the passed security controller.
- *
- * @param srtpControl the security controller that provides the information
- * to be shown on the UI
- * @return An instance of a {@link SecurityPanel} for the security
- * controller or an {@link TransparentPanel} if the controller is
- * unknown or does not have any controls to show.
- */
- public static SecurityPanel<?> create(
- SwingCallPeerRenderer peerRenderer,
- CallPeer callPeer,
- SrtpControl srtpControl)
- {
- if(srtpControl instanceof ZrtpControl)
- {
- return
- new ZrtpSecurityPanel(
- peerRenderer,
- callPeer,
- (ZrtpControl) srtpControl);
- }
- else
- {
- return
- new SecurityPanel<SrtpControl>(srtpControl)
- {
- public void loadSkin() {}
-
- @Override
- public void securityOn(CallPeerSecurityOnEvent evt) {}
-
- @Override
- public void securityOff(CallPeerSecurityOffEvent evt) {}
-
- @Override
- public void securityTimeout(
- CallPeerSecurityTimeoutEvent evt) {}
- };
- }
- }
-
- /**
- * Indicates that the security is turned on.
- *
- * @param evt details about the event that caused this message.
- */
- public abstract void securityOn(CallPeerSecurityOnEvent evt);
-
- /**
- * Indicates that the security is turned off.
- *
- * @param evt details about the event that caused this message.
- */
- public abstract void securityOff(CallPeerSecurityOffEvent evt);
-
- /**
- * Indicates that the security is timeouted, is not supported by the
- * other end.
- * @param evt Details about the event that caused this message.
- */
- public abstract void securityTimeout(CallPeerSecurityTimeoutEvent evt);
-}
+package net.java.sip.communicator.impl.gui.main.call; + +import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.skin.*; + +import org.jitsi.service.neomedia.*; + +/** + * Base class for security panels that show encryption specific UI controls. + * + * @author Ingo Bauersachs + */ +public abstract class SecurityPanel<T extends SrtpControl> + extends FadeInBalloonPanel + implements Skinnable +{ + /** + * The currently used security control. + */ + protected T securityControl; + + /** + * Create security panel using the security control. + * @param securityControl + */ + public SecurityPanel(T securityControl) + { + this.securityControl = securityControl; + } + + /** + * The currently used security control. + * @return + */ + public T getSecurityControl() + { + return securityControl; + } + + /** + * The currently used security control. + * @return + */ + public void setSecurityControl(T securityControl) + { + this.securityControl = securityControl; + } + + /** + * Creates the security panel depending on the concrete implementation of + * the passed security controller. + * + * @param srtpControl the security controller that provides the information + * to be shown on the UI + * @return An instance of a {@link SecurityPanel} for the security + * controller or an {@link TransparentPanel} if the controller is + * unknown or does not have any controls to show. + */ + public static SecurityPanel<?> create( + SwingCallPeerRenderer peerRenderer, + CallPeer callPeer, + SrtpControl srtpControl) + { + if(srtpControl instanceof ZrtpControl) + { + return + new ZrtpSecurityPanel( + peerRenderer, + callPeer, + (ZrtpControl) srtpControl); + } + else + { + return + new SecurityPanel<SrtpControl>(srtpControl) + { + public void loadSkin() {} + + @Override + public void securityOn(CallPeerSecurityOnEvent evt) {} + + @Override + public void securityOff(CallPeerSecurityOffEvent evt) {} + + @Override + public void securityTimeout( + CallPeerSecurityTimeoutEvent evt) {} + }; + } + } + + /** + * Indicates that the security is turned on. + * + * @param evt details about the event that caused this message. + */ + public abstract void securityOn(CallPeerSecurityOnEvent evt); + + /** + * Indicates that the security is turned off. + * + * @param evt details about the event that caused this message. + */ + public abstract void securityOff(CallPeerSecurityOffEvent evt); + + /** + * Indicates that the security is timeouted, is not supported by the + * other end. + * @param evt Details about the event that caused this message. + */ + public abstract void securityTimeout(CallPeerSecurityTimeoutEvent evt); +} diff --git a/src/net/java/sip/communicator/impl/gui/main/call/TransferCallDialog.java b/src/net/java/sip/communicator/impl/gui/main/call/TransferCallDialog.java index f2f2bd2..982738b 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/TransferCallDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/TransferCallDialog.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,140 +15,140 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.call;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.util.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*;
-import net.java.sip.communicator.impl.gui.utils.*;
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.protocol.*;
-
-/**
- * Represents a <tt>Dialog</tt> which allows specifying the target contact
- * address of a transfer-call operation.
- *
- * @author Yana Stamcheva
- */
-public class TransferCallDialog
- extends OneChoiceInviteDialog
-{
- /**
- * The peer to transfer.
- */
- private final CallPeer transferPeer;
-
- /**
- * Creates a <tt>TransferCallDialog</tt> by specifying the peer to transfer
- * @param peer the peer to transfer
- */
- public TransferCallDialog(final CallPeer peer)
- {
- super(GuiActivator.getResources()
- .getI18NString("service.gui.TRANSFER_CALL_TITLE"));
-
- this.transferPeer = peer;
-
- this.initContactListData(peer.getProtocolProvider());
-
- this.setInfoText(GuiActivator.getResources()
- .getI18NString("service.gui.TRANSFER_CALL_MSG"));
- this.setOkButtonText(GuiActivator.getResources()
- .getI18NString("service.gui.TRANSFER"));
-
- this.setMinimumSize(new Dimension(300, 300));
-
- addOkButtonListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- UIContact uiContact = getSelectedContact();
-
- if (uiContact != null)
- {
- transferToContact(uiContact);
- }
-
- setVisible(false);
- dispose();
- }
- });
- addCancelButtonListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- setVisible(false);
- dispose();
- }
- });
- }
-
- /**
- * Initializes contact list sources.
- */
- private void initContactSources()
- {
- DemuxContactSourceService demuxCSService
- = GuiActivator.getDemuxContactSourceService();
-
- // If the DemuxContactSourceService isn't registered we use the default
- // contact source set.
- if (demuxCSService == null)
- return;
-
- Iterator<UIContactSource> sourcesIter
- = new ArrayList<UIContactSource>(
- contactList.getContactSources()).iterator();
-
- contactList.removeAllContactSources();
-
- while (sourcesIter.hasNext())
- {
- ContactSourceService contactSource
- = sourcesIter.next().getContactSourceService();
-
- contactList.addContactSource(
- demuxCSService.createDemuxContactSource(contactSource));
- }
- }
-
- /**
- * Initializes the left contact list with the contacts that could be added
- * to the current chat session.
- *
- * @param protocolProvider the protocol provider from which to initialize
- * the contact list data
- */
- private void initContactListData(ProtocolProviderService protocolProvider)
- {
- initContactSources();
-
- contactList.addContactSource(
- new ProtocolContactSourceServiceImpl(
- protocolProvider, OperationSetBasicTelephony.class));
- contactList.addContactSource(
- new StringContactSourceServiceImpl(
- protocolProvider, OperationSetBasicTelephony.class));
-
- contactList.applyDefaultFilter();
- }
-
- /**
- * Transfer the transfer peer to the given <tt>UIContact</tt>.
- *
- * @param uiContact the contact to transfer to
- */
- private void transferToContact(UIContact uiContact)
- {
- UIContactDetail contactDetail = uiContact
- .getDefaultContactDetail(
- OperationSetBasicTelephony.class);
-
- CallManager.transferCall( transferPeer,
- contactDetail.getAddress());
- }
-}
+package net.java.sip.communicator.impl.gui.main.call; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * Represents a <tt>Dialog</tt> which allows specifying the target contact + * address of a transfer-call operation. + * + * @author Yana Stamcheva + */ +public class TransferCallDialog + extends OneChoiceInviteDialog +{ + /** + * The peer to transfer. + */ + private final CallPeer transferPeer; + + /** + * Creates a <tt>TransferCallDialog</tt> by specifying the peer to transfer + * @param peer the peer to transfer + */ + public TransferCallDialog(final CallPeer peer) + { + super(GuiActivator.getResources() + .getI18NString("service.gui.TRANSFER_CALL_TITLE")); + + this.transferPeer = peer; + + this.initContactListData(peer.getProtocolProvider()); + + this.setInfoText(GuiActivator.getResources() + .getI18NString("service.gui.TRANSFER_CALL_MSG")); + this.setOkButtonText(GuiActivator.getResources() + .getI18NString("service.gui.TRANSFER")); + + this.setMinimumSize(new Dimension(300, 300)); + + addOkButtonListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + UIContact uiContact = getSelectedContact(); + + if (uiContact != null) + { + transferToContact(uiContact); + } + + setVisible(false); + dispose(); + } + }); + addCancelButtonListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + setVisible(false); + dispose(); + } + }); + } + + /** + * Initializes contact list sources. + */ + private void initContactSources() + { + DemuxContactSourceService demuxCSService + = GuiActivator.getDemuxContactSourceService(); + + // If the DemuxContactSourceService isn't registered we use the default + // contact source set. + if (demuxCSService == null) + return; + + Iterator<UIContactSource> sourcesIter + = new ArrayList<UIContactSource>( + contactList.getContactSources()).iterator(); + + contactList.removeAllContactSources(); + + while (sourcesIter.hasNext()) + { + ContactSourceService contactSource + = sourcesIter.next().getContactSourceService(); + + contactList.addContactSource( + demuxCSService.createDemuxContactSource(contactSource)); + } + } + + /** + * Initializes the left contact list with the contacts that could be added + * to the current chat session. + * + * @param protocolProvider the protocol provider from which to initialize + * the contact list data + */ + private void initContactListData(ProtocolProviderService protocolProvider) + { + initContactSources(); + + contactList.addContactSource( + new ProtocolContactSourceServiceImpl( + protocolProvider, OperationSetBasicTelephony.class)); + contactList.addContactSource( + new StringContactSourceServiceImpl( + protocolProvider, OperationSetBasicTelephony.class)); + + contactList.applyDefaultFilter(); + } + + /** + * Transfer the transfer peer to the given <tt>UIContact</tt>. + * + * @param uiContact the contact to transfer to + */ + private void transferToContact(UIContact uiContact) + { + UIContactDetail contactDetail = uiContact + .getDefaultContactDetail( + OperationSetBasicTelephony.class); + + CallManager.transferCall( transferPeer, + contactDetail.getAddress()); + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceInviteDialog.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceInviteDialog.java index 80eedc6..bb32858 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceInviteDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceInviteDialog.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,570 +15,570 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.call.conference;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.util.*;
-import java.util.List;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.main.call.*;
-import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*;
-import net.java.sip.communicator.impl.gui.utils.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-/**
- * The invite dialog is the one shown when the user clicks on the conference
- * button in the chat toolbar.
- *
- * @author Yana Stamcheva
- * @author Lyubomir Marinov
- */
-public class ConferenceInviteDialog
- extends InviteDialog
-{
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- /**
- * The account selector box.
- */
- private final JComboBox accountSelectorBox = new JComboBox();
-
- /**
- * The last selected account.
- */
- private Object lastSelectedAccount;
-
- /**
- * The telephony conference into which this instance is to invite
- * participants.
- */
- private final CallConference conference;
-
- /**
- * The current provider contact source.
- */
- private ContactSourceService currentProviderContactSource;
-
- /**
- * The current string contact source.
- */
- private ContactSourceService currentStringContactSource;
-
- /**
- * The previously selected protocol provider, with which this dialog has
- * been instantiated.
- */
- private ProtocolProviderService preselectedProtocolProvider;
-
- /**
- * Indicates whether this conference invite dialog is associated with a
- * Jitsi Videobridge invite.
- */
- private final boolean isJitsiVideobridge;
-
- /**
- * Initializes a new <tt>ConferenceInviteDialog</tt> instance which is to
- * invite contacts/participants in a specific telephony conference.
- *
- * @param conference the telephony conference in which the new instance is
- * to invite contacts/participants
- */
- public ConferenceInviteDialog(
- CallConference conference,
- ProtocolProviderService preselectedProvider,
- List<ProtocolProviderService> protocolProviders,
- final boolean isJitsiVideobridge)
- {
- // Set the correct dialog title depending if we're going to create a
- // video bridge conference call
- super((isJitsiVideobridge
- ? GuiActivator.getResources()
- .getI18NString("service.gui.INVITE_CONTACT_TO_VIDEO_BRIDGE")
- : GuiActivator.getResources()
- .getI18NString("service.gui.INVITE_CONTACT_TO_CALL")),
- false);
-
- this.conference = conference;
- this.preselectedProtocolProvider = preselectedProvider;
- this.isJitsiVideobridge = isJitsiVideobridge;
-
- if (preselectedProtocolProvider == null)
- initAccountSelectorPanel(protocolProviders);
-
- // init the list, as we check whether features are supported
- // it may take some time if we have too much contacts
- SwingUtilities.invokeLater(new Runnable()
- {
- public void run()
- {
- initContactSources();
-
- // Initialize the list of contacts to select from.
- if (preselectedProtocolProvider != null)
- initContactListData(preselectedProtocolProvider);
- else
- initContactListData(
- (ProtocolProviderService) accountSelectorBox
- .getSelectedItem());
- }
- });
-
- this.addInviteButtonListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- Collection<UIContact> selectedContacts
- = destContactList.getContacts(null);
-
- if (selectedContacts != null && selectedContacts.size() > 0)
- {
- if (preselectedProtocolProvider == null)
- preselectedProtocolProvider
- = (ProtocolProviderService) accountSelectorBox
- .getSelectedItem();
-
- if (isJitsiVideobridge)
- inviteJitsiVideobridgeContacts( preselectedProtocolProvider,
- selectedContacts);
- else
- inviteContacts(selectedContacts);
-
- // Store the last used account in order to pre-select it
- // next time.
- ConfigurationUtils.setLastCallConferenceProvider(
- preselectedProtocolProvider);
-
- dispose();
- }
- else
- {
- // TODO: The underlying invite dialog should show a message
- // to the user that she should select at least two contacts
- // in order to create a conference.
- }
- }
- });
-
- this.addCancelButtonListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- dispose();
- }
- });
- }
-
- /**
- * Constructs the <tt>ConferenceInviteDialog</tt>.
- */
- public ConferenceInviteDialog()
- {
- this(null, null, null, false);
- }
-
- /**
- * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying an
- * already created conference. To use when inviting contacts to an existing
- * conference is needed.
- *
- * @param conference the existing <tt>CallConference</tt>
- */
- public ConferenceInviteDialog(CallConference conference)
- {
- this(conference, null, null, false);
- }
-
- /**
- * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying an
- * already created conference. To use when inviting contacts to an existing
- * conference is needed.
- *
- * @param conference the existing <tt>CallConference</tt>
- */
- public ConferenceInviteDialog(
- CallConference conference,
- ProtocolProviderService preselectedProtocolProvider,
- boolean isJitsiVideobridge)
- {
- this(conference, preselectedProtocolProvider, null, isJitsiVideobridge);
- }
-
- /**
- * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying a
- * preselected protocol provider to be used and if this is an invite for
- * a video bridge conference.
- *
- * @param protocolProviders the protocol providers list
- * @param isJitsiVideobridge <tt>true</tt> if this dialog should create a
- * conference through a Jitsi Videobridge; otherwise, <tt>false</tt>
- */
- public ConferenceInviteDialog(
- List<ProtocolProviderService> protocolProviders,
- boolean isJitsiVideobridge)
- {
- this(null, null, protocolProviders, isJitsiVideobridge);
- }
-
- /**
- * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying a
- * preselected protocol provider to be used and if this is an invite for
- * a video bridge conference.
- *
- * @param selectedConfProvider the preselected protocol provider
- * @param isJitsiVideobridge <tt>true</tt> if this dialog should create a
- * conference through a Jitsi Videobridge; otherwise, <tt>false</tt>
- */
- public ConferenceInviteDialog(
- ProtocolProviderService selectedConfProvider,
- boolean isJitsiVideobridge)
- {
- this(null, selectedConfProvider, null, isJitsiVideobridge);
- }
-
- /**
- * Initializes the account selector panel.
- *
- * @param protocolProviders the list of protocol providers we'd like to
- * show in the account selector box
- */
- private void initAccountSelectorPanel(
- List<ProtocolProviderService> protocolProviders)
- {
- JLabel accountSelectorLabel = new JLabel(
- GuiActivator.getResources().getI18NString("service.gui.CALL_VIA"));
-
- TransparentPanel accountSelectorPanel
- = new TransparentPanel(new BorderLayout());
-
- accountSelectorPanel.setBorder(
- BorderFactory.createEmptyBorder(5, 5, 5, 5));
- accountSelectorPanel.add(accountSelectorLabel, BorderLayout.WEST);
- accountSelectorPanel.add(accountSelectorBox, BorderLayout.CENTER);
-
- // Initialize the account selector box.
- if (protocolProviders != null && protocolProviders.size() > 0)
- this.initAccountListData(protocolProviders);
- else
- this.initAccountListData();
-
- this.accountSelectorBox.setRenderer(new DefaultListCellRenderer()
- {
- private static final long serialVersionUID = 0L;
-
- @Override
- public Component getListCellRendererComponent(JList list,
- Object value, int index, boolean isSelected,
- boolean cellHasFocus)
- {
- ProtocolProviderService protocolProvider
- = (ProtocolProviderService) value;
-
- if (protocolProvider != null)
- {
- this.setText(
- protocolProvider.getAccountID().getDisplayName());
- this.setIcon(
- ImageLoader.getAccountStatusImage(protocolProvider));
- }
-
- return this;
- }
- });
-
- this.accountSelectorBox.addActionListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- Object accountSelectorBoxSelectedItem
- = accountSelectorBox.getSelectedItem();
-
- if (lastSelectedAccount == null
- || !lastSelectedAccount
- .equals(accountSelectorBoxSelectedItem))
- {
- lastSelectedAccount = accountSelectorBoxSelectedItem;
-
- initContactListData(
- (ProtocolProviderService) accountSelectorBox
- .getSelectedItem());
-
- if (isJitsiVideobridge)
- destContactList.removeAll();
- }
- }
- });
-
- this.getContentPane().add(accountSelectorPanel, BorderLayout.NORTH);
- }
-
- /**
- * Initializes the account selector box with the given list of
- * <tt>ProtocolProviderService</tt>-s.
- *
- * @param protocolProviders the list of <tt>ProtocolProviderService</tt>-s
- * we'd like to show in the account selector box
- */
- private void initAccountListData(
- List<ProtocolProviderService> protocolProviders)
- {
- Iterator<ProtocolProviderService> providersIter
- = protocolProviders.iterator();
-
- while (providersIter.hasNext())
- {
- ProtocolProviderService protocolProvider
- = providersIter.next();
-
- accountSelectorBox.addItem(protocolProvider);
- }
-
- if (accountSelectorBox.getItemCount() > 0)
- accountSelectorBox.setSelectedIndex(0);
- }
-
- /**
- * Initializes the account list.
- */
- private void initAccountListData()
- {
- Iterator<ProtocolProviderService> protocolProviders
- = GuiActivator.getUIService().getMainFrame().getProtocolProviders();
-
- while(protocolProviders.hasNext())
- {
- ProtocolProviderService protocolProvider
- = protocolProviders.next();
- OperationSet opSet
- = protocolProvider.getOperationSet(
- OperationSetTelephonyConferencing.class);
-
- if ((opSet != null) && protocolProvider.isRegistered())
- accountSelectorBox.addItem(protocolProvider);
- }
-
- // Try to select the last used account if available.
- ProtocolProviderService pps
- = ConfigurationUtils.getLastCallConferenceProvider();
-
- if (pps == null && conference != null)
- {
- /*
- * Pick up the first account from the ones participating in the
- * associated telephony conference which supports
- * OperationSetTelephonyConferencing.
- */
- for (Call call : conference.getCalls())
- {
- ProtocolProviderService callPps = call.getProtocolProvider();
-
- if (callPps.getOperationSet(
- OperationSetTelephonyConferencing.class)
- != null)
- {
- pps = callPps;
- break;
- }
- }
- }
-
- if (pps != null)
- accountSelectorBox.setSelectedItem(pps);
- else if (accountSelectorBox.getItemCount() > 0)
- accountSelectorBox.setSelectedIndex(0);
- }
-
- /**
- * Initializes contact list sources.
- */
- private void initContactSources()
- {
- DemuxContactSourceService demuxCSService
- = GuiActivator.getDemuxContactSourceService();
-
- // If the DemuxContactSourceService isn't registered we use the default
- // contact source set.
- if (demuxCSService == null)
- return;
-
- Iterator<UIContactSource> sourcesIter
- = new ArrayList<UIContactSource>(
- srcContactList.getContactSources()).iterator();
-
- srcContactList.removeAllContactSources();
-
- while (sourcesIter.hasNext())
- {
- ContactSourceService contactSource
- = sourcesIter.next().getContactSourceService();
-
- srcContactList.addContactSource(
- demuxCSService.createDemuxContactSource(contactSource));
- }
- }
-
- /**
- * Initializes the left contact list with the contacts that could be added
- * to the current chat session.
- * @param protocolProvider the protocol provider from which to initialize
- * the contact list data
- */
- private void initContactListData(ProtocolProviderService protocolProvider)
- {
- this.setCurrentProvider(protocolProvider);
-
- Iterator<UIContactSource> sourcesIter
- = new ArrayList<UIContactSource>(
- srcContactList.getContactSources()).iterator();
-
- while (sourcesIter.hasNext())
- {
- ContactSourceService contactSource
- = sourcesIter.next().getContactSourceService();
-
- if (contactSource instanceof ProtocolAwareContactSourceService)
- {
- ((ProtocolAwareContactSourceService) contactSource)
- .setPreferredProtocolProvider(
- OperationSetBasicTelephony.class, protocolProvider);
- }
- }
-
- srcContactList.removeContactSource(currentProviderContactSource);
- srcContactList.removeContactSource(currentStringContactSource);
-
- currentProviderContactSource
- = new ProtocolContactSourceServiceImpl(
- protocolProvider,
- OperationSetBasicTelephony.class);
- currentStringContactSource
- = new StringContactSourceServiceImpl(
- protocolProvider,
- OperationSetBasicTelephony.class);
-
- srcContactList.addContactSource(currentProviderContactSource);
- srcContactList.addContactSource(currentStringContactSource);
-
- srcContactList.applyDefaultFilter();
- }
-
- /**
- * Invites the contacts to the chat conference.
- *
- * @param contacts the list of contacts to invite
- */
- private void inviteContacts(Collection<UIContact> contacts)
- {
- ProtocolProviderService selectedProvider;
- Map<ProtocolProviderService, List<String>> selectedProviderCallees
- = new HashMap<ProtocolProviderService, List<String>>();
- List<String> callees;
-
- Iterator<UIContact> contactsIter = contacts.iterator();
-
- while (contactsIter.hasNext())
- {
- UIContact uiContact = contactsIter.next();
-
- Iterator<UIContactDetail> contactDetailsIter = uiContact
- .getContactDetailsForOperationSet(
- OperationSetBasicTelephony.class).iterator();
-
- // We invite the first protocol contact that corresponds to the
- // invite provider.
- if (contactDetailsIter.hasNext())
- {
- UIContactDetail inviteDetail = contactDetailsIter.next();
- selectedProvider = inviteDetail
- .getPreferredProtocolProvider(
- OperationSetBasicTelephony.class);
-
- if (selectedProvider == null)
- {
- selectedProvider
- = (ProtocolProviderService)
- accountSelectorBox.getSelectedItem();
- }
-
- if(selectedProvider != null
- && selectedProviderCallees.get(selectedProvider) != null)
- {
- callees = selectedProviderCallees.get(selectedProvider);
- }
- else
- {
- callees = new ArrayList<String>();
- }
-
- callees.add(inviteDetail.getAddress());
- selectedProviderCallees.put(selectedProvider, callees);
- }
- }
-
- if(conference != null)
- {
- CallManager.inviteToConferenceCall(
- selectedProviderCallees,
- conference);
- }
- else
- {
- CallManager.createConferenceCall(selectedProviderCallees);
- }
- }
-
- /**
- * Invites the contacts to the chat conference.
- *
- * @param contacts the list of contacts to invite
- */
- private void inviteJitsiVideobridgeContacts(
- ProtocolProviderService preselectedProvider,
- Collection<UIContact> contacts)
- {
- List<String> callees = new ArrayList<String>();
-
- Iterator<UIContact> contactsIter = contacts.iterator();
-
- while (contactsIter.hasNext())
- {
- UIContact uiContact = contactsIter.next();
-
- Iterator<UIContactDetail> contactDetailsIter = uiContact
- .getContactDetailsForOperationSet(
- OperationSetBasicTelephony.class).iterator();
-
- // We invite the first protocol contact that corresponds to the
- // invite provider.
- if (contactDetailsIter.hasNext())
- {
- UIContactDetail inviteDetail = contactDetailsIter.next();
-
- callees.add(inviteDetail.getAddress());
- }
- }
-
- if(conference != null)
- {
- CallManager.inviteToJitsiVideobridgeConfCall(
- callees.toArray(new String[callees.size()]),
- conference.getCalls().get(0));
- }
- else
- {
- CallManager.createJitsiVideobridgeConfCall(
- preselectedProvider,
- callees.toArray(new String[callees.size()]));
- }
- }
-}
+package net.java.sip.communicator.impl.gui.main.call.conference; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.call.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * The invite dialog is the one shown when the user clicks on the conference + * button in the chat toolbar. + * + * @author Yana Stamcheva + * @author Lyubomir Marinov + */ +public class ConferenceInviteDialog + extends InviteDialog +{ + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * The account selector box. + */ + private final JComboBox accountSelectorBox = new JComboBox(); + + /** + * The last selected account. + */ + private Object lastSelectedAccount; + + /** + * The telephony conference into which this instance is to invite + * participants. + */ + private final CallConference conference; + + /** + * The current provider contact source. + */ + private ContactSourceService currentProviderContactSource; + + /** + * The current string contact source. + */ + private ContactSourceService currentStringContactSource; + + /** + * The previously selected protocol provider, with which this dialog has + * been instantiated. + */ + private ProtocolProviderService preselectedProtocolProvider; + + /** + * Indicates whether this conference invite dialog is associated with a + * Jitsi Videobridge invite. + */ + private final boolean isJitsiVideobridge; + + /** + * Initializes a new <tt>ConferenceInviteDialog</tt> instance which is to + * invite contacts/participants in a specific telephony conference. + * + * @param conference the telephony conference in which the new instance is + * to invite contacts/participants + */ + public ConferenceInviteDialog( + CallConference conference, + ProtocolProviderService preselectedProvider, + List<ProtocolProviderService> protocolProviders, + final boolean isJitsiVideobridge) + { + // Set the correct dialog title depending if we're going to create a + // video bridge conference call + super((isJitsiVideobridge + ? GuiActivator.getResources() + .getI18NString("service.gui.INVITE_CONTACT_TO_VIDEO_BRIDGE") + : GuiActivator.getResources() + .getI18NString("service.gui.INVITE_CONTACT_TO_CALL")), + false); + + this.conference = conference; + this.preselectedProtocolProvider = preselectedProvider; + this.isJitsiVideobridge = isJitsiVideobridge; + + if (preselectedProtocolProvider == null) + initAccountSelectorPanel(protocolProviders); + + // init the list, as we check whether features are supported + // it may take some time if we have too much contacts + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + initContactSources(); + + // Initialize the list of contacts to select from. + if (preselectedProtocolProvider != null) + initContactListData(preselectedProtocolProvider); + else + initContactListData( + (ProtocolProviderService) accountSelectorBox + .getSelectedItem()); + } + }); + + this.addInviteButtonListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + Collection<UIContact> selectedContacts + = destContactList.getContacts(null); + + if (selectedContacts != null && selectedContacts.size() > 0) + { + if (preselectedProtocolProvider == null) + preselectedProtocolProvider + = (ProtocolProviderService) accountSelectorBox + .getSelectedItem(); + + if (isJitsiVideobridge) + inviteJitsiVideobridgeContacts( preselectedProtocolProvider, + selectedContacts); + else + inviteContacts(selectedContacts); + + // Store the last used account in order to pre-select it + // next time. + ConfigurationUtils.setLastCallConferenceProvider( + preselectedProtocolProvider); + + dispose(); + } + else + { + // TODO: The underlying invite dialog should show a message + // to the user that she should select at least two contacts + // in order to create a conference. + } + } + }); + + this.addCancelButtonListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + dispose(); + } + }); + } + + /** + * Constructs the <tt>ConferenceInviteDialog</tt>. + */ + public ConferenceInviteDialog() + { + this(null, null, null, false); + } + + /** + * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying an + * already created conference. To use when inviting contacts to an existing + * conference is needed. + * + * @param conference the existing <tt>CallConference</tt> + */ + public ConferenceInviteDialog(CallConference conference) + { + this(conference, null, null, false); + } + + /** + * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying an + * already created conference. To use when inviting contacts to an existing + * conference is needed. + * + * @param conference the existing <tt>CallConference</tt> + */ + public ConferenceInviteDialog( + CallConference conference, + ProtocolProviderService preselectedProtocolProvider, + boolean isJitsiVideobridge) + { + this(conference, preselectedProtocolProvider, null, isJitsiVideobridge); + } + + /** + * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying a + * preselected protocol provider to be used and if this is an invite for + * a video bridge conference. + * + * @param protocolProviders the protocol providers list + * @param isJitsiVideobridge <tt>true</tt> if this dialog should create a + * conference through a Jitsi Videobridge; otherwise, <tt>false</tt> + */ + public ConferenceInviteDialog( + List<ProtocolProviderService> protocolProviders, + boolean isJitsiVideobridge) + { + this(null, null, protocolProviders, isJitsiVideobridge); + } + + /** + * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying a + * preselected protocol provider to be used and if this is an invite for + * a video bridge conference. + * + * @param selectedConfProvider the preselected protocol provider + * @param isJitsiVideobridge <tt>true</tt> if this dialog should create a + * conference through a Jitsi Videobridge; otherwise, <tt>false</tt> + */ + public ConferenceInviteDialog( + ProtocolProviderService selectedConfProvider, + boolean isJitsiVideobridge) + { + this(null, selectedConfProvider, null, isJitsiVideobridge); + } + + /** + * Initializes the account selector panel. + * + * @param protocolProviders the list of protocol providers we'd like to + * show in the account selector box + */ + private void initAccountSelectorPanel( + List<ProtocolProviderService> protocolProviders) + { + JLabel accountSelectorLabel = new JLabel( + GuiActivator.getResources().getI18NString("service.gui.CALL_VIA")); + + TransparentPanel accountSelectorPanel + = new TransparentPanel(new BorderLayout()); + + accountSelectorPanel.setBorder( + BorderFactory.createEmptyBorder(5, 5, 5, 5)); + accountSelectorPanel.add(accountSelectorLabel, BorderLayout.WEST); + accountSelectorPanel.add(accountSelectorBox, BorderLayout.CENTER); + + // Initialize the account selector box. + if (protocolProviders != null && protocolProviders.size() > 0) + this.initAccountListData(protocolProviders); + else + this.initAccountListData(); + + this.accountSelectorBox.setRenderer(new DefaultListCellRenderer() + { + private static final long serialVersionUID = 0L; + + @Override + public Component getListCellRendererComponent(JList list, + Object value, int index, boolean isSelected, + boolean cellHasFocus) + { + ProtocolProviderService protocolProvider + = (ProtocolProviderService) value; + + if (protocolProvider != null) + { + this.setText( + protocolProvider.getAccountID().getDisplayName()); + this.setIcon( + ImageLoader.getAccountStatusImage(protocolProvider)); + } + + return this; + } + }); + + this.accountSelectorBox.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + Object accountSelectorBoxSelectedItem + = accountSelectorBox.getSelectedItem(); + + if (lastSelectedAccount == null + || !lastSelectedAccount + .equals(accountSelectorBoxSelectedItem)) + { + lastSelectedAccount = accountSelectorBoxSelectedItem; + + initContactListData( + (ProtocolProviderService) accountSelectorBox + .getSelectedItem()); + + if (isJitsiVideobridge) + destContactList.removeAll(); + } + } + }); + + this.getContentPane().add(accountSelectorPanel, BorderLayout.NORTH); + } + + /** + * Initializes the account selector box with the given list of + * <tt>ProtocolProviderService</tt>-s. + * + * @param protocolProviders the list of <tt>ProtocolProviderService</tt>-s + * we'd like to show in the account selector box + */ + private void initAccountListData( + List<ProtocolProviderService> protocolProviders) + { + Iterator<ProtocolProviderService> providersIter + = protocolProviders.iterator(); + + while (providersIter.hasNext()) + { + ProtocolProviderService protocolProvider + = providersIter.next(); + + accountSelectorBox.addItem(protocolProvider); + } + + if (accountSelectorBox.getItemCount() > 0) + accountSelectorBox.setSelectedIndex(0); + } + + /** + * Initializes the account list. + */ + private void initAccountListData() + { + Iterator<ProtocolProviderService> protocolProviders + = GuiActivator.getUIService().getMainFrame().getProtocolProviders(); + + while(protocolProviders.hasNext()) + { + ProtocolProviderService protocolProvider + = protocolProviders.next(); + OperationSet opSet + = protocolProvider.getOperationSet( + OperationSetTelephonyConferencing.class); + + if ((opSet != null) && protocolProvider.isRegistered()) + accountSelectorBox.addItem(protocolProvider); + } + + // Try to select the last used account if available. + ProtocolProviderService pps + = ConfigurationUtils.getLastCallConferenceProvider(); + + if (pps == null && conference != null) + { + /* + * Pick up the first account from the ones participating in the + * associated telephony conference which supports + * OperationSetTelephonyConferencing. + */ + for (Call call : conference.getCalls()) + { + ProtocolProviderService callPps = call.getProtocolProvider(); + + if (callPps.getOperationSet( + OperationSetTelephonyConferencing.class) + != null) + { + pps = callPps; + break; + } + } + } + + if (pps != null) + accountSelectorBox.setSelectedItem(pps); + else if (accountSelectorBox.getItemCount() > 0) + accountSelectorBox.setSelectedIndex(0); + } + + /** + * Initializes contact list sources. + */ + private void initContactSources() + { + DemuxContactSourceService demuxCSService + = GuiActivator.getDemuxContactSourceService(); + + // If the DemuxContactSourceService isn't registered we use the default + // contact source set. + if (demuxCSService == null) + return; + + Iterator<UIContactSource> sourcesIter + = new ArrayList<UIContactSource>( + srcContactList.getContactSources()).iterator(); + + srcContactList.removeAllContactSources(); + + while (sourcesIter.hasNext()) + { + ContactSourceService contactSource + = sourcesIter.next().getContactSourceService(); + + srcContactList.addContactSource( + demuxCSService.createDemuxContactSource(contactSource)); + } + } + + /** + * Initializes the left contact list with the contacts that could be added + * to the current chat session. + * @param protocolProvider the protocol provider from which to initialize + * the contact list data + */ + private void initContactListData(ProtocolProviderService protocolProvider) + { + this.setCurrentProvider(protocolProvider); + + Iterator<UIContactSource> sourcesIter + = new ArrayList<UIContactSource>( + srcContactList.getContactSources()).iterator(); + + while (sourcesIter.hasNext()) + { + ContactSourceService contactSource + = sourcesIter.next().getContactSourceService(); + + if (contactSource instanceof ProtocolAwareContactSourceService) + { + ((ProtocolAwareContactSourceService) contactSource) + .setPreferredProtocolProvider( + OperationSetBasicTelephony.class, protocolProvider); + } + } + + srcContactList.removeContactSource(currentProviderContactSource); + srcContactList.removeContactSource(currentStringContactSource); + + currentProviderContactSource + = new ProtocolContactSourceServiceImpl( + protocolProvider, + OperationSetBasicTelephony.class); + currentStringContactSource + = new StringContactSourceServiceImpl( + protocolProvider, + OperationSetBasicTelephony.class); + + srcContactList.addContactSource(currentProviderContactSource); + srcContactList.addContactSource(currentStringContactSource); + + srcContactList.applyDefaultFilter(); + } + + /** + * Invites the contacts to the chat conference. + * + * @param contacts the list of contacts to invite + */ + private void inviteContacts(Collection<UIContact> contacts) + { + ProtocolProviderService selectedProvider; + Map<ProtocolProviderService, List<String>> selectedProviderCallees + = new HashMap<ProtocolProviderService, List<String>>(); + List<String> callees; + + Iterator<UIContact> contactsIter = contacts.iterator(); + + while (contactsIter.hasNext()) + { + UIContact uiContact = contactsIter.next(); + + Iterator<UIContactDetail> contactDetailsIter = uiContact + .getContactDetailsForOperationSet( + OperationSetBasicTelephony.class).iterator(); + + // We invite the first protocol contact that corresponds to the + // invite provider. + if (contactDetailsIter.hasNext()) + { + UIContactDetail inviteDetail = contactDetailsIter.next(); + selectedProvider = inviteDetail + .getPreferredProtocolProvider( + OperationSetBasicTelephony.class); + + if (selectedProvider == null) + { + selectedProvider + = (ProtocolProviderService) + accountSelectorBox.getSelectedItem(); + } + + if(selectedProvider != null + && selectedProviderCallees.get(selectedProvider) != null) + { + callees = selectedProviderCallees.get(selectedProvider); + } + else + { + callees = new ArrayList<String>(); + } + + callees.add(inviteDetail.getAddress()); + selectedProviderCallees.put(selectedProvider, callees); + } + } + + if(conference != null) + { + CallManager.inviteToConferenceCall( + selectedProviderCallees, + conference); + } + else + { + CallManager.createConferenceCall(selectedProviderCallees); + } + } + + /** + * Invites the contacts to the chat conference. + * + * @param contacts the list of contacts to invite + */ + private void inviteJitsiVideobridgeContacts( + ProtocolProviderService preselectedProvider, + Collection<UIContact> contacts) + { + List<String> callees = new ArrayList<String>(); + + Iterator<UIContact> contactsIter = contacts.iterator(); + + while (contactsIter.hasNext()) + { + UIContact uiContact = contactsIter.next(); + + Iterator<UIContactDetail> contactDetailsIter = uiContact + .getContactDetailsForOperationSet( + OperationSetBasicTelephony.class).iterator(); + + // We invite the first protocol contact that corresponds to the + // invite provider. + if (contactDetailsIter.hasNext()) + { + UIContactDetail inviteDetail = contactDetailsIter.next(); + + callees.add(inviteDetail.getAddress()); + } + } + + if(conference != null) + { + CallManager.inviteToJitsiVideobridgeConfCall( + callees.toArray(new String[callees.size()]), + conference.getCalls().get(0)); + } + else + { + CallManager.createJitsiVideobridgeConfCall( + preselectedProvider, + callees.toArray(new String[callees.size()])); + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java index 0dd6ef5..07c653d 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,1395 +15,1395 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.call.conference;
-
-import java.awt.*;
-import java.util.*;
-import java.util.List;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.gui.main.call.*;
-import net.java.sip.communicator.impl.gui.utils.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.util.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.plugin.desktoputil.TransparentPanel;
-
-import org.jitsi.util.swing.*;
-
-/**
- * Extends <tt>BasicConferenceCallPanel</tt> to implement a user interface
- * <tt>Component</tt> which depicts a <tt>CallConference</tt> with audio and
- * video and is contained in a <tt>CallPanel</tt>.
- *
- * @author Yana Stamcheva
- * @author Lyubomir Marinov
- */
-public class VideoConferenceCallPanel
- extends BasicConferenceCallPanel
-{
- /**
- * The <tt>Logger</tt> used by the <tt>VideoConferenceCallPanel</tt> class
- * and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(VideoConferenceCallPanel.class);
-
- /**
- * The compile-time flag which indicates whether each video displayed by
- * <tt>VideoConferenceCallPanel</tt> is to be depicted with an associated
- * tool bar showing information and controls related to the (local or
- * remote) peer sending the respective video.
- */
- private static final boolean SHOW_TOOLBARS = true;
-
- /**
- * The facility which aids this instance with the video-related information.
- */
- private final UIVideoHandler2 uiVideoHandler;
-
- /**
- * The <tt>Observer</tt> which listens to {@link #uiVideoHandler} about
- * changes in the video-related information.
- */
- private final Observer uiVideoHandlerObserver
- = new Observer()
- {
- public void update(Observable o, Object arg)
- {
- updateViewFromModel();
- }
- };
-
- /**
- * The <tt>VideoContainer</tt> which occupies this whole <tt>Component</tt>
- * and arranges the visual <tt>Component</tt>s displaying the video
- * streaming between the local peer/user and the remote peer(s).
- */
- private final VideoContainer videoContainer;
-
- /**
- * The set of visual <tt>Component</tt>s displaying video streaming between
- * the local peer/user and the remote peers which are depicted by this
- * instance.
- */
- private final Set<Component> videos = new HashSet<Component>();
-
- /**
- * The thumbnail container.
- */
- private final ThumbnailConferenceCallPanel thumbnailContainer;
-
- private final JPanel thumbnailPanel;
-
- /**
- * Initializes a new <tt>VideoConferenceCallPanel</tt> instance which is to
- * be used by a specific <tt>CallPanel</tt> to depict a specific
- * <tt>CallConference</tt>. The new instance will depict both the
- * audio-related and the video-related information.
- *
- * @param callPanel the <tt>CallPanel</tt> which will use the new instance
- * to depict the specified <tt>CallConference</tt>.
- * @param callConference the <tt>CallConference</tt> to be depicted by the
- * new instance
- * @param uiVideoHandler the utility which is to aid the new instance in
- * dealing with the video-related information
- */
- public VideoConferenceCallPanel(
- CallPanel callPanel,
- CallConference callConference,
- UIVideoHandler2 uiVideoHandler)
- {
- super(callPanel, callConference);
-
- this.uiVideoHandler = uiVideoHandler;
-
- thumbnailPanel = new JPanel(new BorderLayout());
- thumbnailContainer
- = new ThumbnailConferenceCallPanel( callPanel,
- callConference,
- uiVideoHandler);
-
- videoContainer = createVideoContainer();
-
- /*
- * Our user interface hierarchy has been initialized so we are ready to
- * begin receiving events warranting updates of this view from its
- * model.
- */
- uiVideoHandler.addObserver(uiVideoHandlerObserver);
-
- /*
- * Notify the super that this instance has completed its initialization
- * and the view that it implements is ready to be updated from the
- * model.
- */
- initializeComplete();
- }
-
- private void addConferenceMemberContainers(
- ConferenceParticipantContainer cpc)
- {
- List<ConferenceParticipantContainer> cmcs
- = cpc.conferenceMemberContainers;
-
- if ((cmcs != null) && !cmcs.isEmpty())
- {
- for (ConferenceParticipantContainer cmc : cmcs)
- {
- if (!cmc.toBeRemoved)
- {
- videoContainer.add(
- cmc.getComponent(),
- VideoLayout.CENTER_REMOTE);
- }
- }
- }
- }
-
- private Component createDefaultPhotoPanel(Call call)
- {
- OperationSetServerStoredAccountInfo accountInfo
- = call.getProtocolProvider().getOperationSet(
- OperationSetServerStoredAccountInfo.class);
- ImageIcon photoLabelIcon = null;
-
- if (accountInfo != null)
- {
- byte[] accountImage = AccountInfoUtils.getImage(accountInfo);
-
- // do not set empty images
- if ((accountImage != null) && (accountImage.length > 0))
- photoLabelIcon = new ImageIcon(accountImage);
- }
- if (photoLabelIcon == null)
- {
- photoLabelIcon
- = new ImageIcon(
- ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO));
- }
-
- return createDefaultPhotoPanel(photoLabelIcon);
- }
-
- private Component createDefaultPhotoPanel(CallPeer callPeer)
- {
- byte[] peerImage = CallManager.getPeerImage(callPeer);
- ImageIcon photoLabelIcon
- = (peerImage == null)
- ? new ImageIcon(
- ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO))
- : new ImageIcon(peerImage);
-
- return createDefaultPhotoPanel(photoLabelIcon);
- }
-
- private Component createDefaultPhotoPanel(ConferenceMember conferenceMember)
- {
- return
- createDefaultPhotoPanel(
- new ImageIcon(
- ImageLoader.getImage(
- ImageLoader.DEFAULT_USER_PHOTO)));
- }
-
- /**
- * Creates a new <tt>Component</tt> which is to display a specific
- * <tt>ImageIcon</tt> representing the photo of a participant in a call.
- *
- * @param photoLabelIcon the <tt>ImageIcon</tt> which represents the photo
- * of a participant in a call and which is to be displayed by the new
- * <tt>Component</tt>
- * @return a new <tt>Component</tt> which displays the specified
- * <tt>photoLabelIcon</tt>
- */
- private Component createDefaultPhotoPanel(ImageIcon photoLabelIcon)
- {
- JLabel photoLabel = new JLabel();
-
- photoLabel.setIcon(photoLabelIcon);
-
- @SuppressWarnings("serial")
- JPanel photoPanel
- = new TransparentPanel(new GridBagLayout())
- {
- /**
- * @{inheritDoc}
- */
- @Override
- public void paintComponent(Graphics g)
- {
- super.paintComponent(g);
-
- g = g.create();
- try
- {
- AntialiasingManager.activateAntialiasing(g);
-
- g.setColor(Color.GRAY);
- g.fillRoundRect(
- 0, 0, this.getWidth(), this.getHeight(),
- 6, 6);
- }
- finally
- {
- g.dispose();
- }
- }
- };
-
- photoPanel.setPreferredSize(new Dimension(320, 240));
-
- GridBagConstraints photoPanelConstraints = new GridBagConstraints();
-
- photoPanelConstraints.anchor = GridBagConstraints.CENTER;
- photoPanelConstraints.fill = GridBagConstraints.NONE;
- photoPanel.add(photoLabel, photoPanelConstraints);
-
- return photoPanel;
- }
-
- /**
- * Initializes a new <tt>VideoContainer</tt> instance which is to contain
- * the visual/video <tt>Component</tt>s of the telephony conference depicted
- * by this instance.
- */
- private VideoContainer createVideoContainer()
- {
- VideoContainer videoContainer = new VideoContainer(null, true);
-
- thumbnailPanel.setBackground(Color.DARK_GRAY);
- thumbnailPanel.add(thumbnailContainer, BorderLayout.NORTH);
-
- add(thumbnailPanel, BorderLayout.EAST);
- add(videoContainer, BorderLayout.CENTER);
-
- return videoContainer;
- }
-
- /**
- * Shows/hides the participants thumbnails list.
- *
- * @param show <tt>true</tt> to show the participants list, <tt>false</tt>
- * to hide it
- */
- public void showThumbnailsList(boolean show)
- {
- thumbnailPanel.setVisible(show);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void dispose()
- {
- try
- {
- uiVideoHandler.deleteObserver(uiVideoHandlerObserver);
- }
- finally
- {
- super.dispose();
- }
- }
-
- /**
- * Determines whether a specific <tt>ConferenceMember</tt> represents the
- * same conference participant as a specific <tt>CallPeer</tt>. If the
- * specified <tt>conferenceMember</tt> is <tt>null</tt>, returns
- * <tt>true</tt>. Otherwise, determines whether the addresses of the
- * specified <tt>conferenceMember</tt> and the specified <tt>callPeer</tt>
- * identify one and the same entity.
- *
- * @param conferenceMember the <tt>ConferenceMember</tt> to be checked
- * whether is represents the same conference participant as the specified
- * <tt>callPeer</tt>. If it is <tt>null</tt>, <tt>true</tt> is returned.
- * @param callPeer the <tt>CallPeer</tt> to be checked whether it represents
- * the same conference participant as the specified
- * <tt>conferenceMember</tt>
- * @return <tt>true</tt> if the specified <tt>conferenceMember</tt> and the
- * specified <tt>callPeer</tt> represent the same conference participant or
- * the specified <tt>conferenceMember</tt> is <tt>null</tt>; otherwise,
- * <tt>false</tt>
- */
- private boolean isConferenceMemberCallPeer(
- ConferenceMember conferenceMember,
- CallPeer callPeer)
- {
- return
- (conferenceMember == null)
- ? true
- : CallManager.addressesAreEqual(
- conferenceMember.getAddress(),
- callPeer.getAddress());
- }
-
- /**
- * Determines whether a specific <tt>ConferenceMember</tt> represents the
- * local peer/user. Since this instance depicts a whole telephony
- * conference, the local peer/user may be participating with multiple
- * <tt>Call</tt>s in it. The <tt>Call</tt>s may be through different
- * (local) accounts. That's why the implementation determines whether the
- * address of the specified <tt>conferenceMember</tt> identifies the address
- * of a (local) accounts involved in the telephony conference depicted by
- * this instance.
- *
- * @param conferenceMember the <tt>ConferenceMember</tt> to be checked
- * whether it represents the local peer/user
- * @return <tt>true</tt> if the specified <tt>conferenceMember</tt>
- * represents the local peer/user; otherwise, <tt>false</tt>
- */
- private boolean isConferenceMemberLocalUser(
- ConferenceMember conferenceMember)
- {
- String address = conferenceMember.getAddress();
-
- for (Call call : callConference.getCalls())
- {
- if (CallManager.addressesAreEqual(
- address,
- call.getProtocolProvider().getAccountID()
- .getAccountAddress()))
- {
- return true;
- }
- }
- return false;
- }
-
- private void removeConferenceMemberContainers(
- ConferenceParticipantContainer cpc,
- boolean all)
- {
- List<ConferenceParticipantContainer> cmcs
- = cpc.conferenceMemberContainers;
-
- if ((cmcs != null) && !cmcs.isEmpty())
- {
- Iterator<ConferenceParticipantContainer> i = cmcs.iterator();
-
- while (i.hasNext())
- {
- ConferenceParticipantContainer cmc = i.next();
-
- if (all || cmc.toBeRemoved)
- {
- i.remove();
-
- videoContainer.remove(cmc.getComponent());
- cmc.dispose();
- }
- }
- }
- }
-
- /**
- * Updates the <tt>ConferenceParticipantContainer</tt>s which depict the
- * <tt>ConferenceMember</tt>s of the <tt>CallPeer</tt> depicted by a
- * specific <tt>ConferenceParticipantContainer</tt>.
- *
- * @param cpc the <tt>ConferenceParticipantContainer</tt> which depicts the
- * <tt>CallPeer</tt> whose <tt>ConferenceMember</tt>s are to be depicted
- * @param videos the visual <tt>Component</tt>s displaying video streaming
- * from the remote peer (represented by <tt>cpc</tt>) to the local peer/user
- * @param videoTelephony the <tt>OperationSetVideoTelephony</tt> which
- * retrieved the specified <tt>videos</tt> from the <tt>CallPeer</tt>
- * depicted by <tt>cpc</tt>. While the <tt>CallPeer</tt> could be queried
- * for it, such a query would waste more resources at run time given that
- * the invoker has it already.
- */
- private void updateConferenceMemberContainers(
- ConferenceParticipantContainer cpc,
- List<Component> videos,
- OperationSetVideoTelephony videoTelephony)
- {
- CallPeer callPeer = (CallPeer) cpc.getParticipant();
- List<ConferenceParticipantContainer> cmcs
- = cpc.conferenceMemberContainers;
-
- /*
- * Invalidate all conferenceMemberContainers. Then validate which of
- * them are to remain and which of them are to really be removed
- * later on.
- */
- if (cmcs != null)
- {
- for (ConferenceParticipantContainer cmc : cmcs)
- cmc.toBeRemoved = true;
- }
-
- /*
- * Depict the remote videos. They may or may not be associated with
- * ConferenceMembers so the ConferenceMembers which have no
- * associated videos will be depicted afterwards.
- */
- if (videos != null)
- {
- Component video = cpc.getVideo();
-
- for (Component conferenceMemberVideo : videos)
- {
- /*
- * One of the remote videos is already used to depict the
- * callPeer.
- */
- if (conferenceMemberVideo == video)
- continue;
-
- boolean addNewConferenceParticipantContainer = true;
- ConferenceMember conferenceMember
- = videoTelephony.getConferenceMember(
- callPeer,
- conferenceMemberVideo);
-
- if (cmcs == null)
- {
- cmcs = new LinkedList<ConferenceParticipantContainer>();
- cpc.conferenceMemberContainers = cmcs;
- }
- else
- {
- for (ConferenceParticipantContainer cmc : cmcs)
- {
- Object cmcParticipant = cmc.getParticipant();
-
- if (conferenceMember == null)
- {
- if (cmcParticipant == callPeer)
- {
- Component cmcVideo = cmc.getVideo();
-
- if (cmcVideo == null)
- {
- cmc.setVideo(conferenceMemberVideo);
- cmc.toBeRemoved = false;
- addNewConferenceParticipantContainer
- = false;
- break;
- }
- else if (cmcVideo == conferenceMemberVideo)
- {
- cmc.toBeRemoved = false;
- addNewConferenceParticipantContainer
- = false;
- break;
- }
- }
- }
- else if (cmcParticipant == conferenceMember)
- {
- cmc.setVideo(conferenceMemberVideo);
- cmc.toBeRemoved = false;
- addNewConferenceParticipantContainer = false;
- break;
- }
- }
- }
-
- if (addNewConferenceParticipantContainer)
- {
- ConferenceParticipantContainer cmc
- = (conferenceMember == null)
- ? new ConferenceParticipantContainer(
- callPeer,
- conferenceMemberVideo)
- : new ConferenceParticipantContainer(
- conferenceMember,
- conferenceMemberVideo);
-
- cmcs.add(cmc);
- }
- }
- }
-
- /*
- * Depict the ConferenceMembers which have not been depicted yet.
- * They have no associated videos.
- */
- List<ConferenceMember> conferenceMembers
- = callPeer.getConferenceMembers();
-
- if (!conferenceMembers.isEmpty())
- {
- if (cmcs == null)
- {
- cmcs = new LinkedList<ConferenceParticipantContainer>();
- cpc.conferenceMemberContainers = cmcs;
- }
- for (ConferenceMember conferenceMember : conferenceMembers)
- {
- /*
- * If the callPeer reports itself as a ConferenceMember, then
- * we've already depicted it with cpc.
- */
- if (isConferenceMemberCallPeer(conferenceMember, callPeer))
- continue;
- /*
- * If the callPeer reports the local peer/user as a
- * ConferenceMember, then we've already depicted it.
- */
- if (isConferenceMemberLocalUser(conferenceMember))
- continue;
-
- boolean addNewConferenceParticipantContainer = true;
-
- for (ConferenceParticipantContainer cmc : cmcs)
- {
- if (cmc.getParticipant() == conferenceMember)
- {
- /*
- * It is possible to have a ConferenceMember who is
- * sending video but we just do not have the SSRC of
- * that video to associate the video with the
- * ConferenceMember. In such a case, we may be depicting
- * the ConferenceMember twice: once with video without a
- * ConferenceMember and once with a ConferenceMember
- * without video. This will surely be the case at the
- * time of this writing with non-focus participants in a
- * telephony conference hosted on a Jitsi Videobridge.
- * Such a display is undesirable. If the
- * conferenceMember is known to send video, we will not
- * display it until we associated it with a video. This
- * way, if a ConferenceMember is not sending video, we
- * will depict it and we can be sure that no video
- * without a ConferenceMember association will be
- * depicting it a second time.
- */
- if (cmc.toBeRemoved
- && !conferenceMember
- .getVideoStatus()
- .allowsSending())
- {
- cmc.setVideo(null);
- cmc.toBeRemoved = false;
- }
- addNewConferenceParticipantContainer = false;
- break;
- }
- }
-
- if (addNewConferenceParticipantContainer)
- {
- ConferenceParticipantContainer cmc
- = new ConferenceParticipantContainer(
- conferenceMember,
- null);
-
- cmcs.add(cmc);
- }
- }
- }
-
- if ((cmcs != null) && !cmcs.isEmpty())
- {
- removeConferenceMemberContainers(cpc, false);
- /*
- * If cpc is already added to the user interface hierarchy of this
- * instance, then it was there before the update procedure and it
- * was determined to be appropriate to continue to depict its model.
- * Consequently, its Component will be neither added to (because it
- * was already added) nor removed from the user interface hierarchy
- * of this instance. That's why we have make sure that the
- * Components of its conferenceMemberContainers are also added to
- * the user interface.
- */
- if (UIVideoHandler2.isAncestor(this, cpc.getComponent()))
- addConferenceMemberContainers(cpc);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected ConferenceCallPeerRenderer updateViewFromModel(
- ConferenceCallPeerRenderer callPeerPanel,
- CallPeer callPeer)
- {
- if (callPeer == null)
- {
- /*
- * The local peer/user will be represented by a Call which has a
- * CallPeer who provides local video. However, if the user has
- * selected to hide the local video, the local peer/user will not be
- * represented at all.
- */
- Component video = null;
-
- if (uiVideoHandler.isLocalVideoVisible())
- {
- for (Call aCall : callConference.getCalls())
- {
- Iterator<? extends CallPeer> callPeerIter
- = aCall.getCallPeers();
- OperationSetVideoTelephony videoTelephony
- = aCall.getProtocolProvider().getOperationSet(
- OperationSetVideoTelephony.class);
-
- while (callPeerIter.hasNext())
- {
- callPeer = callPeerIter.next();
-
- if (videoTelephony != null)
- {
- try
- {
- video
- = videoTelephony.getLocalVisualComponent(
- callPeer);
- }
- catch (OperationFailedException ofe)
- {
- logger.error(
- "Failed to retrieve the local video"
- + " for display",
- ofe);
- }
- if (video != null)
- break;
- }
- }
- if (video != null)
- break;
- }
- }
-
- if (callPeer == null)
- callPeerPanel = null;
- else
- {
- Call call = callPeer.getCall();
-
- if (callPeerPanel instanceof ConferenceParticipantContainer)
- {
- ConferenceParticipantContainer cpc
- = (ConferenceParticipantContainer) callPeerPanel;
-
- if (cpc.getParticipant() == call)
- cpc.setVideo(video);
- else
- callPeerPanel = null;
- }
- else
- callPeerPanel = null;
- if (callPeerPanel == null)
- {
- callPeerPanel
- = new ConferenceParticipantContainer(call, video);
- }
- }
- }
- else
- {
- /*
- * The specified callPeer will be represented by one of its remote
- * videos which is not associated with a ConferenceMember or is
- * associated with a ConferenceMember representing the callPeer
- * itself.
- */
- OperationSetVideoTelephony videoTelephony
- = callPeer.getProtocolProvider().getOperationSet(
- OperationSetVideoTelephony.class);
- List<Component> videos = null;
- Component video = null;
-
- if (videoTelephony != null)
- {
- videos = videoTelephony.getVisualComponents(callPeer);
- if ((videos != null) && !videos.isEmpty())
- {
- for (Component aVideo : videos)
- {
- ConferenceMember conferenceMember
- = videoTelephony.getConferenceMember(
- callPeer,
- aVideo);
-
- if (isConferenceMemberCallPeer(
- conferenceMember,
- callPeer))
- {
- video = aVideo;
- break;
- }
- }
- }
- }
-
- ConferenceParticipantContainer cpc = null;
-
- if (callPeerPanel instanceof ConferenceParticipantContainer)
- {
- cpc = (ConferenceParticipantContainer) callPeerPanel;
- if (cpc.getParticipant() == callPeer)
- cpc.setVideo(video);
- else
- cpc = null;
- }
- if (cpc == null)
- cpc = new ConferenceParticipantContainer(callPeer, video);
- callPeerPanel = cpc;
-
- // Update the conferenceMemberContainers of the cpc.
- updateConferenceMemberContainers(cpc, videos, videoTelephony);
- }
- return callPeerPanel;
- }
-
- /**
- * {@inheritDoc}
- *
- * If {@link #SHOW_TOOLBARS} is <tt>false</tt>, disables the use of
- * <tt>ConferenceParticipantContainer</tt>. A reason for such a value of
- * <tt>SHOW_TOOLBARS</tt> may be that the functionality implemented in the
- * model may not fully support mapping of visual <tt>Component</tt>s
- * displaying video to telephony conference participants (e.g. in telephony
- * conferences utilizing the Jitsi Videobridge server-side technology). In
- * such a case displays the videos only, does not map videos to participants
- * and does not display participants who do not have videos.
- */
- @Override
- protected void updateViewFromModelInEventDispatchThread()
- {
- if (SHOW_TOOLBARS)
- {
- super.updateViewFromModelInEventDispatchThread();
- return;
- }
-
- /*
- * Determine the set of visual Components displaying video streaming
- * between the local peer/user and the remote peers which are to be
- * depicted by this instance.
- */
- Component localVideo = null;
- Set<Component> videos = new HashSet<Component>();
-
- for (Call call : callConference.getCalls())
- {
- OperationSetVideoTelephony videoTelephony
- = call.getProtocolProvider().getOperationSet(
- OperationSetVideoTelephony.class);
-
- if (videoTelephony == null)
- continue;
-
- Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
-
- while (callPeerIter.hasNext())
- {
- CallPeer callPeer = callPeerIter.next();
-
- /*
- * TODO VideoConferenceCallPanel respects
- * UIVideoHandler2.isLocalVideoVisible() in order to react to
- * the associated button at the bottom of the CallPanel.
- * However, it does not add a close button on top of the local
- * video in contrast to OneToOneCallPeerPanel. Overall, the
- * result is questionable.
- */
- if (uiVideoHandler.isLocalVideoVisible()
- && (localVideo == null))
- {
- try
- {
- localVideo
- = videoTelephony.getLocalVisualComponent(callPeer);
- }
- catch (OperationFailedException ofe)
- {
- /*
- * We'll just try to get the local video through another
- * CallPeer then.
- */
- }
- if (localVideo != null)
- videos.add(localVideo);
- }
-
- List<Component> callPeerRemoteVideos
- = videoTelephony.getVisualComponents(callPeer);
-
- videos.addAll(callPeerRemoteVideos);
- }
- }
-
- /*
- * Remove the Components of this view which are no longer present in the
- * model.
- */
- Iterator<Component> thisVideoIter = this.videos.iterator();
-
- while (thisVideoIter.hasNext())
- {
- Component thisVideo = thisVideoIter.next();
-
- if (!videos.contains(thisVideo))
- {
- thisVideoIter.remove();
- videoContainer.remove(thisVideo);
- }
-
- /*
- * If a video is known to be depicted by this view and is still
- * present in the model, then we could remove it from the set of
- * videos present in the model in order to prevent going through the
- * procedure of adding it to this view. However, we choose to play
- * on the safe side.
- */
- }
-
- /*
- * Add the Components of the model which are not depicted by this view.
- */
- for (Component video : videos)
- {
- if (!UIVideoHandler2.isAncestor(videoContainer, video))
- {
- this.videos.add(video);
- videoContainer.add(
- video,
- (video == localVideo)
- ? VideoLayout.LOCAL
- : VideoLayout.CENTER_REMOTE);
- }
- }
- }
-
- @Override
- protected void viewForModelAdded(
- ConferenceCallPeerRenderer callPeerPanel,
- CallPeer callPeer)
- {
- videoContainer.add(
- callPeerPanel.getComponent(),
- VideoLayout.CENTER_REMOTE);
- if ((callPeer != null)
- && (callPeerPanel instanceof ConferenceParticipantContainer))
- {
- addConferenceMemberContainers(
- (ConferenceParticipantContainer) callPeerPanel);
- }
- }
-
- @Override
- protected void viewForModelRemoved(
- ConferenceCallPeerRenderer callPeerPanel,
- CallPeer callPeer)
- {
- videoContainer.remove(callPeerPanel.getComponent());
- if ((callPeer != null)
- && (callPeerPanel instanceof ConferenceParticipantContainer))
- {
- removeConferenceMemberContainers(
- (ConferenceParticipantContainer) callPeerPanel,
- true);
- }
- }
-
- /**
- * Implements an AWT <tt>Component</tt> which contains the user interface
- * elements depicting a specific participant in the telephony conference
- * depicted by a <tt>VideoConferenceCallPanel</tt>.
- */
- private class ConferenceParticipantContainer
- extends TransparentPanel
- implements ConferenceCallPeerRenderer
- {
- /**
- * The list of <tt>ConferenceParticipantContainer</tt>s which represent
- * the <tt>ConferenceMember</tt>s of the participant represented by this
- * instance. Since a <tt>CallPeer</tt> may send the local peer/user
- * multiple videos without providing a way to associate a
- * ConferenceMember with each one of them, the list may contain
- * <tt>ConferenceParticipantContainer</tt>s which do not represent a
- * specific <tt>ConferenceMember</tt> instance but rather a video sent
- * by a <tt>CallPeer</tt> to the local peer/user which looks like (in
- * the terms of <tt>VideoConferenceCallPanel) a member of a conference
- * organized by the <tt>CallPeer</tt> in question.
- * <p>
- * Implements a state which is private to
- * <tt>VideoConferenceCallPanel</tt> and is of no concern to
- * <tt>ConferenceParticipantContainer</tt>.
- * </p>
- */
- List<ConferenceParticipantContainer> conferenceMemberContainers;
-
- /**
- * The indicator which determines whether this instance is to be removed
- * because it has become out-of-date, obsolete, unnecessary.
- * <p>
- * Implements a state which is private to
- * <tt>VideoConferenceCallPanel</tt> and is of no concern to
- * <tt>ConferenceParticipantContainer</tt>.
- * </p>
- */
- boolean toBeRemoved;
-
- /**
- * The <tt>BasicConferenceParticipantPanel</tt> which is displayed at
- * the bottom of this instance, bellow the {@link #video} (i.e.
- * {@link #videoContainer}) and is referred to as the tool bar.
- */
- private final BasicConferenceParticipantPanel<?> toolBar;
-
- /**
- * The visual <tt>Component</tt>, if any, displaying video which is
- * depicted by this instance.
- */
- private Component video;
-
- /**
- * The <tt>VideoContainer</tt> which lays out the video depicted by this
- * instance i.e. {@link #video}.
- */
- private final VideoContainer videoContainer;
-
- /**
- * The <tt>CallPeer</tt> associated with this container, if it has been
- * created to represent a <tt>CallPeer</tt>.
- */
- private CallPeer callPeer;
-
- /**
- * The <tt>conferenceMember</tt> associated with this container, if it
- * has been created to represent a <tt>conferenceMember</tt>.
- */
- private ConferenceMember conferenceMember;
-
- /**
- * Indicates that this container contains information for the local
- * user.
- */
- private boolean isLocalUser;
-
- /**
- * Initializes a new <tt>ConferenceParticipantContainer</tt> instance
- * which is to depict the local peer/user.
- *
- * @param call a <tt>Call</tt> which is to provide information about the
- * local peer/user
- * @param video the visual <tt>Component</tt>, if any, displaying the
- * video streaming from the local peer/user to the remote peer(s)
- */
- public ConferenceParticipantContainer(Call call, Component video)
- {
- this(
- createDefaultPhotoPanel(call),
- video,
- new ConferencePeerPanel(
- VideoConferenceCallPanel.this,
- call,
- true),
- null, null, true);
- }
-
- public ConferenceParticipantContainer(
- CallPeer callPeer,
- Component video)
- {
- this( createDefaultPhotoPanel(callPeer),
- video,
- new ConferencePeerPanel(
- VideoConferenceCallPanel.this,
- callPeer,
- true),
- callPeer, null, false);
- }
-
- private ConferenceParticipantContainer(
- Component noVideo,
- Component video,
- BasicConferenceParticipantPanel<?> toolBar,
- CallPeer callPeer,
- ConferenceMember conferenceMember,
- boolean isLocalUser)
- {
- super(new BorderLayout());
-
- this.callPeer = callPeer;
- this.conferenceMember = conferenceMember;
- this.isLocalUser = isLocalUser;
-
- videoContainer = new VideoContainer(noVideo, false);
- add(videoContainer, BorderLayout.CENTER);
-
- this.toolBar = toolBar;
- if (this.toolBar != null)
- add(this.toolBar, BorderLayout.SOUTH);
-
- if (video != null)
- {
- setVideo(video);
- }
- else
- setVisible(false);
- }
-
- public ConferenceParticipantContainer(
- ConferenceMember conferenceMember,
- Component video)
- {
- this(
- createDefaultPhotoPanel(conferenceMember),
- video,
- new ConferenceMemberPanel(
- VideoConferenceCallPanel.this,
- conferenceMember,
- true),
- null, conferenceMember, false);
- }
-
- public void dispose()
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.dispose();
-
- // Dispose of the conferenceMemberContainers if any.
- /*
- * XXX The field conferenceMemberContainers implements a state
- * private to VideoConferenceCallPanel which the latter makes sure
- * to access on the AWT event dispatching thread only. Since we are
- * going out of our way here to help VideoConferenceCallPanel,
- * ensure that the mentioned synchronization rule is not violated.
- */
- CallManager.assertIsEventDispatchingThread();
- if (conferenceMemberContainers != null)
- {
- for (ConferenceParticipantContainer cmc
- : conferenceMemberContainers)
- {
- cmc.dispose();
- }
- }
- }
-
- public CallPanel getCallPanel()
- {
- return getCallRenderer().getCallContainer();
- }
-
- public SwingCallRenderer getCallRenderer()
- {
- return VideoConferenceCallPanel.this;
- }
-
- public Component getComponent()
- {
- return this;
- }
-
- private ConferenceCallPeerRenderer
- getConferenceCallPeerRendererDelegate()
- {
- return
- (toolBar instanceof ConferenceCallPeerRenderer)
- ? (ConferenceCallPeerRenderer) toolBar
- : null;
- }
-
- /**
- * Gets the conference participant depicted by this instance.
- *
- * @return the conference participant depicted by this instance
- */
- public Object getParticipant()
- {
- return (toolBar == null) ? null : toolBar.getParticipant();
- }
-
- public Component getVideo()
- {
- return video;
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only. Otherwise, returns <tt>false</tt>.
- */
- public boolean isLocalVideoVisible()
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- return (delegate == null) ? false : delegate.isLocalVideoVisible();
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void printDTMFTone(char dtmfChar)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.printDTMFTone(dtmfChar);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityNegotiationStarted(
- CallPeerSecurityNegotiationStartedEvent ev)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityNegotiationStarted(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityOff(CallPeerSecurityOffEvent ev)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityOff(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityOn(CallPeerSecurityOnEvent ev)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityOn(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityPending()
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityPending();
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityTimeout(CallPeerSecurityTimeoutEvent ev)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityTimeout(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setErrorReason(String reason)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setErrorReason(reason);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setLocalVideoVisible(boolean visible)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setLocalVideoVisible(visible);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setMute(boolean mute)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setMute(mute);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setOnHold(boolean onHold)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setOnHold(onHold);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setPeerImage(byte[] image)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setPeerImage(image);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setPeerName(String name)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setPeerName(name);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setPeerState(
- CallPeerState oldState,
- CallPeerState newState,
- String stateString)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setPeerState(oldState, newState, stateString);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setSecurityPanelVisible(boolean visible)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setSecurityPanelVisible(visible);
- }
-
- /**
- * Sets the visual <tt>Component</tt> displaying the video associated
- * with the participant depicted by this instance.
- *
- * @param video the visual <tt>Component</tt> displaying video which is
- * to be associated with the participant depicted by this instance
- */
- void setVideo(Component video)
- {
- if (this.video != video)
- {
- if (this.video != null)
- videoContainer.remove(this.video);
-
- this.video = video;
-
- if (this.video != null)
- {
- setVisible(true);
- videoContainer.add(this.video, VideoLayout.CENTER_REMOTE);
- }
- else
- setVisible(false);
-
- // Update thumbnails container according to video status.
- if (thumbnailContainer != null)
- {
- if (conferenceMember != null)
- thumbnailContainer
- .updateThumbnail(conferenceMember, (video != null));
- else if (callPeer != null)
- thumbnailContainer
- .updateThumbnail(callPeer, (video != null));
- else if (isLocalUser)
- thumbnailContainer
- .updateThumbnail((video != null));
- }
- }
- }
- }
-}
+package net.java.sip.communicator.impl.gui.main.call.conference; + +import java.awt.*; +import java.util.*; +import java.util.List; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.main.call.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.plugin.desktoputil.TransparentPanel; + +import org.jitsi.util.swing.*; + +/** + * Extends <tt>BasicConferenceCallPanel</tt> to implement a user interface + * <tt>Component</tt> which depicts a <tt>CallConference</tt> with audio and + * video and is contained in a <tt>CallPanel</tt>. + * + * @author Yana Stamcheva + * @author Lyubomir Marinov + */ +public class VideoConferenceCallPanel + extends BasicConferenceCallPanel +{ + /** + * The <tt>Logger</tt> used by the <tt>VideoConferenceCallPanel</tt> class + * and its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(VideoConferenceCallPanel.class); + + /** + * The compile-time flag which indicates whether each video displayed by + * <tt>VideoConferenceCallPanel</tt> is to be depicted with an associated + * tool bar showing information and controls related to the (local or + * remote) peer sending the respective video. + */ + private static final boolean SHOW_TOOLBARS = true; + + /** + * The facility which aids this instance with the video-related information. + */ + private final UIVideoHandler2 uiVideoHandler; + + /** + * The <tt>Observer</tt> which listens to {@link #uiVideoHandler} about + * changes in the video-related information. + */ + private final Observer uiVideoHandlerObserver + = new Observer() + { + public void update(Observable o, Object arg) + { + updateViewFromModel(); + } + }; + + /** + * The <tt>VideoContainer</tt> which occupies this whole <tt>Component</tt> + * and arranges the visual <tt>Component</tt>s displaying the video + * streaming between the local peer/user and the remote peer(s). + */ + private final VideoContainer videoContainer; + + /** + * The set of visual <tt>Component</tt>s displaying video streaming between + * the local peer/user and the remote peers which are depicted by this + * instance. + */ + private final Set<Component> videos = new HashSet<Component>(); + + /** + * The thumbnail container. + */ + private final ThumbnailConferenceCallPanel thumbnailContainer; + + private final JPanel thumbnailPanel; + + /** + * Initializes a new <tt>VideoConferenceCallPanel</tt> instance which is to + * be used by a specific <tt>CallPanel</tt> to depict a specific + * <tt>CallConference</tt>. The new instance will depict both the + * audio-related and the video-related information. + * + * @param callPanel the <tt>CallPanel</tt> which will use the new instance + * to depict the specified <tt>CallConference</tt>. + * @param callConference the <tt>CallConference</tt> to be depicted by the + * new instance + * @param uiVideoHandler the utility which is to aid the new instance in + * dealing with the video-related information + */ + public VideoConferenceCallPanel( + CallPanel callPanel, + CallConference callConference, + UIVideoHandler2 uiVideoHandler) + { + super(callPanel, callConference); + + this.uiVideoHandler = uiVideoHandler; + + thumbnailPanel = new JPanel(new BorderLayout()); + thumbnailContainer + = new ThumbnailConferenceCallPanel( callPanel, + callConference, + uiVideoHandler); + + videoContainer = createVideoContainer(); + + /* + * Our user interface hierarchy has been initialized so we are ready to + * begin receiving events warranting updates of this view from its + * model. + */ + uiVideoHandler.addObserver(uiVideoHandlerObserver); + + /* + * Notify the super that this instance has completed its initialization + * and the view that it implements is ready to be updated from the + * model. + */ + initializeComplete(); + } + + private void addConferenceMemberContainers( + ConferenceParticipantContainer cpc) + { + List<ConferenceParticipantContainer> cmcs + = cpc.conferenceMemberContainers; + + if ((cmcs != null) && !cmcs.isEmpty()) + { + for (ConferenceParticipantContainer cmc : cmcs) + { + if (!cmc.toBeRemoved) + { + videoContainer.add( + cmc.getComponent(), + VideoLayout.CENTER_REMOTE); + } + } + } + } + + private Component createDefaultPhotoPanel(Call call) + { + OperationSetServerStoredAccountInfo accountInfo + = call.getProtocolProvider().getOperationSet( + OperationSetServerStoredAccountInfo.class); + ImageIcon photoLabelIcon = null; + + if (accountInfo != null) + { + byte[] accountImage = AccountInfoUtils.getImage(accountInfo); + + // do not set empty images + if ((accountImage != null) && (accountImage.length > 0)) + photoLabelIcon = new ImageIcon(accountImage); + } + if (photoLabelIcon == null) + { + photoLabelIcon + = new ImageIcon( + ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO)); + } + + return createDefaultPhotoPanel(photoLabelIcon); + } + + private Component createDefaultPhotoPanel(CallPeer callPeer) + { + byte[] peerImage = CallManager.getPeerImage(callPeer); + ImageIcon photoLabelIcon + = (peerImage == null) + ? new ImageIcon( + ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO)) + : new ImageIcon(peerImage); + + return createDefaultPhotoPanel(photoLabelIcon); + } + + private Component createDefaultPhotoPanel(ConferenceMember conferenceMember) + { + return + createDefaultPhotoPanel( + new ImageIcon( + ImageLoader.getImage( + ImageLoader.DEFAULT_USER_PHOTO))); + } + + /** + * Creates a new <tt>Component</tt> which is to display a specific + * <tt>ImageIcon</tt> representing the photo of a participant in a call. + * + * @param photoLabelIcon the <tt>ImageIcon</tt> which represents the photo + * of a participant in a call and which is to be displayed by the new + * <tt>Component</tt> + * @return a new <tt>Component</tt> which displays the specified + * <tt>photoLabelIcon</tt> + */ + private Component createDefaultPhotoPanel(ImageIcon photoLabelIcon) + { + JLabel photoLabel = new JLabel(); + + photoLabel.setIcon(photoLabelIcon); + + @SuppressWarnings("serial") + JPanel photoPanel + = new TransparentPanel(new GridBagLayout()) + { + /** + * @{inheritDoc} + */ + @Override + public void paintComponent(Graphics g) + { + super.paintComponent(g); + + g = g.create(); + try + { + AntialiasingManager.activateAntialiasing(g); + + g.setColor(Color.GRAY); + g.fillRoundRect( + 0, 0, this.getWidth(), this.getHeight(), + 6, 6); + } + finally + { + g.dispose(); + } + } + }; + + photoPanel.setPreferredSize(new Dimension(320, 240)); + + GridBagConstraints photoPanelConstraints = new GridBagConstraints(); + + photoPanelConstraints.anchor = GridBagConstraints.CENTER; + photoPanelConstraints.fill = GridBagConstraints.NONE; + photoPanel.add(photoLabel, photoPanelConstraints); + + return photoPanel; + } + + /** + * Initializes a new <tt>VideoContainer</tt> instance which is to contain + * the visual/video <tt>Component</tt>s of the telephony conference depicted + * by this instance. + */ + private VideoContainer createVideoContainer() + { + VideoContainer videoContainer = new VideoContainer(null, true); + + thumbnailPanel.setBackground(Color.DARK_GRAY); + thumbnailPanel.add(thumbnailContainer, BorderLayout.NORTH); + + add(thumbnailPanel, BorderLayout.EAST); + add(videoContainer, BorderLayout.CENTER); + + return videoContainer; + } + + /** + * Shows/hides the participants thumbnails list. + * + * @param show <tt>true</tt> to show the participants list, <tt>false</tt> + * to hide it + */ + public void showThumbnailsList(boolean show) + { + thumbnailPanel.setVisible(show); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + try + { + uiVideoHandler.deleteObserver(uiVideoHandlerObserver); + } + finally + { + super.dispose(); + } + } + + /** + * Determines whether a specific <tt>ConferenceMember</tt> represents the + * same conference participant as a specific <tt>CallPeer</tt>. If the + * specified <tt>conferenceMember</tt> is <tt>null</tt>, returns + * <tt>true</tt>. Otherwise, determines whether the addresses of the + * specified <tt>conferenceMember</tt> and the specified <tt>callPeer</tt> + * identify one and the same entity. + * + * @param conferenceMember the <tt>ConferenceMember</tt> to be checked + * whether is represents the same conference participant as the specified + * <tt>callPeer</tt>. If it is <tt>null</tt>, <tt>true</tt> is returned. + * @param callPeer the <tt>CallPeer</tt> to be checked whether it represents + * the same conference participant as the specified + * <tt>conferenceMember</tt> + * @return <tt>true</tt> if the specified <tt>conferenceMember</tt> and the + * specified <tt>callPeer</tt> represent the same conference participant or + * the specified <tt>conferenceMember</tt> is <tt>null</tt>; otherwise, + * <tt>false</tt> + */ + private boolean isConferenceMemberCallPeer( + ConferenceMember conferenceMember, + CallPeer callPeer) + { + return + (conferenceMember == null) + ? true + : CallManager.addressesAreEqual( + conferenceMember.getAddress(), + callPeer.getAddress()); + } + + /** + * Determines whether a specific <tt>ConferenceMember</tt> represents the + * local peer/user. Since this instance depicts a whole telephony + * conference, the local peer/user may be participating with multiple + * <tt>Call</tt>s in it. The <tt>Call</tt>s may be through different + * (local) accounts. That's why the implementation determines whether the + * address of the specified <tt>conferenceMember</tt> identifies the address + * of a (local) accounts involved in the telephony conference depicted by + * this instance. + * + * @param conferenceMember the <tt>ConferenceMember</tt> to be checked + * whether it represents the local peer/user + * @return <tt>true</tt> if the specified <tt>conferenceMember</tt> + * represents the local peer/user; otherwise, <tt>false</tt> + */ + private boolean isConferenceMemberLocalUser( + ConferenceMember conferenceMember) + { + String address = conferenceMember.getAddress(); + + for (Call call : callConference.getCalls()) + { + if (CallManager.addressesAreEqual( + address, + call.getProtocolProvider().getAccountID() + .getAccountAddress())) + { + return true; + } + } + return false; + } + + private void removeConferenceMemberContainers( + ConferenceParticipantContainer cpc, + boolean all) + { + List<ConferenceParticipantContainer> cmcs + = cpc.conferenceMemberContainers; + + if ((cmcs != null) && !cmcs.isEmpty()) + { + Iterator<ConferenceParticipantContainer> i = cmcs.iterator(); + + while (i.hasNext()) + { + ConferenceParticipantContainer cmc = i.next(); + + if (all || cmc.toBeRemoved) + { + i.remove(); + + videoContainer.remove(cmc.getComponent()); + cmc.dispose(); + } + } + } + } + + /** + * Updates the <tt>ConferenceParticipantContainer</tt>s which depict the + * <tt>ConferenceMember</tt>s of the <tt>CallPeer</tt> depicted by a + * specific <tt>ConferenceParticipantContainer</tt>. + * + * @param cpc the <tt>ConferenceParticipantContainer</tt> which depicts the + * <tt>CallPeer</tt> whose <tt>ConferenceMember</tt>s are to be depicted + * @param videos the visual <tt>Component</tt>s displaying video streaming + * from the remote peer (represented by <tt>cpc</tt>) to the local peer/user + * @param videoTelephony the <tt>OperationSetVideoTelephony</tt> which + * retrieved the specified <tt>videos</tt> from the <tt>CallPeer</tt> + * depicted by <tt>cpc</tt>. While the <tt>CallPeer</tt> could be queried + * for it, such a query would waste more resources at run time given that + * the invoker has it already. + */ + private void updateConferenceMemberContainers( + ConferenceParticipantContainer cpc, + List<Component> videos, + OperationSetVideoTelephony videoTelephony) + { + CallPeer callPeer = (CallPeer) cpc.getParticipant(); + List<ConferenceParticipantContainer> cmcs + = cpc.conferenceMemberContainers; + + /* + * Invalidate all conferenceMemberContainers. Then validate which of + * them are to remain and which of them are to really be removed + * later on. + */ + if (cmcs != null) + { + for (ConferenceParticipantContainer cmc : cmcs) + cmc.toBeRemoved = true; + } + + /* + * Depict the remote videos. They may or may not be associated with + * ConferenceMembers so the ConferenceMembers which have no + * associated videos will be depicted afterwards. + */ + if (videos != null) + { + Component video = cpc.getVideo(); + + for (Component conferenceMemberVideo : videos) + { + /* + * One of the remote videos is already used to depict the + * callPeer. + */ + if (conferenceMemberVideo == video) + continue; + + boolean addNewConferenceParticipantContainer = true; + ConferenceMember conferenceMember + = videoTelephony.getConferenceMember( + callPeer, + conferenceMemberVideo); + + if (cmcs == null) + { + cmcs = new LinkedList<ConferenceParticipantContainer>(); + cpc.conferenceMemberContainers = cmcs; + } + else + { + for (ConferenceParticipantContainer cmc : cmcs) + { + Object cmcParticipant = cmc.getParticipant(); + + if (conferenceMember == null) + { + if (cmcParticipant == callPeer) + { + Component cmcVideo = cmc.getVideo(); + + if (cmcVideo == null) + { + cmc.setVideo(conferenceMemberVideo); + cmc.toBeRemoved = false; + addNewConferenceParticipantContainer + = false; + break; + } + else if (cmcVideo == conferenceMemberVideo) + { + cmc.toBeRemoved = false; + addNewConferenceParticipantContainer + = false; + break; + } + } + } + else if (cmcParticipant == conferenceMember) + { + cmc.setVideo(conferenceMemberVideo); + cmc.toBeRemoved = false; + addNewConferenceParticipantContainer = false; + break; + } + } + } + + if (addNewConferenceParticipantContainer) + { + ConferenceParticipantContainer cmc + = (conferenceMember == null) + ? new ConferenceParticipantContainer( + callPeer, + conferenceMemberVideo) + : new ConferenceParticipantContainer( + conferenceMember, + conferenceMemberVideo); + + cmcs.add(cmc); + } + } + } + + /* + * Depict the ConferenceMembers which have not been depicted yet. + * They have no associated videos. + */ + List<ConferenceMember> conferenceMembers + = callPeer.getConferenceMembers(); + + if (!conferenceMembers.isEmpty()) + { + if (cmcs == null) + { + cmcs = new LinkedList<ConferenceParticipantContainer>(); + cpc.conferenceMemberContainers = cmcs; + } + for (ConferenceMember conferenceMember : conferenceMembers) + { + /* + * If the callPeer reports itself as a ConferenceMember, then + * we've already depicted it with cpc. + */ + if (isConferenceMemberCallPeer(conferenceMember, callPeer)) + continue; + /* + * If the callPeer reports the local peer/user as a + * ConferenceMember, then we've already depicted it. + */ + if (isConferenceMemberLocalUser(conferenceMember)) + continue; + + boolean addNewConferenceParticipantContainer = true; + + for (ConferenceParticipantContainer cmc : cmcs) + { + if (cmc.getParticipant() == conferenceMember) + { + /* + * It is possible to have a ConferenceMember who is + * sending video but we just do not have the SSRC of + * that video to associate the video with the + * ConferenceMember. In such a case, we may be depicting + * the ConferenceMember twice: once with video without a + * ConferenceMember and once with a ConferenceMember + * without video. This will surely be the case at the + * time of this writing with non-focus participants in a + * telephony conference hosted on a Jitsi Videobridge. + * Such a display is undesirable. If the + * conferenceMember is known to send video, we will not + * display it until we associated it with a video. This + * way, if a ConferenceMember is not sending video, we + * will depict it and we can be sure that no video + * without a ConferenceMember association will be + * depicting it a second time. + */ + if (cmc.toBeRemoved + && !conferenceMember + .getVideoStatus() + .allowsSending()) + { + cmc.setVideo(null); + cmc.toBeRemoved = false; + } + addNewConferenceParticipantContainer = false; + break; + } + } + + if (addNewConferenceParticipantContainer) + { + ConferenceParticipantContainer cmc + = new ConferenceParticipantContainer( + conferenceMember, + null); + + cmcs.add(cmc); + } + } + } + + if ((cmcs != null) && !cmcs.isEmpty()) + { + removeConferenceMemberContainers(cpc, false); + /* + * If cpc is already added to the user interface hierarchy of this + * instance, then it was there before the update procedure and it + * was determined to be appropriate to continue to depict its model. + * Consequently, its Component will be neither added to (because it + * was already added) nor removed from the user interface hierarchy + * of this instance. That's why we have make sure that the + * Components of its conferenceMemberContainers are also added to + * the user interface. + */ + if (UIVideoHandler2.isAncestor(this, cpc.getComponent())) + addConferenceMemberContainers(cpc); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected ConferenceCallPeerRenderer updateViewFromModel( + ConferenceCallPeerRenderer callPeerPanel, + CallPeer callPeer) + { + if (callPeer == null) + { + /* + * The local peer/user will be represented by a Call which has a + * CallPeer who provides local video. However, if the user has + * selected to hide the local video, the local peer/user will not be + * represented at all. + */ + Component video = null; + + if (uiVideoHandler.isLocalVideoVisible()) + { + for (Call aCall : callConference.getCalls()) + { + Iterator<? extends CallPeer> callPeerIter + = aCall.getCallPeers(); + OperationSetVideoTelephony videoTelephony + = aCall.getProtocolProvider().getOperationSet( + OperationSetVideoTelephony.class); + + while (callPeerIter.hasNext()) + { + callPeer = callPeerIter.next(); + + if (videoTelephony != null) + { + try + { + video + = videoTelephony.getLocalVisualComponent( + callPeer); + } + catch (OperationFailedException ofe) + { + logger.error( + "Failed to retrieve the local video" + + " for display", + ofe); + } + if (video != null) + break; + } + } + if (video != null) + break; + } + } + + if (callPeer == null) + callPeerPanel = null; + else + { + Call call = callPeer.getCall(); + + if (callPeerPanel instanceof ConferenceParticipantContainer) + { + ConferenceParticipantContainer cpc + = (ConferenceParticipantContainer) callPeerPanel; + + if (cpc.getParticipant() == call) + cpc.setVideo(video); + else + callPeerPanel = null; + } + else + callPeerPanel = null; + if (callPeerPanel == null) + { + callPeerPanel + = new ConferenceParticipantContainer(call, video); + } + } + } + else + { + /* + * The specified callPeer will be represented by one of its remote + * videos which is not associated with a ConferenceMember or is + * associated with a ConferenceMember representing the callPeer + * itself. + */ + OperationSetVideoTelephony videoTelephony + = callPeer.getProtocolProvider().getOperationSet( + OperationSetVideoTelephony.class); + List<Component> videos = null; + Component video = null; + + if (videoTelephony != null) + { + videos = videoTelephony.getVisualComponents(callPeer); + if ((videos != null) && !videos.isEmpty()) + { + for (Component aVideo : videos) + { + ConferenceMember conferenceMember + = videoTelephony.getConferenceMember( + callPeer, + aVideo); + + if (isConferenceMemberCallPeer( + conferenceMember, + callPeer)) + { + video = aVideo; + break; + } + } + } + } + + ConferenceParticipantContainer cpc = null; + + if (callPeerPanel instanceof ConferenceParticipantContainer) + { + cpc = (ConferenceParticipantContainer) callPeerPanel; + if (cpc.getParticipant() == callPeer) + cpc.setVideo(video); + else + cpc = null; + } + if (cpc == null) + cpc = new ConferenceParticipantContainer(callPeer, video); + callPeerPanel = cpc; + + // Update the conferenceMemberContainers of the cpc. + updateConferenceMemberContainers(cpc, videos, videoTelephony); + } + return callPeerPanel; + } + + /** + * {@inheritDoc} + * + * If {@link #SHOW_TOOLBARS} is <tt>false</tt>, disables the use of + * <tt>ConferenceParticipantContainer</tt>. A reason for such a value of + * <tt>SHOW_TOOLBARS</tt> may be that the functionality implemented in the + * model may not fully support mapping of visual <tt>Component</tt>s + * displaying video to telephony conference participants (e.g. in telephony + * conferences utilizing the Jitsi Videobridge server-side technology). In + * such a case displays the videos only, does not map videos to participants + * and does not display participants who do not have videos. + */ + @Override + protected void updateViewFromModelInEventDispatchThread() + { + if (SHOW_TOOLBARS) + { + super.updateViewFromModelInEventDispatchThread(); + return; + } + + /* + * Determine the set of visual Components displaying video streaming + * between the local peer/user and the remote peers which are to be + * depicted by this instance. + */ + Component localVideo = null; + Set<Component> videos = new HashSet<Component>(); + + for (Call call : callConference.getCalls()) + { + OperationSetVideoTelephony videoTelephony + = call.getProtocolProvider().getOperationSet( + OperationSetVideoTelephony.class); + + if (videoTelephony == null) + continue; + + Iterator<? extends CallPeer> callPeerIter = call.getCallPeers(); + + while (callPeerIter.hasNext()) + { + CallPeer callPeer = callPeerIter.next(); + + /* + * TODO VideoConferenceCallPanel respects + * UIVideoHandler2.isLocalVideoVisible() in order to react to + * the associated button at the bottom of the CallPanel. + * However, it does not add a close button on top of the local + * video in contrast to OneToOneCallPeerPanel. Overall, the + * result is questionable. + */ + if (uiVideoHandler.isLocalVideoVisible() + && (localVideo == null)) + { + try + { + localVideo + = videoTelephony.getLocalVisualComponent(callPeer); + } + catch (OperationFailedException ofe) + { + /* + * We'll just try to get the local video through another + * CallPeer then. + */ + } + if (localVideo != null) + videos.add(localVideo); + } + + List<Component> callPeerRemoteVideos + = videoTelephony.getVisualComponents(callPeer); + + videos.addAll(callPeerRemoteVideos); + } + } + + /* + * Remove the Components of this view which are no longer present in the + * model. + */ + Iterator<Component> thisVideoIter = this.videos.iterator(); + + while (thisVideoIter.hasNext()) + { + Component thisVideo = thisVideoIter.next(); + + if (!videos.contains(thisVideo)) + { + thisVideoIter.remove(); + videoContainer.remove(thisVideo); + } + + /* + * If a video is known to be depicted by this view and is still + * present in the model, then we could remove it from the set of + * videos present in the model in order to prevent going through the + * procedure of adding it to this view. However, we choose to play + * on the safe side. + */ + } + + /* + * Add the Components of the model which are not depicted by this view. + */ + for (Component video : videos) + { + if (!UIVideoHandler2.isAncestor(videoContainer, video)) + { + this.videos.add(video); + videoContainer.add( + video, + (video == localVideo) + ? VideoLayout.LOCAL + : VideoLayout.CENTER_REMOTE); + } + } + } + + @Override + protected void viewForModelAdded( + ConferenceCallPeerRenderer callPeerPanel, + CallPeer callPeer) + { + videoContainer.add( + callPeerPanel.getComponent(), + VideoLayout.CENTER_REMOTE); + if ((callPeer != null) + && (callPeerPanel instanceof ConferenceParticipantContainer)) + { + addConferenceMemberContainers( + (ConferenceParticipantContainer) callPeerPanel); + } + } + + @Override + protected void viewForModelRemoved( + ConferenceCallPeerRenderer callPeerPanel, + CallPeer callPeer) + { + videoContainer.remove(callPeerPanel.getComponent()); + if ((callPeer != null) + && (callPeerPanel instanceof ConferenceParticipantContainer)) + { + removeConferenceMemberContainers( + (ConferenceParticipantContainer) callPeerPanel, + true); + } + } + + /** + * Implements an AWT <tt>Component</tt> which contains the user interface + * elements depicting a specific participant in the telephony conference + * depicted by a <tt>VideoConferenceCallPanel</tt>. + */ + private class ConferenceParticipantContainer + extends TransparentPanel + implements ConferenceCallPeerRenderer + { + /** + * The list of <tt>ConferenceParticipantContainer</tt>s which represent + * the <tt>ConferenceMember</tt>s of the participant represented by this + * instance. Since a <tt>CallPeer</tt> may send the local peer/user + * multiple videos without providing a way to associate a + * ConferenceMember with each one of them, the list may contain + * <tt>ConferenceParticipantContainer</tt>s which do not represent a + * specific <tt>ConferenceMember</tt> instance but rather a video sent + * by a <tt>CallPeer</tt> to the local peer/user which looks like (in + * the terms of <tt>VideoConferenceCallPanel) a member of a conference + * organized by the <tt>CallPeer</tt> in question. + * <p> + * Implements a state which is private to + * <tt>VideoConferenceCallPanel</tt> and is of no concern to + * <tt>ConferenceParticipantContainer</tt>. + * </p> + */ + List<ConferenceParticipantContainer> conferenceMemberContainers; + + /** + * The indicator which determines whether this instance is to be removed + * because it has become out-of-date, obsolete, unnecessary. + * <p> + * Implements a state which is private to + * <tt>VideoConferenceCallPanel</tt> and is of no concern to + * <tt>ConferenceParticipantContainer</tt>. + * </p> + */ + boolean toBeRemoved; + + /** + * The <tt>BasicConferenceParticipantPanel</tt> which is displayed at + * the bottom of this instance, bellow the {@link #video} (i.e. + * {@link #videoContainer}) and is referred to as the tool bar. + */ + private final BasicConferenceParticipantPanel<?> toolBar; + + /** + * The visual <tt>Component</tt>, if any, displaying video which is + * depicted by this instance. + */ + private Component video; + + /** + * The <tt>VideoContainer</tt> which lays out the video depicted by this + * instance i.e. {@link #video}. + */ + private final VideoContainer videoContainer; + + /** + * The <tt>CallPeer</tt> associated with this container, if it has been + * created to represent a <tt>CallPeer</tt>. + */ + private CallPeer callPeer; + + /** + * The <tt>conferenceMember</tt> associated with this container, if it + * has been created to represent a <tt>conferenceMember</tt>. + */ + private ConferenceMember conferenceMember; + + /** + * Indicates that this container contains information for the local + * user. + */ + private boolean isLocalUser; + + /** + * Initializes a new <tt>ConferenceParticipantContainer</tt> instance + * which is to depict the local peer/user. + * + * @param call a <tt>Call</tt> which is to provide information about the + * local peer/user + * @param video the visual <tt>Component</tt>, if any, displaying the + * video streaming from the local peer/user to the remote peer(s) + */ + public ConferenceParticipantContainer(Call call, Component video) + { + this( + createDefaultPhotoPanel(call), + video, + new ConferencePeerPanel( + VideoConferenceCallPanel.this, + call, + true), + null, null, true); + } + + public ConferenceParticipantContainer( + CallPeer callPeer, + Component video) + { + this( createDefaultPhotoPanel(callPeer), + video, + new ConferencePeerPanel( + VideoConferenceCallPanel.this, + callPeer, + true), + callPeer, null, false); + } + + private ConferenceParticipantContainer( + Component noVideo, + Component video, + BasicConferenceParticipantPanel<?> toolBar, + CallPeer callPeer, + ConferenceMember conferenceMember, + boolean isLocalUser) + { + super(new BorderLayout()); + + this.callPeer = callPeer; + this.conferenceMember = conferenceMember; + this.isLocalUser = isLocalUser; + + videoContainer = new VideoContainer(noVideo, false); + add(videoContainer, BorderLayout.CENTER); + + this.toolBar = toolBar; + if (this.toolBar != null) + add(this.toolBar, BorderLayout.SOUTH); + + if (video != null) + { + setVideo(video); + } + else + setVisible(false); + } + + public ConferenceParticipantContainer( + ConferenceMember conferenceMember, + Component video) + { + this( + createDefaultPhotoPanel(conferenceMember), + video, + new ConferenceMemberPanel( + VideoConferenceCallPanel.this, + conferenceMember, + true), + null, conferenceMember, false); + } + + public void dispose() + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.dispose(); + + // Dispose of the conferenceMemberContainers if any. + /* + * XXX The field conferenceMemberContainers implements a state + * private to VideoConferenceCallPanel which the latter makes sure + * to access on the AWT event dispatching thread only. Since we are + * going out of our way here to help VideoConferenceCallPanel, + * ensure that the mentioned synchronization rule is not violated. + */ + CallManager.assertIsEventDispatchingThread(); + if (conferenceMemberContainers != null) + { + for (ConferenceParticipantContainer cmc + : conferenceMemberContainers) + { + cmc.dispose(); + } + } + } + + public CallPanel getCallPanel() + { + return getCallRenderer().getCallContainer(); + } + + public SwingCallRenderer getCallRenderer() + { + return VideoConferenceCallPanel.this; + } + + public Component getComponent() + { + return this; + } + + private ConferenceCallPeerRenderer + getConferenceCallPeerRendererDelegate() + { + return + (toolBar instanceof ConferenceCallPeerRenderer) + ? (ConferenceCallPeerRenderer) toolBar + : null; + } + + /** + * Gets the conference participant depicted by this instance. + * + * @return the conference participant depicted by this instance + */ + public Object getParticipant() + { + return (toolBar == null) ? null : toolBar.getParticipant(); + } + + public Component getVideo() + { + return video; + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. Otherwise, returns <tt>false</tt>. + */ + public boolean isLocalVideoVisible() + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + return (delegate == null) ? false : delegate.isLocalVideoVisible(); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void printDTMFTone(char dtmfChar) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.printDTMFTone(dtmfChar); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void securityNegotiationStarted( + CallPeerSecurityNegotiationStartedEvent ev) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.securityNegotiationStarted(ev); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void securityOff(CallPeerSecurityOffEvent ev) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.securityOff(ev); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void securityOn(CallPeerSecurityOnEvent ev) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.securityOn(ev); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void securityPending() + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.securityPending(); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void securityTimeout(CallPeerSecurityTimeoutEvent ev) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.securityTimeout(ev); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void setErrorReason(String reason) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.setErrorReason(reason); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void setLocalVideoVisible(boolean visible) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.setLocalVideoVisible(visible); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void setMute(boolean mute) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.setMute(mute); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void setOnHold(boolean onHold) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.setOnHold(onHold); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void setPeerImage(byte[] image) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.setPeerImage(image); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void setPeerName(String name) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.setPeerName(name); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void setPeerState( + CallPeerState oldState, + CallPeerState newState, + String stateString) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.setPeerState(oldState, newState, stateString); + } + + /** + * {@inheritDoc} + * + * Delegates to the <tt>toolBar</tt>, if the latter implements + * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a + * container only. + */ + public void setSecurityPanelVisible(boolean visible) + { + ConferenceCallPeerRenderer delegate + = getConferenceCallPeerRendererDelegate(); + + if (delegate != null) + delegate.setSecurityPanelVisible(visible); + } + + /** + * Sets the visual <tt>Component</tt> displaying the video associated + * with the participant depicted by this instance. + * + * @param video the visual <tt>Component</tt> displaying video which is + * to be associated with the participant depicted by this instance + */ + void setVideo(Component video) + { + if (this.video != video) + { + if (this.video != null) + videoContainer.remove(this.video); + + this.video = video; + + if (this.video != null) + { + setVisible(true); + videoContainer.add(this.video, VideoLayout.CENTER_REMOTE); + } + else + setVisible(false); + + // Update thumbnails container according to video status. + if (thumbnailContainer != null) + { + if (conferenceMember != null) + thumbnailContainer + .updateThumbnail(conferenceMember, (video != null)); + else if (callPeer != null) + thumbnailContainer + .updateThumbnail(callPeer, (video != null)); + else if (isLocalUser) + thumbnailContainer + .updateThumbnail((video != null)); + } + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java index 127e28b..0283e25 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java @@ -790,8 +790,6 @@ public class ChatConversationPanel */ public void correctMessage(final ChatMessage chatMessage) { - lastMessageUID = chatMessage.getMessageUID(); - if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() @@ -805,6 +803,11 @@ public class ChatConversationPanel } String correctedUID = chatMessage.getCorrectedMessageUID(); + if (correctedUID != null && correctedUID.equals(lastMessageUID)) + { + lastMessageUID = chatMessage.getMessageUID(); + } + Element root = document.getDefaultRootElement(); Element correctedMsgElement = document.getElement(root, diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatSessionChangeListener.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatSessionChangeListener.java index 0b96069..1e6127e 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatSessionChangeListener.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatSessionChangeListener.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,32 +15,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.chat;
-
-/**
- * Listens for changes in {@link ChatSession}.
- * @author George Politis
- */
-public interface ChatSessionChangeListener
-{
- /**
- * The icon representing the ChatTransport has changed.
- */
- public static final int ICON_UPDATED = 1;
-
- /**
- * Called when the current {@link ChatTransport} has
- * changed.
- *
- * @param chatSession the {@link ChatSession} it's current
- * {@link ChatTransport} has changed
- */
- public void currentChatTransportChanged(ChatSession chatSession);
-
- /**
- * When a property of the chatTransport has changed.
- * @param eventID the event id representing the property of the transport
- * that has changed.
- */
- public void currentChatTransportUpdated(int eventID);
-}
+package net.java.sip.communicator.impl.gui.main.chat; + +/** + * Listens for changes in {@link ChatSession}. + * @author George Politis + */ +public interface ChatSessionChangeListener +{ + /** + * The icon representing the ChatTransport has changed. + */ + public static final int ICON_UPDATED = 1; + + /** + * Called when the current {@link ChatTransport} has + * changed. + * + * @param chatSession the {@link ChatSession} it's current + * {@link ChatTransport} has changed + */ + public void currentChatTransportChanged(ChatSession chatSession); + + /** + * When a property of the chatTransport has changed. + * @param eventID the event id representing the property of the transport + * that has changed. + */ + public void currentChatTransportUpdated(int eventID); +} diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactListModel.java b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactListModel.java index d42ecfb..acf36bf 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactListModel.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactListModel.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,235 +15,235 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.chat.conference;
-
-import java.util.*;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.gui.main.chat.*;
-import net.java.sip.communicator.service.muc.*;
-import net.java.sip.communicator.service.protocol.event.*;
-
-/**
- * Implements an <tt>AbstractListModel</tt> which represents a member list of
- * <tt>ChatContact</tt>s. The primary purpose of the implementation is to sort
- * the <tt>ChatContact</tt>s according to their member roles and in alphabetical
- * order according to their names.
- *
- * @author Lyubomir Marinov
- */
-public class ChatContactListModel
- extends AbstractListModel
- implements ChatRoomMemberPropertyChangeListener
-{
-
- /**
- * The backing store of this <tt>AbstractListModel</tt> listing the
- * <tt>ChatContact</tt>s.
- */
- private final List<ChatContact<?>> chatContacts
- = new ArrayList<ChatContact<?>>();
-
- /**
- * Current chat session.
- */
- private ChatSession chatSession;
-
- /**
- * The implementation of the sorting rules - the <tt>ChatContact</tt>s are
- * first sorted according to their roles in decreasing order of their
- * privileges and then they are sorted according to their names in
- * alphabetical order.
- */
- private final Comparator<ChatContact<?>> sorter
- = new Comparator<ChatContact<?>>()
- {
- public int compare(ChatContact<?> chatContact0, ChatContact<?> chatContact1)
- {
- /*
- * Place ChatMembers with more privileges at the beginning of
- * the list.
- */
- if (chatContact0 instanceof ConferenceChatContact)
- {
- if (chatContact1 instanceof ConferenceChatContact)
- {
- int role0
- = ((ConferenceChatContact) chatContact0).getRole()
- .getRoleIndex();
- int role1
- = ((ConferenceChatContact) chatContact1).getRole()
- .getRoleIndex();
-
- if (role0 > role1)
- return -1;
- else if (role0 < role1)
- return 1;
- }
- else
- return -1;
- }
- else if (chatContact1 instanceof ConferenceChatContact)
- return 1;
-
- /* By default, sort the ChatContacts in alphabetical order. */
- return
- chatContact0.getName().compareToIgnoreCase(
- chatContact1.getName());
- }
- };
-
- /**
- * Creates the model.
- * @param chatSession The current model chat session.
- */
- public ChatContactListModel(ChatSession chatSession)
- {
- this.chatSession = chatSession;
-
- // when something like rename on a member change update the UI to
- // reflect it
- Object descriptor = chatSession.getDescriptor();
-
- if(descriptor instanceof ChatRoomWrapper)
- {
- ((ChatRoomWrapper) descriptor)
- .getChatRoom().addMemberPropertyChangeListener(this);
- }
- }
-
- /**
- * Listens for property change in chat room members.
- * @param ev the event
- */
- public void chatRoomPropertyChanged(ChatRoomMemberPropertyChangeEvent ev)
- {
- // Translate into
- // ListDataListener.contentsChanged.
- int chatContactCount = chatContacts.size();
-
- for (int i = 0; i < chatContactCount; i++)
- {
- ChatContact<?> chatContact = chatContacts.get(i);
-
- if(chatContact.getDescriptor().equals(ev.getSourceChatRoomMember()))
- {
- fireContentsChanged(chatContact, i, i);
- /*
- * TODO Can ev.sourceChatRoomMember
- * equal more than one chatContacts
- * element? If it cannot, it will be
- * more efficient to break here.
- */
- }
- }
- }
-
- /**
- * Adds a specific <tt>ChatContact</tt> to this <tt>AbstractListModel</tt>
- * implementation and preserves the sorting it applies.
- *
- * @param chatContact a <tt>ChatContact</tt> to be added to this
- * <tt>AbstractListModel</tt>
- */
- public void addElement(ChatContact<?> chatContact)
- {
- if (chatContact == null)
- throw new IllegalArgumentException("chatContact");
-
- int index = -1;
-
- synchronized(chatContacts)
- {
- int chatContactCount = chatContacts.size();
-
- for (int i = 0; i < chatContactCount; i++)
- {
- ChatContact<?> containedChatContact = chatContacts.get(i);
-
- // We don't want duplicates.
- if (chatContact.equals(containedChatContact))
- return;
- if ((index == -1)
- && (sorter.compare(containedChatContact, chatContact) > 0))
- {
- index = i;
- // Continue in order to prevent duplicates.
- }
- }
- if (index == -1)
- index = chatContactCount;
-
- chatContacts.add(index, chatContact);
- }
- fireIntervalAdded(this, index, index);
- }
-
- /* Implements ListModel#getElementAt(int). */
- public ChatContact<?> getElementAt(int index)
- {
- synchronized(chatContacts)
- {
- return chatContacts.get(index);
- }
- }
-
- /* Implements ListModel#getSize(). */
- public int getSize()
- {
- synchronized(chatContacts)
- {
- return chatContacts.size();
- }
- }
-
- /**
- * Removes a specific <tt>ChatContact</tt> from this
- * <tt>AbstractListModel</tt> implementation.
- *
- * @param chatContact a <tt>ChatContact</tt> to be removed from this
- * <tt>AbstractListModel</tt> if it's already contained
- */
- public void removeElement(ChatContact<?> chatContact)
- {
- synchronized(chatContacts)
- {
- int index = chatContacts.indexOf(chatContact);
-
- if ((index >= 0) && chatContacts.remove(chatContact))
- fireIntervalRemoved(this, index, index);
- }
- }
-
- /**
- * Removes all the elements from this model.
- */
- public void removeAllElements()
- {
- if (chatContacts == null || chatContacts.size() <= 0)
- return;
-
- synchronized(chatContacts)
- {
- int contactsSize = chatContacts.size();
- chatContacts.clear();
-
- fireIntervalRemoved(this, 0, contactsSize - 1);
- }
- }
-
- /**
- * Runs clean-up.
- */
- public void dispose()
- {
- Object descriptor = chatSession.getDescriptor();
-
- if(descriptor instanceof ChatRoomWrapper)
- {
- ((ChatRoomWrapper) descriptor)
- .getChatRoom().removeMemberPropertyChangeListener(this);
- }
- }
-}
+package net.java.sip.communicator.impl.gui.main.chat.conference; + +import java.util.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.main.chat.*; +import net.java.sip.communicator.service.muc.*; +import net.java.sip.communicator.service.protocol.event.*; + +/** + * Implements an <tt>AbstractListModel</tt> which represents a member list of + * <tt>ChatContact</tt>s. The primary purpose of the implementation is to sort + * the <tt>ChatContact</tt>s according to their member roles and in alphabetical + * order according to their names. + * + * @author Lyubomir Marinov + */ +public class ChatContactListModel + extends AbstractListModel + implements ChatRoomMemberPropertyChangeListener +{ + + /** + * The backing store of this <tt>AbstractListModel</tt> listing the + * <tt>ChatContact</tt>s. + */ + private final List<ChatContact<?>> chatContacts + = new ArrayList<ChatContact<?>>(); + + /** + * Current chat session. + */ + private ChatSession chatSession; + + /** + * The implementation of the sorting rules - the <tt>ChatContact</tt>s are + * first sorted according to their roles in decreasing order of their + * privileges and then they are sorted according to their names in + * alphabetical order. + */ + private final Comparator<ChatContact<?>> sorter + = new Comparator<ChatContact<?>>() + { + public int compare(ChatContact<?> chatContact0, ChatContact<?> chatContact1) + { + /* + * Place ChatMembers with more privileges at the beginning of + * the list. + */ + if (chatContact0 instanceof ConferenceChatContact) + { + if (chatContact1 instanceof ConferenceChatContact) + { + int role0 + = ((ConferenceChatContact) chatContact0).getRole() + .getRoleIndex(); + int role1 + = ((ConferenceChatContact) chatContact1).getRole() + .getRoleIndex(); + + if (role0 > role1) + return -1; + else if (role0 < role1) + return 1; + } + else + return -1; + } + else if (chatContact1 instanceof ConferenceChatContact) + return 1; + + /* By default, sort the ChatContacts in alphabetical order. */ + return + chatContact0.getName().compareToIgnoreCase( + chatContact1.getName()); + } + }; + + /** + * Creates the model. + * @param chatSession The current model chat session. + */ + public ChatContactListModel(ChatSession chatSession) + { + this.chatSession = chatSession; + + // when something like rename on a member change update the UI to + // reflect it + Object descriptor = chatSession.getDescriptor(); + + if(descriptor instanceof ChatRoomWrapper) + { + ((ChatRoomWrapper) descriptor) + .getChatRoom().addMemberPropertyChangeListener(this); + } + } + + /** + * Listens for property change in chat room members. + * @param ev the event + */ + public void chatRoomPropertyChanged(ChatRoomMemberPropertyChangeEvent ev) + { + // Translate into + // ListDataListener.contentsChanged. + int chatContactCount = chatContacts.size(); + + for (int i = 0; i < chatContactCount; i++) + { + ChatContact<?> chatContact = chatContacts.get(i); + + if(chatContact.getDescriptor().equals(ev.getSourceChatRoomMember())) + { + fireContentsChanged(chatContact, i, i); + /* + * TODO Can ev.sourceChatRoomMember + * equal more than one chatContacts + * element? If it cannot, it will be + * more efficient to break here. + */ + } + } + } + + /** + * Adds a specific <tt>ChatContact</tt> to this <tt>AbstractListModel</tt> + * implementation and preserves the sorting it applies. + * + * @param chatContact a <tt>ChatContact</tt> to be added to this + * <tt>AbstractListModel</tt> + */ + public void addElement(ChatContact<?> chatContact) + { + if (chatContact == null) + throw new IllegalArgumentException("chatContact"); + + int index = -1; + + synchronized(chatContacts) + { + int chatContactCount = chatContacts.size(); + + for (int i = 0; i < chatContactCount; i++) + { + ChatContact<?> containedChatContact = chatContacts.get(i); + + // We don't want duplicates. + if (chatContact.equals(containedChatContact)) + return; + if ((index == -1) + && (sorter.compare(containedChatContact, chatContact) > 0)) + { + index = i; + // Continue in order to prevent duplicates. + } + } + if (index == -1) + index = chatContactCount; + + chatContacts.add(index, chatContact); + } + fireIntervalAdded(this, index, index); + } + + /* Implements ListModel#getElementAt(int). */ + public ChatContact<?> getElementAt(int index) + { + synchronized(chatContacts) + { + return chatContacts.get(index); + } + } + + /* Implements ListModel#getSize(). */ + public int getSize() + { + synchronized(chatContacts) + { + return chatContacts.size(); + } + } + + /** + * Removes a specific <tt>ChatContact</tt> from this + * <tt>AbstractListModel</tt> implementation. + * + * @param chatContact a <tt>ChatContact</tt> to be removed from this + * <tt>AbstractListModel</tt> if it's already contained + */ + public void removeElement(ChatContact<?> chatContact) + { + synchronized(chatContacts) + { + int index = chatContacts.indexOf(chatContact); + + if ((index >= 0) && chatContacts.remove(chatContact)) + fireIntervalRemoved(this, index, index); + } + } + + /** + * Removes all the elements from this model. + */ + public void removeAllElements() + { + if (chatContacts == null || chatContacts.size() <= 0) + return; + + synchronized(chatContacts) + { + int contactsSize = chatContacts.size(); + chatContacts.clear(); + + fireIntervalRemoved(this, 0, contactsSize - 1); + } + } + + /** + * Runs clean-up. + */ + public void dispose() + { + Object descriptor = chatSession.getDescriptor(); + + if(descriptor instanceof ChatRoomWrapper) + { + ((ChatRoomWrapper) descriptor) + .getChatRoom().removeMemberPropertyChangeListener(this); + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java index 58a1db4..89ceb48 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,1390 +15,1390 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.chat.conference;
-
-import java.util.*;
-import java.util.concurrent.*;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.main.chat.*;
-import net.java.sip.communicator.impl.gui.main.chat.history.*;
-import net.java.sip.communicator.impl.gui.main.chatroomslist.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.muc.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.service.protocol.globalstatus.*;
-import net.java.sip.communicator.util.*;
-import net.java.sip.communicator.util.Logger;
-
-import org.jdesktop.swingworker.SwingWorker;
-import org.jitsi.util.*;
-import org.osgi.framework.*;
-
-/**
- * The <tt>ConferenceChatManager</tt> is the one that manages both chat room and
- * ad-hoc chat rooms invitations.
- *
- * @author Yana Stamcheva
- * @author Lubomir Marinov
- * @author Valentin Martinet
- * @author Hristo Terezov
- */
-public class ConferenceChatManager
- implements ChatRoomMessageListener,
- ChatRoomInvitationListener,
- ChatRoomInvitationRejectionListener,
- AdHocChatRoomMessageListener,
- AdHocChatRoomInvitationListener,
- AdHocChatRoomInvitationRejectionListener,
- LocalUserChatRoomPresenceListener,
- LocalUserAdHocChatRoomPresenceListener,
- ServiceListener, ChatRoomLocalUserRoleListener
-{
- /**
- * The object used for logging.
- */
- private static final Logger logger
- = Logger.getLogger(ConferenceChatManager.class);
-
- /**
- * Maps each history window to a <tt>ChatRoomWrapper</tt>.
- */
- private final Hashtable<ChatRoomWrapper, HistoryWindow> chatRoomHistory =
- new Hashtable<ChatRoomWrapper, HistoryWindow>();
-
- /**
- * The list of ad-hoc chat rooms.
- */
- private final AdHocChatRoomList adHocChatRoomList = new AdHocChatRoomList();
-
- /**
- * A list of all <tt>AdHocChatRoomListChangeListener</tt>-s.
- */
- private final Vector<AdHocChatRoomListChangeListener>
- adHoclistChangeListeners = new Vector<AdHocChatRoomListChangeListener>();
-
- /**
- * Creates an instance of <tt>ConferenceChatManager</tt>.
- */
- public ConferenceChatManager()
- {
- // Loads the chat rooms list in a separate thread.
- new Thread()
- {
- @Override
- public void run()
- {
- adHocChatRoomList.loadList();
- }
- }.start();
-
- GuiActivator.bundleContext.addServiceListener(this);
-
- }
-
- /**
- * Returns all chat room providers currently contained in the ad-hoc chat
- * room list.
- *
- * @return all chat room providers currently contained in the ad-hoc chat
- * room list.
- */
- public AdHocChatRoomList getAdHocChatRoomList()
- {
- return adHocChatRoomList;
- }
-
- /**
- * Handles <tt>ChatRoomInvitationReceivedEvent</tt>-s.
- */
- public void invitationReceived(ChatRoomInvitationReceivedEvent evt)
- {
- InvitationReceivedDialog dialog
- = new InvitationReceivedDialog(
- this,
- evt.getSourceOperationSet(),
- evt.getInvitation());
-
- dialog.setVisible(true);
- }
-
- public void invitationRejected(ChatRoomInvitationRejectedEvent evt) {}
-
- /**
- * Implements the <tt>ChatRoomMessageListener.messageDelivered</tt> method.
- * <br>
- * Shows the message in the conversation area and clears the write message
- * area.
- * @param evt the <tt>ChatRoomMessageDeliveredEvent</tt> that notified us
- * that the message was delivered to its destination
- */
- public void messageDelivered(ChatRoomMessageDeliveredEvent evt)
- {
- ChatRoom sourceChatRoom = (ChatRoom) evt.getSource();
-
- if (logger.isTraceEnabled())
- logger.trace(
- "MESSAGE DELIVERED to chat room: " + sourceChatRoom.getName());
-
- ChatPanel chatPanel = GuiActivator.getUIService().getChatWindowManager()
- .getMultiChat(sourceChatRoom, false);
-
- if(chatPanel != null)
- {
- String messageType;
-
- switch (evt.getEventType())
- {
- case ChatRoomMessageDeliveredEvent.CONVERSATION_MESSAGE_DELIVERED:
- messageType = Chat.OUTGOING_MESSAGE;
- break;
- case ChatRoomMessageDeliveredEvent.ACTION_MESSAGE_DELIVERED:
- messageType = Chat.ACTION_MESSAGE;
- break;
- default:
- messageType = null;
- break;
- }
-
- Message msg = evt.getMessage();
-
- chatPanel.addMessage(
- sourceChatRoom.getUserNickname(),
- null,
- evt.getTimestamp(),
- messageType,
- msg.getContent(),
- msg.getContentType(),
- msg.getMessageUID(),
- null);
- }
- }
-
- /**
- * Implements the <tt>ChatRoomMessageListener.messageReceived</tt> method.
- * <br>
- * Obtains the corresponding <tt>ChatPanel</tt> and process the message
- * there.
- * @param evt the <tt>ChatRoomMessageReceivedEvent</tt> that notified us
- * that a message has been received
- */
- public void messageReceived(ChatRoomMessageReceivedEvent evt)
- {
- ChatRoom sourceChatRoom = evt.getSourceChatRoom();
- ChatRoomMember sourceMember = evt.getSourceChatRoomMember();
-
- String messageType = null;
-
- switch (evt.getEventType())
- {
- case ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED:
- messageType = Chat.INCOMING_MESSAGE;
- break;
- case ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED:
- messageType = Chat.SYSTEM_MESSAGE;
- break;
- case ChatRoomMessageReceivedEvent.ACTION_MESSAGE_RECEIVED:
- messageType = Chat.ACTION_MESSAGE;
- break;
- }
-
- if (logger.isTraceEnabled())
- logger.trace("MESSAGE RECEIVED from contact: "
- + sourceMember.getContactAddress());
-
- Message message = evt.getMessage();
-
- ChatPanel chatPanel = null;
-
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
-
- boolean createWindow = false;
- String autoOpenConfig
- = MUCService.getChatRoomAutoOpenOption(
- sourceChatRoom.getParentProvider(),
- sourceChatRoom.getIdentifier());
- if(autoOpenConfig == null)
- autoOpenConfig = MUCService.DEFAULT_AUTO_OPEN_BEHAVIOUR;
-
- if(autoOpenConfig.equals(MUCService.OPEN_ON_ACTIVITY)
- || (autoOpenConfig.equals(MUCService.OPEN_ON_MESSAGE)
- && !evt.isHistoryMessage())
- || evt.isImportantMessage())
- createWindow = true;
-
- if(sourceChatRoom.isSystem())
- {
- ChatRoomProviderWrapper serverWrapper
- = GuiActivator.getMUCService().findServerWrapperFromProvider(
- sourceChatRoom.getParentProvider());
-
- chatPanel = chatWindowManager.getMultiChat(
- serverWrapper.getSystemRoomWrapper(), createWindow);
- }
- else
- {
- chatPanel = chatWindowManager.getMultiChat(
- sourceChatRoom, createWindow, message.getMessageUID());
- }
-
- if(chatPanel == null)
- return;
-
- String messageContent = message.getContent();
-
- if (evt.isHistoryMessage())
- {
- Date timeStamp = chatPanel.getChatConversationPanel()
- .getLastIncomingMsgTimestamp();
- Collection<Object> c =
- chatPanel.getChatSession().getHistoryBeforeDate(
- new Date(
- timeStamp.equals(new Date(0))
- ? System.currentTimeMillis() - 10000
- : timeStamp.getTime()
- ), 20);
- if (c.size() > 0)
- {
- boolean isPresent = false;
- for (Object o : c)
- {
- if (o instanceof ChatRoomMessageDeliveredEvent)
- {
- ChatRoomMessageDeliveredEvent ev =
- (ChatRoomMessageDeliveredEvent) o;
- if (evt.getTimestamp() != null
- && evt.getTimestamp().equals(ev.getTimestamp()))
- {
- isPresent = true;
- break;
- }
- }
- else if(o instanceof ChatRoomMessageReceivedEvent)
- {
- ChatRoomMessageReceivedEvent ev =
- (ChatRoomMessageReceivedEvent) o;
- if (evt.getTimestamp() != null
- && evt.getTimestamp().equals(ev.getTimestamp()))
- {
- isPresent = true;
- break;
- }
- }
-
- Message m2 = evt.getMessage();
-
- if(m2 != null
- && m2.getContent().equals(messageContent))
- {
- isPresent = true;
- break;
- }
- }
-
- if (isPresent)
- return;
- }
- }
-
- chatPanel.addMessage(
- sourceMember.getName(),
- null,
- evt.getTimestamp(),
- messageType,
- messageContent,
- message.getContentType(),
- message.getMessageUID(),
- null);
-
- if(createWindow)
- chatWindowManager.openChat(chatPanel, false);
- }
-
- /**
- * Implements the <tt>ChatRoomMessageListener.messageDeliveryFailed</tt>
- * method.
- * <br>
- * In the conversation area shows an error message, explaining the problem.
- * @param evt the <tt>ChatRoomMessageDeliveryFailedEvent</tt> that notified
- * us of a delivery failure
- */
- public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt)
- {
- ChatRoom sourceChatRoom = evt.getSourceChatRoom();
-
- String errorMsg = null;
-
- /*
- * FIXME ChatRoomMessageDeliveryFailedEvent#getSource() is not a Message
- * instance at the time of this writing and the attempt "(Message)
- * evt.getSource()" seems to be to get the message which failed to be
- * delivered. I'm not sure it's
- * ChatRoomMessageDeliveryFailedEvent#getMessage() but since it's the
- * only message I can get out of ChatRoomMessageDeliveryFailedEvent, I'm
- * using it.
- */
- Message sourceMessage = evt.getMessage();
-
- ChatRoomMember destMember = evt.getDestinationChatRoomMember();
-
- if (evt.getErrorCode()
- == MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED)
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.MSG_DELIVERY_NOT_SUPPORTED",
- new String[]{destMember.getName()});
- }
- else if (evt.getErrorCode()
- == MessageDeliveryFailedEvent.NETWORK_FAILURE)
- {
- errorMsg = GuiActivator.getResources()
- .getI18NString("service.gui.MSG_NOT_DELIVERED");
- }
- else if (evt.getErrorCode()
- == MessageDeliveryFailedEvent.PROVIDER_NOT_REGISTERED)
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.MSG_SEND_CONNECTION_PROBLEM");
- }
- else if (evt.getErrorCode()
- == MessageDeliveryFailedEvent.INTERNAL_ERROR)
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.MSG_DELIVERY_INTERNAL_ERROR");
- }
- else if (evt.getErrorCode()
- == ChatRoomMessageDeliveryFailedEvent.FORBIDDEN)
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.CHAT_ROOM_SEND_MSG_FORBIDDEN");
- }
- else if (evt.getErrorCode()
- == ChatRoomMessageDeliveryFailedEvent.UNSUPPORTED_OPERATION)
- {
- errorMsg =
- GuiActivator.getResources().getI18NString(
- "service.gui.MSG_DELIVERY_UNSUPPORTED_OPERATION");
- }
- else
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.MSG_DELIVERY_UNKNOWN_ERROR");
- }
-
- String reason = evt.getReason();
- if (reason != null)
- errorMsg += " " + GuiActivator.getResources().getI18NString(
- "service.gui.ERROR_WAS",
- new String[]{reason});
-
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
- ChatPanel chatPanel
- = chatWindowManager.getMultiChat(sourceChatRoom, true);
-
- chatPanel.addMessage(
- destMember != null ? destMember.getName()
- : sourceChatRoom.getName(),
- new Date(),
- Chat.OUTGOING_MESSAGE,
- sourceMessage.getContent(),
- sourceMessage.getContentType());
-
- chatPanel.addErrorMessage(
- destMember != null ? destMember.getName()
- : sourceChatRoom.getName(),
- errorMsg);
-
- chatWindowManager.openChat(chatPanel, false);
- }
-
- /**
- * Implements the
- * <tt>LocalUserAdHocChatRoomPresenceListener.localUserPresenceChanged</tt>
- * method
- *
- * @param evt the <tt>LocalUserAdHocChatRoomPresenceChangeEvent</tt> that
- * notified us of a presence change
- */
- public void localUserAdHocPresenceChanged(
- LocalUserAdHocChatRoomPresenceChangeEvent evt)
- {
- AdHocChatRoom sourceAdHocChatRoom = evt.getAdHocChatRoom();
- AdHocChatRoomWrapper adHocChatRoomWrapper
- = adHocChatRoomList
- .findChatRoomWrapperFromAdHocChatRoom(sourceAdHocChatRoom);
-
- String eventType = evt.getEventType();
-
- if (LocalUserAdHocChatRoomPresenceChangeEvent
- .LOCAL_USER_JOINED.equals(eventType))
- {
- if(adHocChatRoomWrapper != null)
- {
- this.fireAdHocChatRoomListChangedEvent(
- adHocChatRoomWrapper,
- AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_CHANGED);
-
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
- ChatPanel chatPanel
- = chatWindowManager
- .getMultiChat(adHocChatRoomWrapper, true);
-
- // Check if we have already opened a chat window for this chat
- // wrapper and load the real chat room corresponding to the
- // wrapper.
- if(chatPanel.isShown())
- ((AdHocConferenceChatSession) chatPanel.getChatSession())
- .loadChatRoom(sourceAdHocChatRoom);
- else
- chatWindowManager.openChat(chatPanel, true);
- }
-
- sourceAdHocChatRoom.addMessageListener(this);
- }
- else if (evt.getEventType().equals(
- LocalUserAdHocChatRoomPresenceChangeEvent.LOCAL_USER_JOIN_FAILED))
- {
- GuiActivator.getAlertUIService().showAlertPopup(
- GuiActivator.getResources().getI18NString("service.gui.ERROR"),
- GuiActivator.getResources().getI18NString(
- "service.gui.FAILED_TO_JOIN_CHAT_ROOM",
- new String[]{sourceAdHocChatRoom.getName()})
- + evt.getReason());
- }
- else if (LocalUserAdHocChatRoomPresenceChangeEvent
- .LOCAL_USER_LEFT.equals(eventType)
- || LocalUserAdHocChatRoomPresenceChangeEvent
- .LOCAL_USER_DROPPED.equals(eventType))
- {
- this.closeAdHocChatRoom(adHocChatRoomWrapper);
-
- // Need to refresh the chat room's list in order to change
- // the state of the chat room to offline.
- fireAdHocChatRoomListChangedEvent(
- adHocChatRoomWrapper,
- AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_CHANGED);
-
- sourceAdHocChatRoom.removeMessageListener(this);
- }
- }
-
- /**
- * Implements the
- * <tt>LocalUserChatRoomPresenceListener.localUserPresenceChanged</tt>
- * method.
- * @param evt the <tt>LocalUserChatRoomPresenceChangeEvent</tt> that
- * notified us
- */
- public void localUserPresenceChanged(
- final LocalUserChatRoomPresenceChangeEvent evt)
- {
- if(!SwingUtilities.isEventDispatchThread())
- {
- SwingUtilities.invokeLater(new Runnable()
- {
- @Override
- public void run()
- {
- localUserPresenceChanged(evt);
- }
- });
- return;
- }
-
- ChatRoom sourceChatRoom = evt.getChatRoom();
- ChatRoomWrapper chatRoomWrapper
- = GuiActivator.getMUCService().findChatRoomWrapperFromChatRoom(
- sourceChatRoom);
-
- String eventType = evt.getEventType();
-
- if (LocalUserChatRoomPresenceChangeEvent
- .LOCAL_USER_JOINED.equals(eventType))
- {
- if(chatRoomWrapper != null)
- {
- GuiActivator.getMUCService().fireChatRoomListChangedEvent(
- chatRoomWrapper,
- ChatRoomListChangeEvent.CHAT_ROOM_CHANGED);
-
- boolean createWindow = false;
-
- String autoOpenConfig
- = MUCService.getChatRoomAutoOpenOption(
- sourceChatRoom.getParentProvider(),
- sourceChatRoom.getIdentifier());
-
- if(autoOpenConfig != null
- && autoOpenConfig.equals(MUCService.OPEN_ON_ACTIVITY))
- createWindow = true;
-
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
- ChatPanel chatPanel
- = chatWindowManager.getMultiChat(
- chatRoomWrapper, createWindow);
-
- if(chatPanel != null)
- {
- chatPanel.setChatIcon(
- chatPanel.getChatSession().getChatStatusIcon());
-
- // Check if we have already opened a chat window for this chat
- // wrapper and load the real chat room corresponding to the
- // wrapper.
- if(chatPanel.isShown())
- {
- ((ConferenceChatSession) chatPanel.getChatSession())
- .loadChatRoom(sourceChatRoom);
- }
- else
- {
- chatWindowManager.openChat(chatPanel, true);
- }
- }
- }
-
- if (sourceChatRoom.isSystem())
- {
- ChatRoomProviderWrapper serverWrapper
- = GuiActivator.getMUCService()
- .findServerWrapperFromProvider(
- sourceChatRoom.getParentProvider());
-
- serverWrapper.setSystemRoom(sourceChatRoom);
- }
-
- sourceChatRoom.addMessageListener(this);
- sourceChatRoom.addLocalUserRoleListener(this);
- }
- else if (LocalUserChatRoomPresenceChangeEvent
- .LOCAL_USER_JOIN_FAILED.equals(eventType))
- {
- GuiActivator.getAlertUIService().showAlertPopup(
- GuiActivator.getResources().getI18NString("service.gui.ERROR"),
- GuiActivator.getResources().getI18NString(
- "service.gui.FAILED_TO_JOIN_CHAT_ROOM",
- new String[]{sourceChatRoom.getName()})
- + evt.getReason());
- }
- else if (LocalUserChatRoomPresenceChangeEvent
- .LOCAL_USER_LEFT.equals(eventType)
- || LocalUserChatRoomPresenceChangeEvent
- .LOCAL_USER_KICKED.equals(eventType)
- || LocalUserChatRoomPresenceChangeEvent
- .LOCAL_USER_DROPPED.equals(eventType))
- {
- if(chatRoomWrapper != null)
- {
- if(StringUtils.isNullOrEmpty(evt.getReason()))
- {
- GuiActivator.getUIService()
- .closeChatRoomWindow(chatRoomWrapper);
- }
- else
- {
- // send some system messages informing for the
- // reason of leaving
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
-
- ChatPanel chatPanel = chatWindowManager.getMultiChat(
- sourceChatRoom, false);
-
- if(chatPanel != null)
- {
- chatPanel.addMessage(
- sourceChatRoom.getName(),
- null,
- new Date(),
- Chat.SYSTEM_MESSAGE,
- evt.getReason(),
- OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE,
- null,
- null);
-
- // print and the alternate address
- if(!StringUtils.isNullOrEmpty(
- evt.getAlternateAddress()))
- {
- chatPanel.addMessage(
- sourceChatRoom.getName(),
- null,
- new Date(),
- Chat.SYSTEM_MESSAGE,
- GuiActivator.getResources().getI18NString(
- "service.gui.CHAT_ROOM_ALTERNATE_ADDRESS",
- new String[]{evt.getAlternateAddress()}),
- OperationSetBasicInstantMessaging
- .DEFAULT_MIME_TYPE,
- null,
- null);
- }
- }
- }
-
- // Need to refresh the chat room's list in order to change
- // the state of the chat room to offline.
-
- GuiActivator.getMUCService().fireChatRoomListChangedEvent(
- chatRoomWrapper,
- ChatRoomListChangeEvent.CHAT_ROOM_CHANGED);
- }
-
- sourceChatRoom.removeMessageListener(this);
- sourceChatRoom.removelocalUserRoleListener(this);
- }
- }
-
-
- /**
- * 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
- * @param multiUserChatOpSet the operation set for chat conferencing
- * @throws OperationFailedException if the accept fails
- */
- public void acceptInvitation(
- AdHocChatRoomInvitation invitation,
- OperationSetAdHocMultiUserChat multiUserChatOpSet)
- throws OperationFailedException
- {
- AdHocChatRoom chatRoom = invitation.getTargetAdHocChatRoom();
-
- chatRoom.join();
- }
-
- /**
- * Rejects the given invitation with the specified reason.
- *
- * @param multiUserChatAdHocOpSet 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(
- OperationSetAdHocMultiUserChat multiUserChatAdHocOpSet,
- AdHocChatRoomInvitation invitation,
- String reason)
- {
- multiUserChatAdHocOpSet.rejectInvitation(invitation, reason);
- }
-
- /**
- * Creates an ad-hoc chat room, by specifying the ad-hoc chat room name, the
- * parent protocol provider and eventually, the contacts invited to
- * participate in this ad-hoc chat room.
- *
- * @param protocolProvider the parent protocol provider.
- * @param contacts the contacts invited when creating the chat room.
- * @param reason the reason for this invitation
- * @return the <tt>AdHocChatRoomWrapper</tt> corresponding to the created
- * ad hoc chat room
- */
- public AdHocChatRoomWrapper createAdHocChatRoom(
- ProtocolProviderService protocolProvider,
- Collection<String> contacts,
- String reason)
- {
- AdHocChatRoomWrapper chatRoomWrapper = null;
-
- OperationSetAdHocMultiUserChat groupChatOpSet
- = protocolProvider
- .getOperationSet(OperationSetAdHocMultiUserChat.class);
-
- // If there's no group chat operation set we have nothing to do here.
- if (groupChatOpSet == null)
- return null;
-
- AdHocChatRoom chatRoom = null;
-
- try
- {
- java.util.List<String> members = new LinkedList<String>();
-
- for(String address : contacts)
- members.add(address);
-
- chatRoom = groupChatOpSet.createAdHocChatRoom(
- "chatroom-" + new Date().getTime(), members, reason);
- }
- catch (OperationFailedException ex)
- {
- new ErrorDialog(
- GuiActivator.getUIService().getMainFrame(),
- GuiActivator.getResources().getI18NString("service.gui.ERROR"),
- GuiActivator.getResources().getI18NString(
- "service.gui.CREATE_CHAT_ROOM_ERROR",
- new String[]{protocolProvider.getProtocolDisplayName()}),
- ex)
- .showDialog();
- }
- catch (OperationNotSupportedException ex)
- {
- new ErrorDialog(
- GuiActivator.getUIService().getMainFrame(),
- GuiActivator.getResources().getI18NString("service.gui.ERROR"),
- GuiActivator.getResources().getI18NString(
- "service.gui.CREATE_CHAT_ROOM_ERROR",
- new String[]{protocolProvider.getProtocolDisplayName()}),
- ex)
- .showDialog();
- }
-
- if(chatRoom != null)
- {
- AdHocChatRoomProviderWrapper parentProvider
- = adHocChatRoomList.findServerWrapperFromProvider(
- protocolProvider);
-
- chatRoomWrapper = new AdHocChatRoomWrapper(
- parentProvider, chatRoom);
- parentProvider.addAdHocChatRoom(chatRoomWrapper);
- adHocChatRoomList.addAdHocChatRoom(chatRoomWrapper);
-
- fireAdHocChatRoomListChangedEvent(
- chatRoomWrapper,
- AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_ADDED);
- }
-
- return chatRoomWrapper;
- }
-
- /**
- * Joins the given ad-hoc chat room
- *
- * @param chatRoomWrapper
- */
- public void joinChatRoom(AdHocChatRoomWrapper chatRoomWrapper)
- {
- AdHocChatRoom chatRoom = chatRoomWrapper.getAdHocChatRoom();
-
- if(chatRoom == null)
- {
- new ErrorDialog(
- GuiActivator.getUIService().getMainFrame(),
- GuiActivator.getResources().getI18NString("service.gui.WARNING"),
- GuiActivator.getResources().getI18NString(
- "service.gui.CHAT_ROOM_NOT_CONNECTED",
- new String[]{chatRoomWrapper.getAdHocChatRoomName()}))
- .showDialog();
-
- return;
- }
-
- new JoinAdHocChatRoomTask(chatRoomWrapper).execute();
- }
-
- /**
- * Removes the given chat room from the UI.
- *
- * @param chatRoomWrapper the chat room to remove.
- */
- public void removeChatRoom(ChatRoomWrapper chatRoomWrapper)
- {
- ChatRoom chatRoom = chatRoomWrapper.getChatRoom();
-
- if (chatRoom != null)
- leaveChatRoom(chatRoomWrapper);
-
- GuiActivator.getUIService().closeChatRoomWindow(chatRoomWrapper);
-
- GuiActivator.getMUCService().removeChatRoom(chatRoomWrapper);
-
- }
-
- /**
- * Joins the given chat room and manages all the exceptions that could
- * occur during the join process.
- *
- * @param chatRoom the chat room to join
- */
- public void joinChatRoom(AdHocChatRoom chatRoom)
- {
- AdHocChatRoomWrapper chatRoomWrapper
- = adHocChatRoomList.findChatRoomWrapperFromAdHocChatRoom(chatRoom);
-
- if(chatRoomWrapper == null)
- {
- AdHocChatRoomProviderWrapper parentProvider
- = adHocChatRoomList.findServerWrapperFromProvider(
- chatRoom.getParentProvider());
-
- chatRoomWrapper =
- new AdHocChatRoomWrapper(parentProvider, chatRoom);
-
- adHocChatRoomList.addAdHocChatRoom(chatRoomWrapper);
-
- fireAdHocChatRoomListChangedEvent(
- chatRoomWrapper,
- AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_ADDED);
- }
-
- this.joinChatRoom(chatRoomWrapper);
-
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
-
- chatWindowManager
- .openChat(
- chatWindowManager.getMultiChat(chatRoomWrapper, true),
- true);
- }
-
- /**
- * Leaves the given <tt>ChatRoom</tt>.
- *
- * @param chatRoomWrapper the <tt>ChatRoom</tt> to leave.
- */
- public void leaveChatRoom(ChatRoomWrapper chatRoomWrapper)
- {
- ChatRoomWrapper leavedRoomWrapped
- = GuiActivator.getMUCService().leaveChatRoom(chatRoomWrapper);
- if(leavedRoomWrapped != null)
- GuiActivator.getUIService().closeChatRoomWindow(leavedRoomWrapped);
- }
-
- /**
- * Leaves the given <tt>ChatRoom</tt>.
- *
- * @param chatRoomWrapper the <tt>ChatRoom</tt> to leave.
- */
- public void leaveChatRoom(AdHocChatRoomWrapper chatRoomWrapper)
- {
- AdHocChatRoom chatRoom = chatRoomWrapper.getAdHocChatRoom();
-
- if (chatRoom != null)
- {
- chatRoom.leave();
- }
- else
- {
- new ErrorDialog(
- GuiActivator.getUIService().getMainFrame(),
- GuiActivator.getResources().getI18NString("service.gui.WARNING"),
- GuiActivator.getResources().getI18NString(
- "service.gui.CHAT_ROOM_LEAVE_NOT_CONNECTED"))
- .showDialog();
- }
- }
-
- /**
- * Checks if there's an open history window for the given chat room.
- *
- * @param chatRoomWrapper the chat room wrapper to check for
- * @return TRUE if there's an opened history window for the given chat room,
- * FALSE otherwise.
- */
- public boolean containsHistoryWindowForChatRoom(
- ChatRoomWrapper chatRoomWrapper)
- {
- return chatRoomHistory.containsKey(chatRoomWrapper);
- }
-
- /**
- * Returns the history window for the given chat room.
- *
- * @param chatRoomWrapper the chat room wrapper to search for
- * @return the history window for the given chat room
- */
- public HistoryWindow getHistoryWindowForChatRoom(
- ChatRoomWrapper chatRoomWrapper)
- {
- return chatRoomHistory.get(chatRoomWrapper);
- }
-
- /**
- * Adds a history window for a given chat room in the table of opened
- * history windows.
- *
- * @param chatRoomWrapper the chat room wrapper to add
- * @param historyWindow the history window to add
- */
- public void addHistoryWindowForChatRoom(ChatRoomWrapper chatRoomWrapper,
- HistoryWindow historyWindow)
- {
- chatRoomHistory.put(chatRoomWrapper, historyWindow);
- }
-
- /**
- * Removes the history window for the given chat room.
- *
- * @param chatRoomWrapper the chat room wrapper to remove the history window
- */
- public void removeHistoryWindowForChatRoom(ChatRoomWrapper chatRoomWrapper)
- {
- chatRoomHistory.remove(chatRoomWrapper);
- }
-
- /**
- * Adds the given <tt>AdHocChatRoomListChangeListener</tt> that will listen
- * for all changes of the chat room list data model.
- *
- * @param l the listener to add.
- */
- public void addAdHocChatRoomListChangeListener(
- AdHocChatRoomListChangeListener l)
- {
- synchronized (adHoclistChangeListeners)
- {
- adHoclistChangeListeners.add(l);
- }
- }
-
- /**
- * Removes the given <tt>AdHocChatRoomListChangeListener</tt>.
- *
- * @param l the listener to remove.
- */
- public void removeAdHocChatRoomListChangeListener(
- AdHocChatRoomListChangeListener l)
- {
- synchronized (adHoclistChangeListeners)
- {
- adHoclistChangeListeners.remove(l);
- }
- }
-
- /**
- * Notifies all interested listeners that a change in the chat room list
- * model has occurred.
- * @param adHocChatRoomWrapper the chat room wrapper that identifies the
- * chat room
- * @param eventID the identifier of the event
- */
- private void fireAdHocChatRoomListChangedEvent(
- AdHocChatRoomWrapper adHocChatRoomWrapper,
- int eventID)
- {
- AdHocChatRoomListChangeEvent evt
- = new AdHocChatRoomListChangeEvent(adHocChatRoomWrapper, eventID);
-
- for (AdHocChatRoomListChangeListener l : adHoclistChangeListeners)
- {
- l.contentChanged(evt);
- }
- }
-
-
- /**
- * Closes the chat corresponding to the given ad-hoc chat room wrapper, if
- * such exists.
- *
- * @param chatRoomWrapper the ad-hoc chat room wrapper for which we search a
- * chat to close.
- */
- private void closeAdHocChatRoom(AdHocChatRoomWrapper chatRoomWrapper)
- {
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
- ChatPanel chatPanel
- = chatWindowManager.getMultiChat(chatRoomWrapper, false);
-
- if (chatPanel != null)
- chatWindowManager.closeChat(chatPanel);
- }
-
- /**
- * Handles <tt>ServiceEvent</tt>s triggered by adding or removing a
- * ProtocolProviderService. Updates the list of available chat rooms and
- * chat room servers.
- *
- * @param event The event to handle.
- */
- public void serviceChanged(ServiceEvent event)
- {
- // if the event is caused by a bundle being stopped, we don't want to
- // know
- if (event.getServiceReference().getBundle().getState()
- == Bundle.STOPPING)
- return;
-
- Object service = GuiActivator.bundleContext.getService(event
- .getServiceReference());
-
- // we don't care if the source service is not a protocol provider
- if (!(service instanceof ProtocolProviderService))
- return;
-
- ProtocolProviderService protocolProvider
- = (ProtocolProviderService) service;
-
-
- Object multiUserChatAdHocOpSet
- = protocolProvider
- .getOperationSet(OperationSetAdHocMultiUserChat.class);
-
- if (multiUserChatAdHocOpSet != null)
- {
- if (event.getType() == ServiceEvent.REGISTERED)
- {
- adHocChatRoomList.addChatProvider(protocolProvider);
- }
- else if (event.getType() == ServiceEvent.UNREGISTERING)
- {
- adHocChatRoomList.removeChatProvider(protocolProvider);
- }
- }
- }
-
- /**
- * Joins an ad-hoc chat room in an asynchronous way.
- */
- private static class JoinAdHocChatRoomTask
- extends SwingWorker<String, Object>
- {
- private static final String SUCCESS = "Success";
-
- private static final String AUTHENTICATION_FAILED
- = "AuthenticationFailed";
-
- private static final String REGISTRATION_REQUIRED
- = "RegistrationRequired";
-
- private static final String PROVIDER_NOT_REGISTERED
- = "ProviderNotRegistered";
-
- private static final String SUBSCRIPTION_ALREADY_EXISTS
- = "SubscriptionAlreadyExists";
-
- private static final String UNKNOWN_ERROR
- = "UnknownError";
-
- private final AdHocChatRoomWrapper adHocChatRoomWrapper;
-
- JoinAdHocChatRoomTask(AdHocChatRoomWrapper chatRoomWrapper)
- {
- this.adHocChatRoomWrapper = chatRoomWrapper;
- }
-
- /**
- * @override {@link SwingWorker}{@link #doInBackground()} to perform
- * all asynchronous tasks.
- * @return SUCCESS if success, otherwise the error code
- */
- @Override
- public String doInBackground()
- {
- AdHocChatRoom chatRoom = adHocChatRoomWrapper.getAdHocChatRoom();
-
- try
- {
- chatRoom.join();
-
- return SUCCESS;
- }
- catch (OperationFailedException e)
- {
- if (logger.isTraceEnabled())
- logger.trace("Failed to join ad-hoc chat room: "
- + chatRoom.getName(), e);
-
- switch (e.getErrorCode())
- {
- case OperationFailedException.AUTHENTICATION_FAILED:
- return AUTHENTICATION_FAILED;
- case OperationFailedException.REGISTRATION_REQUIRED:
- return REGISTRATION_REQUIRED;
- case OperationFailedException.PROVIDER_NOT_REGISTERED:
- return PROVIDER_NOT_REGISTERED;
- case OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS:
- return SUBSCRIPTION_ALREADY_EXISTS;
- default:
- return UNKNOWN_ERROR;
- }
- }
- }
-
- /**
- * @override {@link SwingWorker}{@link #done()} to perform UI changes
- * after the ad-hoc chat room join task has finished.
- */
- @Override
- protected void done()
- {
- String returnCode = null;
- try
- {
- returnCode = get();
- }
- catch (InterruptedException ignore)
- {}
- catch (ExecutionException ignore)
- {}
-
- ConfigurationUtils.updateChatRoomStatus(
- adHocChatRoomWrapper.getParentProvider().getProtocolProvider(),
- adHocChatRoomWrapper.getAdHocChatRoomID(),
- GlobalStatusEnum.ONLINE_STATUS);
-
- String errorMessage = null;
- if(PROVIDER_NOT_REGISTERED.equals(returnCode))
- {
- errorMessage
- = GuiActivator.getResources()
- .getI18NString("service.gui.CHAT_ROOM_NOT_CONNECTED",
- new String[]{
- adHocChatRoomWrapper.getAdHocChatRoomName()});
- }
- else if(SUBSCRIPTION_ALREADY_EXISTS.equals(returnCode))
- {
- errorMessage
- = GuiActivator.getResources()
- .getI18NString("service.gui.CHAT_ROOM_ALREADY_JOINED",
- new String[]{
- adHocChatRoomWrapper.getAdHocChatRoomName()});
- }
- else
- {
- errorMessage
- = GuiActivator.getResources()
- .getI18NString("service.gui.FAILED_TO_JOIN_CHAT_ROOM",
- new String[]{
- adHocChatRoomWrapper.getAdHocChatRoomName()});
- }
-
- if (!SUCCESS.equals(returnCode)
- && !AUTHENTICATION_FAILED.equals(returnCode))
- {
- GuiActivator.getAlertUIService().showAlertPopup(
- GuiActivator.getResources().getI18NString(
- "service.gui.ERROR"), errorMessage);
- }
- }
- }
-
-
- /**
- * Indicates that an invitation has been received and opens the invitation
- * dialog to notify the user.
- * @param evt the <tt>AdHocChatRoomInvitationReceivedEvent</tt> that
- * notified us
- */
- public void invitationReceived(AdHocChatRoomInvitationReceivedEvent evt)
- {
- if (logger.isInfoEnabled())
- logger.info("Invitation received: "+evt.toString());
- OperationSetAdHocMultiUserChat multiUserChatOpSet
- = evt.getSourceOperationSet();
-
- InvitationReceivedDialog dialog = new InvitationReceivedDialog(
- this, multiUserChatOpSet, evt.getInvitation());
-
- dialog.setVisible(true);
- }
-
- /**
- * Implements the <tt>AdHocChatRoomMessageListener.messageDelivered</tt>
- * method.
- * <br>
- * Shows the message in the conversation area and clears the write message
- * area.
- * @param evt the <tt>AdHocChatRoomMessageDeliveredEvent</tt> that notified
- * us
- */
- public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt)
- {
- AdHocChatRoom sourceChatRoom = (AdHocChatRoom) evt.getSource();
-
- if (logger.isInfoEnabled())
- logger.info("MESSAGE DELIVERED to ad-hoc chat room: "
- + sourceChatRoom.getName());
-
- ChatPanel chatPanel
- = GuiActivator
- .getUIService()
- .getChatWindowManager()
- .getMultiChat(sourceChatRoom, false);
-
- if(chatPanel != null)
- {
- String messageType;
- switch (evt.getEventType())
- {
- case AdHocChatRoomMessageDeliveredEvent
- .CONVERSATION_MESSAGE_DELIVERED:
- messageType = Chat.OUTGOING_MESSAGE;
- break;
- case AdHocChatRoomMessageDeliveredEvent.ACTION_MESSAGE_DELIVERED:
- messageType = Chat.ACTION_MESSAGE;
- break;
- default:
- messageType = null;
- }
-
- Message msg = evt.getMessage();
-
- chatPanel
- .addMessage(
- sourceChatRoom
- .getParentProvider().getAccountID().getUserID(),
- null,
- evt.getTimestamp(),
- messageType,
- msg.getContent(),
- msg.getContentType(),
- msg.getMessageUID(),
- null);
- }
- else
- {
- logger.error("chat panel is null, message NOT DELIVERED !");
- }
- }
-
- /**
- * Implements <tt>AdHocChatRoomMessageListener.messageDeliveryFailed</tt>
- * method.
- * <br>
- * In the conversation area shows an error message, explaining the problem.
- * @param evt the <tt>AdHocChatRoomMessageDeliveryFailedEvent</tt> that
- * notified us
- */
- public void messageDeliveryFailed(
- AdHocChatRoomMessageDeliveryFailedEvent evt)
- {
- AdHocChatRoom sourceChatRoom = evt.getSourceChatRoom();
- Message sourceMessage = evt.getMessage();
- Contact destParticipant = evt.getDestinationParticipant();
-
- String errorMsg = null;
- if (evt.getErrorCode()
- == MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED)
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.MSG_DELIVERY_NOT_SUPPORTED",
- new String[]{destParticipant.getDisplayName()});
- }
- else if (evt.getErrorCode()
- == MessageDeliveryFailedEvent.NETWORK_FAILURE)
- {
- errorMsg = GuiActivator.getResources()
- .getI18NString("service.gui.MSG_NOT_DELIVERED");
- }
- else if (evt.getErrorCode()
- == MessageDeliveryFailedEvent.PROVIDER_NOT_REGISTERED)
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.MSG_SEND_CONNECTION_PROBLEM");
- }
- else if (evt.getErrorCode()
- == MessageDeliveryFailedEvent.INTERNAL_ERROR)
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.MSG_DELIVERY_INTERNAL_ERROR");
- }
- else if (evt.getErrorCode()
- == MessageDeliveryFailedEvent.UNSUPPORTED_OPERATION)
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.MSG_DELIVERY_UNSUPPORTED_OPERATION");
- }
- else
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.MSG_DELIVERY_UNKNOWN_ERROR");
- }
-
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
- ChatPanel chatPanel
- = chatWindowManager.getMultiChat(sourceChatRoom, true);
-
- chatPanel.addMessage(
- destParticipant.getDisplayName(),
- new Date(),
- Chat.OUTGOING_MESSAGE,
- sourceMessage.getContent(),
- sourceMessage.getContentType());
-
- chatPanel.addErrorMessage(
- destParticipant.getDisplayName(),
- errorMsg);
-
- chatWindowManager.openChat(chatPanel, false);
- }
-
- /**
- * Implements the <tt>AdHocChatRoomMessageListener.messageReceived</tt>
- * method.
- * <br>
- * Obtains the corresponding <tt>ChatPanel</tt> and process the message
- * there.
- * @param evt the <tt>AdHocChatRoomMessageReceivedEvent</tt> that notified
- * us
- */
- public void messageReceived(AdHocChatRoomMessageReceivedEvent evt)
- {
- AdHocChatRoom sourceChatRoom = evt.getSourceChatRoom();
- Contact sourceParticipant = evt.getSourceChatRoomParticipant();
-
- String messageType = null;
-
- switch (evt.getEventType())
- {
- case AdHocChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED:
- messageType = Chat.INCOMING_MESSAGE;
- break;
- case AdHocChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED:
- messageType = Chat.SYSTEM_MESSAGE;
- break;
- case AdHocChatRoomMessageReceivedEvent.ACTION_MESSAGE_RECEIVED:
- messageType = Chat.ACTION_MESSAGE;
- break;
- }
-
- if (logger.isInfoEnabled())
- logger.info("MESSAGE RECEIVED from contact: "
- + sourceParticipant.getAddress());
-
- Message message = evt.getMessage();
-
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
- ChatPanel chatPanel
- = chatWindowManager
- .getMultiChat(sourceChatRoom, true, message.getMessageUID());
-
- String messageContent = message.getContent();
-
- chatPanel.addMessage(
- sourceParticipant.getDisplayName(),
- null,
- evt.getTimestamp(),
- messageType,
- messageContent,
- message.getContentType(),
- message.getMessageUID(),
- null);
-
- chatWindowManager.openChat(chatPanel, false);
- }
-
- public void invitationRejected(AdHocChatRoomInvitationRejectedEvent evt) {}
-
- @Override
- public void localUserRoleChanged(ChatRoomLocalUserRoleChangeEvent evt)
- {
- if(evt.isInitial())
- return;
- ChatRoom sourceChatRoom = evt.getSourceChatRoom();
- ChatRoomWrapper chatRoomWrapper
- = GuiActivator.getMUCService().findChatRoomWrapperFromChatRoom(
- sourceChatRoom);
- ChatWindowManager chatWindowManager
- = GuiActivator.getUIService().getChatWindowManager();
- ChatPanel chatPanel
- = chatWindowManager.getMultiChat(chatRoomWrapper, true);
- chatWindowManager.openChat(chatPanel, true);
- }
-
-}
+package net.java.sip.communicator.impl.gui.main.chat.conference; + +import java.util.*; +import java.util.concurrent.*; + +import javax.swing.*; +import javax.swing.SwingWorker; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.chat.*; +import net.java.sip.communicator.impl.gui.main.chat.history.*; +import net.java.sip.communicator.impl.gui.main.chatroomslist.*; +import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.muc.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.globalstatus.*; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.Logger; + +import org.jitsi.util.*; +import org.osgi.framework.*; + +/** + * The <tt>ConferenceChatManager</tt> is the one that manages both chat room and + * ad-hoc chat rooms invitations. + * + * @author Yana Stamcheva + * @author Lubomir Marinov + * @author Valentin Martinet + * @author Hristo Terezov + */ +public class ConferenceChatManager + implements ChatRoomMessageListener, + ChatRoomInvitationListener, + ChatRoomInvitationRejectionListener, + AdHocChatRoomMessageListener, + AdHocChatRoomInvitationListener, + AdHocChatRoomInvitationRejectionListener, + LocalUserChatRoomPresenceListener, + LocalUserAdHocChatRoomPresenceListener, + ServiceListener, ChatRoomLocalUserRoleListener +{ + /** + * The object used for logging. + */ + private static final Logger logger + = Logger.getLogger(ConferenceChatManager.class); + + /** + * Maps each history window to a <tt>ChatRoomWrapper</tt>. + */ + private final Hashtable<ChatRoomWrapper, HistoryWindow> chatRoomHistory = + new Hashtable<ChatRoomWrapper, HistoryWindow>(); + + /** + * The list of ad-hoc chat rooms. + */ + private final AdHocChatRoomList adHocChatRoomList = new AdHocChatRoomList(); + + /** + * A list of all <tt>AdHocChatRoomListChangeListener</tt>-s. + */ + private final Vector<AdHocChatRoomListChangeListener> + adHoclistChangeListeners = new Vector<AdHocChatRoomListChangeListener>(); + + /** + * Creates an instance of <tt>ConferenceChatManager</tt>. + */ + public ConferenceChatManager() + { + // Loads the chat rooms list in a separate thread. + new Thread() + { + @Override + public void run() + { + adHocChatRoomList.loadList(); + } + }.start(); + + GuiActivator.bundleContext.addServiceListener(this); + + } + + /** + * Returns all chat room providers currently contained in the ad-hoc chat + * room list. + * + * @return all chat room providers currently contained in the ad-hoc chat + * room list. + */ + public AdHocChatRoomList getAdHocChatRoomList() + { + return adHocChatRoomList; + } + + /** + * Handles <tt>ChatRoomInvitationReceivedEvent</tt>-s. + */ + public void invitationReceived(ChatRoomInvitationReceivedEvent evt) + { + InvitationReceivedDialog dialog + = new InvitationReceivedDialog( + this, + evt.getSourceOperationSet(), + evt.getInvitation()); + + dialog.setVisible(true); + } + + public void invitationRejected(ChatRoomInvitationRejectedEvent evt) {} + + /** + * Implements the <tt>ChatRoomMessageListener.messageDelivered</tt> method. + * <br> + * Shows the message in the conversation area and clears the write message + * area. + * @param evt the <tt>ChatRoomMessageDeliveredEvent</tt> that notified us + * that the message was delivered to its destination + */ + public void messageDelivered(ChatRoomMessageDeliveredEvent evt) + { + ChatRoom sourceChatRoom = (ChatRoom) evt.getSource(); + + if (logger.isTraceEnabled()) + logger.trace( + "MESSAGE DELIVERED to chat room: " + sourceChatRoom.getName()); + + ChatPanel chatPanel = GuiActivator.getUIService().getChatWindowManager() + .getMultiChat(sourceChatRoom, false); + + if(chatPanel != null) + { + String messageType; + + switch (evt.getEventType()) + { + case ChatRoomMessageDeliveredEvent.CONVERSATION_MESSAGE_DELIVERED: + messageType = Chat.OUTGOING_MESSAGE; + break; + case ChatRoomMessageDeliveredEvent.ACTION_MESSAGE_DELIVERED: + messageType = Chat.ACTION_MESSAGE; + break; + default: + messageType = null; + break; + } + + Message msg = evt.getMessage(); + + chatPanel.addMessage( + sourceChatRoom.getUserNickname(), + null, + evt.getTimestamp(), + messageType, + msg.getContent(), + msg.getContentType(), + msg.getMessageUID(), + null); + } + } + + /** + * Implements the <tt>ChatRoomMessageListener.messageReceived</tt> method. + * <br> + * Obtains the corresponding <tt>ChatPanel</tt> and process the message + * there. + * @param evt the <tt>ChatRoomMessageReceivedEvent</tt> that notified us + * that a message has been received + */ + public void messageReceived(ChatRoomMessageReceivedEvent evt) + { + ChatRoom sourceChatRoom = evt.getSourceChatRoom(); + ChatRoomMember sourceMember = evt.getSourceChatRoomMember(); + + String messageType = null; + + switch (evt.getEventType()) + { + case ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED: + messageType = Chat.INCOMING_MESSAGE; + break; + case ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED: + messageType = Chat.SYSTEM_MESSAGE; + break; + case ChatRoomMessageReceivedEvent.ACTION_MESSAGE_RECEIVED: + messageType = Chat.ACTION_MESSAGE; + break; + } + + if (logger.isTraceEnabled()) + logger.trace("MESSAGE RECEIVED from contact: " + + sourceMember.getContactAddress()); + + Message message = evt.getMessage(); + + ChatPanel chatPanel = null; + + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + + boolean createWindow = false; + String autoOpenConfig + = MUCService.getChatRoomAutoOpenOption( + sourceChatRoom.getParentProvider(), + sourceChatRoom.getIdentifier()); + if(autoOpenConfig == null) + autoOpenConfig = MUCService.DEFAULT_AUTO_OPEN_BEHAVIOUR; + + if(autoOpenConfig.equals(MUCService.OPEN_ON_ACTIVITY) + || (autoOpenConfig.equals(MUCService.OPEN_ON_MESSAGE) + && !evt.isHistoryMessage()) + || evt.isImportantMessage()) + createWindow = true; + + if(sourceChatRoom.isSystem()) + { + ChatRoomProviderWrapper serverWrapper + = GuiActivator.getMUCService().findServerWrapperFromProvider( + sourceChatRoom.getParentProvider()); + + chatPanel = chatWindowManager.getMultiChat( + serverWrapper.getSystemRoomWrapper(), createWindow); + } + else + { + chatPanel = chatWindowManager.getMultiChat( + sourceChatRoom, createWindow, message.getMessageUID()); + } + + if(chatPanel == null) + return; + + String messageContent = message.getContent(); + + if (evt.isHistoryMessage()) + { + Date timeStamp = chatPanel.getChatConversationPanel() + .getLastIncomingMsgTimestamp(); + Collection<Object> c = + chatPanel.getChatSession().getHistoryBeforeDate( + new Date( + timeStamp.equals(new Date(0)) + ? System.currentTimeMillis() - 10000 + : timeStamp.getTime() + ), 20); + if (c.size() > 0) + { + boolean isPresent = false; + for (Object o : c) + { + if (o instanceof ChatRoomMessageDeliveredEvent) + { + ChatRoomMessageDeliveredEvent ev = + (ChatRoomMessageDeliveredEvent) o; + if (evt.getTimestamp() != null + && evt.getTimestamp().equals(ev.getTimestamp())) + { + isPresent = true; + break; + } + } + else if(o instanceof ChatRoomMessageReceivedEvent) + { + ChatRoomMessageReceivedEvent ev = + (ChatRoomMessageReceivedEvent) o; + if (evt.getTimestamp() != null + && evt.getTimestamp().equals(ev.getTimestamp())) + { + isPresent = true; + break; + } + } + + Message m2 = evt.getMessage(); + + if(m2 != null + && m2.getContent().equals(messageContent)) + { + isPresent = true; + break; + } + } + + if (isPresent) + return; + } + } + + chatPanel.addMessage( + sourceMember.getName(), + null, + evt.getTimestamp(), + messageType, + messageContent, + message.getContentType(), + message.getMessageUID(), + null); + + if(createWindow) + chatWindowManager.openChat(chatPanel, false); + } + + /** + * Implements the <tt>ChatRoomMessageListener.messageDeliveryFailed</tt> + * method. + * <br> + * In the conversation area shows an error message, explaining the problem. + * @param evt the <tt>ChatRoomMessageDeliveryFailedEvent</tt> that notified + * us of a delivery failure + */ + public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt) + { + ChatRoom sourceChatRoom = evt.getSourceChatRoom(); + + String errorMsg = null; + + /* + * FIXME ChatRoomMessageDeliveryFailedEvent#getSource() is not a Message + * instance at the time of this writing and the attempt "(Message) + * evt.getSource()" seems to be to get the message which failed to be + * delivered. I'm not sure it's + * ChatRoomMessageDeliveryFailedEvent#getMessage() but since it's the + * only message I can get out of ChatRoomMessageDeliveryFailedEvent, I'm + * using it. + */ + Message sourceMessage = evt.getMessage(); + + ChatRoomMember destMember = evt.getDestinationChatRoomMember(); + + if (evt.getErrorCode() + == MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED) + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.MSG_DELIVERY_NOT_SUPPORTED", + new String[]{destMember.getName()}); + } + else if (evt.getErrorCode() + == MessageDeliveryFailedEvent.NETWORK_FAILURE) + { + errorMsg = GuiActivator.getResources() + .getI18NString("service.gui.MSG_NOT_DELIVERED"); + } + else if (evt.getErrorCode() + == MessageDeliveryFailedEvent.PROVIDER_NOT_REGISTERED) + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.MSG_SEND_CONNECTION_PROBLEM"); + } + else if (evt.getErrorCode() + == MessageDeliveryFailedEvent.INTERNAL_ERROR) + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.MSG_DELIVERY_INTERNAL_ERROR"); + } + else if (evt.getErrorCode() + == ChatRoomMessageDeliveryFailedEvent.FORBIDDEN) + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.CHAT_ROOM_SEND_MSG_FORBIDDEN"); + } + else if (evt.getErrorCode() + == ChatRoomMessageDeliveryFailedEvent.UNSUPPORTED_OPERATION) + { + errorMsg = + GuiActivator.getResources().getI18NString( + "service.gui.MSG_DELIVERY_UNSUPPORTED_OPERATION"); + } + else + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.MSG_DELIVERY_UNKNOWN_ERROR"); + } + + String reason = evt.getReason(); + if (reason != null) + errorMsg += " " + GuiActivator.getResources().getI18NString( + "service.gui.ERROR_WAS", + new String[]{reason}); + + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + ChatPanel chatPanel + = chatWindowManager.getMultiChat(sourceChatRoom, true); + + chatPanel.addMessage( + destMember != null ? destMember.getName() + : sourceChatRoom.getName(), + new Date(), + Chat.OUTGOING_MESSAGE, + sourceMessage.getContent(), + sourceMessage.getContentType()); + + chatPanel.addErrorMessage( + destMember != null ? destMember.getName() + : sourceChatRoom.getName(), + errorMsg); + + chatWindowManager.openChat(chatPanel, false); + } + + /** + * Implements the + * <tt>LocalUserAdHocChatRoomPresenceListener.localUserPresenceChanged</tt> + * method + * + * @param evt the <tt>LocalUserAdHocChatRoomPresenceChangeEvent</tt> that + * notified us of a presence change + */ + public void localUserAdHocPresenceChanged( + LocalUserAdHocChatRoomPresenceChangeEvent evt) + { + AdHocChatRoom sourceAdHocChatRoom = evt.getAdHocChatRoom(); + AdHocChatRoomWrapper adHocChatRoomWrapper + = adHocChatRoomList + .findChatRoomWrapperFromAdHocChatRoom(sourceAdHocChatRoom); + + String eventType = evt.getEventType(); + + if (LocalUserAdHocChatRoomPresenceChangeEvent + .LOCAL_USER_JOINED.equals(eventType)) + { + if(adHocChatRoomWrapper != null) + { + this.fireAdHocChatRoomListChangedEvent( + adHocChatRoomWrapper, + AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_CHANGED); + + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + ChatPanel chatPanel + = chatWindowManager + .getMultiChat(adHocChatRoomWrapper, true); + + // Check if we have already opened a chat window for this chat + // wrapper and load the real chat room corresponding to the + // wrapper. + if(chatPanel.isShown()) + ((AdHocConferenceChatSession) chatPanel.getChatSession()) + .loadChatRoom(sourceAdHocChatRoom); + else + chatWindowManager.openChat(chatPanel, true); + } + + sourceAdHocChatRoom.addMessageListener(this); + } + else if (evt.getEventType().equals( + LocalUserAdHocChatRoomPresenceChangeEvent.LOCAL_USER_JOIN_FAILED)) + { + GuiActivator.getAlertUIService().showAlertPopup( + GuiActivator.getResources().getI18NString("service.gui.ERROR"), + GuiActivator.getResources().getI18NString( + "service.gui.FAILED_TO_JOIN_CHAT_ROOM", + new String[]{sourceAdHocChatRoom.getName()}) + + evt.getReason()); + } + else if (LocalUserAdHocChatRoomPresenceChangeEvent + .LOCAL_USER_LEFT.equals(eventType) + || LocalUserAdHocChatRoomPresenceChangeEvent + .LOCAL_USER_DROPPED.equals(eventType)) + { + this.closeAdHocChatRoom(adHocChatRoomWrapper); + + // Need to refresh the chat room's list in order to change + // the state of the chat room to offline. + fireAdHocChatRoomListChangedEvent( + adHocChatRoomWrapper, + AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_CHANGED); + + sourceAdHocChatRoom.removeMessageListener(this); + } + } + + /** + * Implements the + * <tt>LocalUserChatRoomPresenceListener.localUserPresenceChanged</tt> + * method. + * @param evt the <tt>LocalUserChatRoomPresenceChangeEvent</tt> that + * notified us + */ + public void localUserPresenceChanged( + final LocalUserChatRoomPresenceChangeEvent evt) + { + if(!SwingUtilities.isEventDispatchThread()) + { + SwingUtilities.invokeLater(new Runnable() + { + @Override + public void run() + { + localUserPresenceChanged(evt); + } + }); + return; + } + + ChatRoom sourceChatRoom = evt.getChatRoom(); + ChatRoomWrapper chatRoomWrapper + = GuiActivator.getMUCService().findChatRoomWrapperFromChatRoom( + sourceChatRoom); + + String eventType = evt.getEventType(); + + if (LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_JOINED.equals(eventType)) + { + if(chatRoomWrapper != null) + { + GuiActivator.getMUCService().fireChatRoomListChangedEvent( + chatRoomWrapper, + ChatRoomListChangeEvent.CHAT_ROOM_CHANGED); + + boolean createWindow = false; + + String autoOpenConfig + = MUCService.getChatRoomAutoOpenOption( + sourceChatRoom.getParentProvider(), + sourceChatRoom.getIdentifier()); + + if(autoOpenConfig != null + && autoOpenConfig.equals(MUCService.OPEN_ON_ACTIVITY)) + createWindow = true; + + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + ChatPanel chatPanel + = chatWindowManager.getMultiChat( + chatRoomWrapper, createWindow); + + if(chatPanel != null) + { + chatPanel.setChatIcon( + chatPanel.getChatSession().getChatStatusIcon()); + + // Check if we have already opened a chat window for this chat + // wrapper and load the real chat room corresponding to the + // wrapper. + if(chatPanel.isShown()) + { + ((ConferenceChatSession) chatPanel.getChatSession()) + .loadChatRoom(sourceChatRoom); + } + else + { + chatWindowManager.openChat(chatPanel, true); + } + } + } + + if (sourceChatRoom.isSystem()) + { + ChatRoomProviderWrapper serverWrapper + = GuiActivator.getMUCService() + .findServerWrapperFromProvider( + sourceChatRoom.getParentProvider()); + + serverWrapper.setSystemRoom(sourceChatRoom); + } + + sourceChatRoom.addMessageListener(this); + sourceChatRoom.addLocalUserRoleListener(this); + } + else if (LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_JOIN_FAILED.equals(eventType)) + { + GuiActivator.getAlertUIService().showAlertPopup( + GuiActivator.getResources().getI18NString("service.gui.ERROR"), + GuiActivator.getResources().getI18NString( + "service.gui.FAILED_TO_JOIN_CHAT_ROOM", + new String[]{sourceChatRoom.getName()}) + + evt.getReason()); + } + else if (LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_LEFT.equals(eventType) + || LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_KICKED.equals(eventType) + || LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_DROPPED.equals(eventType)) + { + if(chatRoomWrapper != null) + { + if(StringUtils.isNullOrEmpty(evt.getReason())) + { + GuiActivator.getUIService() + .closeChatRoomWindow(chatRoomWrapper); + } + else + { + // send some system messages informing for the + // reason of leaving + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + + ChatPanel chatPanel = chatWindowManager.getMultiChat( + sourceChatRoom, false); + + if(chatPanel != null) + { + chatPanel.addMessage( + sourceChatRoom.getName(), + null, + new Date(), + Chat.SYSTEM_MESSAGE, + evt.getReason(), + OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE, + null, + null); + + // print and the alternate address + if(!StringUtils.isNullOrEmpty( + evt.getAlternateAddress())) + { + chatPanel.addMessage( + sourceChatRoom.getName(), + null, + new Date(), + Chat.SYSTEM_MESSAGE, + GuiActivator.getResources().getI18NString( + "service.gui.CHAT_ROOM_ALTERNATE_ADDRESS", + new String[]{evt.getAlternateAddress()}), + OperationSetBasicInstantMessaging + .DEFAULT_MIME_TYPE, + null, + null); + } + } + } + + // Need to refresh the chat room's list in order to change + // the state of the chat room to offline. + + GuiActivator.getMUCService().fireChatRoomListChangedEvent( + chatRoomWrapper, + ChatRoomListChangeEvent.CHAT_ROOM_CHANGED); + } + + sourceChatRoom.removeMessageListener(this); + sourceChatRoom.removelocalUserRoleListener(this); + } + } + + + /** + * 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 + * @param multiUserChatOpSet the operation set for chat conferencing + * @throws OperationFailedException if the accept fails + */ + public void acceptInvitation( + AdHocChatRoomInvitation invitation, + OperationSetAdHocMultiUserChat multiUserChatOpSet) + throws OperationFailedException + { + AdHocChatRoom chatRoom = invitation.getTargetAdHocChatRoom(); + + chatRoom.join(); + } + + /** + * Rejects the given invitation with the specified reason. + * + * @param multiUserChatAdHocOpSet 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( + OperationSetAdHocMultiUserChat multiUserChatAdHocOpSet, + AdHocChatRoomInvitation invitation, + String reason) + { + multiUserChatAdHocOpSet.rejectInvitation(invitation, reason); + } + + /** + * Creates an ad-hoc chat room, by specifying the ad-hoc chat room name, the + * parent protocol provider and eventually, the contacts invited to + * participate in this ad-hoc chat room. + * + * @param protocolProvider the parent protocol provider. + * @param contacts the contacts invited when creating the chat room. + * @param reason the reason for this invitation + * @return the <tt>AdHocChatRoomWrapper</tt> corresponding to the created + * ad hoc chat room + */ + public AdHocChatRoomWrapper createAdHocChatRoom( + ProtocolProviderService protocolProvider, + Collection<String> contacts, + String reason) + { + AdHocChatRoomWrapper chatRoomWrapper = null; + + OperationSetAdHocMultiUserChat groupChatOpSet + = protocolProvider + .getOperationSet(OperationSetAdHocMultiUserChat.class); + + // If there's no group chat operation set we have nothing to do here. + if (groupChatOpSet == null) + return null; + + AdHocChatRoom chatRoom = null; + + try + { + java.util.List<String> members = new LinkedList<String>(); + + for(String address : contacts) + members.add(address); + + chatRoom = groupChatOpSet.createAdHocChatRoom( + "chatroom-" + new Date().getTime(), members, reason); + } + catch (OperationFailedException ex) + { + new ErrorDialog( + GuiActivator.getUIService().getMainFrame(), + GuiActivator.getResources().getI18NString("service.gui.ERROR"), + GuiActivator.getResources().getI18NString( + "service.gui.CREATE_CHAT_ROOM_ERROR", + new String[]{protocolProvider.getProtocolDisplayName()}), + ex) + .showDialog(); + } + catch (OperationNotSupportedException ex) + { + new ErrorDialog( + GuiActivator.getUIService().getMainFrame(), + GuiActivator.getResources().getI18NString("service.gui.ERROR"), + GuiActivator.getResources().getI18NString( + "service.gui.CREATE_CHAT_ROOM_ERROR", + new String[]{protocolProvider.getProtocolDisplayName()}), + ex) + .showDialog(); + } + + if(chatRoom != null) + { + AdHocChatRoomProviderWrapper parentProvider + = adHocChatRoomList.findServerWrapperFromProvider( + protocolProvider); + + chatRoomWrapper = new AdHocChatRoomWrapper( + parentProvider, chatRoom); + parentProvider.addAdHocChatRoom(chatRoomWrapper); + adHocChatRoomList.addAdHocChatRoom(chatRoomWrapper); + + fireAdHocChatRoomListChangedEvent( + chatRoomWrapper, + AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_ADDED); + } + + return chatRoomWrapper; + } + + /** + * Joins the given ad-hoc chat room + * + * @param chatRoomWrapper + */ + public void joinChatRoom(AdHocChatRoomWrapper chatRoomWrapper) + { + AdHocChatRoom chatRoom = chatRoomWrapper.getAdHocChatRoom(); + + if(chatRoom == null) + { + new ErrorDialog( + GuiActivator.getUIService().getMainFrame(), + GuiActivator.getResources().getI18NString("service.gui.WARNING"), + GuiActivator.getResources().getI18NString( + "service.gui.CHAT_ROOM_NOT_CONNECTED", + new String[]{chatRoomWrapper.getAdHocChatRoomName()})) + .showDialog(); + + return; + } + + new JoinAdHocChatRoomTask(chatRoomWrapper).execute(); + } + + /** + * Removes the given chat room from the UI. + * + * @param chatRoomWrapper the chat room to remove. + */ + public void removeChatRoom(ChatRoomWrapper chatRoomWrapper) + { + ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); + + if (chatRoom != null) + leaveChatRoom(chatRoomWrapper); + + GuiActivator.getUIService().closeChatRoomWindow(chatRoomWrapper); + + GuiActivator.getMUCService().removeChatRoom(chatRoomWrapper); + + } + + /** + * Joins the given chat room and manages all the exceptions that could + * occur during the join process. + * + * @param chatRoom the chat room to join + */ + public void joinChatRoom(AdHocChatRoom chatRoom) + { + AdHocChatRoomWrapper chatRoomWrapper + = adHocChatRoomList.findChatRoomWrapperFromAdHocChatRoom(chatRoom); + + if(chatRoomWrapper == null) + { + AdHocChatRoomProviderWrapper parentProvider + = adHocChatRoomList.findServerWrapperFromProvider( + chatRoom.getParentProvider()); + + chatRoomWrapper = + new AdHocChatRoomWrapper(parentProvider, chatRoom); + + adHocChatRoomList.addAdHocChatRoom(chatRoomWrapper); + + fireAdHocChatRoomListChangedEvent( + chatRoomWrapper, + AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_ADDED); + } + + this.joinChatRoom(chatRoomWrapper); + + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + + chatWindowManager + .openChat( + chatWindowManager.getMultiChat(chatRoomWrapper, true), + true); + } + + /** + * Leaves the given <tt>ChatRoom</tt>. + * + * @param chatRoomWrapper the <tt>ChatRoom</tt> to leave. + */ + public void leaveChatRoom(ChatRoomWrapper chatRoomWrapper) + { + ChatRoomWrapper leavedRoomWrapped + = GuiActivator.getMUCService().leaveChatRoom(chatRoomWrapper); + if(leavedRoomWrapped != null) + GuiActivator.getUIService().closeChatRoomWindow(leavedRoomWrapped); + } + + /** + * Leaves the given <tt>ChatRoom</tt>. + * + * @param chatRoomWrapper the <tt>ChatRoom</tt> to leave. + */ + public void leaveChatRoom(AdHocChatRoomWrapper chatRoomWrapper) + { + AdHocChatRoom chatRoom = chatRoomWrapper.getAdHocChatRoom(); + + if (chatRoom != null) + { + chatRoom.leave(); + } + else + { + new ErrorDialog( + GuiActivator.getUIService().getMainFrame(), + GuiActivator.getResources().getI18NString("service.gui.WARNING"), + GuiActivator.getResources().getI18NString( + "service.gui.CHAT_ROOM_LEAVE_NOT_CONNECTED")) + .showDialog(); + } + } + + /** + * Checks if there's an open history window for the given chat room. + * + * @param chatRoomWrapper the chat room wrapper to check for + * @return TRUE if there's an opened history window for the given chat room, + * FALSE otherwise. + */ + public boolean containsHistoryWindowForChatRoom( + ChatRoomWrapper chatRoomWrapper) + { + return chatRoomHistory.containsKey(chatRoomWrapper); + } + + /** + * Returns the history window for the given chat room. + * + * @param chatRoomWrapper the chat room wrapper to search for + * @return the history window for the given chat room + */ + public HistoryWindow getHistoryWindowForChatRoom( + ChatRoomWrapper chatRoomWrapper) + { + return chatRoomHistory.get(chatRoomWrapper); + } + + /** + * Adds a history window for a given chat room in the table of opened + * history windows. + * + * @param chatRoomWrapper the chat room wrapper to add + * @param historyWindow the history window to add + */ + public void addHistoryWindowForChatRoom(ChatRoomWrapper chatRoomWrapper, + HistoryWindow historyWindow) + { + chatRoomHistory.put(chatRoomWrapper, historyWindow); + } + + /** + * Removes the history window for the given chat room. + * + * @param chatRoomWrapper the chat room wrapper to remove the history window + */ + public void removeHistoryWindowForChatRoom(ChatRoomWrapper chatRoomWrapper) + { + chatRoomHistory.remove(chatRoomWrapper); + } + + /** + * Adds the given <tt>AdHocChatRoomListChangeListener</tt> that will listen + * for all changes of the chat room list data model. + * + * @param l the listener to add. + */ + public void addAdHocChatRoomListChangeListener( + AdHocChatRoomListChangeListener l) + { + synchronized (adHoclistChangeListeners) + { + adHoclistChangeListeners.add(l); + } + } + + /** + * Removes the given <tt>AdHocChatRoomListChangeListener</tt>. + * + * @param l the listener to remove. + */ + public void removeAdHocChatRoomListChangeListener( + AdHocChatRoomListChangeListener l) + { + synchronized (adHoclistChangeListeners) + { + adHoclistChangeListeners.remove(l); + } + } + + /** + * Notifies all interested listeners that a change in the chat room list + * model has occurred. + * @param adHocChatRoomWrapper the chat room wrapper that identifies the + * chat room + * @param eventID the identifier of the event + */ + private void fireAdHocChatRoomListChangedEvent( + AdHocChatRoomWrapper adHocChatRoomWrapper, + int eventID) + { + AdHocChatRoomListChangeEvent evt + = new AdHocChatRoomListChangeEvent(adHocChatRoomWrapper, eventID); + + for (AdHocChatRoomListChangeListener l : adHoclistChangeListeners) + { + l.contentChanged(evt); + } + } + + + /** + * Closes the chat corresponding to the given ad-hoc chat room wrapper, if + * such exists. + * + * @param chatRoomWrapper the ad-hoc chat room wrapper for which we search a + * chat to close. + */ + private void closeAdHocChatRoom(AdHocChatRoomWrapper chatRoomWrapper) + { + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + ChatPanel chatPanel + = chatWindowManager.getMultiChat(chatRoomWrapper, false); + + if (chatPanel != null) + chatWindowManager.closeChat(chatPanel); + } + + /** + * Handles <tt>ServiceEvent</tt>s triggered by adding or removing a + * ProtocolProviderService. Updates the list of available chat rooms and + * chat room servers. + * + * @param event The event to handle. + */ + public void serviceChanged(ServiceEvent event) + { + // if the event is caused by a bundle being stopped, we don't want to + // know + if (event.getServiceReference().getBundle().getState() + == Bundle.STOPPING) + return; + + Object service = GuiActivator.bundleContext.getService(event + .getServiceReference()); + + // we don't care if the source service is not a protocol provider + if (!(service instanceof ProtocolProviderService)) + return; + + ProtocolProviderService protocolProvider + = (ProtocolProviderService) service; + + + Object multiUserChatAdHocOpSet + = protocolProvider + .getOperationSet(OperationSetAdHocMultiUserChat.class); + + if (multiUserChatAdHocOpSet != null) + { + if (event.getType() == ServiceEvent.REGISTERED) + { + adHocChatRoomList.addChatProvider(protocolProvider); + } + else if (event.getType() == ServiceEvent.UNREGISTERING) + { + adHocChatRoomList.removeChatProvider(protocolProvider); + } + } + } + + /** + * Joins an ad-hoc chat room in an asynchronous way. + */ + private static class JoinAdHocChatRoomTask + extends SwingWorker<String, Object> + { + private static final String SUCCESS = "Success"; + + private static final String AUTHENTICATION_FAILED + = "AuthenticationFailed"; + + private static final String REGISTRATION_REQUIRED + = "RegistrationRequired"; + + private static final String PROVIDER_NOT_REGISTERED + = "ProviderNotRegistered"; + + private static final String SUBSCRIPTION_ALREADY_EXISTS + = "SubscriptionAlreadyExists"; + + private static final String UNKNOWN_ERROR + = "UnknownError"; + + private final AdHocChatRoomWrapper adHocChatRoomWrapper; + + JoinAdHocChatRoomTask(AdHocChatRoomWrapper chatRoomWrapper) + { + this.adHocChatRoomWrapper = chatRoomWrapper; + } + + /** + * @override {@link SwingWorker}{@link #doInBackground()} to perform + * all asynchronous tasks. + * @return SUCCESS if success, otherwise the error code + */ + @Override + public String doInBackground() + { + AdHocChatRoom chatRoom = adHocChatRoomWrapper.getAdHocChatRoom(); + + try + { + chatRoom.join(); + + return SUCCESS; + } + catch (OperationFailedException e) + { + if (logger.isTraceEnabled()) + logger.trace("Failed to join ad-hoc chat room: " + + chatRoom.getName(), e); + + switch (e.getErrorCode()) + { + case OperationFailedException.AUTHENTICATION_FAILED: + return AUTHENTICATION_FAILED; + case OperationFailedException.REGISTRATION_REQUIRED: + return REGISTRATION_REQUIRED; + case OperationFailedException.PROVIDER_NOT_REGISTERED: + return PROVIDER_NOT_REGISTERED; + case OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS: + return SUBSCRIPTION_ALREADY_EXISTS; + default: + return UNKNOWN_ERROR; + } + } + } + + /** + * @override {@link SwingWorker}{@link #done()} to perform UI changes + * after the ad-hoc chat room join task has finished. + */ + @Override + protected void done() + { + String returnCode = null; + try + { + returnCode = get(); + } + catch (InterruptedException ignore) + {} + catch (ExecutionException ignore) + {} + + ConfigurationUtils.updateChatRoomStatus( + adHocChatRoomWrapper.getParentProvider().getProtocolProvider(), + adHocChatRoomWrapper.getAdHocChatRoomID(), + GlobalStatusEnum.ONLINE_STATUS); + + String errorMessage = null; + if(PROVIDER_NOT_REGISTERED.equals(returnCode)) + { + errorMessage + = GuiActivator.getResources() + .getI18NString("service.gui.CHAT_ROOM_NOT_CONNECTED", + new String[]{ + adHocChatRoomWrapper.getAdHocChatRoomName()}); + } + else if(SUBSCRIPTION_ALREADY_EXISTS.equals(returnCode)) + { + errorMessage + = GuiActivator.getResources() + .getI18NString("service.gui.CHAT_ROOM_ALREADY_JOINED", + new String[]{ + adHocChatRoomWrapper.getAdHocChatRoomName()}); + } + else + { + errorMessage + = GuiActivator.getResources() + .getI18NString("service.gui.FAILED_TO_JOIN_CHAT_ROOM", + new String[]{ + adHocChatRoomWrapper.getAdHocChatRoomName()}); + } + + if (!SUCCESS.equals(returnCode) + && !AUTHENTICATION_FAILED.equals(returnCode)) + { + GuiActivator.getAlertUIService().showAlertPopup( + GuiActivator.getResources().getI18NString( + "service.gui.ERROR"), errorMessage); + } + } + } + + + /** + * Indicates that an invitation has been received and opens the invitation + * dialog to notify the user. + * @param evt the <tt>AdHocChatRoomInvitationReceivedEvent</tt> that + * notified us + */ + public void invitationReceived(AdHocChatRoomInvitationReceivedEvent evt) + { + if (logger.isInfoEnabled()) + logger.info("Invitation received: "+evt.toString()); + OperationSetAdHocMultiUserChat multiUserChatOpSet + = evt.getSourceOperationSet(); + + InvitationReceivedDialog dialog = new InvitationReceivedDialog( + this, multiUserChatOpSet, evt.getInvitation()); + + dialog.setVisible(true); + } + + /** + * Implements the <tt>AdHocChatRoomMessageListener.messageDelivered</tt> + * method. + * <br> + * Shows the message in the conversation area and clears the write message + * area. + * @param evt the <tt>AdHocChatRoomMessageDeliveredEvent</tt> that notified + * us + */ + public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt) + { + AdHocChatRoom sourceChatRoom = (AdHocChatRoom) evt.getSource(); + + if (logger.isInfoEnabled()) + logger.info("MESSAGE DELIVERED to ad-hoc chat room: " + + sourceChatRoom.getName()); + + ChatPanel chatPanel + = GuiActivator + .getUIService() + .getChatWindowManager() + .getMultiChat(sourceChatRoom, false); + + if(chatPanel != null) + { + String messageType; + switch (evt.getEventType()) + { + case AdHocChatRoomMessageDeliveredEvent + .CONVERSATION_MESSAGE_DELIVERED: + messageType = Chat.OUTGOING_MESSAGE; + break; + case AdHocChatRoomMessageDeliveredEvent.ACTION_MESSAGE_DELIVERED: + messageType = Chat.ACTION_MESSAGE; + break; + default: + messageType = null; + } + + Message msg = evt.getMessage(); + + chatPanel + .addMessage( + sourceChatRoom + .getParentProvider().getAccountID().getUserID(), + null, + evt.getTimestamp(), + messageType, + msg.getContent(), + msg.getContentType(), + msg.getMessageUID(), + null); + } + else + { + logger.error("chat panel is null, message NOT DELIVERED !"); + } + } + + /** + * Implements <tt>AdHocChatRoomMessageListener.messageDeliveryFailed</tt> + * method. + * <br> + * In the conversation area shows an error message, explaining the problem. + * @param evt the <tt>AdHocChatRoomMessageDeliveryFailedEvent</tt> that + * notified us + */ + public void messageDeliveryFailed( + AdHocChatRoomMessageDeliveryFailedEvent evt) + { + AdHocChatRoom sourceChatRoom = evt.getSourceChatRoom(); + Message sourceMessage = evt.getMessage(); + Contact destParticipant = evt.getDestinationParticipant(); + + String errorMsg = null; + if (evt.getErrorCode() + == MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED) + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.MSG_DELIVERY_NOT_SUPPORTED", + new String[]{destParticipant.getDisplayName()}); + } + else if (evt.getErrorCode() + == MessageDeliveryFailedEvent.NETWORK_FAILURE) + { + errorMsg = GuiActivator.getResources() + .getI18NString("service.gui.MSG_NOT_DELIVERED"); + } + else if (evt.getErrorCode() + == MessageDeliveryFailedEvent.PROVIDER_NOT_REGISTERED) + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.MSG_SEND_CONNECTION_PROBLEM"); + } + else if (evt.getErrorCode() + == MessageDeliveryFailedEvent.INTERNAL_ERROR) + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.MSG_DELIVERY_INTERNAL_ERROR"); + } + else if (evt.getErrorCode() + == MessageDeliveryFailedEvent.UNSUPPORTED_OPERATION) + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.MSG_DELIVERY_UNSUPPORTED_OPERATION"); + } + else + { + errorMsg = GuiActivator.getResources().getI18NString( + "service.gui.MSG_DELIVERY_UNKNOWN_ERROR"); + } + + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + ChatPanel chatPanel + = chatWindowManager.getMultiChat(sourceChatRoom, true); + + chatPanel.addMessage( + destParticipant.getDisplayName(), + new Date(), + Chat.OUTGOING_MESSAGE, + sourceMessage.getContent(), + sourceMessage.getContentType()); + + chatPanel.addErrorMessage( + destParticipant.getDisplayName(), + errorMsg); + + chatWindowManager.openChat(chatPanel, false); + } + + /** + * Implements the <tt>AdHocChatRoomMessageListener.messageReceived</tt> + * method. + * <br> + * Obtains the corresponding <tt>ChatPanel</tt> and process the message + * there. + * @param evt the <tt>AdHocChatRoomMessageReceivedEvent</tt> that notified + * us + */ + public void messageReceived(AdHocChatRoomMessageReceivedEvent evt) + { + AdHocChatRoom sourceChatRoom = evt.getSourceChatRoom(); + Contact sourceParticipant = evt.getSourceChatRoomParticipant(); + + String messageType = null; + + switch (evt.getEventType()) + { + case AdHocChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED: + messageType = Chat.INCOMING_MESSAGE; + break; + case AdHocChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED: + messageType = Chat.SYSTEM_MESSAGE; + break; + case AdHocChatRoomMessageReceivedEvent.ACTION_MESSAGE_RECEIVED: + messageType = Chat.ACTION_MESSAGE; + break; + } + + if (logger.isInfoEnabled()) + logger.info("MESSAGE RECEIVED from contact: " + + sourceParticipant.getAddress()); + + Message message = evt.getMessage(); + + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + ChatPanel chatPanel + = chatWindowManager + .getMultiChat(sourceChatRoom, true, message.getMessageUID()); + + String messageContent = message.getContent(); + + chatPanel.addMessage( + sourceParticipant.getDisplayName(), + null, + evt.getTimestamp(), + messageType, + messageContent, + message.getContentType(), + message.getMessageUID(), + null); + + chatWindowManager.openChat(chatPanel, false); + } + + public void invitationRejected(AdHocChatRoomInvitationRejectedEvent evt) {} + + @Override + public void localUserRoleChanged(ChatRoomLocalUserRoleChangeEvent evt) + { + if(evt.isInitial()) + return; + ChatRoom sourceChatRoom = evt.getSourceChatRoom(); + ChatRoomWrapper chatRoomWrapper + = GuiActivator.getMUCService().findChatRoomWrapperFromChatRoom( + sourceChatRoom); + ChatWindowManager chatWindowManager + = GuiActivator.getUIService().getChatWindowManager(); + ChatPanel chatPanel + = chatWindowManager.getMultiChat(chatRoomWrapper, true); + chatWindowManager.openChat(chatPanel, true); + } + +} diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/ReceiveFileConversationComponent.java b/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/ReceiveFileConversationComponent.java index ef72a89..f5a698b 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/ReceiveFileConversationComponent.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/ReceiveFileConversationComponent.java @@ -164,6 +164,10 @@ public class ReceiveFileConversationComponent File downloadDir = null; String incomingFileName = fileTransferRequest.getFileName(); + // strip characters that are invalid on Windows and maybe other + // platforms too + incomingFileName = incomingFileName + .replaceAll("[\\\\/:*?\"<>|]", "_"); try { downloadDir = GuiActivator.getFileAccessService() diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java b/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java index 6905df9..7080d13 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java @@ -715,14 +715,19 @@ public class MainToolBar m.put(opSetClass, ct.getProtocolProvider()); UIContactDetailImpl d = new UIContactDetailImpl( - ct.getName(), + ct.getName() + (ct.getResourceName() == null + ? "" + : "/" + ct.getResourceName()), ct.getDisplayName(), null, - null, + (ct.getResourceName() == null + ? Arrays.asList(GuiActivator.getResources().getI18NString("service.gui.VIA") + ": " + + ct.getProtocolProvider().getAccountID().getAccountAddress()) + : null), null, m, null, - ct.getName()); + ct); PresenceStatus status = ct.getStatus(); byte[] statusIconBytes = status.getStatusIcon(); @@ -736,7 +741,7 @@ public class MainToolBar res.add(d); } - + Point location = new Point(callButton.getX(), callButton.getY() + callButton.getHeight()); diff --git a/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ChatRoomTableDialog.java b/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ChatRoomTableDialog.java index 0efb860..28f3e08 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ChatRoomTableDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ChatRoomTableDialog.java @@ -55,6 +55,13 @@ public class ChatRoomTableDialog "REMOVE_ROOM_ON_FIRST_JOIN_FAILED"; /** + * Whether we should make rooms autojoin by default. + */ + private static final String ENABLE_ROOM_AUTO_JOIN_ON_CREATION + = "net.java.sip.communicator.impl.gui.main.chatroomslist." + + "ENABLE_ROOM_AUTO_JOIN_ON_CREATION"; + + /** * The global/shared <code>ChatRoomTableDialog</code> currently showing. */ private static ChatRoomTableDialog chatRoomTableDialog; @@ -425,6 +432,12 @@ public class ChatRoomTableDialog chatRoomWrapper.getChatRoomID(), chatRoomWrapper.getChatRoomID(), chatRoomWrapper.getChatRoomName()); + + if(GuiActivator.getConfigurationService() + .getBoolean(ENABLE_ROOM_AUTO_JOIN_ON_CREATION, false)) + { + chatRoomWrapper.setAutoJoin(true); + } } String nickName = nicknameField.getText().trim(); diff --git a/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ServerChatRoomsChoiceDialog.java b/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ServerChatRoomsChoiceDialog.java index 3eeb783..4725a08 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ServerChatRoomsChoiceDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ServerChatRoomsChoiceDialog.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,97 +15,97 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.chatroomslist;
-
-import java.awt.*;
-import java.awt.event.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.main.contactlist.*;
-import net.java.sip.communicator.impl.gui.utils.*;
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.muc.*;
-
-/**
- * A dialog that lists the existing chat rooms on the server.
- *
- * @author Hristo Terezov
- */
-public class ServerChatRoomsChoiceDialog
- extends OneChoiceInviteDialog
-{
-
- /**
- * Generated serial id.
- */
- private static final long serialVersionUID = 428358553225114162L;
-
- /**
- * The contact source that generates the list of chat rooms.
- */
- private ContactSourceService contactSource;
-
- /**
- * Creates new instance of <tt>ServerChatRoomsChoiceDialog</tt>.
- *
- * @param title the title of the window.
- * @param pps the protocol provider service associated with the list of chat
- * rooms.
- */
- public ServerChatRoomsChoiceDialog(String title,
- ChatRoomProviderWrapper pps)
- {
- super(title);
- contactList.setDefaultFilter(new SearchFilter(contactList));
- contactList.removeAllContactSources();
- contactSource = GuiActivator.getMUCService()
- .getServerChatRoomsContactSourceForProvider(pps);
- contactList.addContactSource(
- contactSource);
-
- setInfoText(GuiActivator.getResources().getI18NString(
- "service.gui.SERVER_CHAT_ROOMS_DIALOG_TEXT"));
-
- contactList.applyDefaultFilter();
- this.setMinimumSize(new Dimension(300, 300));
- addOkButtonListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- UIContact uiContact = getSelectedContact();
-
- if (uiContact != null)
- {
- ChatRoomTableDialog.setChatRoomField(
- uiContact.getDisplayName());
- }
-
- setVisible(false);
- dispose();
- }
- });
- addCancelButtonListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- setVisible(false);
- dispose();
- }
- });
- }
-
- /**
- * Handles provider change.
- *
- * @param provider the provider.
- */
- public void changeProtocolProvider(ChatRoomProviderWrapper provider)
- {
- contactList.removeContactSource(contactSource);
- contactSource = GuiActivator.getMUCService()
- .getServerChatRoomsContactSourceForProvider(provider);
- contactList.addContactSource(contactSource);
- contactList.applyDefaultFilter();
- }
-}
+package net.java.sip.communicator.impl.gui.main.chatroomslist; + +import java.awt.*; +import java.awt.event.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.muc.*; + +/** + * A dialog that lists the existing chat rooms on the server. + * + * @author Hristo Terezov + */ +public class ServerChatRoomsChoiceDialog + extends OneChoiceInviteDialog +{ + + /** + * Generated serial id. + */ + private static final long serialVersionUID = 428358553225114162L; + + /** + * The contact source that generates the list of chat rooms. + */ + private ContactSourceService contactSource; + + /** + * Creates new instance of <tt>ServerChatRoomsChoiceDialog</tt>. + * + * @param title the title of the window. + * @param pps the protocol provider service associated with the list of chat + * rooms. + */ + public ServerChatRoomsChoiceDialog(String title, + ChatRoomProviderWrapper pps) + { + super(title); + contactList.setDefaultFilter(new SearchFilter(contactList)); + contactList.removeAllContactSources(); + contactSource = GuiActivator.getMUCService() + .getServerChatRoomsContactSourceForProvider(pps); + contactList.addContactSource( + contactSource); + + setInfoText(GuiActivator.getResources().getI18NString( + "service.gui.SERVER_CHAT_ROOMS_DIALOG_TEXT")); + + contactList.applyDefaultFilter(); + this.setMinimumSize(new Dimension(300, 300)); + addOkButtonListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + UIContact uiContact = getSelectedContact(); + + if (uiContact != null) + { + ChatRoomTableDialog.setChatRoomField( + uiContact.getDisplayName()); + } + + setVisible(false); + dispose(); + } + }); + addCancelButtonListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + setVisible(false); + dispose(); + } + }); + } + + /** + * Handles provider change. + * + * @param provider the provider. + */ + public void changeProtocolProvider(ChatRoomProviderWrapper provider) + { + contactList.removeContactSource(contactSource); + contactSource = GuiActivator.getMUCService() + .getServerChatRoomsContactSourceForProvider(provider); + contactList.addContactSource(contactSource); + contactList.applyDefaultFilter(); + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/AddContactDialog.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/AddContactDialog.java index 856ae5b..8d43c7f 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/AddContactDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/AddContactDialog.java @@ -21,9 +21,14 @@ import java.awt.*; import java.awt.Container; import java.awt.event.*; import java.util.*; +import java.util.List; import javax.swing.*; +import javax.swing.border.*; import javax.swing.event.*; +import javax.swing.text.*; + +import org.jitsi.util.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.contactlist.addgroup.*; @@ -163,31 +168,100 @@ public class AddContactDialog } /** + * Adds a faint gray prompt to the provided text field that + * will vanish as soon as text is entered into the field. + */ + private void addPrompt(JTextField field, String text) + { + final JLabel prompt = new JLabel(text); + + // Give prompt a foreground color like the original + // text field, but with half transparency. + final Color fg = field.getForeground(); + final Color color = new Color( + fg.getRed(), fg.getGreen(), fg.getBlue(), 128); + + // Mimic properties of given text field + prompt.setFont(field.getFont()); + prompt.setForeground(color); + prompt.setBorder(new EmptyBorder(field.getInsets())); + prompt.setHorizontalAlignment(JLabel.LEADING); + + // Add handler to hide prompt when text is entered + final Document doc = field.getDocument(); + doc.addDocumentListener( new DocumentListener() { + public void insertUpdate(DocumentEvent e) + { + prompt.setVisible(doc.getLength() == 0); + } + + public void removeUpdate(DocumentEvent e) + { + prompt.setVisible(doc.getLength() == 0); + } + + public void changedUpdate(DocumentEvent e) {} + }); + + // Add prompt to text field + field.setLayout( new BorderLayout() ); + field.add(prompt); + } + + /** * Initializes the dialog. */ private void init() { + // Get tool tip text for primary controls + final String displayNameInfo = + GuiActivator.getResources().getI18NString( + "service.gui.DISPLAY_NAME_INFO"); + final String contactInfo = + GuiActivator.getResources().getI18NString( + "service.gui.CONTACT_NAME_INFO"); + final String accountInfo = + GuiActivator.getResources().getI18NString( + "service.gui.SELECT_ACCOUNT_INFO"); + final String groupInfo = + GuiActivator.getResources().getI18NString( + "service.gui.SELECT_GROUP_INFO"); + + // Initialize controls this.accountLabel = new JLabel( GuiActivator.getResources().getI18NString( "service.gui.SELECT_ACCOUNT") + ": "); + this.accountLabel.setToolTipText(accountInfo); this.accountCombo = new JComboBox(); - - this.groupLabel = new JLabel( - GuiActivator.getResources().getI18NString( - "service.gui.SELECT_GROUP") + ": "); + this.accountCombo.setToolTipText(accountInfo); this.contactAddressLabel = new JLabel( GuiActivator.getResources().getI18NString( "service.gui.CONTACT_NAME") + ": "); + this.contactAddressLabel.setToolTipText(contactInfo); this.displayNameLabel = new JLabel( GuiActivator.getResources().getI18NString( "service.gui.DISPLAY_NAME") + ": "); + this.displayNameLabel.setToolTipText(displayNameInfo); this.contactAddressField = new JTextField(); + this.contactAddressField.setToolTipText(contactInfo); + addPrompt(this.contactAddressField, + GuiActivator.getResources().getI18NString( + "service.gui.CONTACT_NAME_PROMPT")); this.displayNameField = new JTextField(); + this.displayNameField.setToolTipText(displayNameInfo); + addPrompt(this.displayNameField, + GuiActivator.getResources().getI18NString( + "service.gui.DISPLAY_NAME_PROMPT")); + + this.groupLabel = new JLabel( + GuiActivator.getResources().getI18NString( + "service.gui.SELECT_GROUP") + ": "); + this.groupLabel.setToolTipText(groupInfo); this.addButton = new JButton( GuiActivator.getResources().getI18NString("service.gui.ADD")); @@ -198,6 +272,7 @@ public class AddContactDialog this.imageLabel = new JLabel(); this.groupCombo = createGroupCombo(this); + this.groupCombo.setToolTipText(groupInfo); if(metaContact != null) { @@ -225,15 +300,15 @@ public class AddContactDialog fieldsPanel.add(accountCombo); } - labelsPanel.add(groupLabel); - fieldsPanel.add(groupCombo); - labelsPanel.add(contactAddressLabel); fieldsPanel.add(contactAddressField); labelsPanel.add(displayNameLabel); fieldsPanel.add(displayNameField); + labelsPanel.add(groupLabel); + fieldsPanel.add(groupCombo); + contactAddressField.getDocument().addDocumentListener( new DocumentListener() { @@ -499,6 +574,26 @@ public class AddContactDialog final String contactAddress = contactAddressField.getText().trim(); final String displayName = displayNameField.getText(); + List<String> validationResult = new ArrayList<>(2); + if (!protocolProvider.validateContactAddress(contactAddress, + validationResult)) + { + new ErrorDialog(GuiActivator.getUIService().getMainFrame(), + GuiActivator.getResources() + .getI18NString("service.gui.ADD_CONTACT_ERROR_TITLE"), + validationResult.get(0), ErrorDialog.WARNING).showDialog(); + if (validationResult.size() >= 2) + { + contactAddressField.setText(validationResult.get(1)); + if (StringUtils.isNullOrEmpty(displayName, true)) + { + displayNameField.setText(contactAddress); + } + } + + return; + } + if (!protocolProvider.isRegistered()) { new ErrorDialog( diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactInfoDialog.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactInfoDialog.java deleted file mode 100644 index 325be90..0000000 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactInfoDialog.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.gui.main.contactlist; - -import java.awt.*; -import java.awt.event.*; - -import javax.swing.*; - -import net.java.sip.communicator.impl.gui.customcontrols.*; -import net.java.sip.communicator.plugin.desktoputil.*; -import net.java.sip.communicator.service.contactlist.*; - -/** - * The <tt>ContactInfoPanel</tt> is a popup dialog containing the contact - * detailed info. - * - * @author Yana Stamcheva - */ -public class ContactInfoDialog - extends SIPCommDialog - implements WindowFocusListener -{ - - private JPanel protocolsPanel = new TransparentPanel(new GridLayout(0, 1)); - - private TransparentBackground bg; - - /** - * Creates an instance of the <tt>ContactInfoPanel</tt>. - * - * @param owner The frame owner of this dialog. - * @param contactItem The <tt>MetaContact</tt> for the info. - */ - public ContactInfoDialog(Frame owner, MetaContact contactItem) - { - super(owner); - - this.setUndecorated(true); - - this.setModal(true); - - // Create the transparent background component - this.bg = new TransparentBackground(this); - - this.bg.setLayout(new BorderLayout()); - - this.bg.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - this.getContentPane().setLayout(new BorderLayout()); - - this.init(); - - this.getContentPane().add(bg, BorderLayout.CENTER); - - this.pack(); - - this.setSize(140, 50); - - this.addWindowFocusListener(this); - } - - /** - * Initializes the <tt>ContactInfoPanel</tt>. - */ - private void init() - { - /* - * String[] protocolList = this.contactItem.getC(); - * - * if(protocolsPanel.getComponentCount() == 0){ for(int i = 0; i < - * protocolList.length; i ++){ - * - * JLabel protocolLabel = new JLabel(protocolList[i], new - * ImageIcon(Constants.getProtocolIcon(protocolList[i])), JLabel.LEFT); - * - * this.protocolsPanel.add(protocolLabel); } } - * - * this.bg.add(protocolsPanel, BorderLayout.CENTER); - */ - } - - /** - * Returns the panel containing all contact protocols' information. - * - * @return the panel containing all contact protocols' information. - */ - public JPanel getProtocolsPanel() - { - return protocolsPanel; - } - - public void windowGainedFocus(WindowEvent e) - { - - } - - public void windowLostFocus(WindowEvent e) - { - close(false); - } - - public void setPopupLocation(int x, int y) - { - this.setLocation(x, y); - - this.bg.updateBackground(x, y); - } - - @Override - protected void close(boolean isEscaped) - { - this.setVisible(false); - this.dispose(); - } -} diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java index bfe2fd3..ae0e3b0 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java @@ -1078,7 +1078,7 @@ public class ContactListTreeCellRenderer UIContactDetail desktopContact = uiContact.getDefaultContactDetail( - OperationSetDesktopStreaming.class); + OperationSetDesktopSharingServer.class); if (desktopContact != null || (contactPhoneUtil != null diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java index cf7108f..011b00a 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,353 +15,353 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.contactlist;
-
-import java.util.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*;
-import net.java.sip.communicator.service.contactlist.*;
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.gui.event.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-/**
- * The <tt>PresenceFilter</tt> is used to filter offline contacts from the
- * contact list.
- *
- * @author Yana Stamcheva
- */
-public class PresenceFilter
- implements ContactListFilter
-{
- /**
- * The <tt>Logger</tt> used by the <tt>PresenceFilter</tt> class and its
- * instances to print debugging information.
- */
- private static final Logger logger = Logger.getLogger(PresenceFilter.class);
-
- /**
- * Indicates if this presence filter shows or hides the offline contacts.
- */
- private boolean isShowOffline;
-
- /**
- * The initial result count below which we insert all filter results
- * directly to the contact list without firing events.
- */
- private static final int INITIAL_CONTACT_COUNT = 30;
-
- /**
- * Creates an instance of <tt>PresenceFilter</tt>.
- */
- public PresenceFilter()
- {
- isShowOffline = ConfigurationUtils.isShowOffline();
- }
-
- /**
- * Applies this filter. This filter is applied over the
- * <tt>MetaContactListService</tt>.
- *
- * @param filterQuery the query which keeps track of the filtering results
- */
- public void applyFilter(FilterQuery filterQuery)
- {
- // Create the query that will track filtering.
- MetaContactQuery query = new MetaContactQuery();
-
- // Add this query to the filterQuery.
- filterQuery.addContactQuery(query);
-
- TreeContactList contactList = GuiActivator.getContactList();
-
- Collection<UIContactSource> uiContactSourceCollection
- = contactList.getContactSources(
- ContactSourceService.CONTACT_LIST_TYPE);
-
- Iterator<UIContactSource> filterSources
- = uiContactSourceCollection.iterator();
- int maxIndex = 0;
- while (filterSources.hasNext())
- {
- UIContactSource filterSource = filterSources.next();
- int currIx = filterSource.getContactSourceService().getIndex();
- if(maxIndex < currIx)
- maxIndex = currIx;
- }
-
- contactList.getMetaContactListSource().setIndex(maxIndex + 1);
-
- filterSources = uiContactSourceCollection.iterator();
- while (filterSources.hasNext())
- {
- UIContactSource filterSource = filterSources.next();
-
- filterSource.setContactSourceIndex(
- filterSource.getContactSourceService().getIndex());
-
- ContactSourceService sourceService
- = filterSource.getContactSourceService();
-
- ContactQuery contactQuery
- = sourceService.createContactQuery(null);
-
- if(contactQuery == null)
- continue;
-
- // Add this query to the filterQuery.
- filterQuery.addContactQuery(contactQuery);
-
- contactQuery.addContactQueryListener(contactList);
-
- contactQuery.start();
- }
-
- // Closes this filter to indicate that we finished adding queries to it.
- filterQuery.close();
-
- query.addContactQueryListener(GuiActivator.getContactList());
- int resultCount = 0;
-
- addMatching(GuiActivator.getContactListService().getRoot(),
- query,
- resultCount);
-
- query.fireQueryEvent(
- query.isCanceled()
- ? MetaContactQueryStatusEvent.QUERY_CANCELED
- : MetaContactQueryStatusEvent.QUERY_COMPLETED);
- }
-
- /**
- * Indicates if the given <tt>uiContact</tt> is matching this filter.
- *
- * @param uiContact the <tt>UIContact</tt> to check
- * @return <tt>true</tt> if the given <tt>uiContact</tt> is matching
- * this filter, otherwise returns <tt>false</tt>
- */
- public boolean isMatching(UIContact uiContact)
- {
- Object descriptor = uiContact.getDescriptor();
-
- if (descriptor instanceof MetaContact)
- return isMatching((MetaContact) descriptor);
- else if (descriptor instanceof SourceContact)
- return isMatching((SourceContact)descriptor);
- else
- return false;
- }
-
- /**
- * Indicates if the given <tt>uiGroup</tt> is matching this filter.
- *
- * @param uiGroup the <tt>UIGroup</tt> to check
- * @return <tt>true</tt> if the given <tt>uiGroup</tt> is matching
- * this filter, otherwise returns <tt>false</tt>
- */
- public boolean isMatching(UIGroup uiGroup)
- {
- Object descriptor = uiGroup.getDescriptor();
-
- if (descriptor instanceof MetaContactGroup)
- return isMatching((MetaContactGroup) descriptor);
- else
- return false;
- }
-
- /**
- * Sets the show offline property.
- *
- * @param isShowOffline indicates if offline contacts are shown
- */
- public void setShowOffline(boolean isShowOffline)
- {
- this.isShowOffline = isShowOffline;
-
- ConfigurationUtils.setShowOffline(isShowOffline);
- }
-
- /**
- * Returns <tt>true</tt> if offline contacts are shown, otherwise returns
- * <tt>false</tt>.
- *
- * @return <tt>true</tt> if offline contacts are shown, otherwise returns
- * <tt>false</tt>
- */
- public boolean isShowOffline()
- {
- return isShowOffline;
- }
-
- /**
- * Returns <tt>true</tt> if offline contacts are shown or if the given
- * <tt>MetaContact</tt> is online, otherwise returns false.
- *
- * @param metaContact the <tt>MetaContact</tt> to check
- * @return <tt>true</tt> if the given <tt>MetaContact</tt> is matching this
- * filter
- */
- public boolean isMatching(MetaContact metaContact)
- {
- return isShowOffline || isContactOnline(metaContact);
- }
-
- /**
- * Returns <tt>true</tt> if offline contacts are shown or if the given
- * <tt>MetaContact</tt> is online, otherwise returns false.
- *
- * @param contact the <tt>MetaContact</tt> to check
- * @return <tt>true</tt> if the given <tt>MetaContact</tt> is matching this
- * filter
- */
- public boolean isMatching(SourceContact contact)
- {
- // make sure we always show chat rooms and recent messages
- return
- isShowOffline
- || contact.getPresenceStatus().isOnline()
- || contact.getContactSource().getType()
- == ContactSourceService.CONTACT_LIST_TYPE;
- }
-
- /**
- * Returns <tt>true</tt> if offline contacts are shown or if the given
- * <tt>MetaContactGroup</tt> contains online contacts.
- *
- * @param metaGroup the <tt>MetaContactGroup</tt> to check
- * @return <tt>true</tt> if the given <tt>MetaContactGroup</tt> is matching
- * this filter
- */
- private boolean isMatching(MetaContactGroup metaGroup)
- {
- return
- isShowOffline
- || (metaGroup.countOnlineChildContacts() > 0)
- || MetaContactListSource.isNewGroup(metaGroup);
- }
-
- /**
- * Returns <tt>true</tt> if the given meta contact is online, <tt>false</tt>
- * otherwise.
- *
- * @param contact the meta contact
- * @return <tt>true</tt> if the given meta contact is online, <tt>false</tt>
- * otherwise
- */
- private boolean isContactOnline(MetaContact contact)
- {
- // If for some reason the default contact is null we return false.
- Contact defaultContact = contact.getDefaultContact();
- if(defaultContact == null)
- return false;
-
- // Lays on the fact that the default contact is the most connected.
- return defaultContact.getPresenceStatus().getStatus()
- >= PresenceStatus.ONLINE_THRESHOLD;
- }
-
- /**
- * Adds all contacts contained in the given <tt>MetaContactGroup</tt>
- * matching the current filter and not contained in the contact list.
- *
- * @param metaGroup the <tt>MetaContactGroup</tt>, which matching contacts
- * to add
- * @param query the <tt>MetaContactQuery</tt> that notifies interested
- * listeners of the results of this matching
- * @param resultCount the initial result count we would insert directly to
- * the contact list without firing events
- */
- private void addMatching( MetaContactGroup metaGroup,
- MetaContactQuery query,
- int resultCount)
- {
- Iterator<MetaContact> childContacts = metaGroup.getChildContacts();
-
- while (childContacts.hasNext() && !query.isCanceled())
- {
- MetaContact metaContact = childContacts.next();
-
- if(isMatching(metaContact))
- {
-
- resultCount++;
- if (resultCount <= INITIAL_CONTACT_COUNT)
- {
- UIGroup uiGroup = null;
-
- if (!MetaContactListSource.isRootGroup(metaGroup))
- {
- synchronized (metaGroup)
- {
- uiGroup = MetaContactListSource
- .getUIGroup(metaGroup);
- if (uiGroup == null)
- uiGroup = MetaContactListSource
- .createUIGroup(metaGroup);
- }
- }
-
- if (logger.isDebugEnabled())
- logger.debug("Presence filter contact added: "
- + metaContact.getDisplayName());
-
- UIContactImpl newUIContact;
- synchronized (metaContact)
- {
- newUIContact
- = MetaContactListSource.getUIContact(metaContact);
-
- if (newUIContact == null)
- {
- newUIContact = MetaContactListSource
- .createUIContact(metaContact);
- }
- }
-
- GuiActivator.getContactList().addContact(
- newUIContact,
- uiGroup,
- true,
- true);
-
- query.setInitialResultCount(resultCount);
- }
- else
- {
- query.fireQueryEvent(metaContact);
- }
- }
- }
-
- // If in the meantime the filtering has been stopped we return here.
- if (query.isCanceled())
- return;
-
- Iterator<MetaContactGroup> subgroups = metaGroup.getSubgroups();
- while(subgroups.hasNext() && !query.isCanceled())
- {
- MetaContactGroup subgroup = subgroups.next();
-
- if (isMatching(subgroup))
- {
- UIGroup uiGroup;
- synchronized(subgroup)
- {
- uiGroup = MetaContactListSource
- .getUIGroup(subgroup);
-
- if (uiGroup == null)
- uiGroup = MetaContactListSource
- .createUIGroup(subgroup);
- }
-
- GuiActivator.getContactList().addGroup(uiGroup, true);
-
- addMatching(subgroup, query, resultCount);
- }
- }
- }
-}
+package net.java.sip.communicator.impl.gui.main.contactlist; + +import java.util.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.gui.event.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * The <tt>PresenceFilter</tt> is used to filter offline contacts from the + * contact list. + * + * @author Yana Stamcheva + */ +public class PresenceFilter + implements ContactListFilter +{ + /** + * The <tt>Logger</tt> used by the <tt>PresenceFilter</tt> class and its + * instances to print debugging information. + */ + private static final Logger logger = Logger.getLogger(PresenceFilter.class); + + /** + * Indicates if this presence filter shows or hides the offline contacts. + */ + private boolean isShowOffline; + + /** + * The initial result count below which we insert all filter results + * directly to the contact list without firing events. + */ + private static final int INITIAL_CONTACT_COUNT = 30; + + /** + * Creates an instance of <tt>PresenceFilter</tt>. + */ + public PresenceFilter() + { + isShowOffline = ConfigurationUtils.isShowOffline(); + } + + /** + * Applies this filter. This filter is applied over the + * <tt>MetaContactListService</tt>. + * + * @param filterQuery the query which keeps track of the filtering results + */ + public void applyFilter(FilterQuery filterQuery) + { + // Create the query that will track filtering. + MetaContactQuery query = new MetaContactQuery(); + + // Add this query to the filterQuery. + filterQuery.addContactQuery(query); + + TreeContactList contactList = GuiActivator.getContactList(); + + Collection<UIContactSource> uiContactSourceCollection + = contactList.getContactSources( + ContactSourceService.CONTACT_LIST_TYPE); + + Iterator<UIContactSource> filterSources + = uiContactSourceCollection.iterator(); + int maxIndex = 0; + while (filterSources.hasNext()) + { + UIContactSource filterSource = filterSources.next(); + int currIx = filterSource.getContactSourceService().getIndex(); + if(maxIndex < currIx) + maxIndex = currIx; + } + + contactList.getMetaContactListSource().setIndex(maxIndex + 1); + + filterSources = uiContactSourceCollection.iterator(); + while (filterSources.hasNext()) + { + UIContactSource filterSource = filterSources.next(); + + filterSource.setContactSourceIndex( + filterSource.getContactSourceService().getIndex()); + + ContactSourceService sourceService + = filterSource.getContactSourceService(); + + ContactQuery contactQuery + = sourceService.createContactQuery(null); + + if(contactQuery == null) + continue; + + // Add this query to the filterQuery. + filterQuery.addContactQuery(contactQuery); + + contactQuery.addContactQueryListener(contactList); + + contactQuery.start(); + } + + // Closes this filter to indicate that we finished adding queries to it. + filterQuery.close(); + + query.addContactQueryListener(GuiActivator.getContactList()); + int resultCount = 0; + + addMatching(GuiActivator.getContactListService().getRoot(), + query, + resultCount); + + query.fireQueryEvent( + query.isCanceled() + ? MetaContactQueryStatusEvent.QUERY_CANCELED + : MetaContactQueryStatusEvent.QUERY_COMPLETED); + } + + /** + * Indicates if the given <tt>uiContact</tt> is matching this filter. + * + * @param uiContact the <tt>UIContact</tt> to check + * @return <tt>true</tt> if the given <tt>uiContact</tt> is matching + * this filter, otherwise returns <tt>false</tt> + */ + public boolean isMatching(UIContact uiContact) + { + Object descriptor = uiContact.getDescriptor(); + + if (descriptor instanceof MetaContact) + return isMatching((MetaContact) descriptor); + else if (descriptor instanceof SourceContact) + return isMatching((SourceContact)descriptor); + else + return false; + } + + /** + * Indicates if the given <tt>uiGroup</tt> is matching this filter. + * + * @param uiGroup the <tt>UIGroup</tt> to check + * @return <tt>true</tt> if the given <tt>uiGroup</tt> is matching + * this filter, otherwise returns <tt>false</tt> + */ + public boolean isMatching(UIGroup uiGroup) + { + Object descriptor = uiGroup.getDescriptor(); + + if (descriptor instanceof MetaContactGroup) + return isMatching((MetaContactGroup) descriptor); + else + return false; + } + + /** + * Sets the show offline property. + * + * @param isShowOffline indicates if offline contacts are shown + */ + public void setShowOffline(boolean isShowOffline) + { + this.isShowOffline = isShowOffline; + + ConfigurationUtils.setShowOffline(isShowOffline); + } + + /** + * Returns <tt>true</tt> if offline contacts are shown, otherwise returns + * <tt>false</tt>. + * + * @return <tt>true</tt> if offline contacts are shown, otherwise returns + * <tt>false</tt> + */ + public boolean isShowOffline() + { + return isShowOffline; + } + + /** + * Returns <tt>true</tt> if offline contacts are shown or if the given + * <tt>MetaContact</tt> is online, otherwise returns false. + * + * @param metaContact the <tt>MetaContact</tt> to check + * @return <tt>true</tt> if the given <tt>MetaContact</tt> is matching this + * filter + */ + public boolean isMatching(MetaContact metaContact) + { + return isShowOffline || isContactOnline(metaContact); + } + + /** + * Returns <tt>true</tt> if offline contacts are shown or if the given + * <tt>MetaContact</tt> is online, otherwise returns false. + * + * @param contact the <tt>MetaContact</tt> to check + * @return <tt>true</tt> if the given <tt>MetaContact</tt> is matching this + * filter + */ + public boolean isMatching(SourceContact contact) + { + // make sure we always show chat rooms and recent messages + return + isShowOffline + || contact.getPresenceStatus().isOnline() + || contact.getContactSource().getType() + == ContactSourceService.CONTACT_LIST_TYPE; + } + + /** + * Returns <tt>true</tt> if offline contacts are shown or if the given + * <tt>MetaContactGroup</tt> contains online contacts. + * + * @param metaGroup the <tt>MetaContactGroup</tt> to check + * @return <tt>true</tt> if the given <tt>MetaContactGroup</tt> is matching + * this filter + */ + private boolean isMatching(MetaContactGroup metaGroup) + { + return + isShowOffline + || (metaGroup.countOnlineChildContacts() > 0) + || MetaContactListSource.isNewGroup(metaGroup); + } + + /** + * Returns <tt>true</tt> if the given meta contact is online, <tt>false</tt> + * otherwise. + * + * @param contact the meta contact + * @return <tt>true</tt> if the given meta contact is online, <tt>false</tt> + * otherwise + */ + private boolean isContactOnline(MetaContact contact) + { + // If for some reason the default contact is null we return false. + Contact defaultContact = contact.getDefaultContact(); + if(defaultContact == null) + return false; + + // Lays on the fact that the default contact is the most connected. + return defaultContact.getPresenceStatus().getStatus() + >= PresenceStatus.ONLINE_THRESHOLD; + } + + /** + * Adds all contacts contained in the given <tt>MetaContactGroup</tt> + * matching the current filter and not contained in the contact list. + * + * @param metaGroup the <tt>MetaContactGroup</tt>, which matching contacts + * to add + * @param query the <tt>MetaContactQuery</tt> that notifies interested + * listeners of the results of this matching + * @param resultCount the initial result count we would insert directly to + * the contact list without firing events + */ + private void addMatching( MetaContactGroup metaGroup, + MetaContactQuery query, + int resultCount) + { + Iterator<MetaContact> childContacts = metaGroup.getChildContacts(); + + while (childContacts.hasNext() && !query.isCanceled()) + { + MetaContact metaContact = childContacts.next(); + + if(isMatching(metaContact)) + { + + resultCount++; + if (resultCount <= INITIAL_CONTACT_COUNT) + { + UIGroup uiGroup = null; + + if (!MetaContactListSource.isRootGroup(metaGroup)) + { + synchronized (metaGroup) + { + uiGroup = MetaContactListSource + .getUIGroup(metaGroup); + if (uiGroup == null) + uiGroup = MetaContactListSource + .createUIGroup(metaGroup); + } + } + + if (logger.isDebugEnabled()) + logger.debug("Presence filter contact added: " + + metaContact.getDisplayName()); + + UIContactImpl newUIContact; + synchronized (metaContact) + { + newUIContact + = MetaContactListSource.getUIContact(metaContact); + + if (newUIContact == null) + { + newUIContact = MetaContactListSource + .createUIContact(metaContact); + } + } + + GuiActivator.getContactList().addContact( + newUIContact, + uiGroup, + true, + true); + + query.setInitialResultCount(resultCount); + } + else + { + query.fireQueryEvent(metaContact); + } + } + } + + // If in the meantime the filtering has been stopped we return here. + if (query.isCanceled()) + return; + + Iterator<MetaContactGroup> subgroups = metaGroup.getSubgroups(); + while(subgroups.hasNext() && !query.isCanceled()) + { + MetaContactGroup subgroup = subgroups.next(); + + if (isMatching(subgroup)) + { + UIGroup uiGroup; + synchronized(subgroup) + { + uiGroup = MetaContactListSource + .getUIGroup(subgroup); + + if (uiGroup == null) + uiGroup = MetaContactListSource + .createUIGroup(subgroup); + } + + GuiActivator.getContactList().addGroup(uiGroup, true); + + addMatching(subgroup, query, resultCount); + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchFilter.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchFilter.java index 837d369..4735892 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchFilter.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchFilter.java @@ -270,14 +270,13 @@ public class SearchFilter */ private boolean isMatching(String text) { - if (filterPattern != null) - return filterPattern.matcher(text).find(); + if (filterPattern != null && filterPattern.matcher(text).find()) + return true; if(isSearchingPhoneNumber && this.filterString != null) return GuiActivator.getPhoneNumberI18nService() .phoneNumbersMatch(this.filterString, text); return true; - } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java index a916b2b..119e000 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java @@ -565,10 +565,6 @@ public class TreeContactList if (isActive) { activeContacts.add(contactNode); -// SystrayService stray = GuiActivator.getSystrayService(); -// -// if (stray != null) -// stray.setSystrayIcon(SystrayService.ENVELOPE_IMG_TYPE); } else activeContacts.remove(contactNode); diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/SourceUIContact.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/SourceUIContact.java index 72bec25..1419416 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/SourceUIContact.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/SourceUIContact.java @@ -573,7 +573,7 @@ public class SourceUIContact } else { - labelText = contactDetail.getDetail(); + labelText = contactDetail.getDisplayName(); } jLabels[i] = new JLabel(filterAddressDisplay(labelText)); @@ -709,11 +709,9 @@ public class SourceUIContact case AIM: case ICQ: case Jabber: - case MSN: case Yahoo: case Skype: case GoogleTalk: - case Facebook: label = subCategory.value(); break; default: diff --git a/src/net/java/sip/communicator/impl/gui/main/menus/MacOSXPreferencesRegistration.java b/src/net/java/sip/communicator/impl/gui/main/menus/MacOSXPreferencesRegistration.java index cfb5420..5a75ff1 100644 --- a/src/net/java/sip/communicator/impl/gui/main/menus/MacOSXPreferencesRegistration.java +++ b/src/net/java/sip/communicator/impl/gui/main/menus/MacOSXPreferencesRegistration.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,30 +15,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.menus;
-
-import com.apple.eawt.*;
-
-/**
- * @author Lubomir Marinov
- */
-public final class MacOSXPreferencesRegistration
-{
- public static boolean run(final Object userData)
- {
- Application application = Application.getApplication();
- if (application != null)
- {
- application.setPreferencesHandler(new PreferencesHandler()
- {
- public void handlePreferences(
- AppEvent.PreferencesEvent preferencesEvent)
- {
- ((ToolsMenu) userData).configActionPerformed();
- }
- });
- return true;
- }
- return false;
- }
-}
+package net.java.sip.communicator.impl.gui.main.menus; + +import com.apple.eawt.*; + +/** + * @author Lubomir Marinov + */ +public final class MacOSXPreferencesRegistration +{ + public static boolean run(final Object userData) + { + Application application = Application.getApplication(); + if (application != null) + { + application.setPreferencesHandler(new PreferencesHandler() + { + public void handlePreferences( + AppEvent.PreferencesEvent preferencesEvent) + { + ((ToolsMenu) userData).configActionPerformed(); + } + }); + return true; + } + return false; + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/menus/MacOSXQuitRegistration.java b/src/net/java/sip/communicator/impl/gui/main/menus/MacOSXQuitRegistration.java index 594623b..4b786b2 100644 --- a/src/net/java/sip/communicator/impl/gui/main/menus/MacOSXQuitRegistration.java +++ b/src/net/java/sip/communicator/impl/gui/main/menus/MacOSXQuitRegistration.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,68 +15,68 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.main.menus;
-
-import com.apple.eawt.*;
-
-/**
- * @author Lubomir Marinov
- */
-public final class MacOSXQuitRegistration
-{
- public static boolean run(final Object userData)
- {
- Application application = Application.getApplication();
- if (application != null)
- {
- application.setQuitHandler(new QuitHandler()
- {
- public void handleQuitRequestWith(AppEvent.QuitEvent quitEvent,
- final QuitResponse quitResponse)
- {
- ((FileMenu) userData).closeActionPerformed();
-
- /*
- * Tell Mac OS X that it shouldn't terminate the
- * application. We've already initiated the quit and we'll
- * eventually complete it i.e. we'll honor the request of
- * Mac OS X to quit.
- *
- * (2011-06-10) Changed to true, we tell that quit is handled
- * as otherwise will stop OS from logout or shutdown and
- * a notification will be shown to user to inform about it.
- *
- * (2011-07-12) Wait before answering to the OS or we will
- * end too quickly. 15sec is the time our shutdown timer
- * waits before force the shutdown.
- */
-
- synchronized(this)
- {
- try
- {
- wait(15000);
- }catch (InterruptedException ex){}
- }
-
- /**
- * Free the event dispatch thread before performing the
- * quit (System.exit), shutdown timer may also has started
- * the quit and is waiting to free the threads which
- * we may be blocking.
- */
- new Thread(new Runnable()
- {
- public void run()
- {
- quitResponse.performQuit();
- }
- }).start();
- }
- });
-
- return true;
- }
- return false;
- }
-}
+package net.java.sip.communicator.impl.gui.main.menus; + +import com.apple.eawt.*; + +/** + * @author Lubomir Marinov + */ +public final class MacOSXQuitRegistration +{ + public static boolean run(final Object userData) + { + Application application = Application.getApplication(); + if (application != null) + { + application.setQuitHandler(new QuitHandler() + { + public void handleQuitRequestWith(AppEvent.QuitEvent quitEvent, + final QuitResponse quitResponse) + { + ((FileMenu) userData).closeActionPerformed(); + + /* + * Tell Mac OS X that it shouldn't terminate the + * application. We've already initiated the quit and we'll + * eventually complete it i.e. we'll honor the request of + * Mac OS X to quit. + * + * (2011-06-10) Changed to true, we tell that quit is handled + * as otherwise will stop OS from logout or shutdown and + * a notification will be shown to user to inform about it. + * + * (2011-07-12) Wait before answering to the OS or we will + * end too quickly. 15sec is the time our shutdown timer + * waits before force the shutdown. + */ + + synchronized(this) + { + try + { + wait(15000); + }catch (InterruptedException ex){} + } + + /** + * Free the event dispatch thread before performing the + * quit (System.exit), shutdown timer may also has started + * the quit and is waiting to free the threads which + * we may be blocking. + */ + new Thread(new Runnable() + { + public void run() + { + quitResponse.performQuit(); + } + }).start(); + } + }); + + return true; + } + return false; + } +} diff --git a/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf b/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf index 1861c4a..2795ef9 100644 --- a/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf +++ b/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf @@ -72,12 +72,12 @@ Import-Package: com.apple.eawt, org.jitsi.service.neomedia.event, org.jitsi.service.neomedia.format, org.jitsi.service.neomedia.recording, + org.jitsi.service.neomedia.stats, org.jitsi.service.resources, org.jitsi.util, org.jitsi.util.event, org.jitsi.util.swing, org.osgi.framework, - say.swing, net.java.sip.communicator.service.credentialsstorage, net.java.sip.communicator.service.muc, net.java.sip.communicator.plugin.desktoputil.chat, diff --git a/src/net/java/sip/communicator/impl/gui/utils/PluginContainer.java b/src/net/java/sip/communicator/impl/gui/utils/PluginContainer.java index e85c51c..d24855f 100644 --- a/src/net/java/sip/communicator/impl/gui/utils/PluginContainer.java +++ b/src/net/java/sip/communicator/impl/gui/utils/PluginContainer.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,318 +15,318 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.gui.utils;
-
-import java.awt.*;
-import java.util.*;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.event.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.gui.Container;
-import net.java.sip.communicator.util.*;
-
-import org.osgi.framework.*;
-
-/**
- * Provides capabilities to a specific <code>JComponent</code> to contain
- * <code>PluginComponent</code>s, track when they are added and removed.
- *
- * @author Lyubomir Marinov
- */
-public class PluginContainer
- implements PluginComponentListener
-{
- /**
- * The <tt>Logger</tt> used by the <tt>PluginContainer</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(PluginContainer.class);
-
- /**
- * The <code>JComponent</code> which contains the components of the
- * <code>PluginComponent</code>s managed by this instance.
- */
- private final JComponent container;
-
- /**
- * The container id of the <code>PluginComponent</code> managed by this
- * instance.
- */
- private final Container containerId;
-
- /**
- * The list of <code>PluginComponent</code> instances which have their
- * components added to this <code>PluginContainer</code>.
- */
- private final java.util.List<PluginComponent> pluginComponents
- = new LinkedList<PluginComponent>();
-
- /**
- * Initializes a new <code>PluginContainer</code> instance which is to
- * provide capabilities to a specific <code>JComponent</code> container with
- * a specific <code>Container</code> id to contain
- * <code>PluginComponent</code> and track when they are added and removed.
- *
- * @param container
- * the <code>JComponent</code> container the new instance is to
- * provide its capabilities to
- * @param containerId
- * the <code>Container</code> id of the specified
- * <code>container</code>
- */
- public PluginContainer(JComponent container, Container containerId)
- {
- this.container = container;
- this.containerId = containerId;
-
- initPluginComponents();
- }
-
- /**
- * Adds a specific <tt>Component</tt> to a specific <tt>JComponent</tt>
- * container. Allows extenders to apply custom logic to the exact placement
- * of the specified <tt>Component</tt> in the specified container.
- *
- * @param component the <tt>Component</tt> to be added to the specified
- * <tt>JComponent</tt> container
- * @param container the <tt>JComponent</tt> container to add the specified
- * <tt>Component</tt> to
- * @param preferredIndex the index at which <tt>component</tt> is to be
- * added to <tt>container</tt> if possible or <tt>-1</tt> if there is no
- * preference with respect to the index in question
- */
- protected void addComponentToContainer(
- Component component,
- JComponent container,
- int preferredIndex)
- {
- if ((0 <= preferredIndex)
- && (preferredIndex < getComponentCount(container)))
- container.add(component, preferredIndex);
- else
- container.add(component);
- }
-
- /**
- * Adds the component of a specific <tt>PluginComponent</tt> to the
- * associated <tt>Container</tt>.
- *
- * @param factory the <tt>PluginComponentFactory</tt> which is to have its
- * component added to the <tt>Container</tt> associated with this
- * <tt>PluginContainer</tt>
- */
- private synchronized void addPluginComponent(PluginComponentFactory factory)
- {
- PluginComponent c =
- factory.getPluginComponentInstance(PluginContainer.this);
-
- if (logger.isInfoEnabled())
- logger.info("Will add plugin component: " + c);
-
- /*
- * Try to respect positionIndex of PluginComponent to some extent:
- * PluginComponents with positionIndex equal to 0 go at the beginning,
- * these with positionIndex equal to -1 follow them and then go these
- * with positionIndex greater than 0.
- */
- int cIndex = factory.getPositionIndex();
- int index = -1;
- int i = 0;
-
- for (PluginComponent pluginComponent : pluginComponents)
- {
- if (pluginComponent.equals(c))
- return;
-
- if (-1 == index)
- {
- int pluginComponentIndex = factory.getPositionIndex();
-
- if ((0 == cIndex) || (-1 == cIndex))
- {
- if ((0 != pluginComponentIndex)
- && (cIndex != pluginComponentIndex))
- index = i;
- }
- else if (cIndex < pluginComponentIndex)
- index = i;
- }
-
- i++;
- }
-
- int pluginComponentCount = pluginComponents.size();
-
- if (-1 == index)
- index = pluginComponents.size();
-
- /*
- * The container may have added Components of its own apart from the
- * ones this PluginContainer has added to it. Since the common case for
- * the additional Components is to have them appear at the beginning,
- * adjust the index so it gets correct in the common case.
- */
- int containerComponentCount = getComponentCount(container);
-
- addComponentToContainer(
- (Component) c.getComponent(),
- container,
- (containerComponentCount > pluginComponentCount)
- ? (index + (containerComponentCount - pluginComponentCount))
- : index);
- pluginComponents.add(index, c);
-
- container.revalidate();
- container.repaint();
- }
-
- /**
- * Runs clean-up for associated resources which need explicit disposal (e.g.
- * listeners keeping this instance alive because they were added to the
- * model which operationally outlives this instance).
- */
- public void dispose()
- {
- GuiActivator.getUIService().removePluginComponentListener(this);
-
- /*
- * Explicitly remove the components of the PluginComponent instances
- * because the latter are registered with OSGi and are thus global.
- */
- synchronized (this)
- {
- for (PluginComponent pluginComponent : pluginComponents)
- container.remove((Component) pluginComponent.getComponent());
- pluginComponents.clear();
- }
- }
-
- /**
- * Gets the number of <tt>Component</tt>s in a specific <tt>JComponent</tt>
- * container. For example, returns the result of
- * <tt>getMenuComponentCount()</tt> if <tt>container</tt> is an instance of
- * <tt>JMenu</tt>.
- *
- * @param container the <tt>JComponent</tt> container to get the number of
- * <tt>Component</tt>s of
- * @return the number of <tt>Component</tt>s in the specified
- * <tt>container</tt>
- */
- protected int getComponentCount(JComponent container)
- {
- return
- (container instanceof JMenu)
- ? ((JMenu) container).getMenuComponentCount()
- : container.getComponentCount();
- }
-
- /**
- * Gets the <tt>PluginComponent</tt>s of this <tt>PluginContainer</tt>.
- *
- * @return an <tt>Iterable</tt> over the <tt>PluginComponent</tt>s of this
- * <tt>PluginContainer</tt>
- */
- public Iterable<PluginComponent> getPluginComponents()
- {
- return pluginComponents;
- }
-
- /**
- * Adds the <tt>Component</tt>s of the <tt>PluginComponent</tt>s registered
- * in the OSGi <tt>BundleContext</tt> in the associated <tt>Container</tt>.
- */
- private void initPluginComponents()
- {
- GuiActivator.getUIService().addPluginComponentListener(this);
-
- // Look for PluginComponents registered in the OSGi BundleContext.
- ServiceReference[] serRefs = null;
-
- try
- {
- serRefs
- = GuiActivator
- .bundleContext
- .getServiceReferences(
- PluginComponentFactory.class.getName(),
- "("
- + Container.CONTAINER_ID
- + "="
- + containerId.getID()
- + ")");
- }
- catch (InvalidSyntaxException exc)
- {
- logger.error("Could not obtain plugin reference.", exc);
- }
-
- if (serRefs != null)
- {
- for (ServiceReference serRef : serRefs)
- {
- PluginComponentFactory factory
- = (PluginComponentFactory)
- GuiActivator.bundleContext.getService(serRef);
-
- addPluginComponent(factory);
- }
- }
- }
-
- /**
- * Implements
- * {@link PluginComponentListener#pluginComponentAdded(PluginComponentEvent)}.
- *
- * @param event a <tt>PluginComponentEvent</tt> which specifies the
- * <tt>PluginComponent</tt> which has been added
- */
- public void pluginComponentAdded(PluginComponentEvent event)
- {
- PluginComponentFactory factory = event.getPluginComponentFactory();
-
- if (factory.getContainer().equals(containerId))
- addPluginComponent(factory);
- }
-
- /**
- * Implements
- * {@link PluginComponentListener#pluginComponentRemoved(PluginComponentEvent)}.
- *
- * @param event a <tt>PluginComponentEvent</tt> which specifies the
- * <tt>PluginComponent</tt> which has been added
- */
- public void pluginComponentRemoved(PluginComponentEvent event)
- {
- PluginComponentFactory factory = event.getPluginComponentFactory();
-
- if (factory.getContainer().equals(containerId))
- removePluginComponent(factory);
- }
-
- /**
- * Removes the component of a specific <code>PluginComponent</code> from
- * this <code>PluginContainer</code>.
- *
- * @param factory
- * the <code>PluginComponent</code> which is to have its
- * component removed from this <code>PluginContainer</code>
- */
- private synchronized void removePluginComponent(
- PluginComponentFactory factory)
- {
- Iterator<PluginComponent> iterator = pluginComponents.iterator();
- while(iterator.hasNext())
- {
- PluginComponent c = iterator.next();
- if(c.getParentFactory().equals(factory))
- {
- iterator.remove();
- container.remove((Component)c.getComponent());
- }
- }
- }
-}
+package net.java.sip.communicator.impl.gui.utils; + +import java.awt.*; +import java.util.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.event.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.gui.Container; +import net.java.sip.communicator.util.*; + +import org.osgi.framework.*; + +/** + * Provides capabilities to a specific <code>JComponent</code> to contain + * <code>PluginComponent</code>s, track when they are added and removed. + * + * @author Lyubomir Marinov + */ +public class PluginContainer + implements PluginComponentListener +{ + /** + * The <tt>Logger</tt> used by the <tt>PluginContainer</tt> class and its + * instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(PluginContainer.class); + + /** + * The <code>JComponent</code> which contains the components of the + * <code>PluginComponent</code>s managed by this instance. + */ + private final JComponent container; + + /** + * The container id of the <code>PluginComponent</code> managed by this + * instance. + */ + private final Container containerId; + + /** + * The list of <code>PluginComponent</code> instances which have their + * components added to this <code>PluginContainer</code>. + */ + private final java.util.List<PluginComponent> pluginComponents + = new LinkedList<PluginComponent>(); + + /** + * Initializes a new <code>PluginContainer</code> instance which is to + * provide capabilities to a specific <code>JComponent</code> container with + * a specific <code>Container</code> id to contain + * <code>PluginComponent</code> and track when they are added and removed. + * + * @param container + * the <code>JComponent</code> container the new instance is to + * provide its capabilities to + * @param containerId + * the <code>Container</code> id of the specified + * <code>container</code> + */ + public PluginContainer(JComponent container, Container containerId) + { + this.container = container; + this.containerId = containerId; + + initPluginComponents(); + } + + /** + * Adds a specific <tt>Component</tt> to a specific <tt>JComponent</tt> + * container. Allows extenders to apply custom logic to the exact placement + * of the specified <tt>Component</tt> in the specified container. + * + * @param component the <tt>Component</tt> to be added to the specified + * <tt>JComponent</tt> container + * @param container the <tt>JComponent</tt> container to add the specified + * <tt>Component</tt> to + * @param preferredIndex the index at which <tt>component</tt> is to be + * added to <tt>container</tt> if possible or <tt>-1</tt> if there is no + * preference with respect to the index in question + */ + protected void addComponentToContainer( + Component component, + JComponent container, + int preferredIndex) + { + if ((0 <= preferredIndex) + && (preferredIndex < getComponentCount(container))) + container.add(component, preferredIndex); + else + container.add(component); + } + + /** + * Adds the component of a specific <tt>PluginComponent</tt> to the + * associated <tt>Container</tt>. + * + * @param factory the <tt>PluginComponentFactory</tt> which is to have its + * component added to the <tt>Container</tt> associated with this + * <tt>PluginContainer</tt> + */ + private synchronized void addPluginComponent(PluginComponentFactory factory) + { + PluginComponent c = + factory.getPluginComponentInstance(PluginContainer.this); + + if (logger.isInfoEnabled()) + logger.info("Will add plugin component: " + c); + + /* + * Try to respect positionIndex of PluginComponent to some extent: + * PluginComponents with positionIndex equal to 0 go at the beginning, + * these with positionIndex equal to -1 follow them and then go these + * with positionIndex greater than 0. + */ + int cIndex = factory.getPositionIndex(); + int index = -1; + int i = 0; + + for (PluginComponent pluginComponent : pluginComponents) + { + if (pluginComponent.equals(c)) + return; + + if (-1 == index) + { + int pluginComponentIndex = factory.getPositionIndex(); + + if ((0 == cIndex) || (-1 == cIndex)) + { + if ((0 != pluginComponentIndex) + && (cIndex != pluginComponentIndex)) + index = i; + } + else if (cIndex < pluginComponentIndex) + index = i; + } + + i++; + } + + int pluginComponentCount = pluginComponents.size(); + + if (-1 == index) + index = pluginComponents.size(); + + /* + * The container may have added Components of its own apart from the + * ones this PluginContainer has added to it. Since the common case for + * the additional Components is to have them appear at the beginning, + * adjust the index so it gets correct in the common case. + */ + int containerComponentCount = getComponentCount(container); + + addComponentToContainer( + (Component) c.getComponent(), + container, + (containerComponentCount > pluginComponentCount) + ? (index + (containerComponentCount - pluginComponentCount)) + : index); + pluginComponents.add(index, c); + + container.revalidate(); + container.repaint(); + } + + /** + * Runs clean-up for associated resources which need explicit disposal (e.g. + * listeners keeping this instance alive because they were added to the + * model which operationally outlives this instance). + */ + public void dispose() + { + GuiActivator.getUIService().removePluginComponentListener(this); + + /* + * Explicitly remove the components of the PluginComponent instances + * because the latter are registered with OSGi and are thus global. + */ + synchronized (this) + { + for (PluginComponent pluginComponent : pluginComponents) + container.remove((Component) pluginComponent.getComponent()); + pluginComponents.clear(); + } + } + + /** + * Gets the number of <tt>Component</tt>s in a specific <tt>JComponent</tt> + * container. For example, returns the result of + * <tt>getMenuComponentCount()</tt> if <tt>container</tt> is an instance of + * <tt>JMenu</tt>. + * + * @param container the <tt>JComponent</tt> container to get the number of + * <tt>Component</tt>s of + * @return the number of <tt>Component</tt>s in the specified + * <tt>container</tt> + */ + protected int getComponentCount(JComponent container) + { + return + (container instanceof JMenu) + ? ((JMenu) container).getMenuComponentCount() + : container.getComponentCount(); + } + + /** + * Gets the <tt>PluginComponent</tt>s of this <tt>PluginContainer</tt>. + * + * @return an <tt>Iterable</tt> over the <tt>PluginComponent</tt>s of this + * <tt>PluginContainer</tt> + */ + public Iterable<PluginComponent> getPluginComponents() + { + return pluginComponents; + } + + /** + * Adds the <tt>Component</tt>s of the <tt>PluginComponent</tt>s registered + * in the OSGi <tt>BundleContext</tt> in the associated <tt>Container</tt>. + */ + private void initPluginComponents() + { + GuiActivator.getUIService().addPluginComponentListener(this); + + // Look for PluginComponents registered in the OSGi BundleContext. + ServiceReference[] serRefs = null; + + try + { + serRefs + = GuiActivator + .bundleContext + .getServiceReferences( + PluginComponentFactory.class.getName(), + "(" + + Container.CONTAINER_ID + + "=" + + containerId.getID() + + ")"); + } + catch (InvalidSyntaxException exc) + { + logger.error("Could not obtain plugin reference.", exc); + } + + if (serRefs != null) + { + for (ServiceReference serRef : serRefs) + { + PluginComponentFactory factory + = (PluginComponentFactory) + GuiActivator.bundleContext.getService(serRef); + + addPluginComponent(factory); + } + } + } + + /** + * Implements + * {@link PluginComponentListener#pluginComponentAdded(PluginComponentEvent)}. + * + * @param event a <tt>PluginComponentEvent</tt> which specifies the + * <tt>PluginComponent</tt> which has been added + */ + public void pluginComponentAdded(PluginComponentEvent event) + { + PluginComponentFactory factory = event.getPluginComponentFactory(); + + if (factory.getContainer().equals(containerId)) + addPluginComponent(factory); + } + + /** + * Implements + * {@link PluginComponentListener#pluginComponentRemoved(PluginComponentEvent)}. + * + * @param event a <tt>PluginComponentEvent</tt> which specifies the + * <tt>PluginComponent</tt> which has been added + */ + public void pluginComponentRemoved(PluginComponentEvent event) + { + PluginComponentFactory factory = event.getPluginComponentFactory(); + + if (factory.getContainer().equals(containerId)) + removePluginComponent(factory); + } + + /** + * Removes the component of a specific <code>PluginComponent</code> from + * this <code>PluginContainer</code>. + * + * @param factory + * the <code>PluginComponent</code> which is to have its + * component removed from this <code>PluginContainer</code> + */ + private synchronized void removePluginComponent( + PluginComponentFactory factory) + { + Iterator<PluginComponent> iterator = pluginComponents.iterator(); + while(iterator.hasNext()) + { + PluginComponent c = iterator.next(); + if(c.getParentFactory().equals(factory)) + { + iterator.remove(); + container.remove((Component)c.getComponent()); + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/hid/HIDServiceImpl.java b/src/net/java/sip/communicator/impl/hid/HIDServiceImpl.java index 00046d7..02552db 100644 --- a/src/net/java/sip/communicator/impl/hid/HIDServiceImpl.java +++ b/src/net/java/sip/communicator/impl/hid/HIDServiceImpl.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,223 +15,223 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -
-package net.java.sip.communicator.impl.hid;
-
-import java.awt.*;
-import java.awt.event.*;
-
-import net.java.sip.communicator.service.hid.*;
-import net.java.sip.communicator.util.Logger;
-
-import org.jitsi.util.*;
-
-/**
- * Implementation of the HID service to provide way of regenerate key press
- * and mouse interactions.
- *
- * @author Sebastien Vincent
- */
-public class HIDServiceImpl implements HIDService
-{
- /**
- * The <tt>Logger</tt> used by the <tt>NeomediaActivator</tt> class and its
- * instances for logging output.
- */
- private final Logger logger = Logger.getLogger(HIDServiceImpl.class);
-
- /**
- * The robot used to perform some operations (mouse/key interactions).
- */
- private Robot robot = null;
-
- /**
- * Object to regenerates keys with JNI.
- */
- private NativeKeyboard nativeKeyboard = null;
-
- /**
- * Constructor.
- */
- protected HIDServiceImpl()
- {
- try
- {
- robot = new Robot();
- nativeKeyboard = new NativeKeyboard();
- }
- catch(Throwable e)
- {
- if(!GraphicsEnvironment.isHeadless())
- logger.error(
- "Error when creating Robot/NativeKeyboard instance", e);
- }
- }
-
- /**
- * Press a specific key using its keycode.
- *
- * @param keycode the Java keycode, all available keycodes can be found
- * in java.awt.event.KeyEvent class (VK_A, VK_SPACE, ...)
- * @see java.awt.event.KeyEvent
- */
- public void keyPress(int keycode)
- {
- if(OSUtils.IS_WINDOWS || OSUtils.IS_MAC)
- {
- /* do not allow modifiers for Windows (as
- * they are handled in native code with
- * VkScanCode) and Mac OS X
- */
- if(keycode == KeyEvent.VK_ALT ||
- keycode == KeyEvent.VK_SHIFT ||
- keycode == KeyEvent.VK_ALT_GRAPH)
- {
- return;
- }
- }
-
- /* AltGr does not seems to work with robot, handle it via our
- * JNI code
- */
- if(keycode == KeyEvent.VK_ALT_GRAPH)
- {
- symbolPress("altgr");
- }
- else
- {
- robot.keyPress(keycode);
- }
- }
-
- /**
- * Release a specific key using its keycode.
- *
- * @param keycode the Java keycode, all available keycode can be found
- * in java.awt.event.KeyEvent class (VK_A, VK_SPACE, ...)
- * @see java.awt.event.KeyEvent
- */
- public void keyRelease(int keycode)
- {
- /* AltGr does not seems to work with robot, handle it via our
- * JNI code
- */
- if(keycode == KeyEvent.VK_ALT_GRAPH)
- {
- symbolRelease("altgr");
- }
- else
- {
- robot.keyRelease(keycode);
- }
- }
-
- /**
- * Press a specific key using its char representation.
- *
- * @param key char representation of the key
- */
- public void keyPress(char key)
- {
- /* check for CTRL+X where X is [A-Z]
- * CTRL+A = 1, A = 65
- */
- if(key >= 1 && key <= 0x1A)
- {
- key = (char)(key + 64);
- robot.keyPress(key);
- return;
- }
-
- nativeKeyboard.keyPress(key);
- }
-
- /**
- * Release a specific key using its char representation.
- *
- * @param key char representation of the key
- */
- public void keyRelease(char key)
- {
- /* check for CTRL+X where X is [A-Z]
- * CTRL+A = 1, A = 65
- */
- if(key >= 1 && key <= 0x1A)
- {
- key = (char)(key + 64);
- robot.keyRelease(key);
- return;
- }
-
- if(nativeKeyboard != null)
- nativeKeyboard.keyRelease(key);
- }
-
- /**
- * Press a specific symbol (such as SHIFT or CTRL).
- *
- * @param symbol symbol name
- */
- private void symbolPress(String symbol)
- {
- if(nativeKeyboard != null)
- nativeKeyboard.symbolPress(symbol);
- }
-
- /**
- * Release a specific symbol (such as SHIFT or CTRL).
- *
- * @param symbol symbol name
- */
- private void symbolRelease(String symbol)
- {
- if(nativeKeyboard != null)
- nativeKeyboard.symbolRelease(symbol);
- }
-
- /**
- * Press a mouse button(s).
- *
- * @param btns button masks
- * @see java.awt.Robot#mousePress(int btns)
- */
- public void mousePress(int btns)
- {
- robot.mousePress(btns);
- }
-
- /**
- * Release a mouse button(s).
- *
- * @param btns button masks
- * @see java.awt.Robot#mouseRelease(int btns)
- */
- public void mouseRelease(int btns)
- {
- robot.mouseRelease(btns);
- }
-
- /**
- * Move the mouse on the screen.
- *
- * @param x x position on the screen
- * @param y y position on the screen
- * @see java.awt.Robot#mouseMove(int x, int y)
- */
- public void mouseMove(int x, int y)
- {
- robot.mouseMove(x, y);
- }
-
- /**
- * Release a mouse button(s).
- *
- * @param rotation wheel rotation (could be negative or positive depending
- * on the direction).
- * @see java.awt.Robot#mouseWheel(int wheelAmt)
- */
- public void mouseWheel(int rotation)
- {
- robot.mouseWheel(rotation);
- }
-}
+ +package net.java.sip.communicator.impl.hid; + +import java.awt.*; +import java.awt.event.*; + +import net.java.sip.communicator.service.hid.*; +import net.java.sip.communicator.util.Logger; + +import org.jitsi.util.*; + +/** + * Implementation of the HID service to provide way of regenerate key press + * and mouse interactions. + * + * @author Sebastien Vincent + */ +public class HIDServiceImpl implements HIDService +{ + /** + * The <tt>Logger</tt> used by the <tt>NeomediaActivator</tt> class and its + * instances for logging output. + */ + private final Logger logger = Logger.getLogger(HIDServiceImpl.class); + + /** + * The robot used to perform some operations (mouse/key interactions). + */ + private Robot robot = null; + + /** + * Object to regenerates keys with JNI. + */ + private NativeKeyboard nativeKeyboard = null; + + /** + * Constructor. + */ + protected HIDServiceImpl() + { + try + { + robot = new Robot(); + nativeKeyboard = new NativeKeyboard(); + } + catch(Throwable e) + { + if(!GraphicsEnvironment.isHeadless()) + logger.error( + "Error when creating Robot/NativeKeyboard instance", e); + } + } + + /** + * Press a specific key using its keycode. + * + * @param keycode the Java keycode, all available keycodes can be found + * in java.awt.event.KeyEvent class (VK_A, VK_SPACE, ...) + * @see java.awt.event.KeyEvent + */ + public void keyPress(int keycode) + { + if(OSUtils.IS_WINDOWS || OSUtils.IS_MAC) + { + /* do not allow modifiers for Windows (as + * they are handled in native code with + * VkScanCode) and Mac OS X + */ + if(keycode == KeyEvent.VK_ALT || + keycode == KeyEvent.VK_SHIFT || + keycode == KeyEvent.VK_ALT_GRAPH) + { + return; + } + } + + /* AltGr does not seems to work with robot, handle it via our + * JNI code + */ + if(keycode == KeyEvent.VK_ALT_GRAPH) + { + symbolPress("altgr"); + } + else + { + robot.keyPress(keycode); + } + } + + /** + * Release a specific key using its keycode. + * + * @param keycode the Java keycode, all available keycode can be found + * in java.awt.event.KeyEvent class (VK_A, VK_SPACE, ...) + * @see java.awt.event.KeyEvent + */ + public void keyRelease(int keycode) + { + /* AltGr does not seems to work with robot, handle it via our + * JNI code + */ + if(keycode == KeyEvent.VK_ALT_GRAPH) + { + symbolRelease("altgr"); + } + else + { + robot.keyRelease(keycode); + } + } + + /** + * Press a specific key using its char representation. + * + * @param key char representation of the key + */ + public void keyPress(char key) + { + /* check for CTRL+X where X is [A-Z] + * CTRL+A = 1, A = 65 + */ + if(key >= 1 && key <= 0x1A) + { + key = (char)(key + 64); + robot.keyPress(key); + return; + } + + nativeKeyboard.keyPress(key); + } + + /** + * Release a specific key using its char representation. + * + * @param key char representation of the key + */ + public void keyRelease(char key) + { + /* check for CTRL+X where X is [A-Z] + * CTRL+A = 1, A = 65 + */ + if(key >= 1 && key <= 0x1A) + { + key = (char)(key + 64); + robot.keyRelease(key); + return; + } + + if(nativeKeyboard != null) + nativeKeyboard.keyRelease(key); + } + + /** + * Press a specific symbol (such as SHIFT or CTRL). + * + * @param symbol symbol name + */ + private void symbolPress(String symbol) + { + if(nativeKeyboard != null) + nativeKeyboard.symbolPress(symbol); + } + + /** + * Release a specific symbol (such as SHIFT or CTRL). + * + * @param symbol symbol name + */ + private void symbolRelease(String symbol) + { + if(nativeKeyboard != null) + nativeKeyboard.symbolRelease(symbol); + } + + /** + * Press a mouse button(s). + * + * @param btns button masks + * @see java.awt.Robot#mousePress(int btns) + */ + public void mousePress(int btns) + { + robot.mousePress(btns); + } + + /** + * Release a mouse button(s). + * + * @param btns button masks + * @see java.awt.Robot#mouseRelease(int btns) + */ + public void mouseRelease(int btns) + { + robot.mouseRelease(btns); + } + + /** + * Move the mouse on the screen. + * + * @param x x position on the screen + * @param y y position on the screen + * @see java.awt.Robot#mouseMove(int x, int y) + */ + public void mouseMove(int x, int y) + { + robot.mouseMove(x, y); + } + + /** + * Release a mouse button(s). + * + * @param rotation wheel rotation (could be negative or positive depending + * on the direction). + * @see java.awt.Robot#mouseWheel(int wheelAmt) + */ + public void mouseWheel(int rotation) + { + robot.mouseWheel(rotation); + } +} diff --git a/src/net/java/sip/communicator/impl/ldap/LdapPersonFoundImpl.java b/src/net/java/sip/communicator/impl/ldap/LdapPersonFoundImpl.java index 3d8e6bf..36632fa 100644 --- a/src/net/java/sip/communicator/impl/ldap/LdapPersonFoundImpl.java +++ b/src/net/java/sip/communicator/impl/ldap/LdapPersonFoundImpl.java @@ -433,4 +433,10 @@ public class LdapPersonFoundImpl return this.toString().equals(o.toString()) && this.getDN().equals(((LdapPersonFound) o).getDN()); } + + @Override + public int hashCode() + { + return Objects.hash(toString(), getDN()); + } } diff --git a/src/net/java/sip/communicator/impl/ldap/LdapSSLSocketFactoryDelegate.java b/src/net/java/sip/communicator/impl/ldap/LdapSSLSocketFactoryDelegate.java index dabb4c6..9888628 100644 --- a/src/net/java/sip/communicator/impl/ldap/LdapSSLSocketFactoryDelegate.java +++ b/src/net/java/sip/communicator/impl/ldap/LdapSSLSocketFactoryDelegate.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,75 +15,75 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.ldap;
-
-import java.io.*;
-import java.net.*;
-import java.security.*;
-
-import javax.net.*;
-
-import net.java.sip.communicator.service.certificate.*;
-import net.java.sip.communicator.util.*;
-
-/**
- * Utility class to delegate the creation of sockets to LDAP servers to our
- * {@link CertificateService}.
- * <p>
- * Note that the documentation says to extend {@link SocketFactory}, but the
- * LDAP directory context tries to create an unconnected socket without a
- * hostname first by calling <tt>createSocket</tt>. It would be impossible to
- * validate the hostname against the certificate, which leads to an insecure
- * communication. It only calls {@link #createSocket(String, int)} when
- * <tt>createSocket</tt> is not found
- *
- * @author Ingo Bauersachs
- */
-public class LdapSSLSocketFactoryDelegate
-{
- /**
- * Logger for this class.
- */
- private final static Logger logger =
- Logger.getLogger(LdapSSLSocketFactoryDelegate.class);
-
- /**
- * Get default SSL socket factory delegate.
- *
- * @return default SSL socket factory delegate.
- */
- public static Object getDefault()
- {
- return new LdapSSLSocketFactoryDelegate();
- }
-
- /**
- * Creates a socket for the specified destination host and port.
- *
- * @param host The hostname that the socket connects to.
- * @param port The port that the socket connects to.
- * @return The created socket.
- * @throws IOException
- * @throws UnknownHostException When the hostname cannot be resolved to an
- * IP address.
- */
- public Socket createSocket(String host, int port)
- throws IOException,
- UnknownHostException
- {
- try
- {
- return LdapServiceImpl
- .getCertificateService()
- .getSSLContext(
- LdapServiceImpl.getCertificateService().getTrustManager(
- host)).getSocketFactory().createSocket(host, port);
- }
- catch (GeneralSecurityException e)
- {
- logger.error(
- "unable to create socket through the certificate service", e);
- throw new IOException(e.getMessage());
- }
- }
-}
+package net.java.sip.communicator.impl.ldap; + +import java.io.*; +import java.net.*; +import java.security.*; + +import javax.net.*; + +import net.java.sip.communicator.service.certificate.*; +import net.java.sip.communicator.util.*; + +/** + * Utility class to delegate the creation of sockets to LDAP servers to our + * {@link CertificateService}. + * <p> + * Note that the documentation says to extend {@link SocketFactory}, but the + * LDAP directory context tries to create an unconnected socket without a + * hostname first by calling <tt>createSocket</tt>. It would be impossible to + * validate the hostname against the certificate, which leads to an insecure + * communication. It only calls {@link #createSocket(String, int)} when + * <tt>createSocket</tt> is not found + * + * @author Ingo Bauersachs + */ +public class LdapSSLSocketFactoryDelegate +{ + /** + * Logger for this class. + */ + private final static Logger logger = + Logger.getLogger(LdapSSLSocketFactoryDelegate.class); + + /** + * Get default SSL socket factory delegate. + * + * @return default SSL socket factory delegate. + */ + public static Object getDefault() + { + return new LdapSSLSocketFactoryDelegate(); + } + + /** + * Creates a socket for the specified destination host and port. + * + * @param host The hostname that the socket connects to. + * @param port The port that the socket connects to. + * @return The created socket. + * @throws IOException + * @throws UnknownHostException When the hostname cannot be resolved to an + * IP address. + */ + public Socket createSocket(String host, int port) + throws IOException, + UnknownHostException + { + try + { + return LdapServiceImpl + .getCertificateService() + .getSSLContext( + LdapServiceImpl.getCertificateService().getTrustManager( + host)).getSocketFactory().createSocket(host, port); + } + catch (GeneralSecurityException e) + { + logger.error( + "unable to create socket through the certificate service", e); + throw new IOException(e.getMessage()); + } + } +} diff --git a/src/net/java/sip/communicator/impl/muc/BaseChatRoomSourceContact.java b/src/net/java/sip/communicator/impl/muc/BaseChatRoomSourceContact.java index 3225af9..28f43e3 100644 --- a/src/net/java/sip/communicator/impl/muc/BaseChatRoomSourceContact.java +++ b/src/net/java/sip/communicator/impl/muc/BaseChatRoomSourceContact.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,141 +15,141 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.muc;
-
-import java.util.*;
-
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.muc.*;
-import net.java.sip.communicator.service.protocol.*;
-
-/**
- * Basic source contact for the chat rooms.
- *
- * @author Hristo Terezov
- */
-public class BaseChatRoomSourceContact
- extends SortedGenericSourceContact
-{
-
- /**
- * The parent contact query.
- */
- protected final ContactQuery parentQuery;
-
- /**
- * The name of the chat room associated with the contact.
- */
- private String chatRoomName;
-
- /**
- * The ID of the chat room associated with the contact.
- */
- private String chatRoomID;
-
- /**
- * The protocol provider of the chat room associated with the contact.
- */
- private ProtocolProviderService provider;
-
- /**
- * Contsructs new chat room source contact.
- * @param chatRoomName the name of the chat room associated with the room.
- * @param chatRoomID the id of the chat room associated with the room.
- * @param query the query associated with the contact.
- * @param pps the protocol provider of the contact.
- */
- public BaseChatRoomSourceContact(String chatRoomName,
- String chatRoomID, ContactQuery query, ProtocolProviderService pps)
- {
- super(query, query.getContactSource(), chatRoomName,
- generateDefaultContactDetails(chatRoomName));
-
- this.chatRoomName = chatRoomName;
- this.chatRoomID = chatRoomID;
- this.provider = pps;
- this.parentQuery = query;
-
- initContactProperties(ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE);
- setDisplayDetails(pps.getAccountID().getDisplayName());
- }
-
-
- /**
- * Sets the given presence status and the name of the chat room associated
- * with the contact.
- * @param status the presence status to be set.
- */
- protected void initContactProperties(PresenceStatus status)
- {
- setPresenceStatus(status);
- setContactAddress(chatRoomName);
- }
-
- /**
- * Generates the default contact details for
- * <tt>BaseChatRoomSourceContact</tt> instances.
- *
- * @param chatRoomName the name of the chat room associated with the contact
- * @return list of default <tt>ContactDetail</tt>s for the contact.
- */
- private static List<ContactDetail> generateDefaultContactDetails(
- String chatRoomName)
- {
- ContactDetail contactDetail
- = new ContactDetail(chatRoomName);
- List<Class<? extends OperationSet>> supportedOpSets
- = new ArrayList<Class<? extends OperationSet>>();
-
- supportedOpSets.add(OperationSetMultiUserChat.class);
- contactDetail.setSupportedOpSets(supportedOpSets);
-
- List<ContactDetail> contactDetails
- = new ArrayList<ContactDetail>();
-
- contactDetails.add(contactDetail);
- return contactDetails;
- }
-
- /**
- * Returns the id of the chat room associated with the contact.
- *
- * @return the chat room id.
- */
- public String getChatRoomID()
- {
- return chatRoomID;
- }
-
- /**
- * Returns the name of the chat room associated with the contact.
- *
- * @return the chat room name
- */
- public String getChatRoomName()
- {
- return chatRoomName;
- }
-
- /**
- * Returns the provider of the chat room associated with the contact.
- *
- * @return the provider
- */
- public ProtocolProviderService getProvider()
- {
- return provider;
- }
-
- /**
- * Returns the index of this source contact in its parent group.
- *
- * @return the index of this contact in its parent
- */
- public int getIndex()
- {
- if(parentQuery instanceof ServerChatRoomQuery)
- return ((ServerChatRoomQuery)parentQuery).indexOf(this);
- return -1;
- }
-}
+package net.java.sip.communicator.impl.muc; + +import java.util.*; + +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.muc.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * Basic source contact for the chat rooms. + * + * @author Hristo Terezov + */ +public class BaseChatRoomSourceContact + extends SortedGenericSourceContact +{ + + /** + * The parent contact query. + */ + protected final ContactQuery parentQuery; + + /** + * The name of the chat room associated with the contact. + */ + private String chatRoomName; + + /** + * The ID of the chat room associated with the contact. + */ + private String chatRoomID; + + /** + * The protocol provider of the chat room associated with the contact. + */ + private ProtocolProviderService provider; + + /** + * Contsructs new chat room source contact. + * @param chatRoomName the name of the chat room associated with the room. + * @param chatRoomID the id of the chat room associated with the room. + * @param query the query associated with the contact. + * @param pps the protocol provider of the contact. + */ + public BaseChatRoomSourceContact(String chatRoomName, + String chatRoomID, ContactQuery query, ProtocolProviderService pps) + { + super(query, query.getContactSource(), chatRoomName, + generateDefaultContactDetails(chatRoomName)); + + this.chatRoomName = chatRoomName; + this.chatRoomID = chatRoomID; + this.provider = pps; + this.parentQuery = query; + + initContactProperties(ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE); + setDisplayDetails(pps.getAccountID().getDisplayName()); + } + + + /** + * Sets the given presence status and the name of the chat room associated + * with the contact. + * @param status the presence status to be set. + */ + protected void initContactProperties(PresenceStatus status) + { + setPresenceStatus(status); + setContactAddress(chatRoomName); + } + + /** + * Generates the default contact details for + * <tt>BaseChatRoomSourceContact</tt> instances. + * + * @param chatRoomName the name of the chat room associated with the contact + * @return list of default <tt>ContactDetail</tt>s for the contact. + */ + private static List<ContactDetail> generateDefaultContactDetails( + String chatRoomName) + { + ContactDetail contactDetail + = new ContactDetail(chatRoomName); + List<Class<? extends OperationSet>> supportedOpSets + = new ArrayList<Class<? extends OperationSet>>(); + + supportedOpSets.add(OperationSetMultiUserChat.class); + contactDetail.setSupportedOpSets(supportedOpSets); + + List<ContactDetail> contactDetails + = new ArrayList<ContactDetail>(); + + contactDetails.add(contactDetail); + return contactDetails; + } + + /** + * Returns the id of the chat room associated with the contact. + * + * @return the chat room id. + */ + public String getChatRoomID() + { + return chatRoomID; + } + + /** + * Returns the name of the chat room associated with the contact. + * + * @return the chat room name + */ + public String getChatRoomName() + { + return chatRoomName; + } + + /** + * Returns the provider of the chat room associated with the contact. + * + * @return the provider + */ + public ProtocolProviderService getProvider() + { + return provider; + } + + /** + * Returns the index of this source contact in its parent group. + * + * @return the index of this contact in its parent + */ + public int getIndex() + { + if(parentQuery instanceof ServerChatRoomQuery) + return ((ServerChatRoomQuery)parentQuery).indexOf(this); + return -1; + } +} diff --git a/src/net/java/sip/communicator/impl/muc/ChatRoomQuery.java b/src/net/java/sip/communicator/impl/muc/ChatRoomQuery.java index 24a3ebd..9cd1c21 100644 --- a/src/net/java/sip/communicator/impl/muc/ChatRoomQuery.java +++ b/src/net/java/sip/communicator/impl/muc/ChatRoomQuery.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,543 +15,543 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.muc;
-
-import java.util.*;
-import java.util.regex.*;
-
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.muc.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-
-import org.osgi.framework.*;
-
-/**
- * The <tt>ChatRoomQuery</tt> is a query over the
- * <tt>ChatRoomContactSourceService</tt>.
- *
- * @author Hristo Terezov
- */
-public class ChatRoomQuery
- extends AsyncContactQuery<ContactSourceService>
- implements LocalUserChatRoomPresenceListener,
- ChatRoomListChangeListener,
- ChatRoomProviderWrapperListener
-{
- /**
- * The query string.
- */
- private String queryString;
-
- /**
- * List with the current results for the query.
- */
- private Set<ChatRoomSourceContact> contactResults
- = new TreeSet<ChatRoomSourceContact>();
-
- /**
- * MUC service.
- */
- private MUCServiceImpl mucService;
-
- /**
- * The number of contact query listeners.
- */
- private int contactQueryListenersCount = 0;
-
- /**
- * The protocol provider registration listener.
- */
- private ServiceListener protolProviderRegistrationListener = null;
-
- /**
- * Creates an instance of <tt>ChatRoomQuery</tt> by specifying
- * the parent contact source, the query string to match and the maximum
- * result contacts to return.
- *
- * @param queryString the query string to match
- * @param contactSource the parent contact source
- */
- public ChatRoomQuery(String queryString,
- ChatRoomContactSourceService contactSource)
- {
- super(contactSource,
- Pattern.compile(queryString, Pattern.CASE_INSENSITIVE
- | Pattern.LITERAL), true);
- this.queryString = queryString;
-
- mucService = MUCActivator.getMUCService();
-
- }
-
- /**
- * Adds listeners for the query
- */
- private void initListeners()
- {
- for(ProtocolProviderService pps : MUCActivator.getChatRoomProviders())
- {
- addQueryToProviderPresenceListeners(pps);
- }
-
- mucService.addChatRoomListChangeListener(this);
- mucService.addChatRoomProviderWrapperListener(this);
- protolProviderRegistrationListener = new ProtocolProviderRegListener();
- MUCActivator.bundleContext.addServiceListener(
- protolProviderRegistrationListener);
- }
-
- /**
- * Adds the query as presence listener to protocol provider service.
- * @param pps the protocol provider service.
- */
- public void addQueryToProviderPresenceListeners(ProtocolProviderService pps)
- {
- OperationSetMultiUserChat opSetMUC
- = pps.getOperationSet(OperationSetMultiUserChat.class);
- if(opSetMUC != null)
- {
- opSetMUC.addPresenceListener(this);
- }
- }
-
- /**
- * Removes the query from protocol provider service presence listeners.
- * @param pps the protocol provider service.
- */
- public void removeQueryFromProviderPresenceListeners(
- ProtocolProviderService pps)
- {
- OperationSetMultiUserChat opSetMUC
- = pps.getOperationSet(OperationSetMultiUserChat.class);
- if(opSetMUC != null)
- {
- opSetMUC.removePresenceListener(this);
- }
- }
-
- @Override
- protected void run()
- {
- Iterator<ChatRoomProviderWrapper> chatRoomProviders
- = mucService.getChatRoomProviders();
-
- while (chatRoomProviders.hasNext())
- {
- ChatRoomProviderWrapper provider = chatRoomProviders.next();
- providerAdded(provider, true);
- }
-
- if (getStatus() != QUERY_CANCELED)
- setStatus(QUERY_COMPLETED);
- }
-
- /**
- * Handles adding a chat room provider.
- * @param provider the provider.
- * @param addQueryResult indicates whether we should add the chat room to
- * the query results or fire an event without adding it to the results.
- */
- private void providerAdded(ChatRoomProviderWrapper provider,
- boolean addQueryResult)
- {
-
- for(int i = 0; i < provider.countChatRooms(); i++)
- {
- ChatRoomWrapper chatRoom = provider.getChatRoom(i);
- addChatRoom( provider.getProtocolProvider(),
- chatRoom.getChatRoomName(), chatRoom.getChatRoomID(),
- addQueryResult, chatRoom.isAutojoin());
- }
- }
-
- /**
- * Handles chat room presence status updates.
- *
- * @param evt the <tt>LocalUserChatRoomPresenceChangeEvent</tt> instance
- * containing the chat room and the type, and reason of the change
- */
- @Override
- public void localUserPresenceChanged(
- LocalUserChatRoomPresenceChangeEvent evt)
- {
- ChatRoom sourceChatRoom = evt.getChatRoom();
-
- String eventType = evt.getEventType();
-
- boolean existingContact = false;
- ChatRoomSourceContact foundContact = null;
- synchronized (contactResults)
- {
- for (ChatRoomSourceContact contact : contactResults)
- {
- if (contactEqualsChatRoom(contact, sourceChatRoom))
- {
- existingContact = true;
- foundContact = contact;
- contactResults.remove(contact);
- break;
- }
- }
- }
-
- if (LocalUserChatRoomPresenceChangeEvent
- .LOCAL_USER_JOINED.equals(eventType))
- {
- if(existingContact)
- {
- foundContact.setPresenceStatus(
- ChatRoomPresenceStatus.CHAT_ROOM_ONLINE);
- synchronized (contactResults)
- {
- contactResults.add(foundContact);
- }
- fireContactChanged(foundContact);
- }
- else
- {
- ChatRoomWrapper chatRoom
- = MUCActivator.getMUCService()
- .findChatRoomWrapperFromChatRoom(sourceChatRoom);
- if(chatRoom != null)
- addChatRoom(sourceChatRoom, false, chatRoom.isAutojoin());
- }
- }
- else if ((LocalUserChatRoomPresenceChangeEvent
- .LOCAL_USER_LEFT.equals(eventType)
- || LocalUserChatRoomPresenceChangeEvent
- .LOCAL_USER_KICKED.equals(eventType)
- || LocalUserChatRoomPresenceChangeEvent
- .LOCAL_USER_DROPPED.equals(eventType))
- )
- {
- if(existingContact)
- {
- foundContact.setPresenceStatus(
- ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE);
- synchronized (contactResults)
- {
- contactResults.add(foundContact);
- }
- fireContactChanged(foundContact);
- }
- }
- }
-
- /**
- * Adds found result to the query results.
- *
- * @param room the chat room.
- * @param addQueryResult indicates whether we should add the chat room to
- * the query results or fire an event without adding it to the results.
- * @param isAutoJoin the auto join state of the contact.
- */
- private void addChatRoom(ChatRoom room, boolean addQueryResult,
- boolean isAutoJoin)
- {
- if(queryString == null
- || ((room.getName().contains(
- queryString)
- || room.getIdentifier().contains(queryString)
- )))
- {
- ChatRoomSourceContact contact
- = new ChatRoomSourceContact(room, this, isAutoJoin);
- synchronized (contactResults)
- {
- contactResults.add(contact);
- }
-
- if(addQueryResult)
- {
- addQueryResult(contact, false);
- }
- else
- {
- fireContactReceived(contact, false);
- }
- }
- }
-
- /**
- * Adds found result to the query results.
- *
- * @param pps the protocol provider associated with the found chat room.
- * @param chatRoomName the name of the chat room.
- * @param chatRoomID the id of the chat room.
- * @param addQueryResult indicates whether we should add the chat room to
- * the query results or fire an event without adding it to the results.
- * @param isAutoJoin the auto join state of the contact.
- */
- private void addChatRoom(ProtocolProviderService pps,
- String chatRoomName, String chatRoomID, boolean addQueryResult,
- boolean isAutoJoin)
- {
- if(queryString == null
- || ((chatRoomName.contains(
- queryString)
- || chatRoomID.contains(queryString)
- )))
- {
- ChatRoomSourceContact contact
- = new ChatRoomSourceContact(chatRoomName, chatRoomID, this, pps,
- isAutoJoin);
- synchronized (contactResults)
- {
- contactResults.add(contact);
- }
-
- if(addQueryResult)
- {
- addQueryResult(contact, false);
- }
- else
- {
- fireContactReceived(contact, false);
- }
- }
- }
-
- /**
- * Indicates that a change has occurred in the chat room data list.
- * @param evt the event that describes the change.
- */
- @Override
- public void contentChanged(final ChatRoomListChangeEvent evt)
- {
- ChatRoomWrapper chatRoom = evt.getSourceChatRoom();
- switch (evt.getEventID())
- {
- case ChatRoomListChangeEvent.CHAT_ROOM_ADDED:
- addChatRoom(chatRoom.getChatRoom(), false,
- chatRoom.isAutojoin());
- break;
- case ChatRoomListChangeEvent.CHAT_ROOM_REMOVED:
- LinkedList<ChatRoomSourceContact> tmpContactResults;
- synchronized (contactResults)
- {
- tmpContactResults
- = new LinkedList<ChatRoomSourceContact>(contactResults);
-
- for (ChatRoomSourceContact contact : tmpContactResults)
- {
- if (contactEqualsChatRoom(contact, chatRoom))
- {
- contactResults.remove(contact);
- fireContactRemoved(contact);
- break;
- }
- }
- }
- break;
- case ChatRoomListChangeEvent.CHAT_ROOM_CHANGED:
- synchronized (contactResults)
- {
- for (ChatRoomSourceContact contact : contactResults)
- {
- if (contactEqualsChatRoom(contact,
- chatRoom.getChatRoom()))
- {
- if (chatRoom.isAutojoin() != contact.isAutoJoin())
- {
- contact.setAutoJoin(chatRoom.isAutojoin());
- fireContactChanged(contact);
- }
- break;
- }
- }
- }
- break;
- default:
- break;
- }
- }
-
- @Override
- public void chatRoomProviderWrapperAdded(ChatRoomProviderWrapper provider)
- {
- providerAdded(provider, false);
- }
-
- @Override
- public void chatRoomProviderWrapperRemoved(ChatRoomProviderWrapper provider)
- {
- LinkedList<ChatRoomSourceContact> tmpContactResults;
- synchronized (contactResults)
- {
- tmpContactResults
- = new LinkedList<ChatRoomSourceContact>(contactResults);
-
- for(ChatRoomSourceContact contact : tmpContactResults)
- {
- if(contact.getProvider().equals(provider.getProtocolProvider()))
- {
- contactResults.remove(contact);
- fireContactRemoved(contact);
- }
- }
- }
- }
-
- /**
- * Test equality of contact to chat room. This test recognizes that chat
- * rooms may have equal names but connected to different accounts.
- *
- * @param contact the contact
- * @param chatRoom the chat room
- * @return returns <tt>true</tt> if they are equal, or <tt>false</tt> if
- * they are different
- */
- private boolean contactEqualsChatRoom(final ChatRoomSourceContact contact,
- final ChatRoom chatRoom)
- {
- return contact.getProvider() == chatRoom.getParentProvider()
- && chatRoom.getIdentifier().equals(contact.getContactAddress());
- }
-
- /**
- * Test equality of contact to chat room wrapper. This method does not rely
- * on a chat room instance, since that may not be available in case of
- * removal.
- *
- * @param contact the contact
- * @param chatRoomWrapper the chat room wrapper
- * @return returns <tt>true</tt> if they are equal, or <tt>false</tt> if
- * they are different.
- */
- private boolean contactEqualsChatRoom(final ChatRoomSourceContact contact,
- final ChatRoomWrapper chatRoomWrapper)
- {
- return contact.getProvider() == chatRoomWrapper.getParentProvider()
- .getProtocolProvider()
- && contact.getContactAddress().equals(
- chatRoomWrapper.getChatRoomID());
- }
-
- /**
- * Returns the index of the contact in the contact results list.
- * @param contact the contact.
- * @return the index of the contact in the contact results list.
- */
- public synchronized int indexOf(ChatRoomSourceContact contact)
- {
- Iterator<ChatRoomSourceContact> it = contactResults.iterator();
- int i = 0;
- while(it.hasNext())
- {
- if(contact.equals(it.next()))
- {
- return i;
- }
- i++;
- }
- return -1;
- }
-
- /**
- * Clears any listener we used.
- */
- private void clearListeners()
- {
- mucService.removeChatRoomListChangeListener(this);
- mucService.removeChatRoomProviderWrapperListener(this);
- if(protolProviderRegistrationListener != null)
- MUCActivator.bundleContext.removeServiceListener(
- protolProviderRegistrationListener);
- protolProviderRegistrationListener = null;
- for(ProtocolProviderService pps : MUCActivator.getChatRoomProviders())
- {
- removeQueryFromProviderPresenceListeners(pps);
- }
- }
-
- /**
- * Cancels this <tt>ContactQuery</tt>.
- *
- * @see ContactQuery#cancel()
- */
- public void cancel()
- {
- clearListeners();
-
- super.cancel();
- }
-
- /**
- * If query has status changed to cancel, let's clear listeners.
- * @param status {@link ContactQuery#QUERY_CANCELED},
- * {@link ContactQuery#QUERY_COMPLETED}
- */
- public void setStatus(int status)
- {
- if(status == QUERY_CANCELED)
- clearListeners();
-
- super.setStatus(status);
- }
-
- @Override
- public void addContactQueryListener(ContactQueryListener l)
- {
- super.addContactQueryListener(l);
- contactQueryListenersCount++;
- if(contactQueryListenersCount == 1)
- {
- initListeners();
- }
- }
-
- @Override
- public void removeContactQueryListener(ContactQueryListener l)
- {
- super.removeContactQueryListener(l);
- contactQueryListenersCount--;
- if(contactQueryListenersCount == 0)
- {
- clearListeners();
- }
- }
-
- /**
- * Listens for <tt>ProtocolProviderService</tt> registrations.
- */
- private class ProtocolProviderRegListener
- implements ServiceListener
- {
- /**
- * Handles service change events.
- */
- public void serviceChanged(ServiceEvent event)
- {
- ServiceReference serviceRef = event.getServiceReference();
-
- // if the event is caused by a bundle being stopped, we don't want to
- // know
- if (serviceRef.getBundle().getState() == Bundle.STOPPING)
- {
- return;
- }
-
- Object service = MUCActivator.bundleContext.getService(serviceRef);
-
- // we don't care if the source service is not a protocol provider
- if (!(service instanceof ProtocolProviderService))
- {
- return;
- }
-
- switch (event.getType())
- {
- case ServiceEvent.REGISTERED:
- addQueryToProviderPresenceListeners(
- (ProtocolProviderService) service);
- break;
- case ServiceEvent.UNREGISTERING:
- removeQueryFromProviderPresenceListeners(
- (ProtocolProviderService) service);
- break;
- }
- }
- }
+package net.java.sip.communicator.impl.muc; + +import java.util.*; +import java.util.regex.*; + +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.muc.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; + +import org.osgi.framework.*; + +/** + * The <tt>ChatRoomQuery</tt> is a query over the + * <tt>ChatRoomContactSourceService</tt>. + * + * @author Hristo Terezov + */ +public class ChatRoomQuery + extends AsyncContactQuery<ContactSourceService> + implements LocalUserChatRoomPresenceListener, + ChatRoomListChangeListener, + ChatRoomProviderWrapperListener +{ + /** + * The query string. + */ + private String queryString; + + /** + * List with the current results for the query. + */ + private Set<ChatRoomSourceContact> contactResults + = new TreeSet<ChatRoomSourceContact>(); + + /** + * MUC service. + */ + private MUCServiceImpl mucService; + + /** + * The number of contact query listeners. + */ + private int contactQueryListenersCount = 0; + + /** + * The protocol provider registration listener. + */ + private ServiceListener protolProviderRegistrationListener = null; + + /** + * Creates an instance of <tt>ChatRoomQuery</tt> by specifying + * the parent contact source, the query string to match and the maximum + * result contacts to return. + * + * @param queryString the query string to match + * @param contactSource the parent contact source + */ + public ChatRoomQuery(String queryString, + ChatRoomContactSourceService contactSource) + { + super(contactSource, + Pattern.compile(queryString, Pattern.CASE_INSENSITIVE + | Pattern.LITERAL), true); + this.queryString = queryString; + + mucService = MUCActivator.getMUCService(); + + } + + /** + * Adds listeners for the query + */ + private void initListeners() + { + for(ProtocolProviderService pps : MUCActivator.getChatRoomProviders()) + { + addQueryToProviderPresenceListeners(pps); + } + + mucService.addChatRoomListChangeListener(this); + mucService.addChatRoomProviderWrapperListener(this); + protolProviderRegistrationListener = new ProtocolProviderRegListener(); + MUCActivator.bundleContext.addServiceListener( + protolProviderRegistrationListener); + } + + /** + * Adds the query as presence listener to protocol provider service. + * @param pps the protocol provider service. + */ + public void addQueryToProviderPresenceListeners(ProtocolProviderService pps) + { + OperationSetMultiUserChat opSetMUC + = pps.getOperationSet(OperationSetMultiUserChat.class); + if(opSetMUC != null) + { + opSetMUC.addPresenceListener(this); + } + } + + /** + * Removes the query from protocol provider service presence listeners. + * @param pps the protocol provider service. + */ + public void removeQueryFromProviderPresenceListeners( + ProtocolProviderService pps) + { + OperationSetMultiUserChat opSetMUC + = pps.getOperationSet(OperationSetMultiUserChat.class); + if(opSetMUC != null) + { + opSetMUC.removePresenceListener(this); + } + } + + @Override + protected void run() + { + Iterator<ChatRoomProviderWrapper> chatRoomProviders + = mucService.getChatRoomProviders(); + + while (chatRoomProviders.hasNext()) + { + ChatRoomProviderWrapper provider = chatRoomProviders.next(); + providerAdded(provider, true); + } + + if (getStatus() != QUERY_CANCELED) + setStatus(QUERY_COMPLETED); + } + + /** + * Handles adding a chat room provider. + * @param provider the provider. + * @param addQueryResult indicates whether we should add the chat room to + * the query results or fire an event without adding it to the results. + */ + private void providerAdded(ChatRoomProviderWrapper provider, + boolean addQueryResult) + { + + for(int i = 0; i < provider.countChatRooms(); i++) + { + ChatRoomWrapper chatRoom = provider.getChatRoom(i); + addChatRoom( provider.getProtocolProvider(), + chatRoom.getChatRoomName(), chatRoom.getChatRoomID(), + addQueryResult, chatRoom.isAutojoin()); + } + } + + /** + * Handles chat room presence status updates. + * + * @param evt the <tt>LocalUserChatRoomPresenceChangeEvent</tt> instance + * containing the chat room and the type, and reason of the change + */ + @Override + public void localUserPresenceChanged( + LocalUserChatRoomPresenceChangeEvent evt) + { + ChatRoom sourceChatRoom = evt.getChatRoom(); + + String eventType = evt.getEventType(); + + boolean existingContact = false; + ChatRoomSourceContact foundContact = null; + synchronized (contactResults) + { + for (ChatRoomSourceContact contact : contactResults) + { + if (contactEqualsChatRoom(contact, sourceChatRoom)) + { + existingContact = true; + foundContact = contact; + contactResults.remove(contact); + break; + } + } + } + + if (LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_JOINED.equals(eventType)) + { + if(existingContact) + { + foundContact.setPresenceStatus( + ChatRoomPresenceStatus.CHAT_ROOM_ONLINE); + synchronized (contactResults) + { + contactResults.add(foundContact); + } + fireContactChanged(foundContact); + } + else + { + ChatRoomWrapper chatRoom + = MUCActivator.getMUCService() + .findChatRoomWrapperFromChatRoom(sourceChatRoom); + if(chatRoom != null) + addChatRoom(sourceChatRoom, false, chatRoom.isAutojoin()); + } + } + else if ((LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_LEFT.equals(eventType) + || LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_KICKED.equals(eventType) + || LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_DROPPED.equals(eventType)) + ) + { + if(existingContact) + { + foundContact.setPresenceStatus( + ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE); + synchronized (contactResults) + { + contactResults.add(foundContact); + } + fireContactChanged(foundContact); + } + } + } + + /** + * Adds found result to the query results. + * + * @param room the chat room. + * @param addQueryResult indicates whether we should add the chat room to + * the query results or fire an event without adding it to the results. + * @param isAutoJoin the auto join state of the contact. + */ + private void addChatRoom(ChatRoom room, boolean addQueryResult, + boolean isAutoJoin) + { + if(queryString == null + || ((room.getName().contains( + queryString) + || room.getIdentifier().contains(queryString) + ))) + { + ChatRoomSourceContact contact + = new ChatRoomSourceContact(room, this, isAutoJoin); + synchronized (contactResults) + { + contactResults.add(contact); + } + + if(addQueryResult) + { + addQueryResult(contact, false); + } + else + { + fireContactReceived(contact, false); + } + } + } + + /** + * Adds found result to the query results. + * + * @param pps the protocol provider associated with the found chat room. + * @param chatRoomName the name of the chat room. + * @param chatRoomID the id of the chat room. + * @param addQueryResult indicates whether we should add the chat room to + * the query results or fire an event without adding it to the results. + * @param isAutoJoin the auto join state of the contact. + */ + private void addChatRoom(ProtocolProviderService pps, + String chatRoomName, String chatRoomID, boolean addQueryResult, + boolean isAutoJoin) + { + if(queryString == null + || ((chatRoomName.contains( + queryString) + || chatRoomID.contains(queryString) + ))) + { + ChatRoomSourceContact contact + = new ChatRoomSourceContact(chatRoomName, chatRoomID, this, pps, + isAutoJoin); + synchronized (contactResults) + { + contactResults.add(contact); + } + + if(addQueryResult) + { + addQueryResult(contact, false); + } + else + { + fireContactReceived(contact, false); + } + } + } + + /** + * Indicates that a change has occurred in the chat room data list. + * @param evt the event that describes the change. + */ + @Override + public void contentChanged(final ChatRoomListChangeEvent evt) + { + ChatRoomWrapper chatRoom = evt.getSourceChatRoom(); + switch (evt.getEventID()) + { + case ChatRoomListChangeEvent.CHAT_ROOM_ADDED: + addChatRoom(chatRoom.getChatRoom(), false, + chatRoom.isAutojoin()); + break; + case ChatRoomListChangeEvent.CHAT_ROOM_REMOVED: + LinkedList<ChatRoomSourceContact> tmpContactResults; + synchronized (contactResults) + { + tmpContactResults + = new LinkedList<ChatRoomSourceContact>(contactResults); + + for (ChatRoomSourceContact contact : tmpContactResults) + { + if (contactEqualsChatRoom(contact, chatRoom)) + { + contactResults.remove(contact); + fireContactRemoved(contact); + break; + } + } + } + break; + case ChatRoomListChangeEvent.CHAT_ROOM_CHANGED: + synchronized (contactResults) + { + for (ChatRoomSourceContact contact : contactResults) + { + if (contactEqualsChatRoom(contact, + chatRoom.getChatRoom())) + { + if (chatRoom.isAutojoin() != contact.isAutoJoin()) + { + contact.setAutoJoin(chatRoom.isAutojoin()); + fireContactChanged(contact); + } + break; + } + } + } + break; + default: + break; + } + } + + @Override + public void chatRoomProviderWrapperAdded(ChatRoomProviderWrapper provider) + { + providerAdded(provider, false); + } + + @Override + public void chatRoomProviderWrapperRemoved(ChatRoomProviderWrapper provider) + { + LinkedList<ChatRoomSourceContact> tmpContactResults; + synchronized (contactResults) + { + tmpContactResults + = new LinkedList<ChatRoomSourceContact>(contactResults); + + for(ChatRoomSourceContact contact : tmpContactResults) + { + if(contact.getProvider().equals(provider.getProtocolProvider())) + { + contactResults.remove(contact); + fireContactRemoved(contact); + } + } + } + } + + /** + * Test equality of contact to chat room. This test recognizes that chat + * rooms may have equal names but connected to different accounts. + * + * @param contact the contact + * @param chatRoom the chat room + * @return returns <tt>true</tt> if they are equal, or <tt>false</tt> if + * they are different + */ + private boolean contactEqualsChatRoom(final ChatRoomSourceContact contact, + final ChatRoom chatRoom) + { + return contact.getProvider() == chatRoom.getParentProvider() + && chatRoom.getIdentifier().equals(contact.getContactAddress()); + } + + /** + * Test equality of contact to chat room wrapper. This method does not rely + * on a chat room instance, since that may not be available in case of + * removal. + * + * @param contact the contact + * @param chatRoomWrapper the chat room wrapper + * @return returns <tt>true</tt> if they are equal, or <tt>false</tt> if + * they are different. + */ + private boolean contactEqualsChatRoom(final ChatRoomSourceContact contact, + final ChatRoomWrapper chatRoomWrapper) + { + return contact.getProvider() == chatRoomWrapper.getParentProvider() + .getProtocolProvider() + && contact.getContactAddress().equals( + chatRoomWrapper.getChatRoomID()); + } + + /** + * Returns the index of the contact in the contact results list. + * @param contact the contact. + * @return the index of the contact in the contact results list. + */ + public synchronized int indexOf(ChatRoomSourceContact contact) + { + Iterator<ChatRoomSourceContact> it = contactResults.iterator(); + int i = 0; + while(it.hasNext()) + { + if(contact.equals(it.next())) + { + return i; + } + i++; + } + return -1; + } + + /** + * Clears any listener we used. + */ + private void clearListeners() + { + mucService.removeChatRoomListChangeListener(this); + mucService.removeChatRoomProviderWrapperListener(this); + if(protolProviderRegistrationListener != null) + MUCActivator.bundleContext.removeServiceListener( + protolProviderRegistrationListener); + protolProviderRegistrationListener = null; + for(ProtocolProviderService pps : MUCActivator.getChatRoomProviders()) + { + removeQueryFromProviderPresenceListeners(pps); + } + } + + /** + * Cancels this <tt>ContactQuery</tt>. + * + * @see ContactQuery#cancel() + */ + public void cancel() + { + clearListeners(); + + super.cancel(); + } + + /** + * If query has status changed to cancel, let's clear listeners. + * @param status {@link ContactQuery#QUERY_CANCELED}, + * {@link ContactQuery#QUERY_COMPLETED} + */ + public void setStatus(int status) + { + if(status == QUERY_CANCELED) + clearListeners(); + + super.setStatus(status); + } + + @Override + public void addContactQueryListener(ContactQueryListener l) + { + super.addContactQueryListener(l); + contactQueryListenersCount++; + if(contactQueryListenersCount == 1) + { + initListeners(); + } + } + + @Override + public void removeContactQueryListener(ContactQueryListener l) + { + super.removeContactQueryListener(l); + contactQueryListenersCount--; + if(contactQueryListenersCount == 0) + { + clearListeners(); + } + } + + /** + * Listens for <tt>ProtocolProviderService</tt> registrations. + */ + private class ProtocolProviderRegListener + implements ServiceListener + { + /** + * Handles service change events. + */ + public void serviceChanged(ServiceEvent event) + { + ServiceReference serviceRef = event.getServiceReference(); + + // if the event is caused by a bundle being stopped, we don't want to + // know + if (serviceRef.getBundle().getState() == Bundle.STOPPING) + { + return; + } + + Object service = MUCActivator.bundleContext.getService(serviceRef); + + // we don't care if the source service is not a protocol provider + if (!(service instanceof ProtocolProviderService)) + { + return; + } + + switch (event.getType()) + { + case ServiceEvent.REGISTERED: + addQueryToProviderPresenceListeners( + (ProtocolProviderService) service); + break; + case ServiceEvent.UNREGISTERING: + removeQueryFromProviderPresenceListeners( + (ProtocolProviderService) service); + break; + } + } + } } diff --git a/src/net/java/sip/communicator/impl/muc/ChatRoomSourceContact.java b/src/net/java/sip/communicator/impl/muc/ChatRoomSourceContact.java index a659a6e..842cc15 100644 --- a/src/net/java/sip/communicator/impl/muc/ChatRoomSourceContact.java +++ b/src/net/java/sip/communicator/impl/muc/ChatRoomSourceContact.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,113 +15,113 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.muc;
-
-import net.java.sip.communicator.service.muc.*;
-import net.java.sip.communicator.service.protocol.*;
-
-/**
- * Source contact for the chat rooms.
- *
- * @author Hristo Terezov
- */
-public class ChatRoomSourceContact
- extends BaseChatRoomSourceContact
-{
- /**
- * The protocol provider of the chat room associated with the contact.
- */
- private boolean isAutoJoin;
-
- /**
- * Constructs a new chat room source contact.
- *
- * @param chatRoomName the name of the chat room associated with the room.
- * @param chatRoomID the id of the chat room associated with the room.
- * @param query the query associated with the contact.
- * @param pps the protocol provider of the contact.
- * @param isAutoJoin the auto join state.
- */
- public ChatRoomSourceContact(String chatRoomName,
- String chatRoomID, ChatRoomQuery query, ProtocolProviderService pps,
- boolean isAutoJoin)
- {
- super(chatRoomName, chatRoomID, query, pps);
-
- this.isAutoJoin = isAutoJoin;
-
- initContactProperties(getChatRoomStateByName());
- }
-
- /**
- * Constructs new chat room source contact.
- *
- * @param chatRoom the chat room associated with the contact.
- * @param query the query associated with the contact.
- * @param isAutoJoin the auto join state
- */
- public ChatRoomSourceContact(ChatRoom chatRoom, ChatRoomQuery query,
- boolean isAutoJoin)
- {
- super(chatRoom.getName(), chatRoom.getIdentifier(), query,
- chatRoom.getParentProvider());
- this.isAutoJoin = isAutoJoin;
-
- initContactProperties(
- chatRoom.isJoined()
- ? ChatRoomPresenceStatus.CHAT_ROOM_ONLINE
- : ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE);
-
- }
-
- /**
- * Checks if the chat room associated with the contact is joined or not and
- * returns it presence status.
- *
- * @return the presence status of the chat room associated with the contact.
- */
- private PresenceStatus getChatRoomStateByName()
- {
- for(ChatRoom room :
- getProvider().getOperationSet(OperationSetMultiUserChat.class)
- .getCurrentlyJoinedChatRooms())
- {
- if(room.getName().equals(getChatRoomName()))
- {
- return ChatRoomPresenceStatus.CHAT_ROOM_ONLINE;
- }
- }
- return ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE;
- }
-
- /**
- * Returns the index of this source contact in its parent group.
- *
- * @return the index of this contact in its parent
- */
- @Override
- public int getIndex()
- {
- return ((ChatRoomQuery)parentQuery).indexOf(this);
- }
-
- /**
- * Returns the auto join state of the contact.
- *
- * @return the auto join state of the contact.
- */
- public boolean isAutoJoin()
- {
- return isAutoJoin;
- }
-
- /**
- * Sets the auto join state of the contact.
- *
- * @param isAutoJoin the auto join state to be set.
- */
- public void setAutoJoin(boolean isAutoJoin)
- {
- this.isAutoJoin = isAutoJoin;
- }
-}
+package net.java.sip.communicator.impl.muc; + +import net.java.sip.communicator.service.muc.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * Source contact for the chat rooms. + * + * @author Hristo Terezov + */ +public class ChatRoomSourceContact + extends BaseChatRoomSourceContact +{ + /** + * The protocol provider of the chat room associated with the contact. + */ + private boolean isAutoJoin; + + /** + * Constructs a new chat room source contact. + * + * @param chatRoomName the name of the chat room associated with the room. + * @param chatRoomID the id of the chat room associated with the room. + * @param query the query associated with the contact. + * @param pps the protocol provider of the contact. + * @param isAutoJoin the auto join state. + */ + public ChatRoomSourceContact(String chatRoomName, + String chatRoomID, ChatRoomQuery query, ProtocolProviderService pps, + boolean isAutoJoin) + { + super(chatRoomName, chatRoomID, query, pps); + + this.isAutoJoin = isAutoJoin; + + initContactProperties(getChatRoomStateByName()); + } + + /** + * Constructs new chat room source contact. + * + * @param chatRoom the chat room associated with the contact. + * @param query the query associated with the contact. + * @param isAutoJoin the auto join state + */ + public ChatRoomSourceContact(ChatRoom chatRoom, ChatRoomQuery query, + boolean isAutoJoin) + { + super(chatRoom.getName(), chatRoom.getIdentifier(), query, + chatRoom.getParentProvider()); + this.isAutoJoin = isAutoJoin; + + initContactProperties( + chatRoom.isJoined() + ? ChatRoomPresenceStatus.CHAT_ROOM_ONLINE + : ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE); + + } + + /** + * Checks if the chat room associated with the contact is joined or not and + * returns it presence status. + * + * @return the presence status of the chat room associated with the contact. + */ + private PresenceStatus getChatRoomStateByName() + { + for(ChatRoom room : + getProvider().getOperationSet(OperationSetMultiUserChat.class) + .getCurrentlyJoinedChatRooms()) + { + if(room.getName().equals(getChatRoomName())) + { + return ChatRoomPresenceStatus.CHAT_ROOM_ONLINE; + } + } + return ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE; + } + + /** + * Returns the index of this source contact in its parent group. + * + * @return the index of this contact in its parent + */ + @Override + public int getIndex() + { + return ((ChatRoomQuery)parentQuery).indexOf(this); + } + + /** + * Returns the auto join state of the contact. + * + * @return the auto join state of the contact. + */ + public boolean isAutoJoin() + { + return isAutoJoin; + } + + /** + * Sets the auto join state of the contact. + * + * @param isAutoJoin the auto join state to be set. + */ + public void setAutoJoin(boolean isAutoJoin) + { + this.isAutoJoin = isAutoJoin; + } +} diff --git a/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java b/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java index 7c45b1d..0f4622d 100644 --- a/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java +++ b/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,845 +15,845 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.muc;
-
-import java.util.*;
-
-import net.java.sip.communicator.plugin.desktoputil.chat.*;
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.customcontactactions.*;
-import net.java.sip.communicator.service.muc.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.resources.*;
-
-/**
- * Implements <tt>CustomContactActionsService</tt> for MUC contact source.
- *
- * @author Hristo Terezov
- */
-public class MUCCustomContactActionService
- implements CustomContactActionsService<SourceContact>
-{
- /**
- * List of custom menu items.
- */
- private final List<ContactActionMenuItem<SourceContact>> mucActionMenuItems
- = new LinkedList<ContactActionMenuItem<SourceContact>>();
-
- /**
- * List of custom actions.
- */
- private final List<ContactAction<SourceContact>> mucActions
- = new LinkedList<ContactAction<SourceContact>>();
-
- /**
- *
- */
- private static final String OWNER_CANT_REMOVE_CHATROOM_PROPERTY
- = "net.java.sip.communicator.impl.muc.OWNER_CANT_REMOVE_CHATROOM";
-
- /**
- * Array of names for the custom actions.
- */
- private String[] actionsNames = {
- "leave",
- "join",
- "autojoin",
- "autojoin_pressed",
- "destroy_chatroom"
- };
-
- /**
- * Array of labels for the custom actions.
- */
- private String[] actionsLabels = {
- "service.gui.LEAVE",
- "service.gui.JOIN",
- "service.gui.JOIN_AUTOMATICALLY",
- "service.gui.JOIN_AUTOMATICALLY",
- "service.gui.DESTROY_CHATROOM"
- };
-
- /**
- * Array of icons for the custom actions.
- */
- private String[] actionsIcons = {
- "service.gui.icons.LEAVE_ICON_BUTTON",
- "service.gui.icons.JOIN_ICON_BUTTON",
- "service.gui.icons.AUTOJOIN_ON_ICON_BUTTON",
- "service.gui.icons.AUTOJOIN_OFF_ICON_BUTTON",
- "service.gui.icons.DESTROY_ICON_BUTTON"
- };
-
- /**
- * Array of rollover icons for the custom actions.
- */
- private String[] actionsIconsRollover = {
- "service.gui.icons.LEAVE_ICON_ROLLOVER_BUTTON",
- "service.gui.icons.JOIN_ICON_ROLLOVER_BUTTON",
- "service.gui.icons.AUTOJOIN_ON_ICON_ROLLOVER_BUTTON",
- "service.gui.icons.AUTOJOIN_OFF_ICON_ROLLOVER_BUTTON",
- "service.gui.icons.DESTROY_ICON_ROLLOVER_BUTTON"
- };
-
- /**
- * Array of pressed icons for the custom actions.
- */
- private String[] actionsIconsPressed = {
- "service.gui.icons.LEAVE_ICON_PRESSED_BUTTON",
- "service.gui.icons.JOIN_ICON_PRESSED_BUTTON",
- "service.gui.icons.AUTOJOIN_ON_ICON_PRESSED_BUTTON",
- "service.gui.icons.AUTOJOIN_OFF_ICON_PRESSED_BUTTON",
- "service.gui.icons.DESTROY_ICON_PRESSED_BUTTON"
- };
-
- /**
- * Array of names for the custom menu items.
- */
- private String[] menuActionsNames = {
- "open",
- "join",
- "join_as",
- "leave",
- "remove",
- "change_nick",
- "autojoin",
- "autojoin_pressed",
- "open_automatically",
- "destroy_chatroom"
- };
-
- /**
- * Array of labels for the custom menu items.
- */
- private String[] menuActionsLabels = {
- "service.gui.OPEN",
- "service.gui.JOIN",
- "service.gui.JOIN_AS",
- "service.gui.LEAVE",
- "service.gui.REMOVE",
- "service.gui.CHANGE_NICK",
- "service.gui.JOIN_AUTOMATICALLY",
- "service.gui.DONT_JOIN_AUTOMATICALLY",
- "service.gui.OPEN_AUTOMATICALLY",
- "service.gui.DESTROY_CHATROOM"
- };
-
- /**
- * Array of icons for the custom menu items.
- */
- private String[] menuActionsIcons = {
- "service.gui.icons.CHAT_ROOM_16x16_ICON",
- "service.gui.icons.JOIN_ICON",
- "service.gui.icons.JOIN_AS_ICON",
- "service.gui.icons.LEAVE_ICON",
- "service.gui.icons.REMOVE_CHAT_ICON",
- "service.gui.icons.RENAME_16x16_ICON",
- "service.gui.icons.AUTOJOIN",
- "service.gui.icons.AUTOJOIN",
- "service.gui.icons.OPEN_AUTOMATICALLY",
- "service.gui.icons.DESTROY_CHATROOM"
- };
-
- /**
- * A runnable that leaves the chat room.
- */
- private MUCCustomActionRunnable leaveRunnable
- = new MUCCustomActionRunnable()
- {
-
- @Override
- public void run()
- {
- ChatRoomWrapper leavedRoomWrapped
- = MUCActivator.getMUCService().leaveChatRoom(
- chatRoomWrapper);
- if(leavedRoomWrapped != null)
- MUCActivator.getUIService().closeChatRoomWindow(
- leavedRoomWrapped);
- }
- };
-
- /**
- * A runnable that joins the chat room.
- */
- private MUCCustomActionRunnable joinRunnable
- = new MUCCustomActionRunnable()
- {
-
- @Override
- public void run()
- {
- String[] joinOptions;
- String subject = null;
- String nickName = null;
-
- nickName =
- ConfigurationUtils.getChatRoomProperty(
- chatRoomWrapper.getParentProvider()
- .getProtocolProvider(), chatRoomWrapper
- .getChatRoomID(), "userNickName");
- if(nickName == null)
- {
- joinOptions = ChatRoomJoinOptionsDialog.getJoinOptions(
- chatRoomWrapper.getParentProvider()
- .getProtocolProvider(),
- chatRoomWrapper.getChatRoomID(),
- MUCActivator.getGlobalDisplayDetailsService()
- .getDisplayName(chatRoomWrapper.getParentProvider()
- .getProtocolProvider()));
- nickName = joinOptions[0];
- subject = joinOptions[1];
- }
-
- if (nickName != null)
- MUCActivator.getMUCService().joinChatRoom(chatRoomWrapper,
- nickName, null, subject);
- }
- };
-
- /**
- * A runnable that sets / unsets auto join setting of the chat room.
- */
- private MUCCustomActionRunnable autoJoinRunnable
- = new MUCCustomActionRunnable()
- {
-
- @Override
- public void run()
- {
- chatRoomWrapper.setAutoJoin(!chatRoomWrapper.isAutojoin());
-
- }
- };
-
- /**
- * A runnable that destroys the chat room.
- */
- private MUCCustomActionRunnable destroyActionRunnable
- = new MUCCustomActionRunnable()
- {
-
- @Override
- public void run()
- {
- String destroyOptions[]
- = ChatRoomDestroyReasonDialog.getDestroyOptions();
- if(destroyOptions == null)
- return;
-
- MUCActivator.getMUCService().destroyChatRoom(chatRoomWrapper,
- destroyOptions[0], destroyOptions[1]);
-
- }
- };
-
- /**
- * Array of <tt>MUCCustomActionRunnable</tt> objects for the custom menu
- * items. They will be executed when the item is pressed.
- */
- private MUCCustomActionRunnable[] actionsRunnable = {
- leaveRunnable,
- joinRunnable,
- autoJoinRunnable,
- autoJoinRunnable,
- destroyActionRunnable
- };
-
- /**
- * Array of <tt>MUCCustomActionRunnable</tt> objects for the custom menu
- * items. They will be executed when the item is pressed.
- */
- private MUCCustomActionRunnable[] menuActionsRunnable = {
- new MUCCustomActionRunnable()
- {
- @Override
- public void run()
- {
- MUCActivator.getMUCService().openChatRoom(chatRoomWrapper);
- }
- },
- joinRunnable,
- new MUCCustomActionRunnable()
- {
-
- @Override
- public void run()
- {
- String[] joinOptions;
- joinOptions = ChatRoomJoinOptionsDialog.getJoinOptions(
- chatRoomWrapper.getParentProvider().getProtocolProvider(),
- chatRoomWrapper.getChatRoomID(),
- MUCActivator.getGlobalDisplayDetailsService()
- .getDisplayName(chatRoomWrapper.getParentProvider()
- .getProtocolProvider()));
- if(joinOptions[0] == null)
- return;
- MUCActivator.getMUCService()
- .joinChatRoom(chatRoomWrapper, joinOptions[0], null,
- joinOptions[1]);
- }
- },
- leaveRunnable,
- new MUCCustomActionRunnable()
- {
-
- @Override
- public void run()
- {
- ChatRoom chatRoom = chatRoomWrapper.getChatRoom();
-
- if (chatRoom != null)
- {
- ChatRoomWrapper leavedRoomWrapped
- = MUCActivator.getMUCService().leaveChatRoom(
- chatRoomWrapper);
- if(leavedRoomWrapped != null)
- MUCActivator.getUIService().closeChatRoomWindow(
- leavedRoomWrapped);
- }
-
- MUCActivator.getUIService().closeChatRoomWindow(chatRoomWrapper);
-
- MUCActivator.getMUCService().removeChatRoom(chatRoomWrapper);
- }
- },
- new MUCCustomActionRunnable()
- {
-
- @Override
- public void run()
- {
- ChatRoomJoinOptionsDialog.getJoinOptions(true,
- chatRoomWrapper.getParentProvider().getProtocolProvider(),
- chatRoomWrapper.getChatRoomID(),
- MUCActivator.getGlobalDisplayDetailsService()
- .getDisplayName(chatRoomWrapper.getParentProvider()
- .getProtocolProvider()));
- }
- },
- autoJoinRunnable,
- autoJoinRunnable,
- new MUCCustomActionRunnable()
- {
-
- @Override
- public void run()
- {
- MUCActivator.getUIService().showChatRoomAutoOpenConfigDialog(
- chatRoomWrapper.getParentProvider().getProtocolProvider(),
- chatRoomWrapper.getChatRoomID());
- }
- },
- destroyActionRunnable
- };
-
- /**
- * Array of <tt>EnableChecker</tt> objects for the custom menu items. They
- * are used to check if the item is enabled or disabled.
- */
- private EnableChecker[] actionsEnabledCheckers = {
- null,
- new JoinEnableChecker(),
- new JoinEnableChecker(),
- new LeaveEnableChecker(),
- null,
- null,
- null,
- null,
- null,
- null
- };
-
- /**
- * The resource management service instance.
- */
- ResourceManagementService resources = MUCActivator.getResources();
-
- /**
- * Constructs the custom actions.
- */
- public MUCCustomContactActionService()
- {
- for(int i = 0; i < menuActionsLabels.length; i++)
- {
- MUCActionMenuItems item
- = new MUCActionMenuItems(
- menuActionsNames[i],
- menuActionsLabels[i],
- menuActionsIcons[i],
- menuActionsRunnable[i]);
- mucActionMenuItems.add(item);
- if(actionsEnabledCheckers[i] != null)
- item.setEnabled(actionsEnabledCheckers[i]);
- }
-
- for(int i = 0; i < actionsLabels.length; i++)
- {
- MUCAction item = new MUCAction(
- actionsNames[i],
- actionsLabels[i],
- actionsIcons[i],
- actionsIconsRollover[i],
- actionsIconsPressed[i],
- actionsRunnable[i]);
- mucActions.add(item);
- }
-
- }
-
- /**
- * Returns the template class that this service has been initialized with
- *
- * @return the template class
- */
- public Class<SourceContact> getContactSourceClass()
- {
- return SourceContact.class;
- }
-
- @Override
- public Iterator<ContactActionMenuItem<SourceContact>>
- getCustomContactActionsMenuItems()
- {
- return mucActionMenuItems.iterator();
- }
-
-
- @Override
- public Iterator<ContactAction<SourceContact>> getCustomContactActions()
- {
- return mucActions.iterator();
- }
-
- /**
- * Implements the MUC custom action.
- */
- private class MUCAction
- implements ContactAction<SourceContact>
- {
- /**
- * The text of the action.
- */
- private String text;
-
- /**
- * The icon of the action
- */
- private byte[] icon;
-
- /**
- * The icon that is shown when the action is pressed.
- */
- private byte[] iconPressed;
-
- /**
- * The runnable that is executed when the action is pressed.
- */
- private MUCCustomActionRunnable actionPerformed;
-
- /**
- * The icon that is shown when the mouse is over the action.
- */
- private byte[] iconRollover;
-
- /**
- * The name of the action.
- */
- private String name;
-
- /**
- * Constructs <tt>MUCAction</tt> instance.
- *
- * @param textKey the key used to retrieve the label for the action.
- * @param iconKey the key used to retrieve the icon for the action.
- * @param actionPerformed the action executed when the action is
- * pressed.
- * @param iconRolloverKey the key used to retrieve the rollover icon for
- * the action.
- * @param iconPressedKey the key used to retrieve the pressed icon for
- * the action.
- */
- public MUCAction(String name, String textKey, String iconKey,
- String iconRolloverKey, String iconPressedKey,
- MUCCustomActionRunnable actionPerformed)
- {
- this.name = name;
- this.text = resources.getI18NString(textKey);
- this.icon = resources.getImageInBytes(iconKey);
- this.iconRollover = resources.getImageInBytes(iconRolloverKey);
- this.iconPressed = resources.getImageInBytes(iconPressedKey);
- this.actionPerformed = actionPerformed;
- }
-
- @Override
- public void actionPerformed(SourceContact actionSource, int x, int y)
- throws OperationFailedException
- {
- if(!(actionSource instanceof ChatRoomSourceContact))
- return;
- actionPerformed.setContact(actionSource);
- new Thread(actionPerformed).start();
- }
-
- @Override
- public byte[] getIcon()
- {
- return icon;
- }
-
- @Override
- public byte[] getRolloverIcon()
- {
- return iconRollover;
- }
-
- @Override
- public byte[] getPressedIcon()
- {
- return iconPressed;
- }
-
- @Override
- public String getToolTipText()
- {
- return text;
- }
-
- @Override
- public boolean isVisible(SourceContact actionSource)
- {
- if(actionSource instanceof ChatRoomSourceContact)
- {
- if(name.equals("leave"))
- {
- return actionsEnabledCheckers[3].check(actionSource);
- }
- else if(name.equals("join"))
- {
- return actionsEnabledCheckers[1].check(actionSource);
- }
- else if(name.equals("destroy_chatroom"))
- {
- ChatRoomSourceContact contact
- = (ChatRoomSourceContact) actionSource;
- ChatRoomWrapper room = MUCActivator.getMUCService()
- .findChatRoomWrapperFromSourceContact(contact);
- if(room == null || room.getChatRoom() == null)
- return false;
- if(room.getChatRoom().getUserRole().equals(ChatRoomMemberRole.OWNER))
- return true;
- return false;
- }
- else
- {
- ChatRoomSourceContact contact
- = (ChatRoomSourceContact) actionSource;
- ChatRoomWrapper room = MUCActivator.getMUCService()
- .findChatRoomWrapperFromSourceContact(contact);
- if(room == null)
- return false;
-
- if(name.equals("autojoin"))
- return room.isAutojoin();
- else if(name.equals("autojoin_pressed"))
- return !room.isAutojoin();
- }
- }
- return false;
- }
-
- }
-
- /**
- * Implements the MUC custom menu items.
- */
- private class MUCActionMenuItems
- implements ContactActionMenuItem<SourceContact>
- {
- /**
- * The label for the menu item.
- */
- private String text;
-
- /**
- * The the icon for the menu item.
- */
- private byte[] image;
-
- /**
- * The action executed when the menu item is pressed.
- */
- private MUCCustomActionRunnable actionPerformed;
-
- /**
- * Object that is used to check if the item is enabled or disabled.
- */
- private EnableChecker enabled;
-
- /**
- * The name of the custom action menu item.
- */
- private String name;
-
- /**
- * The mnemonic for the action.
- */
- private char mnemonics;
-
- /**
- * Constructs <tt>MUCActionMenuItems</tt> instance.
- *
- * @param textKey the key used to retrieve the label for the menu item.
- * @param imageKey the key used to retrieve the icon for the menu item.
- * @param actionPerformed the action executed when the menu item is
- * pressed.
- */
- public MUCActionMenuItems(String name, String textKey, String imageKey,
- MUCCustomActionRunnable actionPerformed)
- {
- this.text = resources.getI18NString(textKey);
- this.image = (imageKey == null)? null :
- resources.getImageInBytes(imageKey);
- this.actionPerformed = actionPerformed;
- this.enabled = new EnableChecker();
- this.name = name;
- this.mnemonics = resources.getI18nMnemonic(textKey);
- }
-
- @Override
- public void actionPerformed(SourceContact actionSource)
- throws OperationFailedException
- {
- if(!(actionSource instanceof ChatRoomSourceContact))
- return;
- actionPerformed.setContact(actionSource);
- new Thread(actionPerformed).start();
- }
-
- @Override
- public byte[] getIcon()
- {
- return image;
- }
-
-
- @Override
- public String getText(SourceContact actionSource)
- {
- if(!(actionSource instanceof ChatRoomSourceContact))
- return "";
-
- if(!name.equals("open_automatically"))
- return text;
-
- String openAutomaticallyValue
- = MUCService.getChatRoomAutoOpenOption(
- ((ChatRoomSourceContact)actionSource).getProvider(),
- ((ChatRoomSourceContact)actionSource).getChatRoomID());
- if(openAutomaticallyValue == null)
- openAutomaticallyValue = MUCService.DEFAULT_AUTO_OPEN_BEHAVIOUR;
- String openAutomaticallyKey = MUCService.autoOpenConfigValuesTexts
- .get(openAutomaticallyValue);
- return "<html>" + text + "...<br><font size=\"2\"><center> ("
- + resources.getI18NString(openAutomaticallyKey)
- + ")</center></font></html>";
- }
-
- @Override
- public boolean isVisible(SourceContact actionSource)
- {
- if(!(actionSource instanceof ChatRoomSourceContact))
- return false;
-
- ChatRoomSourceContact contact
- = (ChatRoomSourceContact) actionSource;
- ChatRoomWrapper room = MUCActivator.getMUCService()
- .findChatRoomWrapperFromSourceContact(contact);
- if(name.equals("autojoin") || name.equals("autojoin_pressed"))
- {
- if(room == null)
- return true;
-
- if(name.equals("autojoin"))
- return !room.isAutojoin();
-
- if(name.equals("autojoin_pressed"))
- return room.isAutojoin();
- }
- else if(name.equals("remove"))
- {
- if(room == null || room.getChatRoom() == null)
- return true;
-
- boolean ownerCannotRemoveRoom
- = MUCActivator.getConfigurationService().getBoolean(
- OWNER_CANT_REMOVE_CHATROOM_PROPERTY, false);
-
- // when joined role will be owner or member
- // when not joined and if we never has entered the room role
- // will be guest, if we joined and left the room the role
- // will be owner or member
- if(room.getChatRoom().getUserRole().equals(
- ChatRoomMemberRole.MEMBER))
- {
- return true;
- }
- else
- {
- if(ownerCannotRemoveRoom)
- return false;
- else
- return true;
- }
- }
- else if(name.equals("destroy_chatroom"))
- {
- if(room == null || room.getChatRoom() == null)
- return false;
- if(room.getChatRoom().getUserRole().equals(
- ChatRoomMemberRole.OWNER))
- return true;
- return false;
- }
- return true;
- }
-
- @Override
- public char getMnemonics()
- {
- return mnemonics;
- }
-
- @Override
- public boolean isEnabled(SourceContact actionSource)
- {
- return enabled.check(actionSource);
- }
-
- /**
- * Sets <tt>EnabledChecker</tt> instance that will be used to check if
- * the item should be enabled or disabled.
- *
- * @param enabled the <tt>EnabledChecker</tt> instance.
- */
- public void setEnabled(EnableChecker enabled)
- {
- this.enabled = enabled;
- }
-
- @Override
- public boolean isCheckBox()
- {
- return false;
- }
-
- @Override
- public boolean isSelected(SourceContact contact)
- {
- ChatRoomWrapper chatRoomWrapper = MUCActivator.getMUCService()
- .findChatRoomWrapperFromSourceContact(contact);
- if(chatRoomWrapper == null)
- return false;
- return chatRoomWrapper.isAutojoin();
- }
-
- }
-
- /**
- * Checks if the menu item should be enabled or disabled. This is default
- * implementation. Always returns that the item should be enabled.
- */
- private static class EnableChecker
- {
- /**
- * Checks if the menu item should be enabled or disabled.
- *
- * @param contact the contact associated with the menu item.
- * @return always <tt>true</tt>
- */
- public boolean check(SourceContact contact)
- {
- return true;
- }
- }
-
- /**
- * Implements <tt>EnableChecker</tt> for the join menu items.
- */
- private static class JoinEnableChecker
- extends EnableChecker
- {
- /**
- * Checks if the menu item should be enabled or disabled.
- *
- * @param contact the contact associated with the menu item.
- * @return <tt>true</tt> if the item should be enabled and
- * <tt>false</tt> if not.
- */
- public boolean check(SourceContact contact)
- {
- ChatRoomWrapper chatRoomWrapper = MUCActivator.getMUCService()
- .findChatRoomWrapperFromSourceContact(contact);
- ChatRoom chatRoom = null;
- if(chatRoomWrapper != null)
- {
- chatRoom = chatRoomWrapper.getChatRoom();
- }
-
- if((chatRoom != null) && chatRoom.isJoined())
- return false;
- return true;
- }
- }
-
- /**
- * Implements <tt>EnableChecker</tt> for the leave menu item.
- */
- private static class LeaveEnableChecker
- extends JoinEnableChecker
- {
- /**
- * Checks if the menu item should be enabled or disabled.
- *
- * @param contact the contact associated with the menu item.
- * @return <tt>true</tt> if the item should be enabled and
- * <tt>false</tt> if not.
- */
- public boolean check(SourceContact contact)
- {
- return !super.check(contact);
- }
- }
-
- /**
- * Implements base properties for the MUC menu items.These properties are
- * used when the menu item is pressed.
- */
- private abstract class MUCCustomActionRunnable
- implements Runnable
- {
- /**
- * The contact associated with the menu item.
- */
- protected SourceContact contact;
-
- /**
- * The contact associated with the menu item.
- */
- protected ChatRoomWrapper chatRoomWrapper;
-
- /**
- * Sets the source contact.
- * @param contact the contact to set
- */
- public void setContact(SourceContact contact)
- {
- this.contact = contact;
- chatRoomWrapper = MUCActivator.getMUCService()
- .findChatRoomWrapperFromSourceContact(contact);
- }
- }
-}
+package net.java.sip.communicator.impl.muc; + +import java.util.*; + +import net.java.sip.communicator.plugin.desktoputil.chat.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.customcontactactions.*; +import net.java.sip.communicator.service.muc.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +import org.jitsi.service.resources.*; + +/** + * Implements <tt>CustomContactActionsService</tt> for MUC contact source. + * + * @author Hristo Terezov + */ +public class MUCCustomContactActionService + implements CustomContactActionsService<SourceContact> +{ + /** + * List of custom menu items. + */ + private final List<ContactActionMenuItem<SourceContact>> mucActionMenuItems + = new LinkedList<ContactActionMenuItem<SourceContact>>(); + + /** + * List of custom actions. + */ + private final List<ContactAction<SourceContact>> mucActions + = new LinkedList<ContactAction<SourceContact>>(); + + /** + * + */ + private static final String OWNER_CANT_REMOVE_CHATROOM_PROPERTY + = "net.java.sip.communicator.impl.muc.OWNER_CANT_REMOVE_CHATROOM"; + + /** + * Array of names for the custom actions. + */ + private String[] actionsNames = { + "leave", + "join", + "autojoin", + "autojoin_pressed", + "destroy_chatroom" + }; + + /** + * Array of labels for the custom actions. + */ + private String[] actionsLabels = { + "service.gui.LEAVE", + "service.gui.JOIN", + "service.gui.JOIN_AUTOMATICALLY", + "service.gui.JOIN_AUTOMATICALLY", + "service.gui.DESTROY_CHATROOM" + }; + + /** + * Array of icons for the custom actions. + */ + private String[] actionsIcons = { + "service.gui.icons.LEAVE_ICON_BUTTON", + "service.gui.icons.JOIN_ICON_BUTTON", + "service.gui.icons.AUTOJOIN_ON_ICON_BUTTON", + "service.gui.icons.AUTOJOIN_OFF_ICON_BUTTON", + "service.gui.icons.DESTROY_ICON_BUTTON" + }; + + /** + * Array of rollover icons for the custom actions. + */ + private String[] actionsIconsRollover = { + "service.gui.icons.LEAVE_ICON_ROLLOVER_BUTTON", + "service.gui.icons.JOIN_ICON_ROLLOVER_BUTTON", + "service.gui.icons.AUTOJOIN_ON_ICON_ROLLOVER_BUTTON", + "service.gui.icons.AUTOJOIN_OFF_ICON_ROLLOVER_BUTTON", + "service.gui.icons.DESTROY_ICON_ROLLOVER_BUTTON" + }; + + /** + * Array of pressed icons for the custom actions. + */ + private String[] actionsIconsPressed = { + "service.gui.icons.LEAVE_ICON_PRESSED_BUTTON", + "service.gui.icons.JOIN_ICON_PRESSED_BUTTON", + "service.gui.icons.AUTOJOIN_ON_ICON_PRESSED_BUTTON", + "service.gui.icons.AUTOJOIN_OFF_ICON_PRESSED_BUTTON", + "service.gui.icons.DESTROY_ICON_PRESSED_BUTTON" + }; + + /** + * Array of names for the custom menu items. + */ + private String[] menuActionsNames = { + "open", + "join", + "join_as", + "leave", + "remove", + "change_nick", + "autojoin", + "autojoin_pressed", + "open_automatically", + "destroy_chatroom" + }; + + /** + * Array of labels for the custom menu items. + */ + private String[] menuActionsLabels = { + "service.gui.OPEN", + "service.gui.JOIN", + "service.gui.JOIN_AS", + "service.gui.LEAVE", + "service.gui.REMOVE", + "service.gui.CHANGE_NICK", + "service.gui.JOIN_AUTOMATICALLY", + "service.gui.DONT_JOIN_AUTOMATICALLY", + "service.gui.OPEN_AUTOMATICALLY", + "service.gui.DESTROY_CHATROOM" + }; + + /** + * Array of icons for the custom menu items. + */ + private String[] menuActionsIcons = { + "service.gui.icons.CHAT_ROOM_16x16_ICON", + "service.gui.icons.JOIN_ICON", + "service.gui.icons.JOIN_AS_ICON", + "service.gui.icons.LEAVE_ICON", + "service.gui.icons.REMOVE_CHAT_ICON", + "service.gui.icons.RENAME_16x16_ICON", + "service.gui.icons.AUTOJOIN", + "service.gui.icons.AUTOJOIN", + "service.gui.icons.OPEN_AUTOMATICALLY", + "service.gui.icons.DESTROY_CHATROOM" + }; + + /** + * A runnable that leaves the chat room. + */ + private MUCCustomActionRunnable leaveRunnable + = new MUCCustomActionRunnable() + { + + @Override + public void run() + { + ChatRoomWrapper leavedRoomWrapped + = MUCActivator.getMUCService().leaveChatRoom( + chatRoomWrapper); + if(leavedRoomWrapped != null) + MUCActivator.getUIService().closeChatRoomWindow( + leavedRoomWrapped); + } + }; + + /** + * A runnable that joins the chat room. + */ + private MUCCustomActionRunnable joinRunnable + = new MUCCustomActionRunnable() + { + + @Override + public void run() + { + String[] joinOptions; + String subject = null; + String nickName = null; + + nickName = + ConfigurationUtils.getChatRoomProperty( + chatRoomWrapper.getParentProvider() + .getProtocolProvider(), chatRoomWrapper + .getChatRoomID(), "userNickName"); + if(nickName == null) + { + joinOptions = ChatRoomJoinOptionsDialog.getJoinOptions( + chatRoomWrapper.getParentProvider() + .getProtocolProvider(), + chatRoomWrapper.getChatRoomID(), + MUCActivator.getGlobalDisplayDetailsService() + .getDisplayName(chatRoomWrapper.getParentProvider() + .getProtocolProvider())); + nickName = joinOptions[0]; + subject = joinOptions[1]; + } + + if (nickName != null) + MUCActivator.getMUCService().joinChatRoom(chatRoomWrapper, + nickName, null, subject); + } + }; + + /** + * A runnable that sets / unsets auto join setting of the chat room. + */ + private MUCCustomActionRunnable autoJoinRunnable + = new MUCCustomActionRunnable() + { + + @Override + public void run() + { + chatRoomWrapper.setAutoJoin(!chatRoomWrapper.isAutojoin()); + + } + }; + + /** + * A runnable that destroys the chat room. + */ + private MUCCustomActionRunnable destroyActionRunnable + = new MUCCustomActionRunnable() + { + + @Override + public void run() + { + String destroyOptions[] + = ChatRoomDestroyReasonDialog.getDestroyOptions(); + if(destroyOptions == null) + return; + + MUCActivator.getMUCService().destroyChatRoom(chatRoomWrapper, + destroyOptions[0], destroyOptions[1]); + + } + }; + + /** + * Array of <tt>MUCCustomActionRunnable</tt> objects for the custom menu + * items. They will be executed when the item is pressed. + */ + private MUCCustomActionRunnable[] actionsRunnable = { + leaveRunnable, + joinRunnable, + autoJoinRunnable, + autoJoinRunnable, + destroyActionRunnable + }; + + /** + * Array of <tt>MUCCustomActionRunnable</tt> objects for the custom menu + * items. They will be executed when the item is pressed. + */ + private MUCCustomActionRunnable[] menuActionsRunnable = { + new MUCCustomActionRunnable() + { + @Override + public void run() + { + MUCActivator.getMUCService().openChatRoom(chatRoomWrapper); + } + }, + joinRunnable, + new MUCCustomActionRunnable() + { + + @Override + public void run() + { + String[] joinOptions; + joinOptions = ChatRoomJoinOptionsDialog.getJoinOptions( + chatRoomWrapper.getParentProvider().getProtocolProvider(), + chatRoomWrapper.getChatRoomID(), + MUCActivator.getGlobalDisplayDetailsService() + .getDisplayName(chatRoomWrapper.getParentProvider() + .getProtocolProvider())); + if(joinOptions[0] == null) + return; + MUCActivator.getMUCService() + .joinChatRoom(chatRoomWrapper, joinOptions[0], null, + joinOptions[1]); + } + }, + leaveRunnable, + new MUCCustomActionRunnable() + { + + @Override + public void run() + { + ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); + + if (chatRoom != null) + { + ChatRoomWrapper leavedRoomWrapped + = MUCActivator.getMUCService().leaveChatRoom( + chatRoomWrapper); + if(leavedRoomWrapped != null) + MUCActivator.getUIService().closeChatRoomWindow( + leavedRoomWrapped); + } + + MUCActivator.getUIService().closeChatRoomWindow(chatRoomWrapper); + + MUCActivator.getMUCService().removeChatRoom(chatRoomWrapper); + } + }, + new MUCCustomActionRunnable() + { + + @Override + public void run() + { + ChatRoomJoinOptionsDialog.getJoinOptions(true, + chatRoomWrapper.getParentProvider().getProtocolProvider(), + chatRoomWrapper.getChatRoomID(), + MUCActivator.getGlobalDisplayDetailsService() + .getDisplayName(chatRoomWrapper.getParentProvider() + .getProtocolProvider())); + } + }, + autoJoinRunnable, + autoJoinRunnable, + new MUCCustomActionRunnable() + { + + @Override + public void run() + { + MUCActivator.getUIService().showChatRoomAutoOpenConfigDialog( + chatRoomWrapper.getParentProvider().getProtocolProvider(), + chatRoomWrapper.getChatRoomID()); + } + }, + destroyActionRunnable + }; + + /** + * Array of <tt>EnableChecker</tt> objects for the custom menu items. They + * are used to check if the item is enabled or disabled. + */ + private EnableChecker[] actionsEnabledCheckers = { + null, + new JoinEnableChecker(), + new JoinEnableChecker(), + new LeaveEnableChecker(), + null, + null, + null, + null, + null, + null + }; + + /** + * The resource management service instance. + */ + ResourceManagementService resources = MUCActivator.getResources(); + + /** + * Constructs the custom actions. + */ + public MUCCustomContactActionService() + { + for(int i = 0; i < menuActionsLabels.length; i++) + { + MUCActionMenuItems item + = new MUCActionMenuItems( + menuActionsNames[i], + menuActionsLabels[i], + menuActionsIcons[i], + menuActionsRunnable[i]); + mucActionMenuItems.add(item); + if(actionsEnabledCheckers[i] != null) + item.setEnabled(actionsEnabledCheckers[i]); + } + + for(int i = 0; i < actionsLabels.length; i++) + { + MUCAction item = new MUCAction( + actionsNames[i], + actionsLabels[i], + actionsIcons[i], + actionsIconsRollover[i], + actionsIconsPressed[i], + actionsRunnable[i]); + mucActions.add(item); + } + + } + + /** + * Returns the template class that this service has been initialized with + * + * @return the template class + */ + public Class<SourceContact> getContactSourceClass() + { + return SourceContact.class; + } + + @Override + public Iterator<ContactActionMenuItem<SourceContact>> + getCustomContactActionsMenuItems() + { + return mucActionMenuItems.iterator(); + } + + + @Override + public Iterator<ContactAction<SourceContact>> getCustomContactActions() + { + return mucActions.iterator(); + } + + /** + * Implements the MUC custom action. + */ + private class MUCAction + implements ContactAction<SourceContact> + { + /** + * The text of the action. + */ + private String text; + + /** + * The icon of the action + */ + private byte[] icon; + + /** + * The icon that is shown when the action is pressed. + */ + private byte[] iconPressed; + + /** + * The runnable that is executed when the action is pressed. + */ + private MUCCustomActionRunnable actionPerformed; + + /** + * The icon that is shown when the mouse is over the action. + */ + private byte[] iconRollover; + + /** + * The name of the action. + */ + private String name; + + /** + * Constructs <tt>MUCAction</tt> instance. + * + * @param textKey the key used to retrieve the label for the action. + * @param iconKey the key used to retrieve the icon for the action. + * @param actionPerformed the action executed when the action is + * pressed. + * @param iconRolloverKey the key used to retrieve the rollover icon for + * the action. + * @param iconPressedKey the key used to retrieve the pressed icon for + * the action. + */ + public MUCAction(String name, String textKey, String iconKey, + String iconRolloverKey, String iconPressedKey, + MUCCustomActionRunnable actionPerformed) + { + this.name = name; + this.text = resources.getI18NString(textKey); + this.icon = resources.getImageInBytes(iconKey); + this.iconRollover = resources.getImageInBytes(iconRolloverKey); + this.iconPressed = resources.getImageInBytes(iconPressedKey); + this.actionPerformed = actionPerformed; + } + + @Override + public void actionPerformed(SourceContact actionSource, int x, int y) + throws OperationFailedException + { + if(!(actionSource instanceof ChatRoomSourceContact)) + return; + actionPerformed.setContact(actionSource); + new Thread(actionPerformed).start(); + } + + @Override + public byte[] getIcon() + { + return icon; + } + + @Override + public byte[] getRolloverIcon() + { + return iconRollover; + } + + @Override + public byte[] getPressedIcon() + { + return iconPressed; + } + + @Override + public String getToolTipText() + { + return text; + } + + @Override + public boolean isVisible(SourceContact actionSource) + { + if(actionSource instanceof ChatRoomSourceContact) + { + if(name.equals("leave")) + { + return actionsEnabledCheckers[3].check(actionSource); + } + else if(name.equals("join")) + { + return actionsEnabledCheckers[1].check(actionSource); + } + else if(name.equals("destroy_chatroom")) + { + ChatRoomSourceContact contact + = (ChatRoomSourceContact) actionSource; + ChatRoomWrapper room = MUCActivator.getMUCService() + .findChatRoomWrapperFromSourceContact(contact); + if(room == null || room.getChatRoom() == null) + return false; + if(room.getChatRoom().getUserRole().equals(ChatRoomMemberRole.OWNER)) + return true; + return false; + } + else + { + ChatRoomSourceContact contact + = (ChatRoomSourceContact) actionSource; + ChatRoomWrapper room = MUCActivator.getMUCService() + .findChatRoomWrapperFromSourceContact(contact); + if(room == null) + return false; + + if(name.equals("autojoin")) + return room.isAutojoin(); + else if(name.equals("autojoin_pressed")) + return !room.isAutojoin(); + } + } + return false; + } + + } + + /** + * Implements the MUC custom menu items. + */ + private class MUCActionMenuItems + implements ContactActionMenuItem<SourceContact> + { + /** + * The label for the menu item. + */ + private String text; + + /** + * The the icon for the menu item. + */ + private byte[] image; + + /** + * The action executed when the menu item is pressed. + */ + private MUCCustomActionRunnable actionPerformed; + + /** + * Object that is used to check if the item is enabled or disabled. + */ + private EnableChecker enabled; + + /** + * The name of the custom action menu item. + */ + private String name; + + /** + * The mnemonic for the action. + */ + private char mnemonics; + + /** + * Constructs <tt>MUCActionMenuItems</tt> instance. + * + * @param textKey the key used to retrieve the label for the menu item. + * @param imageKey the key used to retrieve the icon for the menu item. + * @param actionPerformed the action executed when the menu item is + * pressed. + */ + public MUCActionMenuItems(String name, String textKey, String imageKey, + MUCCustomActionRunnable actionPerformed) + { + this.text = resources.getI18NString(textKey); + this.image = (imageKey == null)? null : + resources.getImageInBytes(imageKey); + this.actionPerformed = actionPerformed; + this.enabled = new EnableChecker(); + this.name = name; + this.mnemonics = resources.getI18nMnemonic(textKey); + } + + @Override + public void actionPerformed(SourceContact actionSource) + throws OperationFailedException + { + if(!(actionSource instanceof ChatRoomSourceContact)) + return; + actionPerformed.setContact(actionSource); + new Thread(actionPerformed).start(); + } + + @Override + public byte[] getIcon() + { + return image; + } + + + @Override + public String getText(SourceContact actionSource) + { + if(!(actionSource instanceof ChatRoomSourceContact)) + return ""; + + if(!name.equals("open_automatically")) + return text; + + String openAutomaticallyValue + = MUCService.getChatRoomAutoOpenOption( + ((ChatRoomSourceContact)actionSource).getProvider(), + ((ChatRoomSourceContact)actionSource).getChatRoomID()); + if(openAutomaticallyValue == null) + openAutomaticallyValue = MUCService.DEFAULT_AUTO_OPEN_BEHAVIOUR; + String openAutomaticallyKey = MUCService.autoOpenConfigValuesTexts + .get(openAutomaticallyValue); + return "<html>" + text + "...<br><font size=\"2\"><center> (" + + resources.getI18NString(openAutomaticallyKey) + + ")</center></font></html>"; + } + + @Override + public boolean isVisible(SourceContact actionSource) + { + if(!(actionSource instanceof ChatRoomSourceContact)) + return false; + + ChatRoomSourceContact contact + = (ChatRoomSourceContact) actionSource; + ChatRoomWrapper room = MUCActivator.getMUCService() + .findChatRoomWrapperFromSourceContact(contact); + if(name.equals("autojoin") || name.equals("autojoin_pressed")) + { + if(room == null) + return true; + + if(name.equals("autojoin")) + return !room.isAutojoin(); + + if(name.equals("autojoin_pressed")) + return room.isAutojoin(); + } + else if(name.equals("remove")) + { + if(room == null || room.getChatRoom() == null) + return true; + + boolean ownerCannotRemoveRoom + = MUCActivator.getConfigurationService().getBoolean( + OWNER_CANT_REMOVE_CHATROOM_PROPERTY, false); + + // when joined role will be owner or member + // when not joined and if we never has entered the room role + // will be guest, if we joined and left the room the role + // will be owner or member + if(room.getChatRoom().getUserRole().equals( + ChatRoomMemberRole.MEMBER)) + { + return true; + } + else + { + if(ownerCannotRemoveRoom) + return false; + else + return true; + } + } + else if(name.equals("destroy_chatroom")) + { + if(room == null || room.getChatRoom() == null) + return false; + if(room.getChatRoom().getUserRole().equals( + ChatRoomMemberRole.OWNER)) + return true; + return false; + } + return true; + } + + @Override + public char getMnemonics() + { + return mnemonics; + } + + @Override + public boolean isEnabled(SourceContact actionSource) + { + return enabled.check(actionSource); + } + + /** + * Sets <tt>EnabledChecker</tt> instance that will be used to check if + * the item should be enabled or disabled. + * + * @param enabled the <tt>EnabledChecker</tt> instance. + */ + public void setEnabled(EnableChecker enabled) + { + this.enabled = enabled; + } + + @Override + public boolean isCheckBox() + { + return false; + } + + @Override + public boolean isSelected(SourceContact contact) + { + ChatRoomWrapper chatRoomWrapper = MUCActivator.getMUCService() + .findChatRoomWrapperFromSourceContact(contact); + if(chatRoomWrapper == null) + return false; + return chatRoomWrapper.isAutojoin(); + } + + } + + /** + * Checks if the menu item should be enabled or disabled. This is default + * implementation. Always returns that the item should be enabled. + */ + private static class EnableChecker + { + /** + * Checks if the menu item should be enabled or disabled. + * + * @param contact the contact associated with the menu item. + * @return always <tt>true</tt> + */ + public boolean check(SourceContact contact) + { + return true; + } + } + + /** + * Implements <tt>EnableChecker</tt> for the join menu items. + */ + private static class JoinEnableChecker + extends EnableChecker + { + /** + * Checks if the menu item should be enabled or disabled. + * + * @param contact the contact associated with the menu item. + * @return <tt>true</tt> if the item should be enabled and + * <tt>false</tt> if not. + */ + public boolean check(SourceContact contact) + { + ChatRoomWrapper chatRoomWrapper = MUCActivator.getMUCService() + .findChatRoomWrapperFromSourceContact(contact); + ChatRoom chatRoom = null; + if(chatRoomWrapper != null) + { + chatRoom = chatRoomWrapper.getChatRoom(); + } + + if((chatRoom != null) && chatRoom.isJoined()) + return false; + return true; + } + } + + /** + * Implements <tt>EnableChecker</tt> for the leave menu item. + */ + private static class LeaveEnableChecker + extends JoinEnableChecker + { + /** + * Checks if the menu item should be enabled or disabled. + * + * @param contact the contact associated with the menu item. + * @return <tt>true</tt> if the item should be enabled and + * <tt>false</tt> if not. + */ + public boolean check(SourceContact contact) + { + return !super.check(contact); + } + } + + /** + * Implements base properties for the MUC menu items.These properties are + * used when the menu item is pressed. + */ + private abstract class MUCCustomActionRunnable + implements Runnable + { + /** + * The contact associated with the menu item. + */ + protected SourceContact contact; + + /** + * The contact associated with the menu item. + */ + protected ChatRoomWrapper chatRoomWrapper; + + /** + * Sets the source contact. + * @param contact the contact to set + */ + public void setContact(SourceContact contact) + { + this.contact = contact; + chatRoomWrapper = MUCActivator.getMUCService() + .findChatRoomWrapperFromSourceContact(contact); + } + } +} diff --git a/src/net/java/sip/communicator/impl/muc/MUCServiceImpl.java b/src/net/java/sip/communicator/impl/muc/MUCServiceImpl.java index f6dd1c7..be02255 100644 --- a/src/net/java/sip/communicator/impl/muc/MUCServiceImpl.java +++ b/src/net/java/sip/communicator/impl/muc/MUCServiceImpl.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,1117 +15,1117 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.muc;
-
-import static net.java.sip.communicator.service.muc.ChatRoomWrapper.*;
-
-import java.util.*;
-
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.plugin.desktoputil.chat.*;
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.muc.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.globalstatus.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.resources.*;
-
-/**
- * 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 =
- ConfigurationUtils.getChatRoomProperty(
- chatRoom.getParentProvider(),
- chatRoom.getIdentifier(), "userNickName");
- if(nickName == null)
- {
- String[] joinOptions = ChatRoomJoinOptionsDialog.getJoinOptions(
- true,
- chatRoom.getParentProvider(),
- chatRoom.getIdentifier(),
- MUCActivator.getGlobalDisplayDetailsService()
- .getDisplayName(chatRoom.getParentProvider()));
- nickName = joinOptions[0];
- }
-
- 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)
- {
- if (chatRoomWrapper.getChatRoom() == null)
- {
- chatRoomWrapper = createChatRoom(
- chatRoomWrapper.getChatRoomName(),
- chatRoomWrapper.getParentProvider().getProtocolProvider(),
- new ArrayList<String>(), "", false, false, true);
- }
-
- 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)
- {
- if (chatRoomWrapper.getChatRoom() == null)
- {
- chatRoomWrapper = createChatRoom(
- chatRoomWrapper.getChatRoomName(),
- chatRoomWrapper.getParentProvider().getProtocolProvider(),
- new ArrayList<String>(), "", false, false, true);
- }
-
- 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)
- {
- if (chatRoomWrapper.getChatRoom() == null)
- {
- chatRoomWrapper = createChatRoom(
- chatRoomWrapper.getChatRoomName(),
- chatRoomWrapper.getParentProvider().getProtocolProvider(),
- new ArrayList<String>(), "", false, false, true);
- }
-
- 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);
-
- }
-
- 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 or
- * <tt>null</tt> if the protocol fails to create the chat 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(),
- MUCActivator.getGlobalDisplayDetailsService()
- .getDisplayName(
- room.getParentProvider().getProtocolProvider()));
- savedNick = joinOptions[0];
- subject = joinOptions[1];
-
- }
-
- if (savedNick != null)
- {
- joinChatRoom(room, savedNick, null,
- subject);
- }
- else
- return;
- }
-
- MUCActivator.getUIService().openChatRoomWindow(room);
- }
-
- /**
- * 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);
- }
-}
+package net.java.sip.communicator.impl.muc; + +import static net.java.sip.communicator.service.muc.ChatRoomWrapper.*; + +import java.util.*; + +import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.plugin.desktoputil.chat.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.muc.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.globalstatus.*; +import net.java.sip.communicator.util.*; + +import org.jitsi.service.resources.*; + +/** + * 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 = + ConfigurationUtils.getChatRoomProperty( + chatRoom.getParentProvider(), + chatRoom.getIdentifier(), "userNickName"); + if(nickName == null) + { + String[] joinOptions = ChatRoomJoinOptionsDialog.getJoinOptions( + true, + chatRoom.getParentProvider(), + chatRoom.getIdentifier(), + MUCActivator.getGlobalDisplayDetailsService() + .getDisplayName(chatRoom.getParentProvider())); + nickName = joinOptions[0]; + } + + 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) + { + if (chatRoomWrapper.getChatRoom() == null) + { + chatRoomWrapper = createChatRoom( + chatRoomWrapper.getChatRoomName(), + chatRoomWrapper.getParentProvider().getProtocolProvider(), + new ArrayList<String>(), "", false, false, true); + } + + 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) + { + if (chatRoomWrapper.getChatRoom() == null) + { + chatRoomWrapper = createChatRoom( + chatRoomWrapper.getChatRoomName(), + chatRoomWrapper.getParentProvider().getProtocolProvider(), + new ArrayList<String>(), "", false, false, true); + } + + 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) + { + if (chatRoomWrapper.getChatRoom() == null) + { + chatRoomWrapper = createChatRoom( + chatRoomWrapper.getChatRoomName(), + chatRoomWrapper.getParentProvider().getProtocolProvider(), + new ArrayList<String>(), "", false, false, true); + } + + 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); + + } + + 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 or + * <tt>null</tt> if the protocol fails to create the chat 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(), + MUCActivator.getGlobalDisplayDetailsService() + .getDisplayName( + room.getParentProvider().getProtocolProvider())); + savedNick = joinOptions[0]; + subject = joinOptions[1]; + + } + + if (savedNick != null) + { + joinChatRoom(room, savedNick, null, + subject); + } + else + return; + } + + MUCActivator.getUIService().openChatRoomWindow(room); + } + + /** + * 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); + } +} diff --git a/src/net/java/sip/communicator/impl/muc/ServerChatRoomQuery.java b/src/net/java/sip/communicator/impl/muc/ServerChatRoomQuery.java index 47785ee..a98e5ed 100644 --- a/src/net/java/sip/communicator/impl/muc/ServerChatRoomQuery.java +++ b/src/net/java/sip/communicator/impl/muc/ServerChatRoomQuery.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,331 +15,331 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.muc;
-
-import java.util.*;
-import java.util.regex.*;
-
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.muc.*;
-import net.java.sip.communicator.service.protocol.*;
-
-/**
- * The <tt>ServerChatRoomQuery</tt> is a query over the
- * <tt>ServerChatRoomContactSourceService</tt>.
- *
- * @author Hristo Terezov
- */
-public class ServerChatRoomQuery
- extends AsyncContactQuery<ContactSourceService>
- implements ChatRoomProviderWrapperListener
-{
- /**
- * The query string.
- */
- private String queryString;
-
- /**
- * List with the current results for the query.
- */
- private Set<BaseChatRoomSourceContact> contactResults
- = new TreeSet<BaseChatRoomSourceContact>();
-
- /**
- * MUC service.
- */
- private MUCServiceImpl mucService;
-
- /**
- * The number of contact query listeners.
- */
- private int contactQueryListenersCount = 0;
-
- /**
- * The provider associated with the query.
- */
- private ChatRoomProviderWrapper provider = null;
-
- /**
- * Creates an instance of <tt>ChatRoomQuery</tt> by specifying
- * the parent contact source, the query string to match and the maximum
- * result contacts to return.
- *
- * @param contactSource the parent contact source
- * @param queryString the query string to match
- * @param provider the provider associated with the query
- */
- public ServerChatRoomQuery(String queryString,
- ServerChatRoomContactSourceService contactSource,
- ChatRoomProviderWrapper provider)
- {
- super(contactSource,
- Pattern.compile(queryString, Pattern.CASE_INSENSITIVE
- | Pattern.LITERAL), true);
- this.queryString = queryString;
-
- mucService = MUCActivator.getMUCService();
-
- this.provider = provider;
- }
-
- /**
- * Adds listeners for the query
- */
- private void initListeners()
- {
- mucService.addChatRoomProviderWrapperListener(this);
- }
-
- @Override
- protected void run()
- {
- if(provider == null)
- {
- Iterator<ChatRoomProviderWrapper> chatRoomProviders
- = mucService.getChatRoomProviders();
- while (chatRoomProviders.hasNext())
- {
- ChatRoomProviderWrapper provider = chatRoomProviders.next();
- providerAdded(provider, true);
- }
- }
- else
- {
- providerAdded(provider, true);
- }
-
- if (getStatus() != QUERY_CANCELED)
- setStatus(QUERY_COMPLETED);
- }
-
- /**
- * Handles adding a chat room provider.
- * @param provider the provider.
- * @param addQueryResult indicates whether we should add the chat room to
- * the query results or fire an event without adding it to the results.
- */
- private void providerAdded(final ChatRoomProviderWrapper provider,
- final boolean addQueryResult)
- {
- final ProtocolProviderService pps = provider.getProtocolProvider();
- List<String> chatRoomNames =
- MUCActivator.getMUCService().getExistingChatRooms(provider);
- if (chatRoomNames == null)
- {
- return;
- }
-
- // Already create all the BaseChatRoomSourceContact instances since all
- // the data is already available.
- final Set<BaseChatRoomSourceContact> chatRooms =
- new HashSet<BaseChatRoomSourceContact>(chatRoomNames.size());
- for (final String name : chatRoomNames)
- {
- chatRooms.add(new BaseChatRoomSourceContact(name, name, this, pps));
- }
- addChatRooms(pps, chatRooms, addQueryResult);
- }
-
-
- /**
- * Adds found result to the query results.
- *
- * @param pps the protocol provider associated with the found chat room.
- * @param chatRoomName the name of the chat room.
- * @param chatRoomID the id of the chat room.
- * @param addQueryResult indicates whether we should add the chat room to
- * the query results or fire an event without adding it to the results.
- */
- private void addChatRoom(ProtocolProviderService pps,
- String chatRoomName, String chatRoomID, boolean addQueryResult)
- {
- if((queryString == null
- || ((chatRoomName.contains(
- queryString)
- || chatRoomID.contains(queryString)
- ))) && isMatching(chatRoomID, pps))
- {
- BaseChatRoomSourceContact contact
- = new BaseChatRoomSourceContact(chatRoomName, chatRoomID, this,
- pps);
- synchronized (contactResults)
- {
- contactResults.add(contact);
- }
-
- if(addQueryResult)
- {
- addQueryResult(contact, false);
- }
- else
- {
- fireContactReceived(contact, false);
- }
- }
- }
-
- /**
- * Adds found results to the query results.
- *
- * @param pps the protocol provider associated with the found chat room.
- * @param chatRooms The set of chat rooms based on
- * BaseChatRoomSourceContact. This is the full set and it will be
- * filtered according to demands of the queryString.
- * @param addQueryResult indicates whether we should add the chat room to
- * the query results or fire an event without adding it to the
- * results.
- */
- private void addChatRooms(final ProtocolProviderService pps,
- final Set<BaseChatRoomSourceContact> chatRooms,
- final boolean addQueryResult)
- {
- BaseChatRoomSourceContact room;
- Iterator<BaseChatRoomSourceContact> iterator = chatRooms.iterator();
- while (iterator.hasNext())
- {
- room = iterator.next();
-
- // Notice the NOT operator at the start ...
- if (!((queryString == null || (room.getChatRoomName().contains(
- queryString) || room.getChatRoomID().contains(queryString)))
- && isMatching(room.getChatRoomID(), pps)))
- {
- iterator.remove();
- }
- }
-
- synchronized (contactResults)
- {
- contactResults.addAll(chatRooms);
- }
-
- if (addQueryResult)
- {
- addQueryResults(chatRooms);
- }
- else
- {
- // TODO Need something to fire one event for multiple contacts.
- for (SourceContact contact : chatRooms)
- {
- fireContactReceived(contact, false);
- }
- }
- }
-
- @Override
- public void chatRoomProviderWrapperAdded(ChatRoomProviderWrapper provider)
- {
- providerAdded(provider, false);
- }
-
- @Override
- public void chatRoomProviderWrapperRemoved(ChatRoomProviderWrapper provider)
- {
- LinkedList<BaseChatRoomSourceContact> tmpContactResults;
- synchronized (contactResults)
- {
- tmpContactResults
- = new LinkedList<BaseChatRoomSourceContact>(contactResults);
-
- for(BaseChatRoomSourceContact contact : tmpContactResults)
- {
- if(contact.getProvider().equals(provider.getProtocolProvider()))
- {
- contactResults.remove(contact);
- fireContactRemoved(contact);
- }
- }
- }
- }
-
-
- /**
- * Clears any listener we used.
- */
- private void clearListeners()
- {
- mucService.removeChatRoomProviderWrapperListener(this);
- }
-
- /**
- * Cancels this <tt>ContactQuery</tt>.
- *
- * @see ContactQuery#cancel()
- */
- public void cancel()
- {
- clearListeners();
-
- super.cancel();
- }
-
- /**
- * If query has status changed to cancel, let's clear listeners.
- * @param status {@link ContactQuery#QUERY_CANCELED},
- * {@link ContactQuery#QUERY_COMPLETED}
- */
- public void setStatus(int status)
- {
- if(status == QUERY_CANCELED)
- clearListeners();
-
- super.setStatus(status);
- }
-
- @Override
- public void addContactQueryListener(ContactQueryListener l)
- {
- super.addContactQueryListener(l);
- contactQueryListenersCount++;
- if(contactQueryListenersCount == 1)
- {
- initListeners();
- }
- }
-
- @Override
- public void removeContactQueryListener(ContactQueryListener l)
- {
- super.removeContactQueryListener(l);
- contactQueryListenersCount--;
- if(contactQueryListenersCount == 0)
- {
- clearListeners();
- }
- }
-
- /**
- * Checks if the contact should be added to results or not.
- * @param chatRoomID the chat room id associated with the contact.
- * @param pps the provider of the chat room contact.
- * @return <tt>true</tt> if the result should be added to the results and
- * <tt>false</tt> if not.
- */
- public boolean isMatching(String chatRoomID, ProtocolProviderService pps)
- {
- return (MUCActivator.getMUCService().findChatRoomWrapperFromChatRoomID(
- chatRoomID, pps) == null);
- }
-
- /**
- * Returns the index of the contact in the contact results list.
- * @param contact the contact.
- * @return the index of the contact in the contact results list.
- */
- public int indexOf(BaseChatRoomSourceContact contact)
- {
- Iterator<BaseChatRoomSourceContact> it = contactResults.iterator();
- int i = 0;
- while(it.hasNext())
- {
- if(contact.equals(it.next()))
- {
- return i;
- }
- i++;
- }
- return -1;
- }
-}
+package net.java.sip.communicator.impl.muc; + +import java.util.*; +import java.util.regex.*; + +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.muc.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * The <tt>ServerChatRoomQuery</tt> is a query over the + * <tt>ServerChatRoomContactSourceService</tt>. + * + * @author Hristo Terezov + */ +public class ServerChatRoomQuery + extends AsyncContactQuery<ContactSourceService> + implements ChatRoomProviderWrapperListener +{ + /** + * The query string. + */ + private String queryString; + + /** + * List with the current results for the query. + */ + private Set<BaseChatRoomSourceContact> contactResults + = new TreeSet<BaseChatRoomSourceContact>(); + + /** + * MUC service. + */ + private MUCServiceImpl mucService; + + /** + * The number of contact query listeners. + */ + private int contactQueryListenersCount = 0; + + /** + * The provider associated with the query. + */ + private ChatRoomProviderWrapper provider = null; + + /** + * Creates an instance of <tt>ChatRoomQuery</tt> by specifying + * the parent contact source, the query string to match and the maximum + * result contacts to return. + * + * @param contactSource the parent contact source + * @param queryString the query string to match + * @param provider the provider associated with the query + */ + public ServerChatRoomQuery(String queryString, + ServerChatRoomContactSourceService contactSource, + ChatRoomProviderWrapper provider) + { + super(contactSource, + Pattern.compile(queryString, Pattern.CASE_INSENSITIVE + | Pattern.LITERAL), true); + this.queryString = queryString; + + mucService = MUCActivator.getMUCService(); + + this.provider = provider; + } + + /** + * Adds listeners for the query + */ + private void initListeners() + { + mucService.addChatRoomProviderWrapperListener(this); + } + + @Override + protected void run() + { + if(provider == null) + { + Iterator<ChatRoomProviderWrapper> chatRoomProviders + = mucService.getChatRoomProviders(); + while (chatRoomProviders.hasNext()) + { + ChatRoomProviderWrapper provider = chatRoomProviders.next(); + providerAdded(provider, true); + } + } + else + { + providerAdded(provider, true); + } + + if (getStatus() != QUERY_CANCELED) + setStatus(QUERY_COMPLETED); + } + + /** + * Handles adding a chat room provider. + * @param provider the provider. + * @param addQueryResult indicates whether we should add the chat room to + * the query results or fire an event without adding it to the results. + */ + private void providerAdded(final ChatRoomProviderWrapper provider, + final boolean addQueryResult) + { + final ProtocolProviderService pps = provider.getProtocolProvider(); + List<String> chatRoomNames = + MUCActivator.getMUCService().getExistingChatRooms(provider); + if (chatRoomNames == null) + { + return; + } + + // Already create all the BaseChatRoomSourceContact instances since all + // the data is already available. + final Set<BaseChatRoomSourceContact> chatRooms = + new HashSet<BaseChatRoomSourceContact>(chatRoomNames.size()); + for (final String name : chatRoomNames) + { + chatRooms.add(new BaseChatRoomSourceContact(name, name, this, pps)); + } + addChatRooms(pps, chatRooms, addQueryResult); + } + + + /** + * Adds found result to the query results. + * + * @param pps the protocol provider associated with the found chat room. + * @param chatRoomName the name of the chat room. + * @param chatRoomID the id of the chat room. + * @param addQueryResult indicates whether we should add the chat room to + * the query results or fire an event without adding it to the results. + */ + private void addChatRoom(ProtocolProviderService pps, + String chatRoomName, String chatRoomID, boolean addQueryResult) + { + if((queryString == null + || ((chatRoomName.contains( + queryString) + || chatRoomID.contains(queryString) + ))) && isMatching(chatRoomID, pps)) + { + BaseChatRoomSourceContact contact + = new BaseChatRoomSourceContact(chatRoomName, chatRoomID, this, + pps); + synchronized (contactResults) + { + contactResults.add(contact); + } + + if(addQueryResult) + { + addQueryResult(contact, false); + } + else + { + fireContactReceived(contact, false); + } + } + } + + /** + * Adds found results to the query results. + * + * @param pps the protocol provider associated with the found chat room. + * @param chatRooms The set of chat rooms based on + * BaseChatRoomSourceContact. This is the full set and it will be + * filtered according to demands of the queryString. + * @param addQueryResult indicates whether we should add the chat room to + * the query results or fire an event without adding it to the + * results. + */ + private void addChatRooms(final ProtocolProviderService pps, + final Set<BaseChatRoomSourceContact> chatRooms, + final boolean addQueryResult) + { + BaseChatRoomSourceContact room; + Iterator<BaseChatRoomSourceContact> iterator = chatRooms.iterator(); + while (iterator.hasNext()) + { + room = iterator.next(); + + // Notice the NOT operator at the start ... + if (!((queryString == null || (room.getChatRoomName().contains( + queryString) || room.getChatRoomID().contains(queryString))) + && isMatching(room.getChatRoomID(), pps))) + { + iterator.remove(); + } + } + + synchronized (contactResults) + { + contactResults.addAll(chatRooms); + } + + if (addQueryResult) + { + addQueryResults(chatRooms); + } + else + { + // TODO Need something to fire one event for multiple contacts. + for (SourceContact contact : chatRooms) + { + fireContactReceived(contact, false); + } + } + } + + @Override + public void chatRoomProviderWrapperAdded(ChatRoomProviderWrapper provider) + { + providerAdded(provider, false); + } + + @Override + public void chatRoomProviderWrapperRemoved(ChatRoomProviderWrapper provider) + { + LinkedList<BaseChatRoomSourceContact> tmpContactResults; + synchronized (contactResults) + { + tmpContactResults + = new LinkedList<BaseChatRoomSourceContact>(contactResults); + + for(BaseChatRoomSourceContact contact : tmpContactResults) + { + if(contact.getProvider().equals(provider.getProtocolProvider())) + { + contactResults.remove(contact); + fireContactRemoved(contact); + } + } + } + } + + + /** + * Clears any listener we used. + */ + private void clearListeners() + { + mucService.removeChatRoomProviderWrapperListener(this); + } + + /** + * Cancels this <tt>ContactQuery</tt>. + * + * @see ContactQuery#cancel() + */ + public void cancel() + { + clearListeners(); + + super.cancel(); + } + + /** + * If query has status changed to cancel, let's clear listeners. + * @param status {@link ContactQuery#QUERY_CANCELED}, + * {@link ContactQuery#QUERY_COMPLETED} + */ + public void setStatus(int status) + { + if(status == QUERY_CANCELED) + clearListeners(); + + super.setStatus(status); + } + + @Override + public void addContactQueryListener(ContactQueryListener l) + { + super.addContactQueryListener(l); + contactQueryListenersCount++; + if(contactQueryListenersCount == 1) + { + initListeners(); + } + } + + @Override + public void removeContactQueryListener(ContactQueryListener l) + { + super.removeContactQueryListener(l); + contactQueryListenersCount--; + if(contactQueryListenersCount == 0) + { + clearListeners(); + } + } + + /** + * Checks if the contact should be added to results or not. + * @param chatRoomID the chat room id associated with the contact. + * @param pps the provider of the chat room contact. + * @return <tt>true</tt> if the result should be added to the results and + * <tt>false</tt> if not. + */ + public boolean isMatching(String chatRoomID, ProtocolProviderService pps) + { + return (MUCActivator.getMUCService().findChatRoomWrapperFromChatRoomID( + chatRoomID, pps) == null); + } + + /** + * Returns the index of the contact in the contact results list. + * @param contact the contact. + * @return the index of the contact in the contact results list. + */ + public int indexOf(BaseChatRoomSourceContact contact) + { + Iterator<BaseChatRoomSourceContact> it = contactResults.iterator(); + int i = 0; + while(it.hasNext()) + { + if(contact.equals(it.next())) + { + return i; + } + i++; + } + return -1; + } +} diff --git a/src/net/java/sip/communicator/impl/neomedia/AbstractDeviceConfigurationListener.java b/src/net/java/sip/communicator/impl/neomedia/AbstractDeviceConfigurationListener.java index a1814e8..3c84aa2 100644 --- a/src/net/java/sip/communicator/impl/neomedia/AbstractDeviceConfigurationListener.java +++ b/src/net/java/sip/communicator/impl/neomedia/AbstractDeviceConfigurationListener.java @@ -187,8 +187,7 @@ public abstract class AbstractDeviceConfigurationListener body + "\r\n\r\n" + NeomediaActivator.getResources().getI18NString( - "impl.media.configform" - + ".AUDIO_DEVICE_CONFIG_MANAGMENT_CLICK"), + "impl.media.configform.AUDIO_DEVICE_CONFIG_MANAGMENT_CLICK"), null, extras); } diff --git a/src/net/java/sip/communicator/impl/neomedia/AudioDeviceConfigurationListener.java b/src/net/java/sip/communicator/impl/neomedia/AudioDeviceConfigurationListener.java index 75f1fc3..fe06f15 100644 --- a/src/net/java/sip/communicator/impl/neomedia/AudioDeviceConfigurationListener.java +++ b/src/net/java/sip/communicator/impl/neomedia/AudioDeviceConfigurationListener.java @@ -159,10 +159,8 @@ public class AudioDeviceConfigurationListener capturePropertyChangeEvent.getOldValue())) { body.append("\r\n") - .append( - r.getI18NString( - "impl.media.configform" - + ".AUDIO_DEVICE_SELECTED_AUDIO_IN")) + .append(r.getI18NString( + "impl.media.configform.AUDIO_DEVICE_SELECTED_AUDIO_IN")) .append("\r\n\t") .append(cdi.getName()); selectedHasChanged = true; @@ -179,10 +177,8 @@ public class AudioDeviceConfigurationListener playbackPropertyChangeEvent.getOldValue())) { body.append("\r\n") - .append( - r.getI18NString( - "impl.media.configform" - + ".AUDIO_DEVICE_SELECTED_AUDIO_OUT")) + .append(r.getI18NString( + "impl.media.configform.AUDIO_DEVICE_SELECTED_AUDIO_OUT")) .append("\r\n\t") .append(cdi.getName()); selectedHasChanged = true; @@ -199,10 +195,8 @@ public class AudioDeviceConfigurationListener notifyPropertyChangeEvent.getOldValue())) { body.append("\r\n") - .append( - r.getI18NString( - "impl.media.configform" - + ".AUDIO_DEVICE_SELECTED_AUDIO_NOTIFICATIONS")) + .append(r.getI18NString( + "impl.media.configform.AUDIO_DEVICE_SELECTED_AUDIO_NOTIFICATIONS")) .append("\r\n\t") .append(cdi.getName()); selectedHasChanged = true; diff --git a/src/net/java/sip/communicator/impl/neomedia/DeviceConfigurationComboBoxModel.java b/src/net/java/sip/communicator/impl/neomedia/DeviceConfigurationComboBoxModel.java index 1c5bf93..9bacb4c 100644 --- a/src/net/java/sip/communicator/impl/neomedia/DeviceConfigurationComboBoxModel.java +++ b/src/net/java/sip/communicator/impl/neomedia/DeviceConfigurationComboBoxModel.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,456 +15,456 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.neomedia;
-
-import java.beans.*;
-import java.util.*;
-
-import javax.media.*;
-import javax.swing.*;
-import javax.swing.event.*;
-
-import org.jitsi.impl.neomedia.device.*;
-import org.jitsi.service.neomedia.*;
-
-/**
- * Implements <tt>ComboBoxModel</tt> for a specific <tt>DeviceConfiguration</tt>
- * so that the latter may be displayed and manipulated in the user interface as
- * a combo box.
- *
- * @author Lyubomir Marinov
- * @author Damian Minkov
- */
-public class DeviceConfigurationComboBoxModel
- implements ComboBoxModel,
- ListModel,
- PropertyChangeListener
-{
- /**
- * Type of the model - audio.
- */
- public static final int AUDIO = 1;
-
- /**
- * Audio Capture Device.
- */
- public static final int AUDIO_CAPTURE = 3;
-
- /**
- * Audio device for notification sounds.
- */
- public static final int AUDIO_NOTIFY = 5;
-
- /**
- * Audio playback device.
- */
- public static final int AUDIO_PLAYBACK = 4;
-
- /**
- * Type of the model - video.
- */
- public static final int VIDEO = 2;
-
- private AudioSystem[] audioSystems;
-
- /**
- * The current device configuration.
- */
- private final DeviceConfiguration deviceConfiguration;
-
- /**
- * All the devices.
- */
- private CaptureDevice[] devices;
-
- /**
- * The <tt>ListDataListener</tt>s registered with this instance.
- */
- private final List<ListDataListener> listeners
- = new ArrayList<ListDataListener>();
-
- /**
- * The type of the media for this combo.
- */
- private final int type;
-
- /**
- * Creates device combobox model
- * @param parent the parent component
- * @param deviceConfiguration the current device configuration
- * @param type the device - audio/video
- */
- public DeviceConfigurationComboBoxModel(
- DeviceConfiguration deviceConfiguration,
- int type)
- {
- if (deviceConfiguration == null)
- throw new IllegalArgumentException("deviceConfiguration");
- if ((type != AUDIO)
- && (type != AUDIO_CAPTURE)
- && (type != AUDIO_NOTIFY)
- && (type != AUDIO_PLAYBACK)
- && (type != VIDEO))
- throw new IllegalArgumentException("type");
-
- this.deviceConfiguration = deviceConfiguration;
- this.type = type;
-
- if (type == AUDIO
- || type == AUDIO_CAPTURE
- || type == AUDIO_NOTIFY
- || type == AUDIO_PLAYBACK)
- {
- deviceConfiguration.addPropertyChangeListener(this);
- }
- }
-
- public void addListDataListener(ListDataListener listener)
- {
- if (listener == null)
- throw new IllegalArgumentException("listener");
-
- if (!listeners.contains(listener))
- listeners.add(listener);
- }
-
- /**
- * Change of the content.
- * @param index0 from index.
- * @param index1 to index.
- */
- protected void fireContentsChanged(int index0, int index1)
- {
- ListDataListener[] listeners
- = this.listeners.toArray(
- new ListDataListener[this.listeners.size()]);
- ListDataEvent event
- = new ListDataEvent(
- this,
- ListDataEvent.CONTENTS_CHANGED,
- index0,
- index1);
-
- for (ListDataListener listener : listeners)
- listener.contentsChanged(event);
- }
-
- private AudioSystem[] getAudioSystems()
- {
- if (type != AUDIO)
- throw new IllegalStateException("type");
-
- audioSystems = deviceConfiguration.getAvailableAudioSystems();
- return audioSystems;
- }
-
- /**
- * Extracts the devices for the current type.
- * @return the devices.
- */
- private CaptureDevice[] getDevices()
- {
- if (type == AUDIO)
- throw new IllegalStateException("type");
-
- if (devices != null)
- return devices;
-
- AudioSystem audioSystem;
- List<? extends CaptureDeviceInfo> infos = null;
-
- switch (type)
- {
- case AUDIO_CAPTURE:
- audioSystem = deviceConfiguration.getAudioSystem();
- infos = (audioSystem == null)
- ? null
- : audioSystem.getDevices(AudioSystem.DataFlow.CAPTURE);
- break;
- case AUDIO_NOTIFY:
- audioSystem = deviceConfiguration.getAudioSystem();
- infos = (audioSystem == null)
- ? null
- : audioSystem.getDevices(AudioSystem.DataFlow.NOTIFY);
- break;
- case AUDIO_PLAYBACK:
- audioSystem = deviceConfiguration.getAudioSystem();
- infos = (audioSystem == null)
- ? null
- : audioSystem.getDevices(AudioSystem.DataFlow.PLAYBACK);
- break;
- case VIDEO:
- infos = deviceConfiguration.getAvailableVideoCaptureDevices(
- MediaUseCase.CALL);
- break;
- default:
- throw new IllegalStateException("type");
- }
-
- final int deviceCount = (infos == null) ? 0 : infos.size();
- devices = new CaptureDevice[deviceCount + 1];
-
- if (deviceCount > 0)
- {
- for (int i = 0; i < deviceCount; i++)
- devices[i] = new CaptureDevice(infos.get(i));
- }
- devices[deviceCount] = new CaptureDevice(null);
-
- return devices;
- }
-
- public Object getElementAt(int index)
- {
- if (type == AUDIO)
- return getAudioSystems()[index];
- else
- return getDevices()[index];
- }
-
- /**
- * Extracts the devices selected by the configuration.
- * @return <tt>CaptureDevice</tt> selected
- */
- private CaptureDevice getSelectedDevice()
- {
- AudioSystem audioSystem;
- CaptureDeviceInfo info;
-
- switch (type)
- {
- case AUDIO_CAPTURE:
- audioSystem = deviceConfiguration.getAudioSystem();
- info = (audioSystem == null)
- ? null
- : audioSystem.getSelectedDevice(AudioSystem.DataFlow.CAPTURE);
- break;
- case AUDIO_NOTIFY:
- audioSystem = deviceConfiguration.getAudioSystem();
- info = (audioSystem == null)
- ? null
- : audioSystem.getSelectedDevice(AudioSystem.DataFlow.NOTIFY);
- break;
- case AUDIO_PLAYBACK:
- audioSystem = deviceConfiguration.getAudioSystem();
- info = (audioSystem == null)
- ? null
- : audioSystem.getSelectedDevice(AudioSystem.DataFlow.PLAYBACK);
- break;
- case VIDEO:
- info = deviceConfiguration.getVideoCaptureDevice(MediaUseCase.ANY);
- break;
- default:
- throw new IllegalStateException("type");
- }
-
- for (CaptureDevice device : getDevices())
- {
- if (device.equals(info))
- return device;
- }
- return null;
- }
-
- public Object getSelectedItem()
- {
- if (type == AUDIO)
- return deviceConfiguration.getAudioSystem();
- else
- return getSelectedDevice();
- }
-
- public int getSize()
- {
- if (type == AUDIO)
- return getAudioSystems().length;
- else
- return getDevices().length;
- }
-
- /**
- * Notifies this instance about changes in the values of the properties of
- * {@link #deviceConfiguration} so that this instance keeps itself
- * up-to-date with respect to the list of devices.
- *
- * @param ev a <tt>PropertyChangeEvent</tt> which describes the name of the
- * property whose value has changed and the old and new values of that
- * property
- */
- public void propertyChange(final PropertyChangeEvent ev)
- {
- if (DeviceConfiguration.PROP_AUDIO_SYSTEM_DEVICES.equals(
- ev.getPropertyName()))
- {
- if (SwingUtilities.isEventDispatchThread())
- {
- audioSystems = null;
- devices = null;
- fireContentsChanged(0, getSize() - 1);
- }
- else
- {
- SwingUtilities.invokeLater(
- new Runnable()
- {
- public void run()
- {
- propertyChange(ev);
- }
- });
- }
- }
- }
-
- public void removeListDataListener(ListDataListener listener)
- {
- if (listener == null)
- throw new IllegalArgumentException("listener");
-
- listeners.remove(listener);
- }
-
- /**
- * Selects and saves the new choice.
- * @param device the device we choose.
- */
- private void setSelectedDevice(CaptureDevice device)
- {
- // We cannot clear the selection of DeviceConfiguration.
- if (device == null)
- return;
-
- CaptureDevice selectedDevice = getSelectedDevice();
-
- if (selectedDevice != device)
- {
- AudioSystem audioSystem;
-
- switch (type)
- {
- case AUDIO_CAPTURE:
- audioSystem = deviceConfiguration.getAudioSystem();
- if (audioSystem != null)
- {
- audioSystem.setDevice(
- AudioSystem.DataFlow.CAPTURE,
- ((CaptureDeviceInfo2) device.info),
- true);
- }
- break;
- case AUDIO_NOTIFY:
- audioSystem = deviceConfiguration.getAudioSystem();
- if (audioSystem != null)
- {
- audioSystem.setDevice(
- AudioSystem.DataFlow.NOTIFY,
- ((CaptureDeviceInfo2) device.info),
- true);
- }
- break;
- case AUDIO_PLAYBACK:
- audioSystem = deviceConfiguration.getAudioSystem();
- if (audioSystem != null)
- {
- audioSystem.setDevice(
- AudioSystem.DataFlow.PLAYBACK,
- ((CaptureDeviceInfo2) device.info),
- true);
- }
- break;
- case VIDEO:
- deviceConfiguration.setVideoCaptureDevice(device.info, true);
- break;
- }
-
- fireContentsChanged(-1, -1);
- }
- }
-
- public void setSelectedItem(Object item)
- {
- if (type == AUDIO)
- {
- AudioSystem audioSystem = (AudioSystem) item;
-
- if(!audioSystem.equals(deviceConfiguration.getAudioSystem()))
- {
- deviceConfiguration.setAudioSystem(audioSystem, true);
- fireContentsChanged(-1, -1);
- }
- }
- else
- setSelectedDevice((CaptureDevice) item);
- }
-
- /**
- * Encapsulates a <tt>CaptureDeviceInfo</tt> for the purposes of its display
- * in the user interface.
- */
- public static class CaptureDevice
- {
- /**
- * The encapsulated info.
- */
- public final CaptureDeviceInfo info;
-
- /**
- * Creates the wrapper.
- * @param info the info object we wrap.
- */
- public CaptureDevice(CaptureDeviceInfo info)
- {
- this.info = info;
- }
-
- /**
- * Determines whether the <tt>CaptureDeviceInfo</tt> encapsulated by
- * this instance is equal (by value) to a specific
- * <tt>CaptureDeviceInfo</tt>.
- *
- * @param cdi the <tt>CaptureDeviceInfo</tt> to be determined whether it
- * is equal (by value) to the <tt>CaptureDeviceInfo</tt> encapsulated by
- * this instance
- * @return <tt>true</tt> if the <tt>CaptureDeviceInfo</tt> encapsulated
- * by this instance is equal (by value) to the specified <tt>cdi</tt>;
- * otherwise, <tt>false</tt>
- */
- public boolean equals(CaptureDeviceInfo cdi)
- {
- return (info == null) ? (cdi == null) : info.equals(cdi);
- }
-
- /**
- * Gets a human-readable <tt>String</tt> representation of this
- * instance.
- *
- * @return a <tt>String</tt> value which is a human-readable
- * representation of this instance
- */
- @Override
- public String toString()
- {
- String s;
-
- if(info == null)
- {
- s
- = NeomediaActivator.getResources().getI18NString(
- "impl.media.configform.NO_DEVICE");
- }
- else
- {
- s = info.getName();
- if(info instanceof CaptureDeviceInfo2)
- {
- String transportType
- = ((CaptureDeviceInfo2) info).getTransportType();
-
- if(transportType != null)
- s += " (" + transportType + ")";
- }
- }
- return s;
- }
- }
-}
+package net.java.sip.communicator.impl.neomedia; + +import java.beans.*; +import java.util.*; + +import javax.media.*; +import javax.swing.*; +import javax.swing.event.*; + +import org.jitsi.impl.neomedia.device.*; +import org.jitsi.service.neomedia.*; + +/** + * Implements <tt>ComboBoxModel</tt> for a specific <tt>DeviceConfiguration</tt> + * so that the latter may be displayed and manipulated in the user interface as + * a combo box. + * + * @author Lyubomir Marinov + * @author Damian Minkov + */ +public class DeviceConfigurationComboBoxModel + implements ComboBoxModel, + ListModel, + PropertyChangeListener +{ + /** + * Type of the model - audio. + */ + public static final int AUDIO = 1; + + /** + * Audio Capture Device. + */ + public static final int AUDIO_CAPTURE = 3; + + /** + * Audio device for notification sounds. + */ + public static final int AUDIO_NOTIFY = 5; + + /** + * Audio playback device. + */ + public static final int AUDIO_PLAYBACK = 4; + + /** + * Type of the model - video. + */ + public static final int VIDEO = 2; + + private AudioSystem[] audioSystems; + + /** + * The current device configuration. + */ + private final DeviceConfiguration deviceConfiguration; + + /** + * All the devices. + */ + private CaptureDevice[] devices; + + /** + * The <tt>ListDataListener</tt>s registered with this instance. + */ + private final List<ListDataListener> listeners + = new ArrayList<ListDataListener>(); + + /** + * The type of the media for this combo. + */ + private final int type; + + /** + * Creates device combobox model + * @param parent the parent component + * @param deviceConfiguration the current device configuration + * @param type the device - audio/video + */ + public DeviceConfigurationComboBoxModel( + DeviceConfiguration deviceConfiguration, + int type) + { + if (deviceConfiguration == null) + throw new IllegalArgumentException("deviceConfiguration"); + if ((type != AUDIO) + && (type != AUDIO_CAPTURE) + && (type != AUDIO_NOTIFY) + && (type != AUDIO_PLAYBACK) + && (type != VIDEO)) + throw new IllegalArgumentException("type"); + + this.deviceConfiguration = deviceConfiguration; + this.type = type; + + if (type == AUDIO + || type == AUDIO_CAPTURE + || type == AUDIO_NOTIFY + || type == AUDIO_PLAYBACK) + { + deviceConfiguration.addPropertyChangeListener(this); + } + } + + public void addListDataListener(ListDataListener listener) + { + if (listener == null) + throw new IllegalArgumentException("listener"); + + if (!listeners.contains(listener)) + listeners.add(listener); + } + + /** + * Change of the content. + * @param index0 from index. + * @param index1 to index. + */ + protected void fireContentsChanged(int index0, int index1) + { + ListDataListener[] listeners + = this.listeners.toArray( + new ListDataListener[this.listeners.size()]); + ListDataEvent event + = new ListDataEvent( + this, + ListDataEvent.CONTENTS_CHANGED, + index0, + index1); + + for (ListDataListener listener : listeners) + listener.contentsChanged(event); + } + + private AudioSystem[] getAudioSystems() + { + if (type != AUDIO) + throw new IllegalStateException("type"); + + audioSystems = deviceConfiguration.getAvailableAudioSystems(); + return audioSystems; + } + + /** + * Extracts the devices for the current type. + * @return the devices. + */ + private CaptureDevice[] getDevices() + { + if (type == AUDIO) + throw new IllegalStateException("type"); + + if (devices != null) + return devices; + + AudioSystem audioSystem; + List<? extends CaptureDeviceInfo> infos = null; + + switch (type) + { + case AUDIO_CAPTURE: + audioSystem = deviceConfiguration.getAudioSystem(); + infos = (audioSystem == null) + ? null + : audioSystem.getDevices(AudioSystem.DataFlow.CAPTURE); + break; + case AUDIO_NOTIFY: + audioSystem = deviceConfiguration.getAudioSystem(); + infos = (audioSystem == null) + ? null + : audioSystem.getDevices(AudioSystem.DataFlow.NOTIFY); + break; + case AUDIO_PLAYBACK: + audioSystem = deviceConfiguration.getAudioSystem(); + infos = (audioSystem == null) + ? null + : audioSystem.getDevices(AudioSystem.DataFlow.PLAYBACK); + break; + case VIDEO: + infos = deviceConfiguration.getAvailableVideoCaptureDevices( + MediaUseCase.CALL); + break; + default: + throw new IllegalStateException("type"); + } + + final int deviceCount = (infos == null) ? 0 : infos.size(); + devices = new CaptureDevice[deviceCount + 1]; + + if (deviceCount > 0) + { + for (int i = 0; i < deviceCount; i++) + devices[i] = new CaptureDevice(infos.get(i)); + } + devices[deviceCount] = new CaptureDevice(null); + + return devices; + } + + public Object getElementAt(int index) + { + if (type == AUDIO) + return getAudioSystems()[index]; + else + return getDevices()[index]; + } + + /** + * Extracts the devices selected by the configuration. + * @return <tt>CaptureDevice</tt> selected + */ + private CaptureDevice getSelectedDevice() + { + AudioSystem audioSystem; + CaptureDeviceInfo info; + + switch (type) + { + case AUDIO_CAPTURE: + audioSystem = deviceConfiguration.getAudioSystem(); + info = (audioSystem == null) + ? null + : audioSystem.getSelectedDevice(AudioSystem.DataFlow.CAPTURE); + break; + case AUDIO_NOTIFY: + audioSystem = deviceConfiguration.getAudioSystem(); + info = (audioSystem == null) + ? null + : audioSystem.getSelectedDevice(AudioSystem.DataFlow.NOTIFY); + break; + case AUDIO_PLAYBACK: + audioSystem = deviceConfiguration.getAudioSystem(); + info = (audioSystem == null) + ? null + : audioSystem.getSelectedDevice(AudioSystem.DataFlow.PLAYBACK); + break; + case VIDEO: + info = deviceConfiguration.getVideoCaptureDevice(MediaUseCase.ANY); + break; + default: + throw new IllegalStateException("type"); + } + + for (CaptureDevice device : getDevices()) + { + if (device.equals(info)) + return device; + } + return null; + } + + public Object getSelectedItem() + { + if (type == AUDIO) + return deviceConfiguration.getAudioSystem(); + else + return getSelectedDevice(); + } + + public int getSize() + { + if (type == AUDIO) + return getAudioSystems().length; + else + return getDevices().length; + } + + /** + * Notifies this instance about changes in the values of the properties of + * {@link #deviceConfiguration} so that this instance keeps itself + * up-to-date with respect to the list of devices. + * + * @param ev a <tt>PropertyChangeEvent</tt> which describes the name of the + * property whose value has changed and the old and new values of that + * property + */ + public void propertyChange(final PropertyChangeEvent ev) + { + if (DeviceConfiguration.PROP_AUDIO_SYSTEM_DEVICES.equals( + ev.getPropertyName())) + { + if (SwingUtilities.isEventDispatchThread()) + { + audioSystems = null; + devices = null; + fireContentsChanged(0, getSize() - 1); + } + else + { + SwingUtilities.invokeLater( + new Runnable() + { + public void run() + { + propertyChange(ev); + } + }); + } + } + } + + public void removeListDataListener(ListDataListener listener) + { + if (listener == null) + throw new IllegalArgumentException("listener"); + + listeners.remove(listener); + } + + /** + * Selects and saves the new choice. + * @param device the device we choose. + */ + private void setSelectedDevice(CaptureDevice device) + { + // We cannot clear the selection of DeviceConfiguration. + if (device == null) + return; + + CaptureDevice selectedDevice = getSelectedDevice(); + + if (selectedDevice != device) + { + AudioSystem audioSystem; + + switch (type) + { + case AUDIO_CAPTURE: + audioSystem = deviceConfiguration.getAudioSystem(); + if (audioSystem != null) + { + audioSystem.setDevice( + AudioSystem.DataFlow.CAPTURE, + ((CaptureDeviceInfo2) device.info), + true); + } + break; + case AUDIO_NOTIFY: + audioSystem = deviceConfiguration.getAudioSystem(); + if (audioSystem != null) + { + audioSystem.setDevice( + AudioSystem.DataFlow.NOTIFY, + ((CaptureDeviceInfo2) device.info), + true); + } + break; + case AUDIO_PLAYBACK: + audioSystem = deviceConfiguration.getAudioSystem(); + if (audioSystem != null) + { + audioSystem.setDevice( + AudioSystem.DataFlow.PLAYBACK, + ((CaptureDeviceInfo2) device.info), + true); + } + break; + case VIDEO: + deviceConfiguration.setVideoCaptureDevice(device.info, true); + break; + } + + fireContentsChanged(-1, -1); + } + } + + public void setSelectedItem(Object item) + { + if (type == AUDIO) + { + AudioSystem audioSystem = (AudioSystem) item; + + if(!audioSystem.equals(deviceConfiguration.getAudioSystem())) + { + deviceConfiguration.setAudioSystem(audioSystem, true); + fireContentsChanged(-1, -1); + } + } + else + setSelectedDevice((CaptureDevice) item); + } + + /** + * Encapsulates a <tt>CaptureDeviceInfo</tt> for the purposes of its display + * in the user interface. + */ + public static class CaptureDevice + { + /** + * The encapsulated info. + */ + public final CaptureDeviceInfo info; + + /** + * Creates the wrapper. + * @param info the info object we wrap. + */ + public CaptureDevice(CaptureDeviceInfo info) + { + this.info = info; + } + + /** + * Determines whether the <tt>CaptureDeviceInfo</tt> encapsulated by + * this instance is equal (by value) to a specific + * <tt>CaptureDeviceInfo</tt>. + * + * @param cdi the <tt>CaptureDeviceInfo</tt> to be determined whether it + * is equal (by value) to the <tt>CaptureDeviceInfo</tt> encapsulated by + * this instance + * @return <tt>true</tt> if the <tt>CaptureDeviceInfo</tt> encapsulated + * by this instance is equal (by value) to the specified <tt>cdi</tt>; + * otherwise, <tt>false</tt> + */ + public boolean equals(CaptureDeviceInfo cdi) + { + return (info == null) ? (cdi == null) : info.equals(cdi); + } + + /** + * Gets a human-readable <tt>String</tt> representation of this + * instance. + * + * @return a <tt>String</tt> value which is a human-readable + * representation of this instance + */ + @Override + public String toString() + { + String s; + + if(info == null) + { + s + = NeomediaActivator.getResources().getI18NString( + "impl.media.configform.NO_DEVICE"); + } + else + { + s = info.getName(); + if(info instanceof CaptureDeviceInfo2) + { + String transportType + = ((CaptureDeviceInfo2) info).getTransportType(); + + if(transportType != null) + s += " (" + transportType + ")"; + } + } + return s; + } + } +} diff --git a/src/net/java/sip/communicator/impl/neomedia/EncodingConfigurationTableModel.java b/src/net/java/sip/communicator/impl/neomedia/EncodingConfigurationTableModel.java index 925184c..f8cf447 100644 --- a/src/net/java/sip/communicator/impl/neomedia/EncodingConfigurationTableModel.java +++ b/src/net/java/sip/communicator/impl/neomedia/EncodingConfigurationTableModel.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,272 +15,272 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.neomedia;
-
-import java.util.*;
-
-import javax.swing.table.*;
-
-import net.java.sip.communicator.plugin.desktoputil.*;
-
-import org.jitsi.impl.neomedia.*;
-import org.jitsi.impl.neomedia.format.*;
-import org.jitsi.service.neomedia.*;
-import org.jitsi.service.neomedia.codec.*;
-import org.jitsi.service.neomedia.format.*;
-
-/**
- * Implements {@link TableModel} for {@link EncodingConfiguration}.
- *
- * @author Lyubomir Marinov
- */
-public class EncodingConfigurationTableModel
- extends MoveableTableModel
-{
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- private final EncodingConfiguration encodingConfiguration;
-
- private MediaFormat[] encodings;
-
- private final MediaType type;
-
- /**
- * Constructor.
- *
- * @param encodingConfiguration the encoding configuration
- * @param type media type
- */
- public EncodingConfigurationTableModel(int type,
- EncodingConfiguration encodingConfiguration)
- {
- if (encodingConfiguration == null)
- throw new IllegalArgumentException("encodingConfiguration");
- this.encodingConfiguration = encodingConfiguration;
-
- switch (type)
- {
- case DeviceConfigurationComboBoxModel.AUDIO:
- this.type = MediaType.AUDIO;
- break;
- case DeviceConfigurationComboBoxModel.VIDEO:
- this.type = MediaType.VIDEO;
- break;
- default:
- throw new IllegalArgumentException("type");
- }
- }
-
- @Override
- public Class<?> getColumnClass(int columnIndex)
- {
- return
- (columnIndex == 0)
- ? Boolean.class
- : super.getColumnClass(columnIndex);
- }
-
- public int getColumnCount()
- {
- return 2;
- }
-
- private MediaFormat[] getEncodings()
- {
- if (encodings != null)
- return encodings;
-
- MediaFormat[] availableEncodings
- = encodingConfiguration.getAllEncodings(type);
- int encodingCount = availableEncodings.length;
-
- if (encodingCount < 1)
- encodings = MediaUtils.EMPTY_MEDIA_FORMATS;
- else
- {
- /*
- * The MediaFormats will be displayed by encoding (name) and clock
- * rate and EncodingConfiguration will store them that way so this
- * TableModel should better display unique encoding-clock rate
- * pairs.
- */
- HashMap<String, MediaFormat> availableEncodingSet
- = new HashMap<String, MediaFormat>();
-
- for (MediaFormat availableEncoding : availableEncodings)
- {
- availableEncodingSet.put(
- availableEncoding.getEncoding()
- + "/"
- + availableEncoding.getClockRateString(),
- availableEncoding);
- }
- availableEncodings
- = availableEncodingSet.values().toArray(
- MediaUtils.EMPTY_MEDIA_FORMATS);
- encodingCount = availableEncodings.length;
-
- encodings = new MediaFormat[encodingCount];
- System
- .arraycopy(availableEncodings, 0, encodings, 0, encodingCount);
- // Display the encodings in decreasing priority.
- Arrays
- .sort(encodings, 0, encodingCount, new Comparator<MediaFormat>()
- {
- public int compare(MediaFormat format0, MediaFormat format1)
- {
- int ret
- = encodingConfiguration.getPriority(format1)
- - encodingConfiguration.getPriority(format0);
-
- if (ret == 0)
- {
- /*
- * In the cases of equal priorities, display them
- * sorted by encoding name in increasing order.
- */
- ret
- = format0.getEncoding().compareToIgnoreCase(
- format1.getEncoding());
- if (ret == 0)
- {
- /*
- * In the cases of equal priorities and equal
- * encoding names, display them sorted by clock
- * rate in decreasing order.
- */
- ret
- = Double.compare(
- format1.getClockRate(),
- format0.getClockRate());
- }
- }
- return ret;
- }
- });
- }
- return encodings;
- }
-
- private int[] getPriorities()
- {
- MediaFormat[] encodings = getEncodings();
- final int count = encodings.length;
- int[] priorities = new int[count];
-
- for (int i = 0; i < count; i++)
- {
- int priority = encodingConfiguration.getPriority(encodings[i]);
-
- priorities[i] = (priority > 0) ? (count - i) : 0;
- }
- return priorities;
- }
-
- public int getRowCount()
- {
- return getEncodings().length;
- }
-
- public Object getValueAt(int rowIndex, int columnIndex)
- {
- MediaFormat encoding = getEncodings()[rowIndex];
-
- switch (columnIndex)
- {
- case 0:
- return (encodingConfiguration.getPriority(encoding) > 0);
- case 1:
- if (MediaType.VIDEO.equals(encoding.getMediaType())
- && (VideoMediaFormatImpl.DEFAULT_CLOCK_RATE
- == encoding.getClockRate()))
- return encoding.getEncoding();
- else
- {
- return encoding.getEncoding()
- + "/"
- + encoding.getRealUsedClockRateString();
- }
- default:
- return null;
- }
- }
-
- @Override
- public boolean isCellEditable(int rowIndex, int columnIndex)
- {
- return (columnIndex == 0);
- }
-
- /**
- * Move the row.
- *
- * @param rowIndex index of the row
- * @param up true to move up, false to move down
- * @return the next row index
- */
- @Override
- public int move(int rowIndex, boolean up)
- {
- if (up)
- {
- if (rowIndex <= 0)
- throw new IllegalArgumentException("rowIndex");
-
- return move(rowIndex - 1, false) - 1;
- }
-
- if (rowIndex >= (getRowCount() - 1))
- throw new IllegalArgumentException("rowIndex");
-
- int[] priorities = getPriorities();
- final int nextRowIndex = rowIndex + 1;
-
- if (priorities[rowIndex] > 0)
- priorities[rowIndex] = priorities.length - nextRowIndex;
- if (priorities[nextRowIndex] > 0)
- priorities[nextRowIndex] = priorities.length - rowIndex;
- setPriorities(priorities);
-
- MediaFormat swap = encodings[rowIndex];
-
- encodings[rowIndex] = encodings[nextRowIndex];
- encodings[nextRowIndex] = swap;
-
- fireTableRowsUpdated(rowIndex, nextRowIndex);
- return nextRowIndex;
- }
-
- private void setPriorities(int[] priorities)
- {
- final int count = encodings.length;
-
- if (priorities.length != count)
- throw new IllegalArgumentException("priorities");
- for (int i = 0; i < count; i++)
- {
- encodingConfiguration.setPriority(encodings[i], priorities[i]);
- }
- }
-
- @Override
- public void setValueAt(Object value, int rowIndex, int columnIndex)
- {
- if ((columnIndex == 0) && (value instanceof Boolean))
- {
- int priority
- = ((Boolean) value) ? (getPriorities().length - rowIndex) : 0;
- MediaFormat encoding = encodings[rowIndex];
-
-
- // We fire the update event before setting the configuration
- // property in order to have more reactive user interface.
- fireTableCellUpdated(rowIndex, columnIndex);
-
- encodingConfiguration.setPriority(encoding, priority);
- }
- }
-}
+package net.java.sip.communicator.impl.neomedia; + +import java.util.*; + +import javax.swing.table.*; + +import net.java.sip.communicator.plugin.desktoputil.*; + +import org.jitsi.impl.neomedia.*; +import org.jitsi.impl.neomedia.format.*; +import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.codec.*; +import org.jitsi.service.neomedia.format.*; + +/** + * Implements {@link TableModel} for {@link EncodingConfiguration}. + * + * @author Lyubomir Marinov + */ +public class EncodingConfigurationTableModel + extends MoveableTableModel +{ + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + private final EncodingConfiguration encodingConfiguration; + + private MediaFormat[] encodings; + + private final MediaType type; + + /** + * Constructor. + * + * @param encodingConfiguration the encoding configuration + * @param type media type + */ + public EncodingConfigurationTableModel(int type, + EncodingConfiguration encodingConfiguration) + { + if (encodingConfiguration == null) + throw new IllegalArgumentException("encodingConfiguration"); + this.encodingConfiguration = encodingConfiguration; + + switch (type) + { + case DeviceConfigurationComboBoxModel.AUDIO: + this.type = MediaType.AUDIO; + break; + case DeviceConfigurationComboBoxModel.VIDEO: + this.type = MediaType.VIDEO; + break; + default: + throw new IllegalArgumentException("type"); + } + } + + @Override + public Class<?> getColumnClass(int columnIndex) + { + return + (columnIndex == 0) + ? Boolean.class + : super.getColumnClass(columnIndex); + } + + public int getColumnCount() + { + return 2; + } + + private MediaFormat[] getEncodings() + { + if (encodings != null) + return encodings; + + MediaFormat[] availableEncodings + = encodingConfiguration.getAllEncodings(type); + int encodingCount = availableEncodings.length; + + if (encodingCount < 1) + encodings = MediaUtils.EMPTY_MEDIA_FORMATS; + else + { + /* + * The MediaFormats will be displayed by encoding (name) and clock + * rate and EncodingConfiguration will store them that way so this + * TableModel should better display unique encoding-clock rate + * pairs. + */ + HashMap<String, MediaFormat> availableEncodingSet + = new HashMap<String, MediaFormat>(); + + for (MediaFormat availableEncoding : availableEncodings) + { + availableEncodingSet.put( + availableEncoding.getEncoding() + + "/" + + availableEncoding.getClockRateString(), + availableEncoding); + } + availableEncodings + = availableEncodingSet.values().toArray( + MediaUtils.EMPTY_MEDIA_FORMATS); + encodingCount = availableEncodings.length; + + encodings = new MediaFormat[encodingCount]; + System + .arraycopy(availableEncodings, 0, encodings, 0, encodingCount); + // Display the encodings in decreasing priority. + Arrays + .sort(encodings, 0, encodingCount, new Comparator<MediaFormat>() + { + public int compare(MediaFormat format0, MediaFormat format1) + { + int ret + = encodingConfiguration.getPriority(format1) + - encodingConfiguration.getPriority(format0); + + if (ret == 0) + { + /* + * In the cases of equal priorities, display them + * sorted by encoding name in increasing order. + */ + ret + = format0.getEncoding().compareToIgnoreCase( + format1.getEncoding()); + if (ret == 0) + { + /* + * In the cases of equal priorities and equal + * encoding names, display them sorted by clock + * rate in decreasing order. + */ + ret + = Double.compare( + format1.getClockRate(), + format0.getClockRate()); + } + } + return ret; + } + }); + } + return encodings; + } + + private int[] getPriorities() + { + MediaFormat[] encodings = getEncodings(); + final int count = encodings.length; + int[] priorities = new int[count]; + + for (int i = 0; i < count; i++) + { + int priority = encodingConfiguration.getPriority(encodings[i]); + + priorities[i] = (priority > 0) ? (count - i) : 0; + } + return priorities; + } + + public int getRowCount() + { + return getEncodings().length; + } + + public Object getValueAt(int rowIndex, int columnIndex) + { + MediaFormat encoding = getEncodings()[rowIndex]; + + switch (columnIndex) + { + case 0: + return (encodingConfiguration.getPriority(encoding) > 0); + case 1: + if (MediaType.VIDEO.equals(encoding.getMediaType()) + && (VideoMediaFormatImpl.DEFAULT_CLOCK_RATE + == encoding.getClockRate())) + return encoding.getEncoding(); + else + { + return encoding.getEncoding() + + "/" + + encoding.getRealUsedClockRateString(); + } + default: + return null; + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) + { + return (columnIndex == 0); + } + + /** + * Move the row. + * + * @param rowIndex index of the row + * @param up true to move up, false to move down + * @return the next row index + */ + @Override + public int move(int rowIndex, boolean up) + { + if (up) + { + if (rowIndex <= 0) + throw new IllegalArgumentException("rowIndex"); + + return move(rowIndex - 1, false) - 1; + } + + if (rowIndex >= (getRowCount() - 1)) + throw new IllegalArgumentException("rowIndex"); + + int[] priorities = getPriorities(); + final int nextRowIndex = rowIndex + 1; + + if (priorities[rowIndex] > 0) + priorities[rowIndex] = priorities.length - nextRowIndex; + if (priorities[nextRowIndex] > 0) + priorities[nextRowIndex] = priorities.length - rowIndex; + setPriorities(priorities); + + MediaFormat swap = encodings[rowIndex]; + + encodings[rowIndex] = encodings[nextRowIndex]; + encodings[nextRowIndex] = swap; + + fireTableRowsUpdated(rowIndex, nextRowIndex); + return nextRowIndex; + } + + private void setPriorities(int[] priorities) + { + final int count = encodings.length; + + if (priorities.length != count) + throw new IllegalArgumentException("priorities"); + for (int i = 0; i < count; i++) + { + encodingConfiguration.setPriority(encodings[i], priorities[i]); + } + } + + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) + { + if ((columnIndex == 0) && (value instanceof Boolean)) + { + int priority + = ((Boolean) value) ? (getPriorities().length - rowIndex) : 0; + MediaFormat encoding = encodings[rowIndex]; + + + // We fire the update event before setting the configuration + // property in order to have more reactive user interface. + fireTableCellUpdated(rowIndex, columnIndex); + + encodingConfiguration.setPriority(encoding, priority); + } + } +} diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaConfigurationImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaConfigurationImpl.java index d561c92..00e8aac 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaConfigurationImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaConfigurationImpl.java @@ -1773,8 +1773,7 @@ public class MediaConfigurationImpl { String noAvailableAudioDevice = NeomediaActivator.getResources().getI18NString( - "impl.media.configform" - + ".NO_AVAILABLE_AUDIO_DEVICE"); + "impl.media.configform.NO_AVAILABLE_AUDIO_DEVICE"); preview = new TransparentPanel(new GridBagLayout()); preview.add(new JLabel(noAvailableAudioDevice)); diff --git a/src/net/java/sip/communicator/impl/neomedia/codec/video/h264/ConfigurationPanel.java b/src/net/java/sip/communicator/impl/neomedia/codec/video/h264/ConfigurationPanel.java index 1381d21..a3ea318 100644 --- a/src/net/java/sip/communicator/impl/neomedia/codec/video/h264/ConfigurationPanel.java +++ b/src/net/java/sip/communicator/impl/neomedia/codec/video/h264/ConfigurationPanel.java @@ -150,8 +150,7 @@ public class ConfigurationPanel JCheckBox defaultIntraRefreshCheckBox = new SIPCommCheckBox( r.getI18NString( - "impl.neomedia.configform.H264" - + ".defaultIntraRefresh")); + "impl.neomedia.configform.H264.defaultIntraRefresh")); cnstrnts.gridwidth = GridBagConstraints.REMAINDER; cnstrnts.gridx = 0; cnstrnts.gridy = 3; diff --git a/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf b/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf index f02d7e4..935ff09 100644 --- a/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf +++ b/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf @@ -12,21 +12,16 @@ Import-Package: org.jitsi.service.configuration, net.java.sip.communicator.service.sysactivity, net.java.sip.communicator.service.sysactivity.event, org.osgi.framework, + org.ice4j, + org.ice4j.ice, + org.ice4j.ice.harvest, + org.ice4j.security, org.ice4j.stack, org.xml.sax, org.xml.sax.helpers, javax.crypto, javax.crypto.spec, - javax.sdp, - gov.nist.javax.sdp.fields, com.sun.jna, com.sun.jna.ptr Export-Package: net.java.sip.communicator.service.netaddr, - net.java.sip.communicator.service.netaddr.event, - org.ice4j, - org.ice4j.socket, - org.ice4j.stack, - org.ice4j.ice, - org.ice4j.ice.harvest, - org.ice4j.ice.sdp, - org.ice4j.security + net.java.sip.communicator.service.netaddr.event diff --git a/src/net/java/sip/communicator/impl/osdependent/Desktop.java b/src/net/java/sip/communicator/impl/osdependent/Desktop.java index 885dd02..53728e8 100644 --- a/src/net/java/sip/communicator/impl/osdependent/Desktop.java +++ b/src/net/java/sip/communicator/impl/osdependent/Desktop.java @@ -22,6 +22,7 @@ import java.io.*; import java.lang.reflect.*; import java.net.*; +import net.java.sip.communicator.impl.osdependent.systemtray.SystemTray; import net.java.sip.communicator.util.Logger; /** diff --git a/src/net/java/sip/communicator/impl/osdependent/PopupMessageHandlerTrayIconImpl.java b/src/net/java/sip/communicator/impl/osdependent/PopupMessageHandlerTrayIconImpl.java index 7416b75..592e5af 100644 --- a/src/net/java/sip/communicator/impl/osdependent/PopupMessageHandlerTrayIconImpl.java +++ b/src/net/java/sip/communicator/impl/osdependent/PopupMessageHandlerTrayIconImpl.java @@ -19,6 +19,7 @@ package net.java.sip.communicator.impl.osdependent; import java.awt.event.*; +import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon; import net.java.sip.communicator.service.systray.*; import net.java.sip.communicator.service.systray.event.*; diff --git a/src/net/java/sip/communicator/impl/osdependent/SystemTray.java b/src/net/java/sip/communicator/impl/osdependent/SystemTray.java deleted file mode 100644 index 0c0a4a2..0000000 --- a/src/net/java/sip/communicator/impl/osdependent/SystemTray.java +++ /dev/null @@ -1,215 +0,0 @@ -/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.osdependent;
-
-import java.awt.*;
-import java.lang.reflect.*;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.osdependent.TrayIcon.AWTTrayIconPeer;
-import net.java.sip.communicator.impl.osdependent.TrayIcon.TrayIconPeer;
-import net.java.sip.communicator.util.*;
-
-/**
- * @author Lubomir Marinov
- */
-public class SystemTray
-{
- /**
- * The <tt>Logger</tt> used by the <tt>SystemTray</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger = Logger.getLogger(SystemTray.class);
-
- private static SystemTray defaultSystemTray;
-
- public static SystemTray getDefaultSystemTray()
- throws UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- if (defaultSystemTray != null)
- return defaultSystemTray;
-
- Class<?> awtSystemTrayClass = null;
- try
- {
- awtSystemTrayClass = Class.forName("java.awt.SystemTray");
- }
- catch (ClassNotFoundException ex)
- {
- // We'll try org.jdesktop.jdic.tray then.
- }
- SystemTrayPeer peer = null;
- if (awtSystemTrayClass != null)
- try
- {
- peer = new AWTSystemTrayPeer(awtSystemTrayClass);
- }
- catch (Exception ex)
- {
- if(!GraphicsEnvironment.isHeadless())
- logger.error("Failed to initialize java.awt.SystemTray",
- ex);
-
- // We'll try org.jdesktop.jdic.tray then.
- }
- if (peer == null)
- {
- logger.error(
- "Failed to initialize the desktop.tray implementation.");
- throw new UnsupportedOperationException(
- "Failed to initialize the desktop.tray implementation.");
- }
- return (defaultSystemTray = new SystemTray(peer));
- }
-
- private final SystemTrayPeer peer;
-
- private SystemTray(SystemTrayPeer peer)
- {
- this.peer = peer;
- }
-
- public void addTrayIcon(TrayIcon trayIcon)
- throws NullPointerException,
- IllegalArgumentException
- {
- if (peer != null)
- peer.addTrayIcon(trayIcon.getPeer());
- }
-
- SystemTrayPeer getPeer()
- {
- return peer;
- }
-
- public boolean isSwing()
- {
- if (peer != null)
- return getPeer().isSwing();
- return false;
- }
-
- static interface SystemTrayPeer
- {
- void addTrayIcon(TrayIconPeer trayIconPeer)
- throws NullPointerException,
- IllegalArgumentException;
-
- TrayIconPeer createTrayIcon(ImageIcon icon,
- String tooltip,
- Object popup)
- throws IllegalArgumentException,
- UnsupportedOperationException,
- HeadlessException,
- SecurityException;
-
- boolean isSwing();
- }
-
- private static class AWTSystemTrayPeer
- implements SystemTrayPeer
- {
- private final Method addTrayIcon;
-
- private final Object impl;
-
- private final Class<?> trayIconClass;
-
- public AWTSystemTrayPeer(Class<?> clazz)
- throws UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- Method getDefaultSystemTray;
- try
- {
- getDefaultSystemTray =
- clazz.getMethod("getSystemTray", (Class<?>[]) null);
- trayIconClass = Class.forName("java.awt.TrayIcon");
- addTrayIcon = clazz.getMethod("add", new Class<?>[]
- { trayIconClass });
- }
- catch (ClassNotFoundException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (NoSuchMethodException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
-
- try
- {
- impl = getDefaultSystemTray.invoke(null, (Object[]) null);
- }
- catch (IllegalAccessException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (InvocationTargetException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- }
-
- public void addTrayIcon(TrayIconPeer trayIconPeer)
- throws NullPointerException,
- IllegalArgumentException
- {
- try
- {
- addTrayIcon.invoke(impl, new Object[]
- { ((AWTTrayIconPeer) trayIconPeer).getImpl() });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- if (cause == null)
- throw new UndeclaredThrowableException(ex);
- if (cause instanceof NullPointerException)
- throw (NullPointerException) cause;
- if (cause instanceof IllegalArgumentException)
- throw (IllegalArgumentException) cause;
- throw new UndeclaredThrowableException(cause);
- }
- }
-
- public TrayIconPeer createTrayIcon(ImageIcon icon, String tooltip,
- Object popup)
- throws IllegalArgumentException,
- UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- return new AWTTrayIconPeer(trayIconClass, (icon == null) ? null
- : icon.getImage(), tooltip, popup);
- }
-
- public boolean isSwing()
- {
- return false;
- }
- }
-}
diff --git a/src/net/java/sip/communicator/impl/osdependent/TrayIcon.java b/src/net/java/sip/communicator/impl/osdependent/TrayIcon.java deleted file mode 100644 index 974ed96..0000000 --- a/src/net/java/sip/communicator/impl/osdependent/TrayIcon.java +++ /dev/null @@ -1,412 +0,0 @@ -/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.osdependent;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.lang.reflect.*;
-
-import javax.swing.*;
-import javax.swing.event.*;
-
-import net.java.sip.communicator.impl.osdependent.SystemTray.SystemTrayPeer;
-
-import org.jitsi.util.*;
-
-/**
- * @author Lubomir Marinov
- */
-public class TrayIcon
-{
- private final TrayIconPeer peer;
-
- public TrayIcon(ImageIcon icon, String tooltip, Object popup)
- throws IllegalArgumentException,
- UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- SystemTrayPeer systemTrayPeer =
- SystemTray.getDefaultSystemTray().getPeer();
- if (systemTrayPeer != null)
- peer = systemTrayPeer.createTrayIcon(icon, tooltip, popup);
- else
- peer = null;
- }
-
- public void addActionListener(ActionListener listener)
- {
- if (peer != null)
- peer.addActionListener(listener);
- }
-
- public void addBalloonActionListener(ActionListener listener)
- {
- if (peer != null)
- peer.addBalloonActionListener(listener);
- }
-
- public void displayMessage(String caption, String text,
- java.awt.TrayIcon.MessageType messageType)
- throws NullPointerException
- {
- if (peer != null)
- peer.displayMessage(caption, text, messageType);
- }
-
- TrayIconPeer getPeer()
- {
- return peer;
- }
-
- public void setIcon(ImageIcon icon) throws NullPointerException
- {
- if (peer != null)
- peer.setIcon(icon);
- }
-
- public void setIconAutoSize(boolean autoSize)
- {
- if (peer != null)
- peer.setIconAutoSize(autoSize);
- }
-
- static interface TrayIconPeer
- {
- void addActionListener(ActionListener listener);
-
- void addBalloonActionListener(ActionListener listener);
-
- void displayMessage(String caption, String text,
- java.awt.TrayIcon.MessageType messageType)
- throws NullPointerException;
-
- void setIcon(ImageIcon icon) throws NullPointerException;
-
- void setIconAutoSize(boolean autoSize);
- }
-
- static class AWTTrayIconPeer
- implements TrayIconPeer
- {
- private final Method addActionListener;
-
- private final Method addMouseListener;
-
- private final Method displayMessage;
-
- private final Object impl;
-
- private final Class<?> messageTypeClass;
-
- private final Method setIcon;
-
- private final Method setIconAutoSize;
-
- public AWTTrayIconPeer(Class<?> clazz, Image image, String tooltip,
- Object popup)
- throws IllegalArgumentException,
- UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- Constructor<?> constructor;
- try
- {
- if (popup instanceof JPopupMenu)
- {
- constructor = clazz.getConstructor(new Class<?>[]
- { Image.class, String.class });
- }
- else
- {
- constructor = clazz.getConstructor(new Class<?>[]
- { Image.class, String.class, PopupMenu.class });
- }
- addActionListener =
- clazz.getMethod("addActionListener", new Class<?>[]
- { ActionListener.class });
- addMouseListener =
- clazz.getMethod("addMouseListener", new Class<?>[]
- { MouseListener.class });
- messageTypeClass =
- Class.forName("java.awt.TrayIcon$MessageType");
- displayMessage =
- clazz.getMethod("displayMessage", new Class<?>[]
- { String.class, String.class, messageTypeClass });
- setIcon = clazz.getMethod("setImage", new Class<?>[]
- { Image.class });
- setIconAutoSize =
- clazz.getMethod("setImageAutoSize", new Class<?>[]
- { boolean.class });
- }
- catch (ClassNotFoundException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (NoSuchMethodException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
-
- try
- {
- if (popup instanceof JPopupMenu)
- {
- impl = constructor.newInstance(
- new Object[] { image, tooltip });
- addMouseListener(new AWTMouseAdapter((JPopupMenu) popup));
- }
- else
- {
- impl = constructor.newInstance(
- new Object[] { image, tooltip, popup });
- }
- }
- catch (IllegalAccessException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (InstantiationException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- if (cause == null)
- throw new UnsupportedOperationException(ex);
- if (cause instanceof IllegalArgumentException)
- throw (IllegalArgumentException) cause;
- if (cause instanceof UnsupportedOperationException)
- throw (UnsupportedOperationException) cause;
- if (cause instanceof HeadlessException)
- throw (HeadlessException) cause;
- if (cause instanceof SecurityException)
- throw (SecurityException) cause;
- throw new UnsupportedOperationException(cause);
- }
- }
-
- public void addActionListener(ActionListener listener)
- {
- try
- {
- addActionListener.invoke(getImpl(), new Object[]
- { listener });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
-
- public void addMouseListener(MouseListener listener)
- {
- try
- {
- addMouseListener.invoke(getImpl(), new Object[] { listener });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
-
- public void addBalloonActionListener(ActionListener listener)
- {
- // java.awt.TrayIcon doesn't support addBalloonActionListener()
- }
-
- public void displayMessage(String caption, String text,
- java.awt.TrayIcon.MessageType messageType)
- throws NullPointerException
- {
- try
- {
- displayMessage.invoke(getImpl(), new Object[]
- { caption, text, messageType.name() });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- if (cause instanceof NullPointerException)
- throw (NullPointerException) cause;
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
-
- public Object getImpl()
- {
- return impl;
- }
-
- public void setIcon(ImageIcon icon) throws NullPointerException
- {
- try
- {
- setIcon.invoke(getImpl(), new Object[]
- { (icon == null) ? null : icon.getImage() });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- if (cause instanceof NullPointerException)
- throw (NullPointerException) cause;
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
-
- public void setIconAutoSize(boolean autoSize)
- {
- try
- {
- setIconAutoSize.invoke(getImpl(), new Object[]
- { autoSize });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
- }
-
- /**
- * Extended mouse adapter to show the JPopupMenu in Java 6
- * Based on : http://weblogs.java.net/blog/ixmal/archive/2006/05/using_jpopupmen.html
- * And : http://weblogs.java.net/blog/alexfromsun/archive/2008/02/jtrayicon_updat.html
- *
- * Use a hidden JWindow (JDialog for Windows) to manage the JPopupMenu.
- *
- * @author Damien Roth
- */
- private static class AWTMouseAdapter
- extends MouseAdapter
- {
- private JPopupMenu popup = null;
- private Window hiddenWindow = null;
-
- public AWTMouseAdapter(JPopupMenu p)
- {
- this.popup = p;
- this.popup.addPopupMenuListener(new PopupMenuListener()
- {
- public void popupMenuWillBecomeVisible(PopupMenuEvent e)
- {}
-
- public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
- {
- if (hiddenWindow != null)
- {
- hiddenWindow.dispose();
- hiddenWindow = null;
- }
- }
-
- public void popupMenuCanceled(PopupMenuEvent e)
- {
- if (hiddenWindow != null)
- {
- hiddenWindow.dispose();
- hiddenWindow = null;
- }
- }
- });
- }
-
- @Override
- public void mouseReleased(MouseEvent e)
- {
- showPopupMenu(e);
- }
-
- @Override
- public void mousePressed(MouseEvent e)
- {
- showPopupMenu(e);
- }
-
- private void showPopupMenu(MouseEvent e)
- {
- if (e.isPopupTrigger() && popup != null)
- {
- if (hiddenWindow == null)
- {
- if (OSUtils.IS_WINDOWS)
- {
- hiddenWindow = new JDialog((Frame) null);
- ((JDialog) hiddenWindow).setUndecorated(true);
- }
- else
- hiddenWindow = new JWindow((Frame) null);
-
- hiddenWindow.setAlwaysOnTop(true);
- Dimension size = popup.getPreferredSize();
-
- Point centerPoint = GraphicsEnvironment
- .getLocalGraphicsEnvironment()
- .getCenterPoint();
-
- if(e.getY() > centerPoint.getY())
- hiddenWindow
- .setLocation(e.getX(), e.getY() - size.height);
- else
- hiddenWindow
- .setLocation(e.getX(), e.getY());
-
- hiddenWindow.setVisible(true);
-
- popup.show(
- ((RootPaneContainer)hiddenWindow).getContentPane(),
- 0, 0);
-
- // popup works only for focused windows
- hiddenWindow.toFront();
- }
- }
- }
- }
-}
diff --git a/src/net/java/sip/communicator/impl/osdependent/jdic/StatusSubMenu.java b/src/net/java/sip/communicator/impl/osdependent/jdic/StatusSubMenu.java index 2d77621..92088fa 100644 --- a/src/net/java/sip/communicator/impl/osdependent/jdic/StatusSubMenu.java +++ b/src/net/java/sip/communicator/impl/osdependent/jdic/StatusSubMenu.java @@ -66,7 +66,7 @@ public class StatusSubMenu * @param swing <tt>true</tt> to represent this instance with a Swing * <tt>JMenu</tt>; <tt>false</tt> to use an AWT <tt>Menu</tt> */ - public StatusSubMenu(boolean swing) + public StatusSubMenu(boolean swing, boolean accountMenuSupported) { String text = Resources.getString("impl.systray.SET_STATUS"); @@ -86,6 +86,7 @@ public class StatusSubMenu this.menu = new Menu(text); } + if (accountMenuSupported) { String hideAccountStatusSelectorsProperty = "impl.gui.HIDE_ACCOUNT_STATUS_SELECTORS"; @@ -103,6 +104,10 @@ public class StatusSubMenu hideAccountStatusSelectorsProperty, hideAccountStatusSelectors); } + else + { + hideAccountStatusSelectors = true; + } PresenceStatus offlineStatus = null; // creates menu item entry for every global status @@ -116,9 +121,11 @@ public class StatusSubMenu // initially it is offline selectItemFromStatus(offlineStatus.getStatus()); - this.addSeparator(); - - addMenuItem(menu, new GlobalStatusMessageMenu(swing).getMenu()); + if (accountMenuSupported) + { + this.addSeparator(); + addMenuItem(menu, new GlobalStatusMessageMenu(swing).getMenu()); + } if(!hideAccountStatusSelectors) this.addSeparator(); diff --git a/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java b/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java index f774b23..0b40798 100644 --- a/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java +++ b/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java @@ -19,20 +19,25 @@ package net.java.sip.communicator.impl.osdependent.jdic; import java.awt.*; import java.awt.event.*; +import java.awt.image.*; import java.net.*; +import java.util.HashMap; +import java.util.Map; import javax.swing.*; import javax.swing.event.*; import net.java.sip.communicator.impl.osdependent.*; -import net.java.sip.communicator.impl.osdependent.SystemTray; -import net.java.sip.communicator.impl.osdependent.TrayIcon; +import net.java.sip.communicator.impl.osdependent.systemtray.SystemTray; +import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon; +import net.java.sip.communicator.impl.osdependent.windows.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.systray.*; import net.java.sip.communicator.service.systray.event.*; import net.java.sip.communicator.util.Logger; +import org.apache.commons.lang3.tuple.Pair; import org.jitsi.util.*; import org.osgi.framework.*; @@ -51,7 +56,6 @@ import com.apple.eawt.*; public class SystrayServiceJdicImpl extends AbstractSystrayService { - /** * The systray. */ @@ -93,10 +97,6 @@ public class SystrayServiceJdicImpl private ImageIcon logoIconWhite; - private ImageIcon envelopeIcon; - - private ImageIcon envelopeIconWhite; - /** * The dock Icons used only in Mac version */ @@ -131,10 +131,9 @@ public class SystrayServiceJdicImpl super(OsDependentActivator.bundleContext); SystemTray systray; - try { - systray = SystemTray.getDefaultSystemTray(); + systray = SystemTray.getSystemTray(); } catch (Throwable t) { @@ -147,10 +146,39 @@ public class SystrayServiceJdicImpl logger.error("Failed to create a systray!", t); } } - this.systray = systray; + this.systray = systray; if (this.systray != null) + { initSystray(); + } + } + + @Override + public Map<String, String> getSystrayModes() + { + return new HashMap<String, String>() + {{ + put("disabled", "service.systray.mode.DISABLED"); + if (java.awt.SystemTray.isSupported()) + { + put("native", "service.systray.mode.NATIVE"); + } + + if (!OSUtils.IS_MAC && !OSUtils.IS_WINDOWS) + { + put("appindicator", + "service.systray.mode.APPINDICATOR"); + put("appindicator_static", + "service.systray.mode.APPINDICATOR_STATIC"); + } + }}; + } + + @Override + public String getActiveSystrayMode() + { + return SystemTray.getSystemTrayMode(); } /** @@ -187,53 +215,33 @@ public class SystrayServiceJdicImpl return; } - menu = TrayMenuFactory.createTrayMenu(this, systray.isSwing()); + Pair<Object, Object> createdMenu = TrayMenuFactory.createTrayMenu( + this, + systray.useSwingPopupMenu(), + systray.supportsDynamicMenu()); + menu = createdMenu.getLeft(); boolean isMac = OSUtils.IS_MAC; - // If we're running under Windows, we use a special icon without - // background. - if (OSUtils.IS_WINDOWS) - { - logoIcon = Resources.getImage("service.systray.TRAY_ICON_WINDOWS"); - logoIconOffline = Resources.getImage( - "service.systray.TRAY_ICON_WINDOWS_OFFLINE"); - logoIconAway = Resources.getImage( - "service.systray.TRAY_ICON_WINDOWS_AWAY"); - logoIconExtendedAway = Resources.getImage( - "service.systray.TRAY_ICON_WINDOWS_EXTENDED_AWAY"); - logoIconFFC = Resources.getImage( - "service.systray.TRAY_ICON_WINDOWS_FFC"); - logoIconDND = Resources.getImage( - "service.systray.TRAY_ICON_WINDOWS_DND"); - envelopeIcon = Resources.getImage( - "service.systray.MESSAGE_ICON_WINDOWS"); - } - /* - * If we're running under Mac OS X, we use special black and white icons - * without background. - */ - else if (isMac) + logoIcon = Resources.getImage("service.systray.TRAY_ICON_WINDOWS"); + logoIconOffline = Resources.getImage( + "service.systray.TRAY_ICON_WINDOWS_OFFLINE"); + logoIconAway = Resources.getImage( + "service.systray.TRAY_ICON_WINDOWS_AWAY"); + logoIconExtendedAway = Resources.getImage( + "service.systray.TRAY_ICON_WINDOWS_EXTENDED_AWAY"); + logoIconFFC = Resources.getImage( + "service.systray.TRAY_ICON_WINDOWS_FFC"); + logoIconDND = Resources.getImage( + "service.systray.TRAY_ICON_WINDOWS_DND"); + + // If we're running under Mac OS X, we use special black and white + // icons without background. + if (isMac) { logoIcon = Resources.getImage("service.systray.TRAY_ICON_MACOSX"); logoIconWhite = Resources.getImage( "service.systray.TRAY_ICON_MACOSX_WHITE"); - envelopeIcon = Resources.getImage( - "service.systray.MESSAGE_ICON_MACOSX"); - envelopeIconWhite = Resources.getImage( - "service.systray.MESSAGE_ICON_MACOSX_WHITE"); - } - else - { - logoIcon = Resources.getImage("service.systray.TRAY_ICON"); - logoIconOffline = Resources.getImage( - "service.systray.TRAY_ICON_OFFLINE"); - logoIconAway = Resources.getImage("service.systray.TRAY_ICON_AWAY"); - logoIconExtendedAway = Resources.getImage( - "service.systray.TRAY_ICON_EXTENDED_AWAY"); - logoIconFFC = Resources.getImage("service.systray.TRAY_ICON_FFC"); - logoIconDND = Resources.getImage("service.systray.TRAY_ICON_DND"); - envelopeIcon = Resources.getImage("service.systray.MESSAGE_ICON"); } /* @@ -243,7 +251,7 @@ public class SystrayServiceJdicImpl currentIcon = isMac ? logoIcon : logoIconOffline; trayIcon - = new TrayIcon( + = systray.createTrayIcon( currentIcon, Resources.getApplicationString( "service.gui.APPLICATION_NAME"), @@ -268,21 +276,15 @@ public class SystrayServiceJdicImpl } //Show/hide the contact list when user clicks on the systray. - trayIcon.addActionListener( - new ActionListener() - { - public void actionPerformed(ActionEvent e) - { - UIService uiService - = OsDependentActivator.getUIService(); - ExportedWindow mainWindow - = uiService.getExportedWindow( - ExportedWindow.MAIN_WINDOW); - boolean setIsVisible = !mainWindow.isVisible(); - - uiService.setVisible(setIsVisible); - } - }); + final Object defaultActionItem; + if (systray.useSwingPopupMenu()) + { + defaultActionItem = ((JMenuItem) createdMenu.getRight()); + } + else + { + defaultActionItem = ((MenuItem) createdMenu.getRight()); + } /* * Change the Mac OS X icon with the white one when the pop-up menu @@ -296,11 +298,7 @@ public class SystrayServiceJdicImpl { public void popupMenuWillBecomeVisible(PopupMenuEvent e) { - ImageIcon newIcon - = (currentIcon == envelopeIcon) - ? envelopeIconWhite - : logoIconWhite; - + ImageIcon newIcon = logoIconWhite; trayIcon.setIcon(newIcon); currentIcon = newIcon; } @@ -308,11 +306,7 @@ public class SystrayServiceJdicImpl public void popupMenuWillBecomeInvisible( PopupMenuEvent e) { - ImageIcon newIcon - = (currentIcon == envelopeIconWhite) - ? envelopeIcon - : logoIcon; - + ImageIcon newIcon = logoIcon; getTrayIcon().setIcon(newIcon); currentIcon = newIcon; } @@ -353,12 +347,12 @@ public class SystrayServiceJdicImpl public void run() { systray.addTrayIcon(trayIcon); + trayIcon.setDefaultAction(defaultActionItem); } }); initialized = true; - - uiService.setExitOnMainWindowClose(false); + uiService.setMainWindowCanHide(true); } /** @@ -402,12 +396,6 @@ public class SystrayServiceJdicImpl if (!isMac) systrayIconToSet = logoIconDND; break; - case SystrayService.ENVELOPE_IMG_TYPE: - systrayIconToSet - = (isMac && TrayMenuFactory.isVisible(menu)) - ? envelopeIconWhite - : envelopeIcon; - break; } if (systrayIconToSet != null) @@ -469,14 +457,90 @@ public class SystrayServiceJdicImpl } } - private boolean checkInitialized() + @Override + public boolean checkInitialized() { - if (!initialized) - logger.error("Systray not init"); return initialized; } /** + * Set the number of pending notifications to the the application icon + * (Dock on OSX, TaskBar on Windows, nothing on Linux currently). + */ + @Override + public void setNotificationCount(int count) + { + if (OSUtils.IS_MAC) + { + Application application = Application.getApplication(); + application.setDockIconBadge(new Integer(count).toString()); + } + else if (OSUtils.IS_WINDOWS) + { + UIService uiService = OsDependentActivator.getUIService(); + if (uiService == null) + { + return; + } + + ExportedWindow mainWindow = + uiService.getExportedWindow(ExportedWindow.MAIN_WINDOW); + if (mainWindow == null + || !(mainWindow.getSource() instanceof Component)) + { + return; + } + + BufferedImage img = null; + if (count > 0) + { + img = createOverlayImage(new Integer(count).toString()); + } + + try + { + TaskBarList3.getInstance().SetOverlayIcon( + (Component) mainWindow.getSource(), img, null); + } + catch (Exception ex) + { + logger.error("Could not set the notification count.", ex); + } + } + } + + private BufferedImage createOverlayImage(String text) + { + int size = 16; + BufferedImage image = + new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + //background + g.setPaint(new Color(0, 0, 0, 102)); + g.fillRoundRect(0, 0, size, size, size, size); + + //filling + int mainRadius = 14; + g.setPaint(new Color(255, 98, 89)); + g.fillRoundRect(size / 2 - mainRadius / 2, size / 2 - mainRadius / 2, + mainRadius, mainRadius, size, size); + + //text + Font font = g.getFont(); + g.setFont(new Font(font.getName(), Font.BOLD, 9)); + FontMetrics fontMetrics = g.getFontMetrics(); + int textWidth = fontMetrics.stringWidth(text); + g.setColor(Color.white); + g.drawString(text, size / 2 - textWidth / 2, + size / 2 - fontMetrics.getHeight() / 2 + fontMetrics.getAscent()); + + return image; + } + + /** * @return the trayIcon */ public TrayIcon getTrayIcon() @@ -551,7 +615,7 @@ public class SystrayServiceJdicImpl OsDependentActivator.bundleContext.removeServiceListener( this); - if (!initialized) + if (!initialized && systray != null) initSystray(); } } diff --git a/src/net/java/sip/communicator/impl/osdependent/jdic/TrayMenuFactory.java b/src/net/java/sip/communicator/impl/osdependent/jdic/TrayMenuFactory.java index 2b5df19..cae3ada 100644 --- a/src/net/java/sip/communicator/impl/osdependent/jdic/TrayMenuFactory.java +++ b/src/net/java/sip/communicator/impl/osdependent/jdic/TrayMenuFactory.java @@ -26,6 +26,7 @@ import javax.swing.event.*; import net.java.sip.communicator.impl.osdependent.*; import net.java.sip.communicator.service.gui.*; +import org.apache.commons.lang3.tuple.*; import org.jitsi.util.*; /** @@ -139,16 +140,16 @@ public final class TrayMenuFactory * * @param tray the system tray for which we're creating a menu * @param swing indicates if we should create a Swing or an AWT menu - * @return a tray menu for the given system tray + * @return a tray menu for the given system tray (first) and the default + * menu item (second) */ - public static Object createTrayMenu(SystrayServiceJdicImpl tray, - boolean swing) + public static Pair<Object, Object> createTrayMenu( + SystrayServiceJdicImpl tray, + boolean swing, + boolean accountMenuSupported + ) { - // Enable swing for java 1.6 except for the mac version - if (!swing && !OSUtils.IS_MAC) - swing = true; - - Object trayMenu = swing ? new JPopupMenu() : new PopupMenu(); + final Object trayMenu = swing ? new JPopupMenu() : new PopupMenu(); ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) @@ -184,9 +185,11 @@ public final class TrayMenuFactory + "CHAT_PRESENCE_DISABLED", false); - if (!chatPresenceDisabled.booleanValue()) + if (!chatPresenceDisabled.booleanValue() && accountMenuSupported) { - add(trayMenu, new StatusSubMenu(swing).getMenu()); + add( + trayMenu, + new StatusSubMenu(swing, accountMenuSupported).getMenu()); addSeparator(trayMenu); } @@ -201,9 +204,11 @@ public final class TrayMenuFactory showHideIconId = "service.gui.icons.SEARCH_ICON_16x16"; } else + { showHideName = "service.gui.SHOW"; showHideTextId = "service.gui.SHOW"; showHideIconId = "service.gui.icons.SEARCH_ICON_16x16"; + } final Object showHideMenuItem = createTrayMenuItem( showHideName, showHideTextId, @@ -245,7 +250,7 @@ public final class TrayMenuFactory } }); - return trayMenu; + return Pair.of(trayMenu, showHideMenuItem); } /** diff --git a/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf b/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf index 0db99ad..91f2791 100644 --- a/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf +++ b/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf @@ -5,26 +5,29 @@ Bundle-Vendor: jitsi.org Bundle-Version: 0.0.1
Bundle-SymbolicName: net.java.sip.communicator.osdependent
Export-Package: net.java.sip.communicator.service.desktop
-Import-Package: org.osgi.framework,
- com.apple.cocoa.application,
+Import-Package: com.apple.cocoa.application,
com.apple.cocoa.foundation,
com.apple.eawt,
- org.jitsi.service.configuration,
+ com.sun.jna,
+ com.sun.jna.platform.win32,
+ com.sun.jna.platform.win32.COM,
+ com.sun.jna.ptr,
+ com.sun.jna.win32,
net.java.sip.communicator.service.gui,
net.java.sip.communicator.service.gui.event,
net.java.sip.communicator.service.protocol,
net.java.sip.communicator.service.protocol.globalstatus,
net.java.sip.communicator.service.protocol.event,
- org.jitsi.service.resources, net.java.sip.communicator.service.resources,
+ net.java.sip.communicator.service.resources,
net.java.sip.communicator.service.shutdown,
net.java.sip.communicator.service.systray,
net.java.sip.communicator.service.systray.event,
- org.jitsi.util,
net.java.sip.communicator.util,
net.java.sip.communicator.plugin.desktoputil,
net.java.sip.communicator.plugin.desktoputil.presence,
javax.accessibility,
javax.imageio,
+ javax.imageio.stream,
javax.swing,
javax.swing.border,
javax.swing.event,
@@ -36,4 +39,9 @@ Import-Package: org.osgi.framework, javax.swing.text,
javax.swing.text.html,
javax.swing.tree,
- javax.swing.undo
+ javax.swing.undo,
+ org.apache.commons.lang3.tuple,
+ org.jitsi.service.configuration,
+ org.jitsi.service.resources,
+ org.jitsi.util,
+ org.osgi.framework
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java new file mode 100644 index 0000000..818d1e7 --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java @@ -0,0 +1,156 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.systemtray; + +import java.awt.*; + +import javax.swing.*; + +import org.jitsi.util.*; + +import net.java.sip.communicator.impl.osdependent.*; +import net.java.sip.communicator.impl.osdependent.systemtray.appindicator.*; +import net.java.sip.communicator.impl.osdependent.systemtray.awt.*; +import net.java.sip.communicator.service.systray.*; +import net.java.sip.communicator.util.Logger; + +/** + * Base class for all wrappers of <tt>SystemTray</tt> implementations. + */ +public abstract class SystemTray +{ + private static final Logger logger = Logger.getLogger(SystemTray.class); + private static SystemTray systemTray; + private static final String DISABLED_TRAY_MODE = "disabled"; + + /** + * Gets or creates the supported <tt>SystemTray</tt> implementations. + * @return a <tt>SystemTray</tt> implementation for the current platform. + */ + public final static SystemTray getSystemTray() + { + if (systemTray == null) + { + String mode = getSystemTrayMode(); + logger.info("Tray for " + mode + " requested"); + switch (mode) + { + case DISABLED_TRAY_MODE: + return null; + case "native": + if (java.awt.SystemTray.isSupported()) + { + systemTray = new AWTSystemTray(); + } + + break; + case "appindicator": + try + { + systemTray = new AppIndicatorTray(true); + } + catch(Exception ex) + { + logger.error("AppIndicator tray not available", ex); + } + break; + case "appindicator_static": + try + { + systemTray = new AppIndicatorTray(false); + } + catch(Exception ex) + { + logger.error("AppIndicator tray not available", ex); + } + + break; + } + + if (systemTray == null) + { + OsDependentActivator.getConfigurationService() + .setProperty(SystrayService.PNMAE_TRAY_MODE, "disabled"); + } + } + + return systemTray; + } + + public static String getSystemTrayMode() + { + String defaultTrayMode = DISABLED_TRAY_MODE; + if (GraphicsEnvironment.isHeadless()) + { + return DISABLED_TRAY_MODE; + } + + // setting from cmd-line: request to disable tray in case it failed + if (Boolean.getBoolean("disable-tray")) + { + OsDependentActivator.getConfigurationService().setProperty( + SystrayService.PNMAE_TRAY_MODE, DISABLED_TRAY_MODE); + } + + if (OSUtils.IS_WINDOWS || OSUtils.IS_MAC) + { + defaultTrayMode = "native"; + } + + return OsDependentActivator.getConfigurationService() + .getString(SystrayService.PNMAE_TRAY_MODE, defaultTrayMode); + } + + /** + * Adds a <tt>TrayIcon</tt> to this system tray implementation. + * + * @param trayIcon the <tt>TrayIcon</tt> to add + */ + public abstract void addTrayIcon(TrayIcon trayIcon); + + /** + * Creates an implementation specific <tt>TrayIcon</tt> that can later be + * added with {@link #addTrayIcon(TrayIcon)}. + * + * @param image the <tt>Image</tt> to be used + * @param tooltip the string to be used as tooltip text; if the value is + * <tt>null</tt> no tooltip is shown + * @param popup the menu to be used for the tray icon's popup menu; if the + * value is <tt>null</tt> no popup menu is shown + * @return a <tt>TrayIcon</tt> instance for this <tt>SystemTray</tt> + * implementation. + */ + public abstract TrayIcon createTrayIcon(ImageIcon icon, String tooltip, + Object popup); + + /** + * Determines if the popup menu for the icon is to be a Swing + * <tt>JPopupMenu</tt> or an AWT <tt>PopupMenu</tt> + * + * @return <tt>true</tt> for a <tt>JPopupMenu</tt>, <tt>false</tt> for a + * <tt>PopupMenu</tt> + */ + public abstract boolean useSwingPopupMenu(); + + /** + * Determines if the tray icon supports dynamic menus. + * + * @return True if the menu can be changed while running, false otherwise. + */ + public abstract boolean supportsDynamicMenu(); +} diff --git a/src/net/java/sip/communicator/impl/dns/UnboundException.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/TrayIcon.java index 3eb2a04..78df44c 100644 --- a/src/net/java/sip/communicator/impl/dns/UnboundException.java +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/TrayIcon.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,28 +15,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.dns;
-
-/**
- * Exception that is being thrown when native Unbound code resulted in an error.
- *
- * @author Ingo Bauersachs
- */
-public class UnboundException
- extends Exception
-{
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- /**
- * Creates a new instance of this class.
- *
- * @param message the detail message.
- */
- public UnboundException(String message)
- {
- super(message);
- }
-}
+package net.java.sip.communicator.impl.osdependent.systemtray; + +import java.awt.event.*; + +import javax.swing.*; + +/** + * Interface for all platform specific TrayIcon implementations. See + * {@link java.awt.TrayIcon} for a description of the methods. + * + * @author Lubomir Marinov + */ +public interface TrayIcon +{ + public void setDefaultAction(Object menuItem); + + public void addBalloonActionListener(ActionListener listener); + + public void displayMessage(String caption, String text, + java.awt.TrayIcon.MessageType messageType); + + public void setIcon(ImageIcon icon) throws NullPointerException; + + public void setIconAutoSize(boolean autoSize); +} diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicator1.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicator1.java new file mode 100644 index 0000000..85fbd73 --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicator1.java @@ -0,0 +1,189 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.systemtray.appindicator; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.*; + +/** + * JNA mappings for libappindicator1. + * + * @author Ingo Bauersachs + */ +interface AppIndicator1 extends Library +{ + static final AppIndicator1 INSTANCE = + (AppIndicator1) Native.loadLibrary("appindicator", AppIndicator1.class); + + static final String APP_INDICATOR_SIGNAL_NEW_ICON = "new-icon"; + static final String APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON = "new-attention-icon"; + static final String APP_INDICATOR_SIGNAL_NEW_STATUS = "new-status"; + static final String APP_INDICATOR_SIGNAL_NEW_LABEL = "new-label"; + static final String APP_INDICATOR_SIGNAL_CONNECTION_CHANGED = "connection-changed"; + static final String APP_INDICATOR_SIGNAL_NEW_ICON_THEME_PATH = "new-icon-theme-path"; + static final String APP_INDICATOR_SIGNAL_SCROLL_EVENT = "scroll-event"; + + /** + * The category provides grouping for the indicators so that users can find + * indicators that are similar together. + */ + enum APP_INDICATOR_CATEGORY + { + /** The indicator is used to display the status of the application. */ + APPLICATION_STATUS, + + /** The application is used for communication with other people. */ + COMMUNICATIONS, + + /** A system indicator relating to something in the user's system. */ + SYSTEM_SERVICES, + + /** An indicator relating to the user's hardware. */ + HARDWARE, + + /** + * Something not defined in this enum, please don't use unless you + * really need it. + */ + OTHER + } + + /** + * These are the states that the indicator can be on in the user's panel. + * The indicator by default starts in the state {@link #PASSIVE} and can be + * shown by setting it to {@link #ACTIVE}. + */ + enum APP_INDICATOR_STATUS + { + /** The indicator should not be shown to the user. */ + PASSIVE, + + /** The indicator should be shown in it's default state. */ + ACTIVE, + + /** The indicator should show it's attention icon. */ + ATTENTION + } + + class AppIndicatorClass extends Structure + { + // Parent + public /*Gobject.GObjectClass*/ Pointer parent_class; + + // DBus Signals + public Pointer new_icon; + public Pointer new_attention_icon; + public Pointer new_status; + public Pointer new_icon_theme_path; + public Pointer new_label; + + // Local Signals + public Pointer connection_changed; + public Pointer scroll_event; + public Pointer app_indicator_reserved_ats; + + // Overridable Functions + public Pointer fallback; + public Pointer unfallback; + + // Reserved + public Pointer app_indicator_reserved_1; + public Pointer app_indicator_reserved_2; + public Pointer app_indicator_reserved_3; + public Pointer app_indicator_reserved_4; + public Pointer app_indicator_reserved_5; + public Pointer app_indicator_reserved_6; + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "parent_class", + "new_icon", + "new_attention_icon", + "new_status", + "new_icon_theme_path", + "new_label", + + "connection_changed", + "scroll_event", + "app_indicator_reserved_ats", + + "fallback", + "unfallback", + + "app_indicator_reserved_1", + "app_indicator_reserved_2", + "app_indicator_reserved_3", + "app_indicator_reserved_4", + "app_indicator_reserved_5", + "app_indicator_reserved_6"); + } + } + + class AppIndicator extends Structure + { + public /*Gobject.GObject*/ Pointer parent; + public Pointer priv; + + @Override + protected List getFieldOrder() + { + return Arrays.asList("parent", "priv"); + } + } + + // GObject Stuff + NativeLong app_indicator_get_type(); + AppIndicator app_indicator_new(String id, String icon_name, int category); + AppIndicator app_indicator_new_with_path(String id, String icon_name, int category, String icon_theme_path); + + // Set properties + void app_indicator_set_status(AppIndicator self, int status); + void app_indicator_set_attention_icon(AppIndicator self, String icon_name); + void app_indicator_set_attention_icon_full(AppIndicator self, String name, String icon_desc); + void app_indicator_set_menu(AppIndicator self, Pointer menu); + void app_indicator_set_icon(AppIndicator self, String icon_name); + void app_indicator_set_icon_full(AppIndicator self, String icon_name, String icon_desc); + void app_indicator_set_label(AppIndicator self, String label, String guide); + void app_indicator_set_icon_theme_path(AppIndicator self, String icon_theme_path); + void app_indicator_set_ordering_index(AppIndicator self, int ordering_index); + void app_indicator_set_secondary_activate_target(AppIndicator self, Pointer menuitem); + void app_indicator_set_title(AppIndicator self, String title); + + // Get properties + String app_indicator_get_id(AppIndicator self); + int app_indicator_get_category(AppIndicator self); + int app_indicator_get_status(AppIndicator self); + String app_indicator_get_icon(AppIndicator self); + String app_indicator_get_icon_desc(AppIndicator self); + String app_indicator_get_icon_theme_path(AppIndicator self); + String app_indicator_get_attention_icon(AppIndicator self); + String app_indicator_get_attention_icon_desc(AppIndicator self); + String app_indicator_get_title(AppIndicator self); + + Pointer app_indicator_get_menu(AppIndicator self); + String app_indicator_get_label(AppIndicator self); + String app_indicator_get_label_guide(AppIndicator self); + int app_indicator_get_ordering_index(AppIndicator self); + Pointer app_indicator_get_secondary_activate_target(AppIndicator self, Pointer widget); + + // Helpers + void app_indicator_build_menu_from_desktop(AppIndicator self, String desktop_file, String destkop_profile); +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTray.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTray.java new file mode 100644 index 0000000..90c949a --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTray.java @@ -0,0 +1,78 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.systemtray.appindicator; + +import javax.swing.*; + +import org.jitsi.util.*; + +import net.java.sip.communicator.impl.osdependent.*; +import net.java.sip.communicator.impl.osdependent.systemtray.*; +import net.java.sip.communicator.util.*; + +/** + * Jitsi system tray abstraction for libappindicator. + * + * @author Ingo Bauersachs + */ +public class AppIndicatorTray extends SystemTray +{ + private boolean dynamicMenu; + + public AppIndicatorTray(boolean dynamicMenu) throws Exception + { + this.dynamicMenu = dynamicMenu; + try + { + // pre-initialize the JNA libraries before attempting to use them + AppIndicator1.INSTANCE.toString(); + Gtk.INSTANCE.toString(); + Gobject.INSTANCE.toString(); + Gtk.INSTANCE.gtk_init(0, null); + } + catch (Throwable t) + { + throw new Exception("AppIndicator1 tray icon not available", t); + } + } + + @Override + public void addTrayIcon(TrayIcon trayIcon) + { + ((AppIndicatorTrayIcon) trayIcon).createTray(); + } + + @Override + public TrayIcon createTrayIcon(ImageIcon icon, String tooltip, Object popup) + { + return new AppIndicatorTrayIcon(icon, tooltip, (JPopupMenu) popup); + } + + @Override + public boolean useSwingPopupMenu() + { + // we want icons + return true; + } + + @Override + public boolean supportsDynamicMenu() + { + return dynamicMenu; + } +} diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTrayIcon.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTrayIcon.java new file mode 100644 index 0000000..12334f3 --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTrayIcon.java @@ -0,0 +1,695 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.systemtray.appindicator; + +import java.awt.*; +import java.awt.TrayIcon.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.beans.*; +import java.io.*; +import java.net.*; +import java.nio.file.*; +import java.util.*; +import java.util.List; +import java.util.Timer; + +import javax.accessibility.*; +import javax.imageio.*; +import javax.imageio.stream.*; +import javax.print.attribute.standard.*; +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.jitsi.util.*; +import org.jitsi.util.Logger; + +import com.sun.jna.*; + +import net.java.sip.communicator.impl.osdependent.*; +import net.java.sip.communicator.impl.osdependent.systemtray.*; +import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon; +import net.java.sip.communicator.impl.osdependent.systemtray.appindicator.Gobject.*; +import net.java.sip.communicator.util.*; + +/** + * System tray icon implementation based on libappindicator1. + * + * @author Ingo Bauersachs + */ +class AppIndicatorTrayIcon implements TrayIcon +{ + private static final Logger logger = + Logger.getLogger(AppIndicatorTrayIcon.class); + + // shortcuts + private static Gobject gobject = Gobject.INSTANCE; + private static Gtk gtk = Gtk.INSTANCE; + private static AppIndicator1 ai = AppIndicator1.INSTANCE; + + // references to the root menu and the native icon + private ImageIcon mainIcon; + private String title; + private JPopupMenu popup; + private Map<String, String> extractedFiles = new HashMap<>(); + private PopupMenuPeer popupPeer; + private AppIndicator1.AppIndicator appIndicator; + + private PopupMenuPeer defaultMenuPeer; + + public AppIndicatorTrayIcon(ImageIcon mainIcon, String title, + JPopupMenu popup) + { + this.mainIcon = mainIcon; + this.title = title; + this.popup = popup; + this.popupPeer = null; + } + + /** + * Combines the references of each swing menu item with the GTK counterpart + */ + private class PopupMenuPeer implements ContainerListener + { + public PopupMenuPeer(PopupMenuPeer parent, Component em) + { + menuItem = em; + + // if this menu item is a submenu, add ourselves as listener to + // add or remove the native counterpart + if (em instanceof JMenu) + { + ((JMenu)em).getPopupMenu().addContainerListener(this); + ((JMenu)em).addContainerListener(this); + } + } + + @Override + protected void finalize() throws Throwable + { + super.finalize(); + if (isDefaultMenuItem) + { + gobject.g_object_unref(gtkMenuItem); + } + } + + public List<PopupMenuPeer> children = new ArrayList<>(); + public Pointer gtkMenuItem; + public Pointer gtkMenu; + public Pointer gtkImage; + public Memory gtkImageBuffer; + public Pointer gtkPixbuf; + public Component menuItem; + public MenuItemSignalHandler signalHandler; + public long gtkSignalHandler; + public boolean isDefaultMenuItem; + + @Override + public void componentAdded(ContainerEvent e) + { + AppIndicatorTrayIcon.this.printMenu(popup.getComponents(), 1); + gtk.gdk_threads_enter(); + try + { + createGtkMenuItems(this, new Component[]{e.getChild()}); + gtk.gtk_widget_show_all(popupPeer.gtkMenu); + } + finally + { + gtk.gdk_threads_leave(); + } + } + + @Override + public void componentRemoved(ContainerEvent e) + { + AppIndicatorTrayIcon.this.printMenu(popup.getComponents(), 1); + for (PopupMenuPeer c : children) + { + if (c.menuItem == e.getChild()) + { + gtk.gdk_threads_enter(); + try + { + cleanMenu(c); + } + finally + { + gtk.gdk_threads_leave(); + } + + children.remove(c); + break; + } + } + } + } + + public void createTray() + { + gtk.gdk_threads_enter(); + try + { + setupGtkMenu(); + } + finally + { + gtk.gdk_threads_leave(); + } + + new Thread() + { + public void run() + { + gtk.gtk_main(); + } + }.start(); + } + + private void setupGtkMenu() + { + File iconFile = new File(imageIconToPath(mainIcon)); + appIndicator = ai.app_indicator_new_with_path( + "jitsi", + iconFile.getName().replaceFirst("[.][^.]+$", ""), + AppIndicator1.APP_INDICATOR_CATEGORY.COMMUNICATIONS.ordinal(), + iconFile.getParent()); + + ai.app_indicator_set_title(appIndicator, title); + ai.app_indicator_set_icon_full( + appIndicator, + iconFile.getAbsolutePath(), + "Jitsi"); + + // create root menu + popupPeer = new PopupMenuPeer(null, popup); + popupPeer.gtkMenu = gtk.gtk_menu_new(); + + // transfer everything in the swing menu to the gtk menu + createGtkMenuItems(popupPeer, popup.getComponents()); + gtk.gtk_widget_show_all(popupPeer.gtkMenu); + + // attach the menu to the indicator + ai.app_indicator_set_menu(appIndicator, popupPeer.gtkMenu); + ai.app_indicator_set_status( + appIndicator, + AppIndicator1.APP_INDICATOR_STATUS.ACTIVE.ordinal()); + } + + private void cleanMenu(PopupMenuPeer peer) + { + assert !peer.isDefaultMenuItem; + for (PopupMenuPeer p : peer.children) + { + cleanMenu(p); + } + + // - the root menu is released when it's unset from the indicator + // - gtk auto-frees menu item, submenu, image, and pixbuf + // - the imagebuffer was jna allocated, GC should take care of freeing + if (peer.gtkSignalHandler > 0) + { + gobject.g_signal_handler_disconnect( + peer.gtkMenuItem, + peer.gtkSignalHandler); + } + + gtk.gtk_widget_destroy(peer.gtkMenuItem); + peer.gtkImageBuffer = null; + if (peer.menuItem instanceof JMenu) + { + ((JMenu)peer.menuItem).removeContainerListener(peer); + ((JMenu)peer.menuItem).getPopupMenu().removeContainerListener(peer); + } + } + + private void createGtkMenuItems( + PopupMenuPeer parent, + Component[] components) + { + for (Component em : components) + { + PopupMenuPeer peer = new PopupMenuPeer(parent, em); + if (em instanceof JPopupMenu.Separator) + { + logger.debug("Creating separator"); + peer.gtkMenuItem = gtk.gtk_separator_menu_item_new(); + } + + if (em instanceof JMenuItem) + { + createGtkMenuItem(peer); + } + + if (em instanceof JMenu && peer.gtkMenuItem != null) + { + JMenu m = (JMenu)em; + logger.debug("Creating submenu on " + m.getText()); + peer.gtkMenu = gtk.gtk_menu_new(); + createGtkMenuItems(peer, m.getMenuComponents()); + gtk.gtk_menu_item_set_submenu(peer.gtkMenuItem, peer.gtkMenu); + } + + if (peer.gtkMenuItem != null) + { + parent.children.add(peer); + gtk.gtk_menu_shell_append(parent.gtkMenu, peer.gtkMenuItem); + } + } + } + + private void createGtkMenuItem(PopupMenuPeer peer) + { + JMenuItem m = (JMenuItem)peer.menuItem; + logger.debug("Creating item for " + m.getClass().getName() + ": " + + m.getText()); + if (m instanceof JCheckBoxMenuItem) + { + peer.gtkMenuItem = gtk.gtk_check_menu_item_new_with_label( + m.getText()); + JCheckBoxMenuItem cb = (JCheckBoxMenuItem)m; + gtk.gtk_check_menu_item_set_active( + peer.gtkMenuItem, + cb.isSelected() ? 1 : 0); + } + else + { + peer.gtkMenuItem = gtk.gtk_image_menu_item_new_with_label( + m.getText()); + if (m.getIcon() instanceof ImageIcon) + { + ImageIcon ii = ((ImageIcon) m.getIcon()); + imageIconToGtkWidget(peer, ii); + if (peer.gtkImage != null) + { + gtk.gtk_image_menu_item_set_image( + peer.gtkMenuItem, + peer.gtkImage); + gtk.gtk_image_menu_item_set_always_show_image( + peer.gtkMenuItem, + 1); + } + } + } + + if (peer.gtkMenuItem == null) + { + logger.debug("Could not create menu item for " + m.getText()); + return; + } + + MenuItemChangeListener micl = new MenuItemChangeListener(peer); + m.addPropertyChangeListener(micl); + m.addChangeListener(micl); + + // skip GTK events if it's a submenu + if (!(m instanceof JMenu)) + { + gtk.gtk_widget_set_sensitive( + peer.gtkMenuItem, + m.isEnabled() ? 1 : 0); + peer.signalHandler = new MenuItemSignalHandler(peer); + peer.gtkSignalHandler = gobject.g_signal_connect_data( + peer.gtkMenuItem, + "activate", + peer.signalHandler, + null, + null, + 0); + } + } + + private String imageIconToPath(ImageIcon ii) + { + if (ii.getDescription() != null) + { + String path = extractedFiles.get(ii.getDescription()); + if (path != null) + { + return path; + } + } + + try + { + File f = File.createTempFile("jitsi-appindicator", ".png"); + f.deleteOnExit(); + try (FileImageOutputStream fios = new FileImageOutputStream(f)) + { + if (!ImageIO.write(getBufferedImage(ii), "png", fios)) + { + return null; + } + + if (ii.getDescription() != null) + { + extractedFiles.put( + ii.getDescription(), + f.getAbsolutePath()); + } + + return f.getAbsolutePath(); + } + } + catch (IOException e) + { + logger.debug("Failed to extract image: " + ii.getDescription(), e); + } + + return null; + } + + BufferedImage getBufferedImage(ImageIcon ii) + { + Image img = ii.getImage(); + if (img == null) + { + return null; + } + + if (img instanceof BufferedImage) + { + return (BufferedImage) img; + } + + BufferedImage bi = new BufferedImage( + img.getWidth(null), + img.getHeight(null), + BufferedImage.TYPE_INT_ARGB); + Graphics g = bi.createGraphics(); + g.drawImage(img, 0, 0, null); + g.dispose(); + return bi; + } + + private void imageIconToGtkWidget(PopupMenuPeer peer, ImageIcon ii) + { + BufferedImage bi = getBufferedImage(ii); + if (bi == null) + { + return; + } + + int[] pixels = bi.getRGB( + 0, + 0, + bi.getWidth(), + bi.getHeight(), + null, + 0, + bi.getWidth()); + + peer.gtkImageBuffer = new Memory(pixels.length * 4); + for (int i = 0; i < pixels.length; i++) + { + // convert from argb (big endian) -> rgba (little endian) => abgr + peer.gtkImageBuffer.setInt(i * 4, (pixels[i] & 0xFF000000) | + (pixels[i] << 16) | + (pixels[i] & 0xFF00) | + (pixels[i] >>> 16 & 0xFF)); + } + + peer.gtkPixbuf = gtk.gdk_pixbuf_new_from_data( + peer.gtkImageBuffer, + 0, + 1, + 8, + bi.getWidth(), + bi.getHeight(), + bi.getWidth() * 4, + null, + null); + peer.gtkImage = gtk.gtk_image_new_from_pixbuf(peer.gtkPixbuf); + + // Now that the image ref's the buffer, we can release our own ref and + // the buffer will be free'd along with the image + gobject.g_object_unref(peer.gtkPixbuf); + } + + private static class MenuItemChangeListener + implements PropertyChangeListener, ChangeListener + { + private PopupMenuPeer peer; + private JMenuItem menu; + + public MenuItemChangeListener(PopupMenuPeer peer) + { + this.peer = peer; + this.menu = (JMenuItem)peer.menuItem; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) + { + if (logger.isDebugEnabled()) + { + logger.debug(menu.getText() + "::" + evt.getPropertyName()); + } + + switch (evt.getPropertyName()) + { + case JMenuItem.TEXT_CHANGED_PROPERTY: + gtk.gdk_threads_enter(); + try + { + gtk.gtk_menu_item_set_label( + peer.gtkMenuItem, + evt.getNewValue().toString()); + } + finally + { + gtk.gdk_threads_leave(); + } + + break; +// case JMenuItem.ICON_CHANGED_PROPERTY: +// gtk.gtk_image_menu_item_set_image(gtkMenuItem, image); +// break; + case AccessibleContext.ACCESSIBLE_STATE_PROPERTY: + gtk.gdk_threads_enter(); + try + { + gtk.gtk_widget_set_sensitive( + peer.gtkMenuItem, + AccessibleState.ENABLED.equals( + evt.getNewValue()) ? 1 : 0); + } + finally + { + gtk.gdk_threads_leave(); + } + break; + } + } + + @Override + public void stateChanged(ChangeEvent e) + { + logger.debug(menu.getText() + " -> " + menu.isSelected()); + gtk.gdk_threads_enter(); + try + { + gtk.gtk_check_menu_item_set_active( + peer.gtkMenuItem, + menu.isSelected() ? 1 : 0); + } + finally + { + gtk.gdk_threads_leave(); + } + } + } + + private static class MenuItemSignalHandler + implements SignalHandler, Runnable + { + private PopupMenuPeer peer; + + MenuItemSignalHandler(PopupMenuPeer peer) + { + this.peer = peer; + } + + @Override + public void signal(Pointer widget, Pointer data) + { + SwingUtilities.invokeLater(this); + } + + @Override + public void run() + { + JMenuItem menu = (JMenuItem)peer.menuItem; + if (menu instanceof JCheckBoxMenuItem) + { + // Ignore GTK callback events if the menu state is + // already the same. Setting the selected state on the + // GTK sends the "activate" event, and would cause + // a loop + logger.debug("Checking selected state on: " + menu.getText()); + if (menu.isSelected() == isGtkSelected()) + { + return; + } + } + + for (ActionListener l : menu.getActionListeners()) + { + logger.debug("Invoking " + l + " on " + menu.getText()); + l.actionPerformed(new ActionEvent(menu, 0, "activate")); + } + } + + private boolean isGtkSelected() + { + gtk.gdk_threads_enter(); + try + { + return gtk.gtk_check_menu_item_get_active(peer.gtkMenuItem) == 1; + } + finally + { + gtk.gdk_threads_leave(); + } + } + } + + @Override + public void setDefaultAction(Object menuItem) + { + // It shouldn't be necessary that we hold a reference to the + // default item, it is contained in the menu. It might even create + // a memory leak. But if not set, the indicator loses track of it + // (at least on Debian). Unref an existing item, then ref the newly + // set + if (defaultMenuPeer != null) + { + gobject.g_object_unref(defaultMenuPeer.gtkMenuItem); + } + + PopupMenuPeer peer = findMenuItem(popupPeer, menuItem); + if (peer != null && peer.gtkMenuItem != null) + { + gtk.gdk_threads_enter(); + try + { + defaultMenuPeer = peer; + gobject.g_object_ref(peer.gtkMenuItem); + ai.app_indicator_set_secondary_activate_target( + appIndicator, + peer.gtkMenuItem); + } + finally + { + gtk.gdk_threads_leave(); + } + } + } + + private PopupMenuPeer findMenuItem(PopupMenuPeer peer, Object menuItem) + { + if (peer.menuItem == menuItem) + { + logger.debug("Setting default action to: " + + ((JMenuItem)menuItem).getText() + + " @" + peer.gtkMenuItem); + return peer; + } + + for (PopupMenuPeer p : peer.children) + { + PopupMenuPeer found = findMenuItem(p, menuItem); + if (found != null) + { + return found; + } + } + + return null; + } + + @Override + public void addBalloonActionListener(ActionListener listener) + { + // not supported + } + + @Override + public void displayMessage(String caption, String text, + MessageType messageType) + { + // not supported + } + + @Override + public void setIcon(ImageIcon icon) throws NullPointerException + { + mainIcon = icon; + if (appIndicator != null) + { + gtk.gdk_threads_enter(); + try + { + ai.app_indicator_set_icon( + appIndicator, + imageIconToPath(icon)); + } + finally + { + gtk.gdk_threads_leave(); + } + } + } + + @Override + public void setIconAutoSize(boolean autoSize) + { + // nothing to do + } + + private void printMenu(Component[] components, int indent) + { + if (!logger.isDebugEnabled()) + { + return; + } + + String p = String.format("%0" + indent * 4 + "d", 0).replace('0', ' '); + for (Component em : components) + { + if (em instanceof JPopupMenu.Separator) + { + logger.debug(p + "-----------------------"); + } + + if (em instanceof JMenuItem) + { + JMenuItem m = (JMenuItem) em; + logger.debug(p + em.getClass().getName() + ": " + m.getText()); + } + + if (em instanceof JMenu) + { + JMenu m = (JMenu) em; + printMenu(m.getMenuComponents(), indent + 1); + } + } + } +};
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gobject.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gobject.java new file mode 100644 index 0000000..cfc7805 --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gobject.java @@ -0,0 +1,90 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.systemtray.appindicator; + +import java.util.*; + +import com.sun.jna.*; + +/** + * JNA mappings for GTK GObject types that are required for the tray menu. + * + * @author Ingo Bauersachs + */ +interface Gobject extends Library +{ + static final Gobject INSTANCE = + (Gobject) Native.loadLibrary("gobject-2.0", Gobject.class); + + interface SignalHandler extends Callback + { + void signal(Pointer widget, Pointer data); + } + + /** + * Connects a GCallback function to a signal for a particular object. + * Similar to g_signal_connect(), but allows to provide a GClosureNotify for + * the data which will be called when the signal handler is disconnected and + * no longer used. Specify connect_flags if you need ..._after() or + * ..._swapped() variants of this function. + * + * @param instance the instance to connect to. + * @param detailed_signal a string of the form "signal-name::detail". + * @param c_handler the GCallback to connect. + * @param data data to pass to c_handler calls. + * @param destroy_data a GClosureNotify for data. + * @param connect_flags a combination of GConnectFlags. + * + * @return the handler id (always greater than 0 for successful connections) + */ + long g_signal_connect_data(Pointer instance, String detailed_signal, + SignalHandler c_handler, Pointer data, Pointer destroy_data, + int connect_flags); + + /** + * Disconnects a handler from an instance so it will not be called during + * any future or currently ongoing emissions of the signal it has been + * connected to. The handler_id becomes invalid and may be reused. The + * handler_id has to be a valid signal handler id, connected to a signal of + * instance . + * + * @param instance The instance to remove the signal handler from. + * @param handler_id Handler id of the handler to be disconnected. + */ + void g_signal_handler_disconnect(Pointer instance, long handler_id); + + /** + * Decreases the reference count of object. When its reference count drops + * to 0, the object is finalized (i.e. its memory is freed). If the pointer + * to the GObject may be reused in future (for example, if it is an instance + * variable of another object), it is recommended to clear the pointer to + * NULL rather than retain a dangling pointer to a potentially invalid + * GObject instance. Use g_clear_object() for this. + * + * @param object a GObject. + */ + void g_object_unref(Pointer object); + + /** + * Increases the reference count of object. + * + * @param object a GObject. + * @return the same object. + */ + Pointer g_object_ref(Pointer object); +} diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gtk.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gtk.java new file mode 100644 index 0000000..b66d59f --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gtk.java @@ -0,0 +1,68 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.systemtray.appindicator; + +import com.sun.jna.*; + +/** + * JNA mappings for the gtk2 library. Only functions required for the tray menu + * are defined. + * + * @author Ingo Bauersachs + */ +interface Gtk extends Library +{ + static final Gtk INSTANCE = + (Gtk) Native.loadLibrary("gtk-x11-2.0", Gtk.class); + + public enum GtkIconSize + { + INVALID, + MENU, + SMALL_TOOLBAR, + LARGE_TOOLBAR, + BUTTON, + DND, + DIALOG + } + + void gtk_init(int argc, String[] argv); + void gtk_main(); + Pointer gtk_menu_new(); + Pointer gtk_image_menu_item_new_with_label(String label); + Pointer gtk_separator_menu_item_new(); + void gtk_menu_item_set_submenu(Pointer menu_item, Pointer submenu); + void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image); + void gtk_image_menu_item_set_always_show_image(Pointer image_menu_item, int always_show); + void gtk_menu_item_set_label(Pointer menu_item, String label); + void gtk_menu_shell_append(Pointer menu_shell, Pointer child); + void gtk_widget_set_sensitive(Pointer widget, int sesitive); + void gtk_widget_show_all(Pointer widget); + void gtk_widget_destroy(Pointer widget); + Pointer gtk_check_menu_item_new_with_label(String label); + int gtk_check_menu_item_get_active(Pointer check_menu_item); + void gtk_check_menu_item_set_active(Pointer check_menu_item, int is_active); + + void gdk_threads_enter(); + void gdk_threads_leave(); + + Pointer gdk_pixbuf_new_from_data(Pointer data, int colorspace, int has_alpha, + int bits_per_sample, int width, int height, int rowstride, + Pointer destroy_fn, Pointer destroy_fn_data); + Pointer gtk_image_new_from_pixbuf(Pointer pixbuf); +} diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTMouseAdapter.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTMouseAdapter.java new file mode 100644 index 0000000..eb4a773 --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTMouseAdapter.java @@ -0,0 +1,124 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.systemtray.awt; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; +import javax.swing.event.*; + +import org.jitsi.util.*; + +/** + * Extended mouse adapter to show the JPopupMenu in Java 6. Based on: <a href= + * "https://community.oracle.com/blogs/ixmal/2006/05/03/using-jpopupmenu-trayicon"> + * Using JPopupMenu in TrayIcon Blog</a> and <a href= + * "https://community.oracle.com/blogs/alexfromsun/2008/02/14/jtrayicon-update"> + * JTrayIcon update Blog</a>. + * + * Use a hidden JWindow (JDialog for Windows) to manage the JPopupMenu. + * + * @author Damien Roth + */ +class AWTMouseAdapter + extends MouseAdapter +{ + private JPopupMenu popup = null; + private Window hiddenWindow = null; + + public AWTMouseAdapter(JPopupMenu p) + { + this.popup = p; + this.popup.addPopupMenuListener(new PopupMenuListener() + { + public void popupMenuWillBecomeVisible(PopupMenuEvent e) + {} + + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) + { + if (hiddenWindow != null) + { + hiddenWindow.dispose(); + hiddenWindow = null; + } + } + + public void popupMenuCanceled(PopupMenuEvent e) + { + if (hiddenWindow != null) + { + hiddenWindow.dispose(); + hiddenWindow = null; + } + } + }); + } + + @Override + public void mouseReleased(MouseEvent e) + { + showPopupMenu(e); + } + + @Override + public void mousePressed(MouseEvent e) + { + showPopupMenu(e); + } + + private void showPopupMenu(MouseEvent e) + { + if (e.isPopupTrigger() && popup != null) + { + if (hiddenWindow == null) + { + if (OSUtils.IS_WINDOWS) + { + hiddenWindow = new JDialog((Frame) null); + ((JDialog) hiddenWindow).setUndecorated(true); + } + else + hiddenWindow = new JWindow((Frame) null); + + hiddenWindow.setAlwaysOnTop(true); + Dimension size = popup.getPreferredSize(); + + Point centerPoint = GraphicsEnvironment + .getLocalGraphicsEnvironment() + .getCenterPoint(); + + if(e.getY() > centerPoint.getY()) + hiddenWindow + .setLocation(e.getX(), e.getY() - size.height); + else + hiddenWindow + .setLocation(e.getX(), e.getY()); + + hiddenWindow.setVisible(true); + + popup.show( + ((RootPaneContainer)hiddenWindow).getContentPane(), + 0, 0); + + // popup works only for focused windows + hiddenWindow.toFront(); + } + } + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java new file mode 100644 index 0000000..1f211c7 --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java @@ -0,0 +1,78 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.systemtray.awt; + +import java.awt.*; + +import javax.swing.*; + +import org.jitsi.util.*; + +import net.java.sip.communicator.impl.osdependent.systemtray.SystemTray; +import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon; + +/** + * Wrapper of the AWT SystemTray class. + */ +public class AWTSystemTray + extends SystemTray +{ + private final java.awt.SystemTray impl; + + /** + * Creates a new instance of this class. + */ + public AWTSystemTray() + { + impl = java.awt.SystemTray.getSystemTray(); + } + + @Override + public void addTrayIcon(TrayIcon trayIcon) + throws IllegalArgumentException + { + try + { + impl.add(((AWTTrayIcon) trayIcon).getImpl()); + } + catch (AWTException e) + { + throw new IllegalArgumentException(e); + } + } + + @Override + public TrayIcon createTrayIcon(ImageIcon icon, String tooltip, + Object popup) + { + return new AWTTrayIcon(icon.getImage(), tooltip, popup); + } + + @Override + public boolean useSwingPopupMenu() + { + // enable swing for Java 1.6 except for the mac version + return !OSUtils.IS_MAC; + } + + @Override + public boolean supportsDynamicMenu() + { + return true; + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java new file mode 100644 index 0000000..2fa0318 --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java @@ -0,0 +1,131 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.systemtray.awt; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon; + +/** + * Wrapper of the AWT TrayIcon class. + */ +public class AWTTrayIcon + implements TrayIcon +{ + private final java.awt.TrayIcon impl; + + /** + * Creates a new instance of this class. + * + * @param image the <tt>Image</tt> to be used + * @param tooltip the string to be used as tooltip text; if the value is + * <tt>null</tt> no tooltip is shown + * @param popup the menu to be used for the tray icon's popup menu; if the + * value is <tt>null</tt> no popup menu is shown + */ + public AWTTrayIcon(Image image, String tooltip, + Object popup) + { + if (popup instanceof JPopupMenu) + { + impl = new java.awt.TrayIcon(image, tooltip); + impl.addMouseListener(new AWTMouseAdapter((JPopupMenu)popup)); + } + else if (popup instanceof PopupMenu) + { + impl = new java.awt.TrayIcon(image, tooltip, (PopupMenu)popup); + } + else if (popup == null) + { + impl = new java.awt.TrayIcon(image, tooltip); + } + else + { + throw new IllegalArgumentException("Invalid popup menu type"); + } + } + + public void setDefaultAction(final Object menuItem) + { + // clear all previous listeners + ActionListener[] previous = impl.getActionListeners(); + for (ActionListener l : previous) + { + impl.removeActionListener(l); + } + + // get the new handlers + final ActionListener[] listeners; + if (menuItem instanceof JMenuItem) + { + listeners = ((JMenuItem) menuItem).getActionListeners(); + } + else if (menuItem instanceof MenuItem) + { + listeners = ((MenuItem) menuItem).getActionListeners(); + } + else + { + return; + } + + // create a custom handler to fake that the source is the menu item + impl.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + for (ActionListener l : listeners) + { + l.actionPerformed(new ActionEvent(menuItem, + e.getID(), e.getActionCommand())); + } + } + }); + } + + public void addBalloonActionListener(ActionListener listener) + { + // java.awt.TrayIcon doesn't support addBalloonActionListener() + } + + public void displayMessage(String caption, String text, + java.awt.TrayIcon.MessageType messageType) + throws NullPointerException + { + impl.displayMessage(caption, text, messageType); + } + + public void setIcon(ImageIcon icon) throws NullPointerException + { + impl.setImage(icon.getImage()); + } + + public void setIconAutoSize(boolean autoSize) + { + impl.setImageAutoSize(autoSize); + } + + java.awt.TrayIcon getImpl() + { + return impl; + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/osdependent/windows/ImageConverter.java b/src/net/java/sip/communicator/impl/osdependent/windows/ImageConverter.java new file mode 100644 index 0000000..99754d3 --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/windows/ImageConverter.java @@ -0,0 +1,172 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright 2000-2016 JetBrains s.r.o. + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package net.java.sip.communicator.impl.osdependent.windows; + +import java.awt.image.*; +import java.nio.*; + +import com.sun.jna.*; +import com.sun.jna.platform.win32.*; +import com.sun.jna.platform.win32.WinDef.*; + +/** + * Image conversion utilities. + * + * Parts of this code are based on AppIcon.java from IntelliJ community. + * Licensed under Apache 2.0, Copyright 2000-2016 JetBrains s.r.o. + */ +public class ImageConverter +{ + /** + * Converts the <tt>BufferedImage</tt> to an ICONDIR structure. + * @param src The source image to convert + * @return an ICONDIR structure with the data of the passed source image + */ + public static byte[] writeTransparentIcoImage(BufferedImage src) + { + int bitCount = 32; + + int scanline_size = (bitCount * src.getWidth() + 7) / 8; + if ((scanline_size % 4) != 0) + scanline_size += 4 - (scanline_size % 4); // pad scanline to 4 byte + // size. + int t_scanline_size = (src.getWidth() + 7) / 8; + if ((t_scanline_size % 4) != 0) + t_scanline_size += 4 - (t_scanline_size % 4); // pad scanline to 4 + // byte size. + int imageSize = 40 + src.getHeight() * scanline_size + + src.getHeight() * t_scanline_size; + + // sizeof(ICONDIR) + // + sizeof(ICONDIRENTRY) + // + (sizeof(BITMAPINFOHEADER)+data) + ByteBuffer bos = ByteBuffer.allocate(6 + 16 + imageSize); + bos.order(ByteOrder.LITTLE_ENDIAN); + + // ICONDIR + bos.putShort((short) 0); // reserved + bos.putShort((short) 1); // 1=ICO, 2=CUR + bos.putShort((short) 1); // count + + // ICONDIRENTRY + int iconDirEntryWidth = src.getWidth(); + int iconDirEntryHeight = src.getHeight(); + if (iconDirEntryWidth > 255 || iconDirEntryHeight > 255) + { + iconDirEntryWidth = 0; + iconDirEntryHeight = 0; + } + bos.put((byte) iconDirEntryWidth); + bos.put((byte) iconDirEntryHeight); + bos.put((byte) 0); + bos.put((byte) 0); // reserved + bos.putShort((short) 1); // color planes + bos.putShort((short) bitCount); + bos.putInt(imageSize); + bos.putInt(22); // image offset + + // BITMAPINFOHEADER + bos.putInt(40); // size + bos.putInt(src.getWidth()); + bos.putInt(2 * src.getHeight()); + bos.putShort((short) 1); // planes + bos.putShort((short) bitCount); + bos.putInt(0); // compression + bos.putInt(0); // image size + bos.putInt(0); // x pixels per meter + bos.putInt(0); // y pixels per meter + bos.putInt(0); // colors used, 0 = (1 << bitCount) (ignored) + bos.putInt(0); // colors important + + int bit_cache = 0; + int bits_in_cache = 0; + int row_padding = scanline_size - (bitCount * src.getWidth() + 7) / 8; + for (int y = src.getHeight() - 1; y >= 0; y--) + { + for (int x = 0; x < src.getWidth(); x++) + { + int argb = src.getRGB(x, y); + + bos.put((byte) (0xff & argb)); + bos.put((byte) (0xff & (argb >> 8))); + bos.put((byte) (0xff & (argb >> 16))); + bos.put((byte) (0xff & (argb >> 24))); + } + + for (int x = 0; x < row_padding; x++) + bos.put((byte) 0); + } + + int t_row_padding = t_scanline_size - (src.getWidth() + 7) / 8; + for (int y = src.getHeight() - 1; y >= 0; y--) + { + for (int x = 0; x < src.getWidth(); x++) + { + int argb = src.getRGB(x, y); + int alpha = 0xff & (argb >> 24); + bit_cache <<= 1; + if (alpha == 0) + bit_cache |= 1; + bits_in_cache++; + if (bits_in_cache >= 8) + { + bos.put((byte) (0xff & bit_cache)); + bit_cache = 0; + bits_in_cache = 0; + } + } + + if (bits_in_cache > 0) + { + bit_cache <<= (8 - bits_in_cache); + bos.put((byte) (0xff & bit_cache)); + bit_cache = 0; + bits_in_cache = 0; + } + + for (int x = 0; x < t_row_padding; x++) + bos.put((byte) 0); + } + + byte[] result = new byte[bos.position()]; + System.arraycopy(bos.array(), 0, result, 0, bos.position()); + return result; + } + + /** + * Converts an ICONDIR ico to an HICON handle + * @param ico the image data + * @return A Windows HICON handle + */ + public static HICON createIcon(byte[] ico) + { + Memory buffer = new Memory(ico.length); + buffer.write(0, ico, 0, ico.length); + int nSize = 100; + int offset = User32Ex.INSTANCE.LookupIconIdFromDirectoryEx(buffer, true, + nSize, nSize, 0); + if (offset != 0) + { + return User32Ex.INSTANCE.CreateIconFromResourceEx( + buffer.share(offset), new WinDef.DWORD(0), true, + new WinDef.DWORD(0x00030000), nSize, nSize, 0); + } + + return null; + } +} diff --git a/src/net/java/sip/communicator/impl/osdependent/windows/TaskBarList3.java b/src/net/java/sip/communicator/impl/osdependent/windows/TaskBarList3.java new file mode 100644 index 0000000..a21cd8f --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/windows/TaskBarList3.java @@ -0,0 +1,150 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.windows; + +import java.awt.*; +import java.awt.image.*; + +import org.jitsi.util.*; + +import com.sun.jna.*; +import com.sun.jna.platform.win32.*; +import com.sun.jna.platform.win32.COM.*; +import com.sun.jna.platform.win32.Guid.*; +import com.sun.jna.platform.win32.WinDef.*; +import com.sun.jna.platform.win32.WinNT.*; +import com.sun.jna.ptr.*; + +/** + * JNA wrapper for the ITaskBarList3 COM interface. + * https://msdn.microsoft.com/en-us/library/dd391696(v=vs.85).aspx + * + * @author Ingo Bauersachs + */ +public class TaskBarList3 + extends Unknown +{ + private static final GUID CLSID_TaskbarList = + new GUID("{56FDF344-FD6D-11d0-958A-006097C9A090}"); + + private static final GUID IID_ITaskbarList3 = + new GUID("{ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf}"); + + private static TaskBarList3 instance; + + /** + * Gets the ITaskBarList3 interface and initializes it with HrInit + * @return A ready to use TaskBarList3 object. + * @throws COMException when the interface could not be accessed + */ + public static TaskBarList3 getInstance() + { + if (instance == null && OSUtils.IS_WINDOWS) + { + Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, 0); + PointerByReference p = new PointerByReference(); + WinNT.HRESULT hr = + Ole32.INSTANCE.CoCreateInstance(CLSID_TaskbarList, Pointer.NULL, + ObjBase.CLSCTX_ALL, IID_ITaskbarList3, p); + COMUtils.checkRC(hr); + instance = new TaskBarList3(p.getValue()); + } + + return instance; + } + + private TaskBarList3(Pointer p) + { + super(p); + HrInit(); + } + + // VTable + // ------ + // IUnknown: + // 0: AddRef + // 1: QueryInterface + // 2: Release + // + // ITaskBarList: + // 3: HrInit + // 4: AddTab + // 5: DeleteTab + // 6: ActivateTab + // 7: SetActiveAlt + // + // ITaskBarList2 + // 8: MarkFullscreenWindow + // + // ITaskBarList3: + // 9: SetProgressValue + // 10: SetProgressState + // 11: RegisterTab + // 12: UnregisterTab + // 13: SetTabOrder + // 14: SetTabActive + // 15: ThumbBarAddButtons + // 16: ThumbBarAddButtons + // 17: ThumbBarSetImageList + // 18: SetOverlayIcon + // 19: SetThumbnailTooltip + // 20: SetThumbnailClip + // + // ITaskbarList4: + // 21: SetTabProperties + + /** + * https://msdn.microsoft.com/en-us/library/bb774650(v=vs.85).aspx + */ + private void HrInit() + { + int hr = this._invokeNativeInt(3, new Object[] + { this.getPointer() }); + COMUtils.checkRC(new HRESULT(hr)); + } + + /** + * https://msdn.microsoft.com/en-us/library/dd391696(v=vs.85).aspx + */ + private void SetOverlayIcon(HWND hwnd, HICON hIcon, String pszDescription) + { + int hr = this._invokeNativeInt(18, new Object[] + { this.getPointer(), hwnd, hIcon, pszDescription }); + COMUtils.checkRC(new HRESULT(hr)); + } + + /** + * Sets an overlay image to the taskbar icon. + * @param frame The window that should receive the overlay + * @param image The overlay image, can be <tt>null</tt> to clear the overlay + * @param description An optional tooltip text, can be <tt>null</tt> + */ + public void SetOverlayIcon(Component frame, BufferedImage image, + String description) + { + HICON ico = null; + if (image != null) + { + byte[] iconBytes = ImageConverter.writeTransparentIcoImage(image); + ico = ImageConverter.createIcon(iconBytes); + } + + HWND hwnd = new HWND(Native.getComponentPointer(frame)); + SetOverlayIcon(hwnd, ico, description); + } +} diff --git a/src/net/java/sip/communicator/impl/osdependent/windows/User32Ex.java b/src/net/java/sip/communicator/impl/osdependent/windows/User32Ex.java new file mode 100644 index 0000000..59f3a7a --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/windows/User32Ex.java @@ -0,0 +1,47 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.osdependent.windows; + +import com.sun.jna.*; +import com.sun.jna.platform.win32.*; +import com.sun.jna.win32.*; + +/** + * Extension to missing user32 Windows APIs + * + * @author Ingo Bauersachs + */ +interface User32Ex + extends StdCallLibrary +{ + User32Ex INSTANCE = (User32Ex) Native.loadLibrary("user32", User32Ex.class, + W32APIOptions.DEFAULT_OPTIONS); + + /** + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms648074(v=vs.85).aspx + */ + int LookupIconIdFromDirectoryEx(Memory presbits, boolean fIcon, + int cxDesired, int cyDesired, int Flags); + + /** + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms648061(v=vs.85).aspx + */ + WinDef.HICON CreateIconFromResourceEx(Pointer pbIconBits, + WinDef.DWORD cbIconBits, boolean fIcon, WinDef.DWORD dwVersion, + int cxDesired, int cyDesired, int uFlags); +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingConfigurationImpl.java b/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingConfigurationImpl.java index b26b11a..978c74c 100644 --- a/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingConfigurationImpl.java +++ b/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingConfigurationImpl.java @@ -60,6 +60,10 @@ public class PacketLoggingConfigurationImpl configService.getBoolean( PACKET_LOGGING_ICE4J_ENABLED_PROPERTY_NAME, isIce4JLoggingEnabled())); + super.setArbitraryLoggingEnabled( + configService.getBoolean( + PACKET_LOGGING_ARBITRARY_ENABLED_PROPERTY_NAME, + isArbitraryLoggingEnabled())); super.setLimit( configService.getLong( PACKET_LOGGING_FILE_SIZE_PROPERTY_NAME, diff --git a/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java b/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java index 788e8db..1e459f1 100644 --- a/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java +++ b/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java @@ -20,6 +20,7 @@ package net.java.sip.communicator.impl.packetlogging; import java.io.*; import java.util.*; +import com.google.common.collect.*; import net.java.sip.communicator.util.*; import org.jitsi.service.fileaccess.*; @@ -41,6 +42,14 @@ public class PacketLoggingServiceImpl = Logger.getLogger(PacketLoggingServiceImpl.class); /** + * The max size of the <tt>EvictingQueue</tt> that the saver thread + * is using. + * + * TODO this needs to be configurable eventually. + */ + private static final int EVICTING_QUEUE_MAX_SIZE = 1000; + + /** * The OutputStream we are currently writing to. */ private FileOutputStream outputStream = null; @@ -62,10 +71,17 @@ public class PacketLoggingServiceImpl new byte[]{ (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, - (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, - (byte)0x08, (byte)0x00 + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 }; + /** IEEE 802.3 EtherType for IPv4 */ + private final static byte[] ipv4EtherType = + new byte[] { 0x08, 0x00 }; + + /** IEEE 802.3 EtherType for IPv6 */ + private final static byte[] ipv6EtherType = + new byte[] { (byte)0x86, (byte)0xdd }; + /** * The fake ipv4 header we use as template. */ @@ -313,22 +329,22 @@ public class PacketLoggingServiceImpl { switch(protocol) { - case SIP: - return cfg.isSipLoggingEnabled(); - case JABBER: - return cfg.isJabberLoggingEnabled(); - case RTP: - return cfg.isRTPLoggingEnabled(); - case ICE4J: - return cfg.isIce4JLoggingEnabled(); - default: - /* - * It may seem like it was unnecessary to invoke - * getConfiguration and isGlobalLoggingEnabled prior to - * checking that the specified protocol is supported but, - * actually, there are no other ProtocolName values. - */ - return false; + case SIP: + return cfg.isSipLoggingEnabled(); + case JABBER: + return cfg.isJabberLoggingEnabled(); + case RTP: + return cfg.isRTPLoggingEnabled(); + case ICE4J: + return cfg.isIce4JLoggingEnabled(); + case ARBITRARY: + return cfg.isArbitraryLoggingEnabled(); + default: + // It may seem like it was unnecessary to invoke + // getConfiguration and isGlobalLoggingEnabled prior to checking + // that the specified protocol is supported but, actually, there + // are no other ProtocolName values. + return false; } } else @@ -553,6 +569,7 @@ public class PacketLoggingServiceImpl int tsSec = (int)(current/1000); int tsUsec = (int)((current%1000) * 1000); int feakHeaderLen = fakeEthernetHeader.length + + (isIPv4 ? ipv4EtherType : ipv6EtherType).length + ipHeader.length + transportHeader.length; int inclLen = packet.packetLength + feakHeaderLen; int origLen = inclLen; @@ -577,6 +594,7 @@ public class PacketLoggingServiceImpl addInt(origLen); outputStream.write(fakeEthernetHeader); + outputStream.write(isIPv4 ? ipv4EtherType : ipv6EtherType); outputStream.write(ipHeader); outputStream.write(transportHeader); outputStream.write( @@ -767,7 +785,8 @@ public class PacketLoggingServiceImpl /** * List of packets queued to be written in the file. */ - private final List<Packet> pktsToSave = new ArrayList<Packet>(); + private final EvictingQueue<Packet> pktsToSave + = EvictingQueue.create(EVICTING_QUEUE_MAX_SIZE); /** * Initializes a new <tt>SaverThread</tt>. @@ -803,7 +822,7 @@ public class PacketLoggingServiceImpl continue; } - pktToSave = pktsToSave.remove(0); + pktToSave = pktsToSave.poll(); } if(pktToSave != null) @@ -842,6 +861,11 @@ public class PacketLoggingServiceImpl */ public synchronized void queuePacket(Packet packet) { + if (EVICTING_QUEUE_MAX_SIZE - pktsToSave.size() == 0) + { + logger.warn("Queue is full, packets are being evicted."); + } + pktsToSave.add(packet); notifyAll(); } diff --git a/src/net/java/sip/communicator/impl/packetlogging/packetlogging.manifest.mf b/src/net/java/sip/communicator/impl/packetlogging/packetlogging.manifest.mf index 374294f..2099f02 100644 --- a/src/net/java/sip/communicator/impl/packetlogging/packetlogging.manifest.mf +++ b/src/net/java/sip/communicator/impl/packetlogging/packetlogging.manifest.mf @@ -16,4 +16,5 @@ Import-Package: org.osgi.framework, javax.swing, javax.swing.border, javax.swing.event, - javax.swing.text + javax.swing.text, + com.google.common.collect diff --git a/src/net/java/sip/communicator/impl/phonenumbers/PhoneNumberI18nServiceImpl.java b/src/net/java/sip/communicator/impl/phonenumbers/PhoneNumberI18nServiceImpl.java index d1942b2..4d4d417 100644 --- a/src/net/java/sip/communicator/impl/phonenumbers/PhoneNumberI18nServiceImpl.java +++ b/src/net/java/sip/communicator/impl/phonenumbers/PhoneNumberI18nServiceImpl.java @@ -18,6 +18,9 @@ package net.java.sip.communicator.impl.phonenumbers; import com.google.i18n.phonenumbers.*; +import com.google.i18n.phonenumbers.PhoneNumberUtil.*; +import com.google.i18n.phonenumbers.Phonenumber.*; + import net.java.sip.communicator.service.protocol.*; import org.jitsi.service.configuration.*; @@ -183,6 +186,33 @@ public class PhoneNumberI18nServiceImpl } /** + * Tries to format the passed phone number into the international format. If + * parsing fails or the string is not recognized as a valid phone number, + * the input is returned as is. + * + * @param phoneNumber The phone number to format. + * @return the formatted phone number in the international format. + */ + public String formatForDisplay(String phoneNumber) + { + try + { + PhoneNumber pn = PhoneNumberUtil.getInstance().parse(phoneNumber, + System.getProperty("user.country")); + if (PhoneNumberUtil.getInstance().isPossibleNumber(pn)) + { + return PhoneNumberUtil.getInstance().format(pn, + PhoneNumberFormat.INTERNATIONAL); + } + } + catch (NumberParseException e) + { + } + + return phoneNumber; + } + + /** * Indicates if the given string is possibly a phone number. * * @param possibleNumber the string to be verified diff --git a/src/net/java/sip/communicator/impl/protocol/dict/ContactDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/ContactDictImpl.java deleted file mode 100644 index 924c0f7..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/ContactDictImpl.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import net.java.dict4j.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -/** - * An implementation of a Dict contact - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class ContactDictImpl - extends AbstractContact -{ - private Logger logger = Logger.getLogger(ContactDictImpl.class); - - /** - * Icon - */ - private static byte[] icon = DictActivator.getResources() - .getImageInBytes("service.protocol.dict.DICT_64x64"); - - /** - * The id of the contact. - */ - private String contactID = null; - - /** - * The provider that created us. - */ - private ProtocolProviderServiceDictImpl parentProvider = null; - - /** - * The group that belong to. - */ - private ContactGroupDictImpl parentGroup = null; - - /** - * The presence status of the contact. - */ - private PresenceStatus presenceStatus = DictStatusEnum.ONLINE; - - /** - * Determines whether this contact is persistent, i.e. member of the contact - * list or whether it is here only temporarily. - */ - private boolean isPersistent = true; - - /** - * Determines whether the contact has been resolved (i.e. we have a - * confirmation that it is still on the server contact list). - */ - private boolean isResolved = true; - - /** - * The string in a "humain readable and understandable representation" of - * the dictionnaire. In brief this is a short description of the dictionary. - */ - private String dictName = null; - - /** - * Creates an instance of a meta contact with the specified string used - * as a name and identifier. - * - * @param databaseCode The identifier of this contact (also used as a name). - * @param parentProvider The provider that created us. - */ - public ContactDictImpl( - String databaseCode, - ProtocolProviderServiceDictImpl parentProvider) - { - this.contactID = databaseCode; - this.parentProvider = parentProvider; - } - - /** - * This method is only called when the contact is added to a new - * <tt>ContactGroupDictImpl</tt> by the - * <tt>ContactGroupDictImpl</tt> itself. - * - * @param newParentGroup the <tt>ContactGroupDictImpl</tt> that is now - * parent of this <tt>ContactDictImpl</tt> - */ - void setParentGroup(ContactGroupDictImpl newParentGroup) - { - this.parentGroup = newParentGroup; - } - - /** - * Returns a String that can be used for identifying the contact. - * - * @return a String id representing and uniquely identifying the contact. - */ - public String getContactID() - { - return contactID; - } - - /** - * Returns a String that can be used for identifying the contact. - * - * @return a String id representing and uniquely identifying the contact. - */ - public String getAddress() - { - return contactID; - } - - /** - * Returns a String that could be used by any user interacting modules - * for referring to this contact. - * - * @return a String that can be used for referring to this contact when - * interacting with the user. - */ - public String getDisplayName() - { - if (dictName == null) - { - if (this.contactID.equals("*")) - { - this.dictName = DictActivator.getResources() - .getI18NString("plugin.dictaccregwizz.ANY_DICTIONARY"); - } - else if (this.contactID.equals("!")) - { - this.dictName = DictActivator.getResources() - .getI18NString("plugin.dictaccregwizz.FIRST_MATCH"); - } - else - { - try - { - this.dictName = this.parentProvider.getConnection() - .getDictionaryName(this.contactID); - } - catch (DictException dx) - { - logger.error("Error while getting dictionary long name", dx); - } - - if (this.dictName == null) - this.dictName = this.contactID; - } - } - - return dictName; - } - - /** - * Returns a byte array containing an image (most often a photo or an - * avatar) that the contact uses as a representation. - * - * @return byte[] an image representing the contact. - */ - public byte[] getImage() - { - return icon; - } - - /** - * Returns the status of the contact. - * - * @return always DictStatusEnum.ONLINE. - */ - public PresenceStatus getPresenceStatus() - { - return this.presenceStatus; - } - - /** - * Sets <tt>dictPresenceStatus</tt> as the PresenceStatus that this - * contact is currently in. - * @param dictPresenceStatus the <tt>DictPresenceStatus</tt> - * currently valid for this contact. - */ - public void setPresenceStatus(PresenceStatus dictPresenceStatus) - { - this.presenceStatus = dictPresenceStatus; - } - - /** - * Returns a reference to the protocol provider that created the contact. - * - * @return a refererence to an instance of the ProtocolProviderService - */ - public ProtocolProviderService getProtocolProvider() - { - return parentProvider; - } - - /** - * Determines whether or not this contact represents our own identity. - * - * @return true in case this is a contact that represents ourselves and - * false otherwise. - */ - public boolean isLocal() - { - return false; - } - - /** - * Returns the group that contains this contact. - * @return a reference to the <tt>ContactGroupDictImpl</tt> that - * contains this contact. - */ - public ContactGroup getParentContactGroup() - { - return this.parentGroup; - } - - /** - * Returns a string representation of this contact, containing most of its - * representative details. - * - * @return a string representation of this contact. - */ - @Override - public String toString() - { - StringBuffer buff - = new StringBuffer("ContactDictImpl[ DisplayName=") - .append(getDisplayName()).append("]"); - - return buff.toString(); - } - - /** - * Determines whether or not this contact is being stored by the server. - * Non persistent contacts are common in the case of simple, non-persistent - * presence operation sets. They could however also be seen in persistent - * presence operation sets when for example we have received an event - * from someone not on our contact list. Non persistent contacts are - * volatile even when coming from a persistent presence op. set. They would - * only exist until the application is closed and will not be there next - * time it is loaded. - * - * @return true if the contact is persistent and false otherwise. - */ - public boolean isPersistent() - { - return isPersistent; - } - - /** - * Specifies whether or not this contact is being stored by the server. - * Non persistent contacts are common in the case of simple, non-persistent - * presence operation sets. They could however also be seen in persistent - * presence operation sets when for example we have received an event - * from someone not on our contact list. Non persistent contacts are - * volatile even when coming from a persistent presence op. set. They would - * only exist until the application is closed and will not be there next - * time it is loaded. - * - * @param isPersistent true if the contact is persistent and false - * otherwise. - */ - public void setPersistent(boolean isPersistent) - { - this.isPersistent = isPersistent; - } - - - /** - * Returns null as no persistent data is required and the contact address is - * sufficient for restoring the contact. - * <p> - * @return null as no such data is needed. - */ - public String getPersistentData() - { - return null; - } - - /** - * Determines whether or not this contact has been resolved against the - * server. Unresolved contacts are used when initially loading a contact - * list that has been stored in a local file until the presence operation - * set has managed to retrieve all the contact list from the server and has - * properly mapped contacts to their on-line buddies. - * - * @return true if the contact has been resolved (mapped against a buddy) - * and false otherwise. - */ - public boolean isResolved() - { - return isResolved; - } - - /** - * Return the current status message of this contact. - * - * @return null as the protocol has currently no support of status messages - */ - public String getStatusMessage() - { - return null; - } - - /** - * Makes the contact resolved or unresolved. - * - * @param resolved true to make the contact resolved; false to - * make it unresolved - */ - public void setResolved(boolean resolved) - { - this.isResolved = resolved; - } - - /** - * Returns the persistent presence operation set that this contact belongs - * to. - * - * @return the <tt>OperationSetPersistentPresenceGibberishImpl</tt> that - * this contact belongs to. - */ - public OperationSetPersistentPresenceDictImpl - getParentPresenceOperationSet() - { - return (OperationSetPersistentPresenceDictImpl) parentProvider - .getOperationSet(OperationSetPersistentPresence.class); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/ContactGroupDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/ContactGroupDictImpl.java deleted file mode 100644 index e52ce96..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/ContactGroupDictImpl.java +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * A simple, straightforward implementation of a dict ContactGroup. Since - * the Dict protocol is not a real one, we simply store all group details - * in class fields. You should know that when implementing a real protocol, - * the contact group implementation would rather encapsulate group objects from - * the protocol stack and group property values should be returned by consulting - * the encapsulated object. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class ContactGroupDictImpl - implements ContactGroup -{ - - /** - * The name of this Dict contact group. - */ - private String groupName = null; - - /** - * The list of this group's members. - */ - private List<Contact> contacts = new ArrayList<Contact>(); - - /** - * The list of sub groups belonging to this group. - */ - private List<ContactGroup> subGroups = new ArrayList<ContactGroup>(); - - /** - * The group that this group belongs to (or null if this is the root group). - */ - private ContactGroupDictImpl parentGroup = null; - - /** - * Determines whether this group is really in the contact list or whether - * it is here only temporarily and will be gone next time we restart. - */ - private boolean isPersistent = true; - - /** - * The protocol provider that created us. - */ - private ProtocolProviderServiceDictImpl parentProvider = null; - - /** - * Determines whether this group has been resolved on the server. - * Unresolved groups are groups that were available on previous runs and - * that the meta contact list has stored. During all next runs, when - * bootstrapping, the meta contact list would create these groups as - * unresolved. Once a protocol provider implementation confirms that the - * groups are still on the server, it would issue an event indicating that - * the groups are now resolved. - */ - private boolean isResolved = true; - - /** - * An id uniquely identifying the group. For many protocols this could be - * the group name itself. - */ - private String uid = null; - private static final String UID_SUFFIX = ".uid"; - - /** - * Creates a ContactGroupDictImpl with the specified name. - * - * @param groupName the name of the group. - * @param parentProvider the protocol provider that created this group. - */ - public ContactGroupDictImpl( - String groupName, - ProtocolProviderServiceDictImpl parentProvider) - { - this.groupName = groupName; - this.uid = groupName + UID_SUFFIX; - this.parentProvider = parentProvider; - } - - /** - * Determines whether the group may contain subgroups or not. - * - * @return always true in this implementation. - */ - public boolean canContainSubgroups() - { - return true; - } - - /** - * Returns the protocol provider that this group belongs to. - * @return a regerence to the ProtocolProviderService instance that this - * ContactGroup belongs to. - */ - public ProtocolProviderService getProtocolProvider() - { - return parentProvider; - } - - /** - * Returns an Iterator over all contacts, member of this - * <tt>ContactGroup</tt>. - * - * @return a java.util.Iterator over all contacts inside this - * <tt>ContactGroup</tt> - */ - public Iterator<Contact> contacts() - { - return contacts.iterator(); - } - - /** - * Adds the specified contact to this group. - * @param contactToAdd the ContactDictImpl to add to this group. - */ - public void addContact(ContactDictImpl contactToAdd) - { - this.contacts.add(contactToAdd); - contactToAdd.setParentGroup(this); - } - - /** - * Returns the number of <tt>Contact</tt> members of this - * <tt>ContactGroup</tt> - * - * @return an int indicating the number of <tt>Contact</tt>s, members of - * this <tt>ContactGroup</tt>. - */ - public int countContacts() - { - return contacts.size(); - } - - /** - * Returns the number of subgroups contained by this - * <tt>ContactGroup</tt>. - * - * @return the number of subGroups currently added to this group. - */ - public int countSubgroups() - { - return subGroups.size(); - } - - /** - * Adds the specified contact group to the contained by this group. - * @param subgroup the ContactGroupDictImpl to add as a subgroup to this group. - */ - public void addSubgroup(ContactGroupDictImpl subgroup) - { - this.subGroups.add(subgroup); - subgroup.setParentGroup(this); - } - - /** - * Sets the group that is the new parent of this group - * @param parent ContactGroupDictImpl - */ - void setParentGroup(ContactGroupDictImpl parent) - { - this.parentGroup = parent; - } - - /** - * Returns the contact group that currently contains this group or null if - * this is the root contact group. - * @return the contact group that currently contains this group or null if - * this is the root contact group. - */ - public ContactGroup getParentContactGroup() - { - return this.parentGroup; - } - - /** - * Removes the specified contact group from the this group's subgroups. - * @param subgroup the ContactGroupDictImpl subgroup to remove. - */ - public void removeSubGroup(ContactGroupDictImpl subgroup) - { - this.subGroups.remove(subgroup); - subgroup.setParentGroup(null); - } - - /** - * Returns the group that is parent of the specified dictGroup or null - * if no parent was found. - * @param dictGroup the group whose parent we're looking for. - * @return the ContactGroupDictImpl instance that dictGroup - * belongs to or null if no parent was found. - */ - public ContactGroupDictImpl findGroupParent(ContactGroupDictImpl dictGroup) - { - if ( subGroups.contains(dictGroup) ) - return this; - - Iterator<ContactGroup> subGroupsIter = subgroups(); - while (subGroupsIter.hasNext()) - { - ContactGroupDictImpl subgroup - = (ContactGroupDictImpl) subGroupsIter.next(); - - ContactGroupDictImpl parent - = subgroup.findGroupParent(dictGroup); - - if(parent != null) - return parent; - } - return null; - } - - /** - * Returns the group that is parent of the specified dictContact or - * null if no parent was found. - * - * @param dictContact the contact whose parent we're looking for. - * @return the ContactGroupDictImpl instance that dictContact - * belongs to or <tt>null</tt> if no parent was found. - */ - public ContactGroupDictImpl findContactParent( - ContactDictImpl dictContact) - { - if ( contacts.contains(dictContact) ) - return this; - - Iterator<ContactGroup> subGroupsIter = subgroups(); - while (subGroupsIter.hasNext()) - { - ContactGroupDictImpl subgroup - = (ContactGroupDictImpl) subGroupsIter.next(); - - ContactGroupDictImpl parent - = subgroup.findContactParent(dictContact); - - if(parent != null) - return parent; - } - return null; - } - - - - /** - * Returns the <tt>Contact</tt> with the specified address or identifier. - * - * @param id the addres or identifier of the <tt>Contact</tt> we are - * looking for. - * @return the <tt>Contact</tt> with the specified id or address. - */ - public Contact getContact(String id) - { - Iterator<Contact> contactsIter = contacts(); - while (contactsIter.hasNext()) - { - ContactDictImpl contact = (ContactDictImpl) contactsIter.next(); - if (contact.getAddress().equals(id)) - return contact; - - } - return null; - } - - /** - * Returns the subgroup with the specified index. - * - * @param index the index of the <tt>ContactGroup</tt> to retrieve. - * @return the <tt>ContactGroup</tt> with the specified index. - */ - public ContactGroup getGroup(int index) - { - return subGroups.get(index); - } - - /** - * Returns the subgroup with the specified name. - * - * @param groupName the name of the <tt>ContactGroup</tt> to retrieve. - * @return the <tt>ContactGroup</tt> with the specified index. - */ - public ContactGroup getGroup(String groupName) - { - Iterator<ContactGroup> groupsIter = subgroups(); - while (groupsIter.hasNext()) - { - ContactGroupDictImpl contactGroup - = (ContactGroupDictImpl) groupsIter.next(); - if (contactGroup.getGroupName().equals(groupName)) - return contactGroup; - - } - return null; - - } - - /** - * Returns the name of this group. - * - * @return a String containing the name of this group. - */ - public String getGroupName() - { - return this.groupName; - } - - /** - * Sets this group a new name. - * @param newGrpName a String containing the new name of this group. - */ - public void setGroupName(String newGrpName) - { - this.groupName = newGrpName; - } - - /** - * Returns an iterator over the sub groups that this - * <tt>ContactGroup</tt> contains. - * - * @return a java.util.Iterator over the <tt>ContactGroup</tt> children - * of this group (i.e. subgroups). - */ - public Iterator<ContactGroup> subgroups() - { - return subGroups.iterator(); - } - - /** - * Removes the specified contact from this group. - * @param contact the ContactDictImpl to remove from this group - */ - public void removeContact(ContactDictImpl contact) - { - this.contacts.remove(contact); - } - - /** - * Returns the contact with the specified id or null if no such contact - * exists. - * @param id the id of the contact we're looking for. - * @return ContactDictImpl - */ - public ContactDictImpl findContactByID(String id) - { - //first go through the contacts that are direct children. - Iterator<Contact> contactsIter = contacts(); - - while(contactsIter.hasNext()) - { - ContactDictImpl mContact = (ContactDictImpl)contactsIter.next(); - - if( mContact.getAddress().equals(id) ) - return mContact; - } - - //if we didn't find it here, let's try in the subougroups - Iterator<ContactGroup> groupsIter = subgroups(); - - while( groupsIter.hasNext() ) - { - ContactGroupDictImpl mGroup = (ContactGroupDictImpl)groupsIter.next(); - - ContactDictImpl mContact = mGroup.findContactByID(id); - - if (mContact != null) - return mContact; - } - - return null; - } - - - /** - * Returns a String representation of this group and the contacts it - * contains (may turn out to be a relatively long string). - * @return a String representing this group and its child contacts. - */ - @Override - public String toString() - { - - StringBuffer buff = new StringBuffer(getGroupName()); - buff.append(".subGroups=" + countSubgroups() + ":\n"); - - Iterator<ContactGroup> subGroups = subgroups(); - while (subGroups.hasNext()) - { - ContactGroupDictImpl group = (ContactGroupDictImpl)subGroups.next(); - buff.append(group.toString()); - if (subGroups.hasNext()) - buff.append("\n"); - } - - buff.append("\nChildContacts="+countContacts()+":["); - - Iterator<Contact> contacts = contacts(); - while (contacts.hasNext()) - { - ContactDictImpl contact = (ContactDictImpl) contacts.next(); - buff.append(contact.toString()); - if(contacts.hasNext()) - buff.append(", "); - } - return buff.append("]").toString(); - } - - /** - * Specifies whether or not this contact group is being stored by the server. - * Non persistent contact groups are common in the case of simple, - * non-persistent presence operation sets. They could however also be seen - * in persistent presence operation sets when for example we have received - * an event from someone not on our contact list and the contact that we - * associated with that user is placed in a non persistent group. Non - * persistent contact groups are volatile even when coming from a persistent - * presence op. set. They would only exist until the application is closed - * and will not be there next time it is loaded. - * - * @param isPersistent true if the contact group is to be persistent and - * false otherwise. - */ - public void setPersistent(boolean isPersistent) - { - this.isPersistent = isPersistent; - } - - /** - * Determines whether or not this contact group is being stored by the - * server. Non persistent contact groups exist for the sole purpose of - * containing non persistent contacts. - * @return true if the contact group is persistent and false otherwise. - */ - public boolean isPersistent() - { - return isPersistent; - } - - /** - * Returns null as no persistent data is required and the contact address is - * sufficient for restoring the contact. - * <p> - * @return null as no such data is needed. - */ - public String getPersistentData() - { - return null; - } - - /** - * Determines whether or not this contact has been resolved against the - * server. Unresolved contacts are used when initially loading a contact - * list that has been stored in a local file until the presence operation - * set has managed to retrieve all the contact list from the server and has - * properly mapped contacts to their on-line buddies. - * @return true if the contact has been resolved (mapped against a buddy) - * and false otherwise. - */ - public boolean isResolved() - { - return isResolved; - } - - /** - * Makes the group resolved or unresolved. - * - * @param resolved true to make the group resolved; false to - * make it unresolved - */ - public void setResolved(boolean resolved) - { - this.isResolved = resolved; - } - - /** - * Returns a <tt>String</tt> that uniquely represnets the group inside - * the current protocol. The string MUST be persistent (it must not change - * across connections or runs of the application). In many cases (Jabber, - * ICQ) the string may match the name of the group as these protocols - * only allow a single level of contact groups and there is no danger of - * having the same name twice in the same contact list. Other protocols - * (no examples come to mind but that doesn't bother me ;) ) may be - * supporting mutilple levels of grooups so it might be possible for group - * A and group B to both contain groups named C. In such cases the - * implementation must find a way to return a unique identifier in this - * method and this UID should never change for a given group. - * - * @return a String representing this group in a unique and persistent - * way. - */ - public String getUID() - { - return uid; - } - - /** - * Ugly but tricky conversion method. - * @param uid the uid we'd like to get a name from - * @return the name of the group with the specified <tt>uid</tt>. - */ - static String createNameFromUID(String uid) - { - return uid.substring(0, uid.length() - (UID_SUFFIX.length())); - } - - /** - * Indicates whether some other object is "equal to" this one which in terms - * of contact groups translates to having the equal names and matching - * subgroups and child contacts. The resolved status of contactgroups and - * contacts is deliberately ignored so that groups and/or contacts would - * be assumed equal even if it differs. - * <p> - * @param obj the reference object with which to compare. - * @return <code>true</code> if this contact group has the equal child - * contacts and subgroups to those of the <code>obj</code> argument. - */ - @Override - public boolean equals(Object obj) - { - if(obj == null - || !(obj instanceof ContactGroupDictImpl)) - return false; - - ContactGroupDictImpl dictGroup - = (ContactGroupDictImpl)obj; - - if( ! dictGroup.getGroupName().equals(getGroupName()) - || ! dictGroup.getUID().equals(getUID()) - || dictGroup.countContacts() != countContacts() - || dictGroup.countSubgroups() != countSubgroups()) - return false; - - //traverse child contacts - Iterator<Contact> theirContacts = dictGroup.contacts(); - - while(theirContacts.hasNext()) - { - ContactDictImpl theirContact - = (ContactDictImpl)theirContacts.next(); - - ContactDictImpl ourContact - = (ContactDictImpl)getContact(theirContact.getAddress()); - - if(ourContact == null - || !ourContact.equals(theirContact)) - return false; - } - - //traverse subgroups - Iterator<ContactGroup> theirSubgroups = dictGroup.subgroups(); - - while(theirSubgroups.hasNext()) - { - ContactGroupDictImpl theirSubgroup - = (ContactGroupDictImpl)theirSubgroups.next(); - - ContactGroupDictImpl ourSubgroup - = (ContactGroupDictImpl)getGroup( - theirSubgroup.getGroupName()); - - if(ourSubgroup == null - || !ourSubgroup.equals(theirSubgroup)) - return false; - } - - return true; - } -} - diff --git a/src/net/java/sip/communicator/impl/protocol/dict/DictAccountID.java b/src/net/java/sip/communicator/impl/protocol/dict/DictAccountID.java deleted file mode 100644 index 538ae71..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/DictAccountID.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * The Dict implementation of a sip-communicator account id. - * @author LITZELMANN Cedric - * @author ROTH Damien - */ -public class DictAccountID - extends AccountID -{ - /** - * Creates an account id from the specified id and account properties. - * - * @param userID the user identifier correspnding to the account - * @param accountProperties any other properties necessary for the account. - */ - DictAccountID(String userID, Map<String, String> accountProperties) - { - super(userID, accountProperties, ProtocolNames.DICT, "dict.org"); - } - - /** - * Returns the dict server adress - * @return the dict server adress - */ - public String getHost() - { - return getAccountPropertyString(ProtocolProviderFactory.SERVER_ADDRESS); - } - - /** - * Returns the dict server port - * @return the dict server port - */ - public int getPort() - { - return Integer - .parseInt(getAccountPropertyString(ProtocolProviderFactory.SERVER_PORT)); - } - - /** - * Returns the selected strategy - * @return the selected strategy - */ - public String getStrategy() - { - return getAccountPropertyString(ProtocolProviderFactory.STRATEGY); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/DictActivator.java b/src/net/java/sip/communicator/impl/protocol/dict/DictActivator.java deleted file mode 100644 index b5976c7..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/DictActivator.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -import org.jitsi.service.resources.*; -import org.osgi.framework.*; - -/** - * Loads the Dict provider factory and registers its services in the OSGI - * bundle context. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class DictActivator - implements BundleActivator -{ - private static final Logger logger = Logger.getLogger(DictActivator.class); - - /** - * The currently valid bundle context. - */ - private static BundleContext bundleContext = null; - - private ServiceRegistration dictPpFactoryServReg = null; - private static ProtocolProviderFactoryDictImpl - dictProviderFactory = null; - - private static ResourceManagementService resourceService; - - /** - * Called when this bundle is started. In here we'll export the - * dict ProtocolProviderFactory implementation so that it could be - * possible to register accounts with it in SIP Communicator. - * - * @param context The execution context of the bundle being started. - * @throws Exception If this method throws an exception, this bundle is - * marked as stopped and the Framework will remove this bundle's - * listeners, unregister all services registered by this bundle, and - * release all services used by this bundle. - */ - public void start(BundleContext context) - throws Exception - { - bundleContext = context; - - Hashtable<String,String> hashtable = new Hashtable<String,String>(); - hashtable.put(ProtocolProviderFactory.PROTOCOL, ProtocolNames.DICT); - - dictProviderFactory = new ProtocolProviderFactoryDictImpl(); - - //reg the dict provider factory. - dictPpFactoryServReg = context.registerService( - ProtocolProviderFactory.class.getName(), - dictProviderFactory, - hashtable); - - if (logger.isInfoEnabled()) - logger.info("DICT protocol implementation [STARTED]."); - } - - /** - * Returns a reference to the bundle context that we were started with. - * @return a reference to the BundleContext instance that we were started - * witn. - */ - public static BundleContext getBundleContext() - { - return bundleContext; - } - - /** - * Called when this bundle is stopped so the Framework can perform the - * bundle-specific activities necessary to stop the bundle. - * - * @param context The execution context of the bundle being stopped. - * @throws Exception If this method throws an exception, the bundle is - * still marked as stopped, and the Framework will remove the bundle's - * listeners, unregister all services registered by the bundle, and - * release all services used by the bundle. - */ - public void stop(BundleContext context) - throws Exception - { - - dictProviderFactory.stop(); - dictPpFactoryServReg.unregister(); - - if (logger.isInfoEnabled()) - logger.info("DICT protocol implementation [STOPPED]."); - } - - /** - * Returns a reference to the protocol provider factory that we have - * registered. - * @return a reference to the <tt>ProtocolProviderFactoryDictImpl</tt> - * instance that we have registered from this package. - */ - public static ProtocolProviderFactoryDictImpl getProtocolProviderFactory() - { - return dictProviderFactory; - } - - - /** - * Returns the <tt>ResourceManagementService</tt>. - * - * @return the <tt>ResourceManagementService</tt>. - */ - public static ResourceManagementService getResources() - { - if (resourceService == null) - { - ServiceReference serviceReference = bundleContext - .getServiceReference(ResourceManagementService.class.getName()); - - if(serviceReference == null) - return null; - - resourceService = (ResourceManagementService) bundleContext - .getService(serviceReference); - } - - return resourceService; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/DictStatusEnum.java b/src/net/java/sip/communicator/impl/protocol/dict/DictStatusEnum.java deleted file mode 100644 index 87086f7..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/DictStatusEnum.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * An implementation of <tt>PresenceStatus</tt> that enumerates all states that - * a Dict contact can fall into. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class DictStatusEnum - extends PresenceStatus -{ - - /** - * Indicates an Offline status or status with 0 connectivity. - */ - public static final DictStatusEnum OFFLINE - = new DictStatusEnum( - 0, "Offline", - DictActivator.getResources() - .getImageInBytes("service.protocol.dict.OFFLINE_STATUS_ICON")); - - /** - * The Online status. Indicate that the user is able and willing to - * communicate. - */ - public static final DictStatusEnum ONLINE - = new DictStatusEnum( - 65, "Online", - DictActivator.getResources() - .getImageInBytes("service.protocol.dict.DICT_16x16")); - - /** - * Initialize the list of supported status states. - */ - private static List<PresenceStatus> supportedStatusSet = new LinkedList<PresenceStatus>(); - static - { - supportedStatusSet.add(OFFLINE); - supportedStatusSet.add(ONLINE); - } - - /** - * Creates an instance of <tt>RssPresneceStatus</tt> with the - * specified parameters. - * @param status the connectivity level of the new presence status instance - * @param statusName the name of the presence status. - * @param statusIcon the icon associated with this status - */ - private DictStatusEnum(int status, - String statusName, - byte[] statusIcon) - { - super(status, statusName, statusIcon); - } - - /** - * Returns an iterator over all status instances supproted by the rss - * provider. - * @return an <tt>Iterator</tt> over all status instances supported by the - * rss provider. - */ - static Iterator<PresenceStatus> supportedStatusSet() - { - return supportedStatusSet.iterator(); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/MessageDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/MessageDictImpl.java deleted file mode 100644 index 5b95dd4..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/MessageDictImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import net.java.sip.communicator.service.protocol.*; - -/** - * Very simple message implementation for the Dict protocol. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - * @author Lubomir Marinov - */ -public class MessageDictImpl - extends AbstractMessage -{ - - /** - * Creates a message instance according to the specified parameters. - * - * @param content the message body - * @param contentType message content type or null for text/plain - * @param contentEncoding message encoding or null for UTF8 - * @param subject the subject of the message or null for no subject. - */ - public MessageDictImpl(String content, String contentType, - String contentEncoding, String subject) - { - super(content, contentType, contentEncoding, subject); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/OperationSetBasicInstantMessagingDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/OperationSetBasicInstantMessagingDictImpl.java deleted file mode 100644 index f1f4973..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/OperationSetBasicInstantMessagingDictImpl.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -import net.java.dict4j.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; - -/** - * Instant messaging functionalities for the Dict protocol. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class OperationSetBasicInstantMessagingDictImpl - extends AbstractOperationSetBasicInstantMessaging - implements RegistrationStateChangeListener -{ - /** - * The currently valid persistent presence operation set. - */ - private OperationSetPersistentPresenceDictImpl opSetPersPresence = null; - - /** - * The protocol provider that created us. - */ - private ProtocolProviderServiceDictImpl parentProvider = null; - - private DictAccountID accountID; - - /** - * Creates an instance of this operation set keeping a reference to the - * parent protocol provider and presence operation set. - * - * @param provider The provider instance that creates us. - * @param opSetPersPresence the currently valid - * <tt>OperationSetPersistentPresenceDictImpl</tt> instance. - */ - public OperationSetBasicInstantMessagingDictImpl( - ProtocolProviderServiceDictImpl provider, - OperationSetPersistentPresenceDictImpl opSetPersPresence) - { - this.opSetPersPresence = opSetPersPresence; - this.parentProvider = provider; - this.accountID = (DictAccountID) provider.getAccountID(); - - parentProvider.addRegistrationStateChangeListener(this); - } - - @Override - public Message createMessage(String content) - { - return new MessageDictImpl(content, HTML_MIME_TYPE, - DEFAULT_MIME_ENCODING, null); - } - - @Override - public Message createMessage(String content, String contentType, - String encoding, String subject) - { - return new MessageDictImpl(content, contentType, encoding, subject); - } - - /** - * Sends the <tt>message</tt> to the destination indicated by the - * <tt>to</tt> contact. - * - * @param to the <tt>Contact</tt> to send <tt>message</tt> to - * @param message the <tt>Message</tt> to send. - * @throws IllegalStateException if the underlying ICQ stack is not - * registered and initialized. - * @throws IllegalArgumentException if <tt>to</tt> is not an instance - * belonging to the underlying implementation. - */ - public void sendInstantMessage(Contact to, Message message) - throws IllegalStateException, - IllegalArgumentException - { - if( !(to instanceof ContactDictImpl) ) - { - throw new IllegalArgumentException( - "The specified contact is not a Dict contact." - + to); - } - - // Remove all html tags from the message - message = createMessage(Html2Text.extractText(message.getContent())); - - // Display the queried word - fireMessageDelivered(message, to); - - this.submitDictQuery((ContactDictImpl) to, message); - } - - /** - * Determines whether the protocol provider (or the protocol itself) supports - * sending and receiving offline messages. Most often this method would - * return true for protocols that support offline messages and false for - * those that don't. It is however possible for a protocol to support these - * messages and yet have a particular account that does not (i.e. feature - * not enabled on the protocol server). In cases like this it is possible - * for this method to return true even when offline messaging is not - * supported, and then have the sendMessage method throw an - * OperationFailedException with code - OFFLINE_MESSAGES_NOT_SUPPORTED. - * - * @return <tt>true</tt> if the protocol supports offline messages and - * <tt>false</tt> otherwise. - */ - public boolean isOfflineMessagingSupported() - { - return false; - } - - /** - * Determines whether the protocol supports the supplied content type. - * - * @param contentType the type we want to check - * @return <tt>true</tt> if the protocol supports it and - * <tt>false</tt> otherwise. - */ - public boolean isContentTypeSupported(String contentType) - { - if(contentType.equals(DEFAULT_MIME_TYPE)) - return true; - else if(contentType.equals(HTML_MIME_TYPE)) - return true; - else - return false; - } - - /** - * Returns the protocol provider that this operation set belongs to. - * - * @return a reference to the <tt>ProtocolProviderServiceDictImpl</tt> - * instance that this operation set belongs to. - */ - public ProtocolProviderServiceDictImpl getParentProvider() - { - return this.parentProvider; - } - - /** - * Returns a reference to the presence operation set instance used by our - * source provider. - * - * @return a reference to the <tt>OperationSetPersistentPresenceDictImpl</tt> - * instance used by this provider. - */ - public OperationSetPersistentPresenceDictImpl getOpSetPersPresence() - { - return this.opSetPersPresence; - } - - /** - * The method is called by the ProtocolProvider whenever a change in the - * registration state of the corresponding provider has occurred. - * - * @param evt ProviderStatusChangeEvent the event describing the status - * change. - */ - public void registrationStateChanged(RegistrationStateChangeEvent evt) - { - - } - - - /** - * Create, execute and display a query to a dictionary (ContactDictImpl) - * - * @param dictContact the contact containing the database name - * @param message the message containing the word - */ - private void submitDictQuery(ContactDictImpl dictContact, Message message) - { - Message msg = this.createMessage(""); - - String database = dictContact.getContactID(); - DictConnection conn = this.parentProvider.getConnection(); - boolean doMatch = false; - - String word; - - // Formatting the query message, if the word as one or more spaces we - // put it between quotes to prevent errors - word = message.getContent().replace("\"", "").trim(); - if (word.indexOf(' ') > 0) - { - word = "\"" + word + "\""; - } - - // Try to get the definition of the work - try - { - List<Definition> definitions = conn.define(database, word); - msg = this.createMessage(retrieveDefine(definitions, word)); - } - catch(DictException dx) - { - if (dx.getErrorCode() == DictReturnCode.NO_MATCH) - { // No word found, we are going to try the match command - doMatch = true; - } - else - { // Otherwise we display the error returned by the server - msg = this.createMessage(manageException(dx, database)); - } - } - - if (doMatch) - { - // Trying the match command - try - { - List<MatchWord> matchWords = conn.match(database, word, - this.accountID.getStrategy()); - msg = this.createMessage(retrieveMatch(matchWords, word)); - } - catch(DictException dx) - { - msg = this.createMessage(manageException(dx, database)); - } - } - - // Send message - fireMessageReceived(msg, dictContact); - } - - /** - * Generate the display of the results of the Define command - * - * @param data the result of the Define command - * @param word the queried word - * @return the formatted result - */ - private String retrieveDefine(List<Definition> data, String word) - { - StringBuffer res = new StringBuffer(); - Definition def; - - for (int i=0; i<data.size(); i++) - { - def = data.get(i); - - if(i != 0 && data.size() > 0) - { - res.append("<hr>"); - } - res.append(def.getDefinition().replaceAll("\n", "<br>")) - .append("<div align=\"right\"><font size=\"-2\">-- From ") - .append(def.getDictionary()) - .append("</font></div>"); - } - - String result = res.toString(); - result = formatResult(result, "\\\\", "<em>", "</em>"); - result = formatResult(result, "[\\[\\]]", "<cite>", "</cite>"); - result = formatResult(result, "[\\{\\}]", "<strong>", "</strong>"); - result = formatWordDefined(result, word); - - return result; - } - - /** - * Makes a stronger emphasis for the word defined. - * @param result The text containing the definition of the word. - * @param word The word defined to display with bold font. For this we - * had the strong HTML tag. - * @return Returns the result text with an strong emphasis of all - * the occurences of the word defined. - */ - private String formatWordDefined(String result, String word) - { - String tmpWord; - - tmpWord = word.toUpperCase(); - result = result.replaceAll("\\b" + tmpWord + "\\b", "<strong>" + tmpWord + "</strong>"); - tmpWord = word.toLowerCase(); - result = result.replaceAll("\\b" + tmpWord + "\\b", "<strong>" + tmpWord + "</strong>"); - if(tmpWord.length() > 1) - { - tmpWord = tmpWord.substring(0, 1).toUpperCase() + tmpWord.substring(1); - result = result.replaceAll("\\b" + tmpWord + "\\b", "<strong>" + tmpWord + "</strong>"); - } - - return result; - } - - /** - * Remplaces special characters into HTML tags to make some emphasis. - * @param result The text containing the definition of the word. - * @param regex The special character to replace with HTML tags. - * @param startTag The start HTML tag to use. - * @param endTag The end HTML tag to use. - * @return The result with all special characters replaced by HTML - * tags. - */ - private String formatResult(String result, String regex, String startTag, String endTag) - { - String[] tmp = result.split(regex); - String res = ""; - - for(int i = 0; i < (tmp.length - 1); i += 2) - { - res += tmp[i] + startTag + tmp[i+1] + endTag; - } - if((tmp.length % 2) != 0) - { - res += tmp[tmp.length - 1]; - } - return res; - } - - /** - * Generate the display of the results of the Match command - * - * @param data the result of the Match command - * @param word the queried word - * @return the formatted result - */ - private String retrieveMatch(List<MatchWord> data, String word) - { - StringBuffer result = new StringBuffer(); - boolean isStart = true; - - result.append(DictActivator.getResources() - .getI18NString("plugin.dictaccregwizz.MATCH_RESULT", new String[] {word})); - - for (int i=0; i<data.size(); i++) - { - if (isStart) - isStart = false; - else - result.append(", "); - - result.append(data.get(i).getWord()); - } - - return result.toString(); - } - - /** - * Manages the return exception of a dict query. - * - * @param dix The exception returned by the adapter - * @param database The dictionary used - * @return Exception message - */ - private String manageException(DictException dix, String database) - { - int errorCode = dix.getErrorCode(); - - // We change the text only for exception 550 (invalid dictionary) and 551 (invalid strategy) - if (errorCode == DictReturnCode.INVALID_DATABASE) - { - return DictActivator.getResources() - .getI18NString("plugin.dictaccregwizz.INVALID_DATABASE", new String[] {database}); - } - else if (errorCode == DictReturnCode.INVALID_STRATEGY) - { - return DictActivator.getResources() - .getI18NString("plugin.dictaccregwizz.INVALID_STRATEGY"); - } - else if (errorCode == DictReturnCode.NO_MATCH) - { - return DictActivator.getResources() - .getI18NString("plugin.dictaccregwizz.NO_MATCH"); - } - - return dix.getMessage(); - } - - /** - * Sends the <tt>message</tt> to the destination indicated by the - * <tt>to</tt>. Resources are not supported by this operation set - * implementation. - * - * @param to the <tt>Contact</tt> to send <tt>message</tt> to - * @param toResource the resource to which the message should be send - * @param message the <tt>Message</tt> to send. - * @throws java.lang.IllegalStateException if the underlying ICQ stack is - * not registered and initialized. - * @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an - * instance belonging to the underlying implementation. - */ - @Override - public void sendInstantMessage( Contact to, - ContactResource toResource, - Message message) - throws IllegalStateException, - IllegalArgumentException - { - sendInstantMessage(to, message); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/OperationSetPersistentPresenceDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/OperationSetPersistentPresenceDictImpl.java deleted file mode 100644 index 8184fe9..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/OperationSetPersistentPresenceDictImpl.java +++ /dev/null @@ -1,988 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; - -import org.osgi.framework.*; - -/** - * A Dict implementation of a persistent presence operation set. In order - * to simulate server persistence, this operation set would simply accept all - * unresolved contacts and resolve them immediately. A real world protocol - * implementation would save it on a server using methods provided by the - * protocol stack. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class OperationSetPersistentPresenceDictImpl - extends AbstractOperationSetPersistentPresence<ProtocolProviderServiceDictImpl> -{ - private static final Logger logger = - Logger.getLogger(OperationSetPersistentPresenceDictImpl.class); - - /** - * The root of the dict contact list. - */ - private ContactGroupDictImpl contactListRoot = null; - - /** - * The currently active status message. - */ - private String statusMessage = "Default Status Message"; - - /** - * Our presence status. - */ - private PresenceStatus presenceStatus = DictStatusEnum.ONLINE; - - /** - * The <tt>AuthorizationHandler</tt> instance that we'd have to transmit - * authorization requests to for approval. - */ - private AuthorizationHandler authorizationHandler = null; - - /** - * Creates an instance of this operation set keeping a reference to the - * specified parent <tt>provider</tt>. - * @param provider the ProtocolProviderServiceDictImpl instance that - * created us. - */ - public OperationSetPersistentPresenceDictImpl( - ProtocolProviderServiceDictImpl provider) - { - super(provider); - - contactListRoot = new ContactGroupDictImpl("RootGroup", provider); - - //add our unregistration listener - parentProvider.addRegistrationStateChangeListener( - new UnregistrationListener()); - } - - /** - * Creates a group with the specified name and parent in the server - * stored contact list. - * - * @param parent the group where the new group should be created - * @param groupName the name of the new group to create. - */ - public void createServerStoredContactGroup(ContactGroup parent, - String groupName) - { - ContactGroupDictImpl newGroup - = new ContactGroupDictImpl(groupName, parentProvider); - - ((ContactGroupDictImpl)parent).addSubgroup(newGroup); - - this.fireServerStoredGroupEvent( - newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - - /** - * A Dict Provider method to use for fast filling of a contact list. - * - * @param contactGroup the group to add - */ - public void addDictGroup(ContactGroupDictImpl contactGroup) - { - contactListRoot.addSubgroup(contactGroup); - } - - /** - * A Dict Provider method to use for fast filling of a contact list. - * This method would add both the group and fire an event. - * - * @param parent the group where <tt>contactGroup</tt> should be added. - * @param contactGroup the group to add - */ - public void addDictGroupAndFireEvent( - ContactGroupDictImpl parent - , ContactGroupDictImpl contactGroup) - { - parent.addSubgroup(contactGroup); - - this.fireServerStoredGroupEvent( - contactGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - - - /** - * Returns a reference to the contact with the specified ID in case we - * have a subscription for it and null otherwise/ - * - * @param contactID a String identifier of the contact which we're - * seeking a reference of. - * @return a reference to the Contact with the specified - * <tt>contactID</tt> or null if we don't have a subscription for the - * that identifier. - */ - public Contact findContactByID(String contactID) - { - return contactListRoot.findContactByID(contactID); - } - - /** - * Sets the specified status message. - * @param statusMessage a String containing the new status message. - */ - public void setStatusMessage(String statusMessage) - { - this.statusMessage = statusMessage; - } - - /** - * Returns the status message that was last set through - * setCurrentStatusMessage. - * - * @return the last status message that we have requested and the aim - * server has confirmed. - */ - public String getCurrentStatusMessage() - { - return statusMessage; - } - - /** - * Returns the protocol specific contact instance representing the local - * user. - * - * @return the Contact (address, phone number, or uin) that the Provider - * implementation is communicating on behalf of. - */ - public Contact getLocalContact() - { - return null; - } - - /** - * Returns a PresenceStatus instance representing the state this provider - * is currently in. - * - * @return the PresenceStatus last published by this provider. - */ - public PresenceStatus getPresenceStatus() - { - return presenceStatus; - } - - /** - * Returns the root group of the server stored contact list. - * - * @return the root ContactGroup for the ContactList stored by this - * service. - */ - public ContactGroup getServerStoredContactListRoot() - { - return contactListRoot; - } - - /** - * Returns the set of PresenceStatus objects that a user of this service - * may request the provider to enter. - * - * @return Iterator a PresenceStatus array containing "enterable" status - * instances. - */ - public Iterator<PresenceStatus> getSupportedStatusSet() - { - return DictStatusEnum.supportedStatusSet(); - } - - /** - * Removes the specified contact from its current parent and places it - * under <tt>newParent</tt>. - * - * @param contactToMove the <tt>Contact</tt> to move - * @param newParent the <tt>ContactGroup</tt> where <tt>Contact</tt> - * would be placed. - */ - public void moveContactToGroup(Contact contactToMove, - ContactGroup newParent) - { - ContactDictImpl dictContact - = (ContactDictImpl)contactToMove; - - ContactGroupDictImpl parentDictGroup - = findContactParent(dictContact); - - parentDictGroup.removeContact(dictContact); - - //if this is a volatile contact then we haven't really subscribed to - //them so we'd need to do so here - if(!dictContact.isPersistent()) - { - //first tell everyone that the volatile contact was removed - fireSubscriptionEvent(dictContact - , parentDictGroup - , SubscriptionEvent.SUBSCRIPTION_REMOVED); - - try - { - //now subscribe - this.subscribe(newParent, contactToMove.getAddress()); - - //now tell everyone that we've added the contact - fireSubscriptionEvent(dictContact - , newParent - , SubscriptionEvent.SUBSCRIPTION_CREATED); - } - catch (Exception ex) - { - logger.error("Failed to move contact " - + dictContact.getAddress() - , ex); - } - } - else - { - ( (ContactGroupDictImpl) newParent) - .addContact(dictContact); - - fireSubscriptionMovedEvent(contactToMove - , parentDictGroup - , newParent); - } - } - - /** - * Requests the provider to enter into a status corresponding to the - * specified paramters. - * - * @param status the PresenceStatus as returned by - * getRequestableStatusSet - * @param statusMessage the message that should be set as the reason to - * enter that status - * @throws IllegalArgumentException if the status requested is not a - * valid PresenceStatus supported by this provider. - * @throws IllegalStateException if the provider is not currently - * registered. - * @throws OperationFailedException with code NETWORK_FAILURE if - * publishing the status fails due to a network error. - */ - public void publishPresenceStatus(PresenceStatus status, - String statusMessage) throws - IllegalArgumentException, IllegalStateException, - OperationFailedException - { - PresenceStatus oldPresenceStatus = this.presenceStatus; - this.presenceStatus = status; - this.statusMessage = statusMessage; - - this.fireProviderStatusChangeEvent(oldPresenceStatus); - - //since we are not a real protocol, we set the contact presence status - //ourselves and make them have the same status as ours. - changePresenceStatusForAllContacts( getServerStoredContactListRoot() - , getPresenceStatus()); - - //now check whether we are in someone else's contact list and modify - //our status there - List<Contact> contacts = findContactsPointingToUs(); - - Iterator<Contact> contactsIter = contacts.iterator(); - while (contactsIter.hasNext()) - { - ContactDictImpl contact - = (ContactDictImpl) contactsIter.next(); - - PresenceStatus oldStatus = contact.getPresenceStatus(); - contact.setPresenceStatus(status); - contact.getParentPresenceOperationSet() - .fireContactPresenceStatusChangeEvent( - contact - , contact.getParentContactGroup() - , oldStatus); - - } - } - - - - /** - * Get the PresenceStatus for a particular contact. - * - * @param contactIdentifier the identifier of the contact whose status - * we're interested in. - * @return PresenceStatus the <tt>PresenceStatus</tt> of the specified - * <tt>contact</tt> - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * retrieving the status fails due to errors experienced during - * network communication - */ - public PresenceStatus queryContactStatus(String contactIdentifier) throws - IllegalArgumentException, IllegalStateException, - OperationFailedException - { - return findContactByID(contactIdentifier).getPresenceStatus(); - } - - /** - * Sets the presence status of <tt>contact</tt> to <tt>newStatus</tt>. - * - * @param contact the <tt>ContactDictImpl</tt> whose status we'd like - * to set. - * @param newStatus the new status we'd like to set to <tt>contact</tt>. - */ - private void changePresenceStatusForContact( - ContactDictImpl contact - , PresenceStatus newStatus) - { - PresenceStatus oldStatus = contact.getPresenceStatus(); - contact.setPresenceStatus(newStatus); - - fireContactPresenceStatusChangeEvent( - contact, findContactParent(contact), oldStatus); - } - - /** - * Sets the presence status of all <tt>contact</tt>s in our contact list - * (except those that correspond to another provider registered with SC) - * to <tt>newStatus</tt>. - * - * @param newStatus the new status we'd like to set to <tt>contact</tt>. - * @param parent the group in which we'd have to update the status of all - * direct and indirect child contacts. - */ - private void changePresenceStatusForAllContacts(ContactGroup parent, - PresenceStatus newStatus) - { - //first set the status for contacts in this group - Iterator<Contact> childContacts = parent.contacts(); - - while(childContacts.hasNext()) - { - ContactDictImpl contact - = (ContactDictImpl)childContacts.next(); - - if(findProviderForDictUserID(contact.getAddress()) != null) - { - //this is a contact corresponding to another SIP Communicator - //provider so we won't change it's status here. - continue; - } - PresenceStatus oldStatus = contact.getPresenceStatus(); - contact.setPresenceStatus(newStatus); - - fireContactPresenceStatusChangeEvent( - contact, parent, oldStatus); - } - - //now call this method recursively for all subgroups - Iterator<ContactGroup> subgroups = parent.subgroups(); - - while(subgroups.hasNext()) - { - ContactGroup subgroup = subgroups.next(); - changePresenceStatusForAllContacts(subgroup, newStatus); - } - } - - /** - * Returns the group that is parent of the specified dictGroup or null - * if no parent was found. - * @param dictGroup the group whose parent we're looking for. - * @return the ContactGroupDictImpl instance that dictGroup - * belongs to or null if no parent was found. - */ - public ContactGroupDictImpl findGroupParent( - ContactGroupDictImpl dictGroup) - { - return contactListRoot.findGroupParent(dictGroup); - } - - /** - * Returns the group that is parent of the specified dictContact or - * null if no parent was found. - * @param dictContact the contact whose parent we're looking for. - * @return the ContactGroupDictImpl instance that dictContact - * belongs to or null if no parent was found. - */ - public ContactGroupDictImpl findContactParent( - ContactDictImpl dictContact) - { - return (ContactGroupDictImpl)dictContact - .getParentContactGroup(); - } - - - /** - * Removes the specified group from the server stored contact list. - * - * @param group the group to remove. - * - * @throws IllegalArgumentException if <tt>group</tt> was not found in this - * protocol's contact list. - */ - public void removeServerStoredContactGroup(ContactGroup group) - throws IllegalArgumentException - { - ContactGroupDictImpl dictGroup - = (ContactGroupDictImpl)group; - - ContactGroupDictImpl parent = findGroupParent(dictGroup); - - if(parent == null){ - throw new IllegalArgumentException( - "group " + group - + " does not seem to belong to this protocol's contact list."); - } - - parent.removeSubGroup(dictGroup); - - this.fireServerStoredGroupEvent( - dictGroup, ServerStoredGroupEvent.GROUP_REMOVED_EVENT); - } - - /** - * Renames the specified group from the server stored contact list. - * - * @param group the group to rename. - * @param newName the new name of the group. - */ - public void renameServerStoredContactGroup(ContactGroup group, - String newName) - { - ((ContactGroupDictImpl)group).setGroupName(newName); - - this.fireServerStoredGroupEvent( - group, ServerStoredGroupEvent.GROUP_RENAMED_EVENT); - } - - /** - * Handler for incoming authorization requests. - * - * @param handler an instance of an AuthorizationHandler for - * authorization requests coming from other users requesting - * permission add us to their contact list. - */ - public void setAuthorizationHandler(AuthorizationHandler handler) - { - this.authorizationHandler = handler; - } - - /** - * Persistently adds a subscription for the presence status of the - * contact corresponding to the specified contactIdentifier and indicates - * that it should be added to the specified group of the server stored - * contact list. - * - * @param parent the parent group of the server stored contact list - * where the contact should be added. <p> - * @param contactIdentifier the contact whose status updates we are - * subscribing for. - * @throws IllegalArgumentException if <tt>contact</tt> or - * <tt>parent</tt> are not a contact known to the underlying protocol - * provider. - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * subscribing fails due to errors experienced during network - * communication - */ - public void subscribe(ContactGroup parent, String contactIdentifier) throws - IllegalArgumentException, IllegalStateException, - OperationFailedException - { - ContactDictImpl contact = new ContactDictImpl( - contactIdentifier - , parentProvider); - - ((ContactGroupDictImpl)parent).addContact(contact); - - fireSubscriptionEvent(contact, - parent, - SubscriptionEvent.SUBSCRIPTION_CREATED); - - //notify presence listeners for the status change. - fireContactPresenceStatusChangeEvent(contact - , parent - , DictStatusEnum.ONLINE); - } - - - /** - * Depending on whether <tt>contact</tt> corresponds to another protocol - * provider installed in sip-communicator, this method would either deliver - * it to that provider or simulate a corresponding request from the - * destination contact and make return a response after it has received - * one If the destination contact matches us, then we'll ask the user to - * act upon the request, and return the response. - * - * @param request the authorization request that we'd like to deliver to the - * destination <tt>contact</tt>. - * @param contact the <tt>Contact</tt> to notify - * - * @return the <tt>AuthorizationResponse</tt> that has been given or - * generated in response to <tt>request</tt>. - */ - private AuthorizationResponse deliverAuthorizationRequest( - AuthorizationRequest request, - Contact contact) - { - String userID = contact.getAddress(); - - //if the user id is our own id, then this request is being routed to us - //from another instance of the dict provider. - if (userID.equals(this.parentProvider.getAccountID().getUserID())) - { - //check who is the provider sending the message - String sourceUserID = contact.getProtocolProvider() - .getAccountID().getUserID(); - - //check whether they are in our contact list - Contact from = findContactByID(sourceUserID); - - //and if not - add them there as volatile. - if (from == null) - { - from = createVolatileContact(sourceUserID); - } - - //and now handle the request. - return authorizationHandler.processAuthorisationRequest( - request, from); - } - else - { - //if userID is not our own, try a check whether another provider - //has that id and if yes - deliver the request to them. - ProtocolProviderServiceDictImpl dictProvider - = this.findProviderForDictUserID(userID); - if (dictProvider != null) - { - OperationSetPersistentPresenceDictImpl opSetPersPresence - = (OperationSetPersistentPresenceDictImpl) - dictProvider.getOperationSet( - OperationSetPersistentPresence.class); - return opSetPersPresence - .deliverAuthorizationRequest(request, contact); - } - else - { - //if we got here then "to" is simply someone in our contact - //list so let's just simulate a reciproce request and generate - //a response accordingly. - - //pretend that the remote contact is asking for authorization - authorizationHandler.processAuthorisationRequest( - request, contact); - - //and now pretend that the remote contact has granted us - //authorization - return new AuthorizationResponse(AuthorizationResponse.ACCEPT - , "You are welcome!"); - } - } - } - - /** - * Adds a subscription for the presence status of the contact - * corresponding to the specified contactIdentifier. - * - * @param contactIdentifier the identifier of the contact whose status - * updates we are subscribing for. <p> - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * subscribing fails due to errors experienced during network - * communication - */ - public void subscribe(String contactIdentifier) throws - IllegalArgumentException, IllegalStateException, - OperationFailedException - { - //subscribe(contactListRoot, contactIdentifier); - - } - - /** - * Removes a subscription for the presence status of the specified - * contact. - * - * @param contact the contact whose status updates we are unsubscribing - * from. - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * unsubscribing fails due to errors experienced during network - * communication - */ - public void unsubscribe(Contact contact) throws IllegalArgumentException, - IllegalStateException, OperationFailedException - { - ContactGroupDictImpl parentGroup - = (ContactGroupDictImpl)((ContactDictImpl)contact) - .getParentContactGroup(); - - parentGroup.removeContact((ContactDictImpl)contact); - - fireSubscriptionEvent(contact, - ((ContactDictImpl)contact).getParentContactGroup() - , SubscriptionEvent.SUBSCRIPTION_REMOVED); - } - - /** - * Creates and returns a unresolved contact from the specified - * <tt>address</tt> and <tt>persistentData</tt>. The method will not try - * to establish a network connection and resolve the newly created Contact - * against the server. The protocol provider may will later try and resolve - * the contact. When this happens the corresponding event would notify - * interested subscription listeners. - * - * @param address an identifier of the contact that we'll be creating. - * @param persistentData a String returned Contact's getPersistentData() - * method during a previous run and that has been persistently stored - * locally. - * @return the unresolved <tt>Contact</tt> created from the specified - * <tt>address</tt> and <tt>persistentData</tt> - */ - public Contact createUnresolvedContact(String address, - String persistentData) - { - return createUnresolvedContact(address - , persistentData - , getServerStoredContactListRoot()); - } - - /** - * Creates and returns a unresolved contact from the specified - * <tt>address</tt> and <tt>persistentData</tt>. The method will not try - * to establish a network connection and resolve the newly created Contact - * against the server. The protocol provider may will later try and resolve - * the contact. When this happens the corresponding event would notify - * interested subscription listeners. - * - * @param address an identifier of the contact that we'll be creating. - * @param persistentData a String returned Contact's getPersistentData() - * method during a previous run and that has been persistently stored - * locally. - * @param parent the group where the unresolved contact is - * supposed to belong to. - * - * @return the unresolved <tt>Contact</tt> created from the specified - * <tt>address</tt> and <tt>persistentData</tt> - */ - public Contact createUnresolvedContact(String address, - String persistentData, - ContactGroup parent) - { - - ContactDictImpl contact = new ContactDictImpl( - address - , parentProvider); - contact.setResolved(false); - - ( (ContactGroupDictImpl) parent).addContact(contact); - - fireSubscriptionEvent(contact, - parent, - SubscriptionEvent.SUBSCRIPTION_CREATED); - - //since we don't have any server, we'll simply resolve the contact - //ourselves as if we've just received an event from the server telling - //us that it has been resolved. - contact.setResolved(true); - fireSubscriptionEvent(contact, parent, SubscriptionEvent.SUBSCRIPTION_RESOLVED); - - //since we are not a real protocol, we set the contact presence status - //ourselves - changePresenceStatusForContact( contact, getPresenceStatus()); - - return contact; - } - - /** - * Looks for a dict protocol provider registered for a user id matching - * <tt>dictUserID</tt>. - * - * @param dictUserID the ID of the Dict user whose corresponding - * protocol provider we'd like to find. - * @return ProtocolProviderServiceDictImpl a dict protocol - * provider registered for a user with id <tt>dictUserID</tt> or null - * if there is no such protocol provider. - */ - public ProtocolProviderServiceDictImpl - findProviderForDictUserID(String dictUserID) - { - BundleContext bc = DictActivator.getBundleContext(); - - String osgiQuery = "(&" - + "(" + ProtocolProviderFactory.PROTOCOL - + "=Dict)" - + "(" + ProtocolProviderFactory.USER_ID - + "=" + dictUserID + ")" - + ")"; - - ServiceReference[] refs = null; - try - { - refs = bc.getServiceReferences( - ProtocolProviderService.class.getName() - ,osgiQuery); - } - catch (InvalidSyntaxException ex) - { - logger.error("Failed to execute the following osgi query: " - + osgiQuery - , ex); - } - - if(refs != null && refs.length > 0) - { - return (ProtocolProviderServiceDictImpl)bc.getService(refs[0]); - } - - return null; - } - - /** - * Looks for dict protocol providers that have added us to their - * contact list and returns list of all contacts representing us in these - * providers. - * - * @return a list of all contacts in other providers' contact lists that - * point to us. - */ - public List<Contact> findContactsPointingToUs() - { - List<Contact> contacts = new LinkedList<Contact>(); - BundleContext bc = DictActivator.getBundleContext(); - - String osgiQuery = - "(" + ProtocolProviderFactory.PROTOCOL - + "=Dict)"; - - ServiceReference[] refs = null; - try - { - refs = bc.getServiceReferences( - ProtocolProviderService.class.getName() - ,osgiQuery); - } - catch (InvalidSyntaxException ex) - { - logger.error("Failed to execute the following osgi query: " - + osgiQuery - , ex); - } - - for (int i =0; refs != null && i < refs.length; i++) - { - ProtocolProviderServiceDictImpl gibProvider - = (ProtocolProviderServiceDictImpl)bc.getService(refs[i]); - - OperationSetPersistentPresenceDictImpl opSetPersPresence - = (OperationSetPersistentPresenceDictImpl)gibProvider - .getOperationSet(OperationSetPersistentPresence.class); - - Contact contact = opSetPersPresence.findContactByID( - parentProvider.getAccountID().getUserID()); - - if (contact != null) - contacts.add(contact); - } - - return contacts; - } - - - /** - * Creates and returns a unresolved contact group from the specified - * <tt>address</tt> and <tt>persistentData</tt>. The method will not try - * to establish a network connection and resolve the newly created - * <tt>ContactGroup</tt> against the server or the contact itself. The - * protocol provider will later resolve the contact group. When this happens - * the corresponding event would notify interested subscription listeners. - * - * @param groupUID an identifier, returned by ContactGroup's getGroupUID, - * that the protocol provider may use in order to create the group. - * @param persistentData a String returned ContactGroups's - * getPersistentData() method during a previous run and that has been - * persistently stored locally. - * @param parentGroup the group under which the new group is to be created - * or null if this is group directly underneath the root. - * @return the unresolved <tt>ContactGroup</tt> created from the specified - * <tt>uid</tt> and <tt>persistentData</tt> - */ - public ContactGroup createUnresolvedContactGroup(String groupUID, - String persistentData, ContactGroup parentGroup) - { - ContactGroupDictImpl newGroup - = new ContactGroupDictImpl( - ContactGroupDictImpl.createNameFromUID(groupUID) - , parentProvider); - newGroup.setResolved(false); - - //if parent is null then we're adding under root. - if(parentGroup == null) - parentGroup = getServerStoredContactListRoot(); - - ((ContactGroupDictImpl)parentGroup).addSubgroup(newGroup); - - this.fireServerStoredGroupEvent( - newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); - - return newGroup; - } - - private class UnregistrationListener - implements RegistrationStateChangeListener - { - /** - * The method is called by a ProtocolProvider implementation whenver - * a change in the registration state of the corresponding provider had - * occurred. The method is particularly interested in events stating - * that the dict provider has unregistered so that it would fire - * status change events for all contacts in our buddy list. - * - * @param evt ProviderStatusChangeEvent the event describing the status - * change. - */ - public void registrationStateChanged(RegistrationStateChangeEvent evt) - { - if (! evt.getNewState().equals(RegistrationState.UNREGISTERED) - && !evt.getNewState().equals(RegistrationState.AUTHENTICATION_FAILED) - && !evt.getNewState().equals(RegistrationState.CONNECTION_FAILED)) - { - return; - } - - //send event notifications saying that all our buddies are - //offline. The icq protocol does not implement top level buddies - //nor subgroups for top level groups so a simple nested loop - //would be enough. - Iterator<ContactGroup> groupsIter = getServerStoredContactListRoot() - .subgroups(); - while (groupsIter.hasNext()) - { - ContactGroupDictImpl group - = (ContactGroupDictImpl) groupsIter.next(); - - Iterator<Contact> contactsIter = group.contacts(); - - while (contactsIter.hasNext()) - { - ContactDictImpl contact - = (ContactDictImpl) contactsIter.next(); - - PresenceStatus oldContactStatus - = contact.getPresenceStatus(); - - if (!oldContactStatus.isOnline()) - continue; - - contact.setPresenceStatus(DictStatusEnum.OFFLINE); - - fireContactPresenceStatusChangeEvent( - contact - , contact.getParentContactGroup() - , oldContactStatus); - } - } - } - } - - /** - * Returns the volatile group or null if this group has not yet been - * created. - * - * @return a volatile group existing in our contact list or <tt>null</tt> - * if such a group has not yet been created. - */ - private ContactGroupDictImpl getNonPersistentGroup() - { - for (int i = 0 - ; i < getServerStoredContactListRoot().countSubgroups() - ; i++) - { - ContactGroupDictImpl gr = - (ContactGroupDictImpl)getServerStoredContactListRoot() - .getGroup(i); - - if(!gr.isPersistent()) - return gr; - } - - return null; - } - - - /** - * Creates a non persistent contact for the specified address. This would - * also create (if necessary) a group for volatile contacts that would not - * be added to the server stored contact list. This method would have no - * effect on the server stored contact list. - * - * @param contactAddress the address of the volatile contact we'd like to - * create. - * @return the newly created volatile contact. - */ - public ContactDictImpl createVolatileContact(String contactAddress) - { - //First create the new volatile contact; - ContactDictImpl newVolatileContact - = new ContactDictImpl(contactAddress - , this.parentProvider); - newVolatileContact.setPersistent(false); - - - //Check whether a volatile group already exists and if not create - //one - ContactGroupDictImpl theVolatileGroup = getNonPersistentGroup(); - - - //if the parent volatile group is null then we create it - if (theVolatileGroup == null) - { - theVolatileGroup = new ContactGroupDictImpl( - DictActivator.getResources().getI18NString( - "service.gui.NOT_IN_CONTACT_LIST_GROUP_NAME") - , parentProvider); - theVolatileGroup.setResolved(false); - theVolatileGroup.setPersistent(false); - - this.contactListRoot.addSubgroup(theVolatileGroup); - - fireServerStoredGroupEvent(theVolatileGroup - , ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - - //now add the volatile contact instide it - theVolatileGroup.addContact(newVolatileContact); - fireSubscriptionEvent(newVolatileContact - , theVolatileGroup - , SubscriptionEvent.SUBSCRIPTION_CREATED); - - return newVolatileContact; - } - -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolIconDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/ProtocolIconDictImpl.java deleted file mode 100644 index 4f09f22..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolIconDictImpl.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * Represents the Dict protocol icon. Implements the <tt>ProtocolIcon</tt> - * interface in order to provide a Dict logo image in two different sizes. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class ProtocolIconDictImpl - implements ProtocolIcon -{ - /** - * 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, - DictActivator.getResources() - .getImageInBytes("service.protocol.dict.DICT_16x16")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_32x32, - DictActivator.getResources() - .getImageInBytes("service.protocol.dict.DICT_32x32")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_48x48, - DictActivator.getResources() - .getImageInBytes("service.protocol.dict.DICT_48x48")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_64x64, - DictActivator.getResources() - .getImageInBytes("service.protocol.dict.DICT_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, - DictActivator.getResources() - .getImagePath("service.protocol.dict.DICT_16x16")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_32x32, - DictActivator.getResources() - .getImagePath("service.protocol.dict.DICT_32x32")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_48x48, - DictActivator.getResources() - .getImagePath("service.protocol.dict.DICT_48x48")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_64x64, - DictActivator.getResources() - .getImagePath("service.protocol.dict.DICT_64x64")); - } - - /** - * Implements the <tt>ProtocolIcon.getSupportedSizes()</tt> method. Returns - * an iterator to a set containing the supported icon sizes. - * @return Returns an iterator to a set containing the supported icon sizes - */ - public Iterator<String> getSupportedSizes() - { - return iconsTable.keySet().iterator(); - } - - /** - * Returns TRUE if an icon with the given size is supported, FALSE otherwise. - * @param iconSize The size of the icon, that we want to know if it is - * supported. - * @return Returns true if the 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 - * @return Returns a byte[] containing the pixels of the icon for the given - * size. - */ - 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 Returns the icon image used to represent the protocol connecting - * state. - */ - public byte[] getConnectingIcon() - { - return iconsTable.get(ProtocolIcon.ICON_SIZE_16x16); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderFactoryDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderFactoryDictImpl.java deleted file mode 100644 index d7eaf30..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderFactoryDictImpl.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -import net.java.sip.communicator.service.contactlist.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -import org.osgi.framework.*; - -/** - * The Dict protocol provider factory creates instances of the Dict - * protocol provider service. One Service instance corresponds to one account. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class ProtocolProviderFactoryDictImpl - extends ProtocolProviderFactory -{ - private static final Logger logger - = Logger.getLogger(ProtocolProviderFactoryDictImpl.class); - - /** - * Creates an instance of the ProtocolProviderFactoryDictImpl. - */ - public ProtocolProviderFactoryDictImpl() - { - super(DictActivator.getBundleContext(), ProtocolNames.DICT); - } - - /** - * Initializaed and creates an account corresponding to the specified - * accountProperties and registers the resulting ProtocolProvider in the - * <tt>context</tt> BundleContext parameter. - * - * @param userIDStr 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. - */ - @Override - public AccountID installAccount( String userIDStr, - Map<String, String> accountProperties) - { - BundleContext context = DictActivator.getBundleContext(); - if (context == null) - { - throw new NullPointerException("The specified BundleContext was null"); - } - if (userIDStr == null) - { - throw new NullPointerException("The specified AccountID was null"); - } - if (accountProperties == null) - { - throw new NullPointerException("The specified property map was null"); - } - - accountProperties.put(USER_ID, userIDStr); - - AccountID accountID = new DictAccountID(userIDStr, accountProperties); - - //make sure we haven't seen this account id before. - if (registeredAccounts.containsKey(accountID)) - { - throw new IllegalStateException("An account for id " + userIDStr + " was already installed!"); - } - - //first store the account and only then load it as the load generates - //an osgi event, the osgi event triggers (through the UI) a call to the - //ProtocolProviderService.register() method and it needs to acces - //the configuration service and check for a stored password. - this.storeAccount(accountID, false); - - accountID = loadAccount(accountProperties); - - // Creates the dict contact group. - this.createGroup(); - // Creates the default conatct for this dict server. - this.createDefaultContact(accountID); - - return accountID; - } - - @Override - protected AccountID createAccountID(String userID, Map<String, String> accountProperties) - { - return new DictAccountID(userID, accountProperties); - } - - @Override - protected ProtocolProviderService createService(String userID, - AccountID accountID) - { - ProtocolProviderServiceDictImpl service = - new ProtocolProviderServiceDictImpl(); - - service.initialize(userID, accountID); - return service; - } - - /** - * Creates a group for the dict contacts - */ - private void createGroup() - { - // Get MetaContactListService - BundleContext bundleContext = getBundleContext(); - ServiceReference<MetaContactListService> mfcServiceRef - = bundleContext.getServiceReference(MetaContactListService.class); - - MetaContactListService mcl = bundleContext.getService(mfcServiceRef); - - try - { - String groupName = DictActivator.getResources() - .getI18NString("service.protocol.DICTIONARIES"); - - mcl.createMetaContactGroup(mcl.getRoot(), groupName); - } - catch (MetaContactListException ex) - { - int errorCode = ex.getErrorCode(); - if (errorCode != MetaContactListException.CODE_GROUP_ALREADY_EXISTS_ERROR) - { - logger.error(ex); - } - } - } - - /** - * Creates a default contact for the new DICT server. - * @param accountID The accountID of the dict protocol provider for which we - * want to add a default contact. - */ - private void createDefaultContact(AccountID accountID) - { - // Gets the MetaContactListService. - BundleContext bundleContext = getBundleContext(); - ServiceReference<MetaContactListService> mfcServiceRef - = bundleContext.getServiceReference(MetaContactListService.class); - MetaContactListService mcl = bundleContext.getService(mfcServiceRef); - - // Gets the ProtocolProviderService. - ServiceReference<ProtocolProviderService> serRef - = getProviderForAccount(accountID); - ProtocolProviderService protocolProvider - = DictActivator.getBundleContext().getService(serRef); - - // Gets group name - String groupName = DictActivator.getResources() - .getI18NString("service.protocol.DICTIONARIES"); - - // Gets contact name - String contactName = DictActivator.getResources() - .getI18NString("plugin.dictaccregwizz.ANY_DICTIONARY_FORM", - new String[] {accountID.getUserID()}); - - // Gets the MetaContactGroup for the "dictionaries" group. - MetaContactGroup group = mcl.getRoot().getMetaContactSubgroup(groupName); - - // Sets the default contact identifier to "*" corresponding to "all the - // dictionaries" available on the server (cf. RFC-2229). - String dict_uin = "*"; - // Create the default contact. - mcl.createMetaContact(protocolProvider, group, dict_uin); - // Rename the default contact. - mcl.renameMetaContact( - group.getMetaContact(protocolProvider, dict_uin), - contactName); - } - - @Override - public void modifyAccount( - ProtocolProviderService protocolProvider, - Map<String, String> accountProperties) - throws NullPointerException - { - // TODO Auto-generated method stub - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderServiceDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderServiceDictImpl.java deleted file mode 100644 index aa9ef50..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderServiceDictImpl.java +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import net.java.dict4j.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; - -import org.jitsi.service.version.*; -import org.osgi.framework.*; - -/** - * A Dict implementation of the ProtocolProviderService. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class ProtocolProviderServiceDictImpl - extends AbstractProtocolProviderService -{ - private static final Logger logger - = Logger.getLogger(ProtocolProviderServiceDictImpl.class); - - /** - * The name of this protocol. - */ - public static final String DICT_PROTOCOL_NAME = "Dict"; - - /** - * The id of the account that this protocol provider represents. - */ - private DictAccountID accountID = null; - - /** - * We use this to lock access to initialization. - */ - private Object initializationLock = new Object(); - - /** - * Indicates whether or not the provider is initialized and ready for use. - */ - private boolean isInitialized = false; - - /** - * The logo corresponding to the gibberish protocol. - */ - private ProtocolIconDictImpl dictIcon = new ProtocolIconDictImpl(); - - /** - * The registration state that we are currently in. Note that in a real - * world protocol implementation this field won't exist and the registration - * state would be retrieved from the protocol stack. - */ - private RegistrationState currentRegistrationState - = RegistrationState.UNREGISTERED; - - /** - * the <tt>DictConnection</tt> opened by this provider - */ - private DictConnection dictConnection; - - /** - * The default constructor for the Dict protocol provider. - */ - public ProtocolProviderServiceDictImpl() - { - if (logger.isTraceEnabled()) - logger.trace("Creating a Dict provider."); - } - - /** - * 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 gibberish 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 = (DictAccountID) accountID; - - this.dictConnection = new DictConnection(this.accountID.getHost(), - this.accountID.getPort()); - this.dictConnection.setClientName(getSCVersion()); - - //initialize the presence operationset - OperationSetPersistentPresenceDictImpl persistentPresence = - new OperationSetPersistentPresenceDictImpl(this); - - addSupportedOperationSet( - OperationSetPersistentPresence.class, - persistentPresence); - //register it once again for those that simply need presence and - //won't be smart enough to check for a persistent presence - //alternative - addSupportedOperationSet( - OperationSetPresence.class, - persistentPresence); - - //initialize the IM operation set - addSupportedOperationSet( - OperationSetBasicInstantMessaging.class, - new OperationSetBasicInstantMessagingDictImpl( - this, - persistentPresence)); - - //initialize the typing notifications operation set - /*OperationSetTypingNotifications typingNotifications = - new OperationSetTypingNotificationsDictImpl( - this, persistentPresence); - - supportedOperationSets.put( - OperationSetTypingNotifications.class.getName(), - typingNotifications); - */ - isInitialized = true; - } - } - - /** - * Returns the <tt>DictConnection</tt> opened by this provider - * @return the <tt>DictConnection</tt> opened by this provider - */ - public DictConnection getConnection() - { - return this.dictConnection; - } - - /** - * 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 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 DICT_PROTOCOL_NAME; - } - - /** - * Returns the dict protocol icon. - * @return the dict protocol icon - */ - public ProtocolIcon getProtocolIcon() - { - return this.dictIcon; - } - - /** - * Returns the state of the registration of this protocol provider with - * the corresponding registration service. - * - * @return ProviderRegistrationState - */ - public RegistrationState getRegistrationState() - { - return currentRegistrationState; - } - - /** - * 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 - { - // Try to connect to the server - boolean connected = connect(); - - if (connected) - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.REGISTERED, - RegistrationStateChangeEvent.REASON_USER_REQUEST, - null); - currentRegistrationState = RegistrationState.REGISTERED; - } - else - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.CONNECTION_FAILED, - RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, - null); - currentRegistrationState = RegistrationState.UNREGISTERED; - } - } - - /** - * Checks if the connection to the dict server is open - * @return TRUE if the connection is open - FALSE otherwise - */ - private boolean connect() - { - if (this.dictConnection.isConnected()) - { - return true; - } - - try - { - return this.dictConnection.isAvailable(); - } - catch (DictException dx) - { - if (logger.isInfoEnabled()) - logger.info(dx); - } - - return false; - } - - /** - * 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; - } - if (logger.isTraceEnabled()) - logger.trace("Killing the Dict Protocol Provider for account " - + this.accountID.getUserID()); - - closeConnection(); - - 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 - { - closeConnection(); - - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.UNREGISTERED, - RegistrationStateChangeEvent.REASON_USER_REQUEST, - null); - } - - /** - * DICT has no support for secure transport. - */ - public boolean isSignalingTransportSecure() - { - return false; - } - - /** - * Returns the "transport" protocol of this instance used to carry the - * control channel for the current protocol service. - * - * @return The "transport" protocol of this instance: TCP. - */ - public TransportProtocol getTransportProtocol() - { - return TransportProtocol.TCP; - } - - /** - * Close the connection to the server - */ - private void closeConnection() - { - try - { - this.dictConnection.close(); - } - catch (DictException dx) - { - if (logger.isInfoEnabled()) - logger.info(dx); - } - } - - /** - * Returns the current version of SIP-Communicator - * @return the current version of SIP-Communicator - */ - private String getSCVersion() - { - BundleContext bc = DictActivator.getBundleContext(); - ServiceReference vsr = bc.getServiceReference(VersionService.class.getName()); - - VersionService vs = (VersionService) bc.getService(vsr); - return vs.getCurrentVersion().toString(); - - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/dict.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/dict/dict.provider.manifest.mf deleted file mode 100644 index d764657..0000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/dict.provider.manifest.mf +++ /dev/null @@ -1,15 +0,0 @@ -Bundle-Activator: net.java.sip.communicator.impl.protocol.dict.DictActivator -Bundle-Name: Dict Protocol Provider -Bundle-Description: A bundle providing support for the Dict protocol. -Bundle-Vendor: jitsi.org -Bundle-Version: 1.0.0 -Bundle-SymbolicName: net.java.sip.communicator.protocol.dict -Import-Package: org.osgi.framework, - org.jitsi.service.version, - net.java.sip.communicator.service.contactlist, - org.jitsi.service.configuration, - org.jitsi.service.resources, net.java.sip.communicator.service.resources, - net.java.sip.communicator.util, - net.java.sip.communicator.service.protocol, - net.java.sip.communicator.service.protocol.event, -Export-Package: net.java.dict4j diff --git a/src/net/java/sip/communicator/impl/protocol/gibberish/ContactGroupGibberishImpl.java b/src/net/java/sip/communicator/impl/protocol/gibberish/ContactGroupGibberishImpl.java index 2a95493..8528267 100644 --- a/src/net/java/sip/communicator/impl/protocol/gibberish/ContactGroupGibberishImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/gibberish/ContactGroupGibberishImpl.java @@ -577,4 +577,29 @@ public class ContactGroupGibberishImpl return true; } + + @Override + public int hashCode() + { + List<Object> objects = new ArrayList<Object>(); + objects.add(getGroupName()); + objects.add(getUID()); + objects.add(countContacts()); + objects.add(countSubgroups()); + + //traverse child contacts + for (Contact c : contacts) + { + objects.add(c.getAddress()); + } + + + //traverse subgroups + for (ContactGroup g : subGroups) + { + objects.add(g.getGroupName()); + } + + return Objects.hash(objects.toArray()); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/icq/icq.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/icq/icq.provider.manifest.mf index da47277..959ae6d 100644 --- a/src/net/java/sip/communicator/impl/protocol/icq/icq.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/icq/icq.provider.manifest.mf @@ -13,5 +13,4 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.dns, net.java.sip.communicator.service.protocol, net.java.sip.communicator.service.protocol.icqconstants, - net.java.sip.communicator.service.protocol.aimconstants, net.java.sip.communicator.service.protocol.event diff --git a/src/net/java/sip/communicator/impl/protocol/irc/OperationSetMultiUserChatIrcImpl.java b/src/net/java/sip/communicator/impl/protocol/irc/OperationSetMultiUserChatIrcImpl.java index 6b09978..7a1c2b3 100644 --- a/src/net/java/sip/communicator/impl/protocol/irc/OperationSetMultiUserChatIrcImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/irc/OperationSetMultiUserChatIrcImpl.java @@ -275,7 +275,7 @@ public class OperationSetMultiUserChatIrcImpl */ protected ChatRoomIrcImpl getChatRoom(final String chatRoomName) { - return (ChatRoomIrcImpl) this.chatRoomCache.get(chatRoomName); + return this.chatRoomCache.get(chatRoomName); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/AnonymousLoginStrategy.java b/src/net/java/sip/communicator/impl/protocol/jabber/AnonymousLoginStrategy.java index c955100..b62d01a 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/AnonymousLoginStrategy.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/AnonymousLoginStrategy.java @@ -70,7 +70,7 @@ public class AnonymousLoginStrategy } @Override - public boolean login(XMPPConnection connection, String userName, + public boolean login(Connection connection, String userName, String resource) throws XMPPException { diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java index e23481a..3987ea8 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java @@ -311,7 +311,7 @@ public class CallJabberImpl contentRequest.addChannel(remoteChannelRequest); } - XMPPConnection connection = protocolProvider.getConnection(); + Connection connection = protocolProvider.getConnection(); PacketCollector packetCollector = connection.createPacketCollector( new PacketIDFilter(conferenceRequest.getPacketID())); diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java index 64eb5fa..c257202 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,1647 +15,1644 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.lang.reflect.*;
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ContentPacketExtension.SendersEnum;
-import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.filter.*;
-import org.jivesoftware.smack.packet.*;
-import org.jivesoftware.smack.util.*;
-import org.jivesoftware.smackx.packet.*;
-
-/**
- * Implements a Jabber <tt>CallPeer</tt>.
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Boris Grozev
- */
-public class CallPeerJabberImpl
- extends AbstractCallPeerJabberGTalkImpl
- <CallJabberImpl, CallPeerMediaHandlerJabberImpl, JingleIQ>
-{
- /**
- * The <tt>Logger</tt> used by the <tt>CallPeerJabberImpl</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(CallPeerJabberImpl.class);
-
- /**
- * If the call is cancelled before session-initiate is sent.
- */
- private boolean cancelled = false;
-
- /**
- * Synchronization object for candidates available.
- */
- private final Object candSyncRoot = new Object();
-
- /**
- * If the content-add does not contains candidates.
- */
- private boolean contentAddWithNoCands = false;
-
- /**
- * If we have processed the session initiate.
- */
- private boolean sessionInitiateProcessed = false;
-
- /**
- * Synchronization object. Synchronization object? Wow, who would have
- * thought! ;) Would be great to have a word on what we are syncing with it
- */
- private final Object sessionInitiateSyncRoot = new Object();
-
- /**
- * Synchronization object for SID.
- */
- private final Object sidSyncRoot = new Object();
-
- /**
- * The current value of the 'senders' field of the audio content in the
- * Jingle session with this <tt>CallPeer</tt>.
- * <tt>null</tt> should be interpreted as 'both', which is the default in
- * Jingle if the XML attribute is missing.
- */
- private SendersEnum audioSenders = SendersEnum.none;
-
- /**
- * The current value of the 'senders' field of the video content in the
- * Jingle session with this <tt>CallPeer</tt>.
- * <tt>null</tt> should be interpreted as 'both', which is the default in
- * Jingle if the XML attribute is missing.
- */
- private SendersEnum videoSenders = SendersEnum.none;
-
- /**
- * Creates a new call peer with address <tt>peerAddress</tt>.
- *
- * @param peerAddress the Jabber address of the new call peer.
- * @param owningCall the call that contains this call peer.
- */
- public CallPeerJabberImpl(String peerAddress,
- CallJabberImpl owningCall)
- {
- super(peerAddress, owningCall);
-
- setMediaHandler(new CallPeerMediaHandlerJabberImpl(this));
- }
-
- /**
- * Creates a new call peer with address <tt>peerAddress</tt>.
- *
- * @param peerAddress the Jabber address of the new call peer.
- * @param owningCall the call that contains this call peer.
- * @param sessionIQ The session-initiate <tt>JingleIQ</tt> which was
- * received from <tt>peerAddress</tt> and caused the creation of this
- * <tt>CallPeerJabberImpl</tt>
- */
- public CallPeerJabberImpl(String peerAddress,
- CallJabberImpl owningCall,
- JingleIQ sessionIQ)
- {
- this(peerAddress, owningCall);
- this.sessionInitIQ = sessionIQ;
- }
-
- /**
- * Send a session-accept <tt>JingleIQ</tt> to this <tt>CallPeer</tt>
- * @throws OperationFailedException if we fail to create or send the
- * response.
- */
- public synchronized void answer()
- throws OperationFailedException
- {
- Iterable<ContentPacketExtension> answer;
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- try
- {
- mediaHandler
- .getTransportManager()
- .wrapupConnectivityEstablishment();
- answer = mediaHandler.generateSessionAccept();
- for (ContentPacketExtension c : answer)
- setSenders(getMediaType(c), c.getSenders());
- }
- catch(Exception exc)
- {
- logger.info("Failed to answer an incoming call", exc);
-
- //send an error response
- String reasonText = "Error: " + exc.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- Reason.FAILED_APPLICATION,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- JingleIQ response
- = JinglePacketFactory.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.
- getProtocolProvider().getConnection().sendPacket(response);
-
- try
- {
- mediaHandler.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();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- Reason.GENERAL_ERROR,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- //tell everyone we are connected 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.getSID() : null;
- }
-
- /**
- * Returns the IQ ID of the Jingle session-initiate packet associated with
- * this call.
- *
- * @return the IQ ID of the Jingle session-initiate packet associated with
- * this call.
- */
- public JingleIQ getSessionIQ()
- {
- return sessionInitIQ;
- }
-
- /**
- * Ends the call with 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)
- {
- CallPeerState prevPeerState = getState();
-
- // do nothing if the call is already ended
- if (CallPeerState.DISCONNECTED.equals(prevPeerState)
- || CallPeerState.FAILED.equals(prevPeerState))
- {
- if (logger.isDebugEnabled())
- logger.debug("Ignoring a request to hangup a call peer "
- + "that is already DISCONNECTED");
- return;
- }
-
- setState(
- failed ? CallPeerState.FAILED : CallPeerState.DISCONNECTED,
- reasonText);
-
- JingleIQ responseIQ = null;
-
- if (prevPeerState.equals(CallPeerState.CONNECTED)
- || CallPeerState.isOnHold(prevPeerState))
- {
- responseIQ = JinglePacketFactory.createBye(
- getProtocolProvider().getOurJID(), peerJID, getSID());
- }
- else if (CallPeerState.CONNECTING.equals(prevPeerState)
- || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState)
- || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState))
- {
- String jingleSID = getSID();
-
- if(jingleSID == null)
- {
- synchronized(sidSyncRoot)
- {
- // we cancelled the call too early because the jingleSID
- // is null (i.e. the session-initiate has not been created)
- // and no need to send the session-terminate
- cancelled = true;
- return;
- }
- }
-
- responseIQ = JinglePacketFactory.createCancel(
- getProtocolProvider().getOurJID(), peerJID, getSID());
- }
- else if (prevPeerState.equals(CallPeerState.INCOMING_CALL))
- {
- responseIQ = JinglePacketFactory.createBusy(
- getProtocolProvider().getOurJID(), peerJID, getSID());
- }
- 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)
- {
- reason.setOtherExtension(reasonOtherExtension);
- }
- else if(reasonOtherExtension instanceof ReasonPacketExtension)
- {
- responseIQ.setReason(
- (ReasonPacketExtension)reasonOtherExtension);
- }
- }
-
- getProtocolProvider().getConnection().sendPacket(responseIQ);
- }
- }
-
- /**
- * Creates and sends a session-initiate {@link JingleIQ}.
- *
- * @param sessionInitiateExtensions a collection of additional and optional
- * <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt>
- * {@link JingleIQ} which is to initiate the session with this
- * <tt>CallPeerJabberImpl</tt>
- * @throws OperationFailedException exception
- */
- protected synchronized void initiateSession(
- Iterable<PacketExtension> sessionInitiateExtensions)
- throws OperationFailedException
- {
- initiator = false;
-
- //Create the media description that we'd like to send to the other side.
- List<ContentPacketExtension> offer
- = getMediaHandler().createContentList();
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
-
- synchronized(sidSyncRoot)
- {
- sessionInitIQ
- = JinglePacketFactory.createSessionInitiate(
- protocolProvider.getOurJID(),
- this.peerJID,
- JingleIQ.generateSID(),
- offer);
-
- if(cancelled)
- {
- // we cancelled the call too early so no need to send the
- // session-initiate to peer
- getMediaHandler().getTransportManager().close();
- return;
- }
- }
-
- if (sessionInitiateExtensions != null)
- {
- for (PacketExtension sessionInitiateExtension
- : sessionInitiateExtensions)
- {
- sessionInitIQ.addExtension(sessionInitiateExtension);
- }
- }
-
- protocolProvider.getConnection().sendPacket(sessionInitIQ);
- }
-
- /**
- * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
- * been received. This <tt>CallPeerJabberImpl</tt> uses the part of the
- * information provided in the specified <tt>conferenceIQ</tt> which
- * concerns it only.
- *
- * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
- * received
- */
- void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
- {
- /*
- * CallPeerJabberImpl does not itself/directly know the specifics
- * related to the channels allocated on the Jitsi Videobridge server.
- * The channels contain transport and media-related information so
- * forward the notification to CallPeerMediaHandlerJabberImpl.
- */
- getMediaHandler().processColibriConferenceIQ(conferenceIQ);
- }
-
- /**
- * Processes the content-accept {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer has accepted
- */
- public void processContentAccept(JingleIQ content)
- {
- List<ContentPacketExtension> contents = content.getContentList();
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- try
- {
- mediaHandler
- .getTransportManager()
- .wrapupConnectivityEstablishment();
- mediaHandler.processAnswer(contents);
- for (ContentPacketExtension c : contents)
- setSenders(getMediaType(c), c.getSenders());
- }
- catch (Exception e)
- {
- logger.warn("Failed to process a content-accept", e);
-
- // Send an error response.
- String reason = "Error: " + e.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- getProtocolProvider().getOurJID(),
- peerJID,
- sessionInitIQ.getSID(),
- Reason.INCOMPATIBLE_PARAMETERS,
- reason);
-
- setState(CallPeerState.FAILED, reason);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- mediaHandler.start();
- }
-
- /**
- * Processes the content-add {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer wants to be added
- */
- public void processContentAdd(final JingleIQ content)
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- List<ContentPacketExtension> contents = content.getContentList();
- Iterable<ContentPacketExtension> answerContents;
- JingleIQ contentIQ;
- boolean noCands = false;
- MediaStream oldVideoStream = mediaHandler.getStream(MediaType.VIDEO);
-
- if(logger.isInfoEnabled())
- logger.info("Looking for candidates in content-add.");
- try
- {
- if(!contentAddWithNoCands)
- {
- mediaHandler.processOffer(contents);
-
- /*
- * Gingle transport will not put candidate in session-initiate
- * and content-add.
- */
- for(ContentPacketExtension c : contents)
- {
- if(JingleUtils.getFirstCandidate(c, 1) == null)
- {
- contentAddWithNoCands = true;
- noCands = true;
- }
- }
- }
-
- // if no candidates are present, launch a new Thread which will
- // process and wait for the connectivity establishment (otherwise
- // the existing thread will be blocked and thus cannot receive
- // transport-info with candidates
- if(noCands)
- {
- new Thread()
- {
- @Override
- public void run()
- {
- try
- {
- synchronized(candSyncRoot)
- {
- candSyncRoot.wait();
- }
- }
- catch(InterruptedException e)
- {
- }
-
- processContentAdd(content);
- contentAddWithNoCands = false;
- }
- }.start();
- if(logger.isInfoEnabled())
- logger.info("No candidates found in content-add, started "
- + "new thread.");
- return;
- }
-
- mediaHandler
- .getTransportManager()
- .wrapupConnectivityEstablishment();
- if(logger.isInfoEnabled())
- logger.info("Wrapping up connectivity establishment");
- answerContents = mediaHandler.generateSessionAccept();
- contentIQ = null;
- }
- catch(Exception e)
- {
- logger.warn("Exception occurred", e);
-
- answerContents = null;
- contentIQ
- = JinglePacketFactory.createContentReject(
- getProtocolProvider().getOurJID(),
- this.peerJID,
- getSID(),
- answerContents);
- }
-
- if(contentIQ == null)
- {
- /* send content-accept */
- contentIQ
- = JinglePacketFactory.createContentAccept(
- getProtocolProvider().getOurJID(),
- this.peerJID,
- getSID(),
- answerContents);
- for (ContentPacketExtension c : answerContents)
- setSenders(getMediaType(c), c.getSenders());
- }
-
- getProtocolProvider().getConnection().sendPacket(contentIQ);
- mediaHandler.start();
-
- /*
- * If a remote peer turns her video on in a conference which is hosted
- * by the local peer and the local peer is not streaming her local
- * video, reinvite the other remote peers to enable RTP translation.
- */
- if (oldVideoStream == null)
- {
- MediaStream newVideoStream
- = mediaHandler.getStream(MediaType.VIDEO);
-
- if ((newVideoStream != null)
- && mediaHandler.isRTPTranslationEnabled(MediaType.VIDEO))
- {
- try
- {
- getCall().modifyVideoContent();
- }
- catch (OperationFailedException ofe)
- {
- logger.error("Failed to enable RTP translation", ofe);
- }
- }
- }
- }
-
- /**
- * Processes the content-modify {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer wants to be modified
- */
- public void processContentModify(JingleIQ content)
- {
- ContentPacketExtension ext = content.getContentList().get(0);
- MediaType mediaType = getMediaType(ext);
-
- try
- {
- boolean modify
- = (ext.getFirstChildOfType(RtpDescriptionPacketExtension.class)
- != null);
-
- getMediaHandler().reinitContent(ext.getName(), ext, modify);
-
- setSenders(mediaType, ext.getSenders());
-
- if (MediaType.VIDEO.equals(mediaType))
- getCall().modifyVideoContent();
- }
- catch(Exception e)
- {
- logger.info("Failed to process an incoming content-modify", e);
-
- // Send an error response.
- String reason = "Error: " + e.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- getProtocolProvider().getOurJID(),
- peerJID,
- sessionInitIQ.getSID(),
- Reason.INCOMPATIBLE_PARAMETERS,
- reason);
-
- setState(CallPeerState.FAILED, reason);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
- }
-
- /**
- * Processes the content-reject {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ}
- */
- public void processContentReject(JingleIQ content)
- {
- if(content.getContentList().isEmpty())
- {
- //send an error response;
- JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
- "Error: content rejected");
-
- setState(CallPeerState.FAILED, "Error: content rejected");
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
- }
-
- /**
- * Processes the content-remove {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer wants to be removed
- */
- public void processContentRemove(JingleIQ content)
- {
- List<ContentPacketExtension> contents = content.getContentList();
- boolean videoContentRemoved = false;
-
- if (!contents.isEmpty())
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- for(ContentPacketExtension c : contents)
- {
- mediaHandler.removeContent(c.getName());
-
- MediaType mediaType = getMediaType(c);
- setSenders(mediaType, SendersEnum.none);
-
- if (MediaType.VIDEO.equals(mediaType))
- videoContentRemoved = true;
- }
-
- /*
- * TODO XEP-0166: Jingle says: If the content-remove results in zero
- * content definitions for the session, the entity that receives the
- * content-remove SHOULD send a session-terminate action to the
- * other party (since a session with no content definitions is
- * void).
- */
- }
-
- if (videoContentRemoved)
- {
- // removing of the video content might affect the other sessions
- // in the call
- try
- {
- getCall().modifyVideoContent();
- }
- catch (Exception e)
- {
- logger.warn("Failed to update Jingle sessions");
- }
- }
- }
-
- /**
- * Processes a session-accept {@link JingleIQ}.
- *
- * @param sessionInitIQ The session-accept {@link JingleIQ} to process.
- */
- public void processSessionAccept(JingleIQ sessionInitIQ)
- {
- this.sessionInitIQ = sessionInitIQ;
-
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- List<ContentPacketExtension> answer = sessionInitIQ.getContentList();
-
- try
- {
- mediaHandler
- .getTransportManager()
- .wrapupConnectivityEstablishment();
- mediaHandler.processAnswer(answer);
- for (ContentPacketExtension c : answer)
- setSenders(getMediaType(c), c.getSenders());
- }
- catch(Exception exc)
- {
- if (logger.isInfoEnabled())
- logger.info("Failed to process a session-accept", exc);
-
- //send an error response;
- JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
- exc.getClass().getName() + ": " + exc.getMessage());
-
- setState(CallPeerState.FAILED, "Error: " + exc.getMessage());
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- //tell everyone we are connected so that the audio notifications would
- //stop
- setState(CallPeerState.CONNECTED);
-
- mediaHandler.start();
-
- /*
- * If video was added to the call after we sent the session-initiate
- * to this peer, it needs to be added to this peer's session with a
- * content-add.
- */
- sendModifyVideoContent();
- }
-
- /**
- * Handles the specified session <tt>info</tt> packet according to its
- * content.
- *
- * @param info the {@link SessionInfoPacketExtension} that we just received.
- */
- public void processSessionInfo(SessionInfoPacketExtension info)
- {
- switch (info.getType())
- {
- case ringing:
- setState(CallPeerState.ALERTING_REMOTE_SIDE);
- break;
- case hold:
- getMediaHandler().setRemotelyOnHold(true);
- reevalRemoteHoldStatus();
- break;
- case unhold:
- case active:
- getMediaHandler().setRemotelyOnHold(false);
- reevalRemoteHoldStatus();
- break;
- default:
- logger.warn("Received SessionInfoPacketExtension of unknown type");
- }
- }
-
- /**
- * Processes the session initiation {@link JingleIQ} that we were created
- * with, passing its content to the media handler and then sends either a
- * "session-info/ringing" or a "session-terminate" response.
- *
- * @param sessionInitIQ The {@link JingleIQ} that created the session that
- * we are handling here.
- */
- protected synchronized void processSessionInitiate(JingleIQ sessionInitIQ)
- {
- // Do initiate the session.
- this.sessionInitIQ = sessionInitIQ;
- this.initiator = true;
-
- // This is the SDP offer that came from the initial session-initiate.
- // Contrary to SIP, we are guaranteed to have content because XEP-0166
- // says: "A session consists of at least one content type at a time."
- List<ContentPacketExtension> offer = sessionInitIQ.getContentList();
-
- try
- {
- getMediaHandler().processOffer(offer);
-
- CoinPacketExtension coin = null;
-
- for(PacketExtension ext : sessionInitIQ.getExtensions())
- {
- if(ext.getElementName().equals(
- CoinPacketExtension.ELEMENT_NAME))
- {
- coin = (CoinPacketExtension)ext;
- break;
- }
- }
-
- /* does the call peer acts as a conference focus ? */
- if(coin != null)
- {
- setConferenceFocus(Boolean.parseBoolean(
- (String)coin.getAttribute("isfocus")));
- }
- }
- catch(Exception ex)
- {
- logger.info("Failed to process an incoming session initiate", ex);
-
- //send an error response;
- String reasonText = "Error: " + ex.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- 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);
- }
-
- //send a ringing response
- if (logger.isTraceEnabled())
- logger.trace("will send ringing response: ");
-
- getProtocolProvider().getConnection().sendPacket(
- JinglePacketFactory.createRinging(sessionInitIQ));
-
- synchronized(sessionInitiateSyncRoot)
- {
- sessionInitiateProcessed = true;
- sessionInitiateSyncRoot.notify();
- }
-
- //if this is a 3264 initiator, let's give them an early peek at our
- //answer so that they could start ICE (SIP-2-Jingle gateways won't
- //be able to send their candidates unless they have this)
- DiscoverInfo discoverInfo = getDiscoveryInfo();
- if ((discoverInfo != null)
- && discoverInfo.containsFeature(
- ProtocolProviderServiceJabberImpl.URN_IETF_RFC_3264))
- {
- getProtocolProvider().getConnection().sendPacket(
- JinglePacketFactory.createDescriptionInfo(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- getMediaHandler().getLocalContentList()));
- }
- }
-
- /**
- * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a
- * reason to the user, if there is one.
- *
- * @param jingleIQ the {@link JingleIQ} that's terminating our session.
- */
- public void processSessionTerminate(JingleIQ jingleIQ)
- {
- String reasonStr = "Call ended by remote side.";
- ReasonPacketExtension reasonExt = jingleIQ.getReason();
-
- if(reasonExt != null)
- {
- Reason reason = reasonExt.getReason();
-
- if(reason != null)
- reasonStr += " Reason: " + reason.toString() + ".";
-
- String text = reasonExt.getText();
-
- if(text != null)
- reasonStr += " " + text;
- }
-
- setState(CallPeerState.DISCONNECTED, reasonStr);
- }
-
- /**
- * Processes a specific "XEP-0251: Jingle Session Transfer"
- * <tt>transfer</tt> packet (extension).
- *
- * @param transfer the "XEP-0251: Jingle Session Transfer" transfer packet
- * (extension) to process
- * @throws OperationFailedException if anything goes wrong while processing
- * the specified <tt>transfer</tt> packet (extension)
- */
- public void processTransfer(TransferPacketExtension transfer)
- throws OperationFailedException
- {
- String attendantAddress = transfer.getFrom();
-
- if (attendantAddress == null)
- {
- throw new OperationFailedException(
- "Session transfer must contain a \'from\' attribute value.",
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
-
- String calleeAddress = transfer.getTo();
-
- if (calleeAddress == null)
- {
- throw new OperationFailedException(
- "Session transfer must contain a \'to\' attribute value.",
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
-
- // Checks if the transfer remote peer is contained by the roster of this
- // account.
- Roster roster = getProtocolProvider().getConnection().getRoster();
- if(!roster.contains(StringUtils.parseBareAddress(calleeAddress)))
- {
- String failedMessage =
- "Transfer impossible:\n"
- + "Account roster does not contain transfer peer: "
- + StringUtils.parseBareAddress(calleeAddress);
- setState(CallPeerState.FAILED, failedMessage);
- logger.info(failedMessage);
- }
-
- OperationSetBasicTelephonyJabberImpl basicTelephony
- = (OperationSetBasicTelephonyJabberImpl)
- getProtocolProvider()
- .getOperationSet(OperationSetBasicTelephony.class);
- CallJabberImpl calleeCall = new CallJabberImpl(basicTelephony);
- TransferPacketExtension calleeTransfer = new TransferPacketExtension();
- String sid = transfer.getSID();
-
- calleeTransfer.setFrom(attendantAddress);
- if (sid != null)
- {
- calleeTransfer.setSID(sid);
- calleeTransfer.setTo(calleeAddress);
- }
- basicTelephony.createOutgoingCall(
- calleeCall,
- calleeAddress,
- Arrays.asList(new PacketExtension[] { calleeTransfer }));
- }
-
- /**
- * Processes the <tt>transport-info</tt> {@link JingleIQ}.
- *
- * @param jingleIQ the <tt>transport-info</tt> {@link JingleIQ} to process
- */
- public void processTransportInfo(JingleIQ jingleIQ)
- {
- /*
- * The transport-info action is used to exchange transport candidates so
- * it only concerns the mediaHandler.
- */
- try
- {
- if(isInitiator())
- {
- synchronized(sessionInitiateSyncRoot)
- {
- if(!sessionInitiateProcessed)
- {
- try
- {
- sessionInitiateSyncRoot.wait();
- }
- catch(InterruptedException e)
- {
- }
- }
- }
- }
-
- getMediaHandler().processTransportInfo(
- jingleIQ.getContentList());
- }
- catch (OperationFailedException ofe)
- {
- logger.warn("Failed to process an incoming transport-info", ofe);
-
- //send an error response
- String reasonText = "Error: " + ofe.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- getProtocolProvider().getOurJID(),
- peerJID,
- sessionInitIQ.getSID(),
- Reason.GENERAL_ERROR,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
-
- return;
- }
-
- synchronized(candSyncRoot)
- {
- candSyncRoot.notify();
- }
- }
-
- /**
- * Puts the <tt>CallPeer</tt> represented by this instance on or off hold.
- *
- * @param onHold <tt>true</tt> to have the <tt>CallPeer</tt> put on hold;
- * <tt>false</tt>, otherwise
- *
- * @throws OperationFailedException if we fail to construct or send the
- * INVITE request putting the remote side on/off hold.
- */
- public void putOnHold(boolean onHold)
- throws OperationFailedException
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- mediaHandler.setLocallyOnHold(onHold);
-
- SessionInfoType type;
-
- if(onHold)
- type = SessionInfoType.hold;
- else
- {
- type = SessionInfoType.unhold;
- getMediaHandler().reinitAllContents();
- }
-
- //we are now on hold and need to realize this before potentially
- //spoiling it all with an exception while sending the packet :).
- reevalLocalHoldStatus();
-
- JingleIQ onHoldIQ = JinglePacketFactory.createSessionInfo(
- getProtocolProvider().getOurJID(),
- peerJID,
- getSID(),
- type);
-
- getProtocolProvider().getConnection().sendPacket(onHoldIQ);
- }
-
- /**
- * Send a <tt>content-add</tt> to add video setup.
- */
- private void sendAddVideoContent()
- {
- List<ContentPacketExtension> contents;
-
- try
- {
- contents = getMediaHandler().createContentList(MediaType.VIDEO);
- }
- catch(Exception exc)
- {
- logger.warn("Failed to gather content for video type", exc);
- return;
- }
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentAdd(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- contents);
-
- protocolProvider.getConnection().sendPacket(contentIQ);
- }
-
- /**
- * Sends a <tt>content</tt> message to reflect changes in the setup such as
- * the local peer/user becoming a conference focus.
- */
- public void sendCoinSessionInfo()
- {
- JingleIQ sessionInfoIQ
- = JinglePacketFactory.createSessionInfo(
- getProtocolProvider().getOurJID(),
- this.peerJID,
- getSID());
- CoinPacketExtension coinExt
- = new CoinPacketExtension(getCall().isConferenceFocus());
-
- sessionInfoIQ.addExtension(coinExt);
- getProtocolProvider().getConnection().sendPacket(sessionInfoIQ);
- }
-
- /**
- * Returns the <tt>MediaDirection</tt> that should be set for the content
- * of type <tt>mediaType</tt> in the Jingle session for this
- * <tt>CallPeer</tt>.
- * If we are the focus of a conference and are doing RTP translation,
- * takes into account the other <tt>CallPeer</tt>s in the <tt>Call</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> for which to return the
- * <tt>MediaDirection</tt>
- * @return the <tt>MediaDirection</tt> that should be used for the content
- * of type <tt>mediaType</tt> in the Jingle session for this
- * <tt>CallPeer</tt>.
- */
- private MediaDirection getDirectionForJingle(MediaType mediaType)
- {
- MediaDirection direction = MediaDirection.INACTIVE;
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- // If we are streaming media, the direction should allow sending
- if ( (MediaType.AUDIO == mediaType &&
- mediaHandler.isLocalAudioTransmissionEnabled()) ||
- (MediaType.VIDEO == mediaType &&
- isLocalVideoStreaming()))
- direction = direction.or(MediaDirection.SENDONLY);
-
- // If we are receiving media from this CallPeer, the direction should
- // allow receiving
- SendersEnum senders = getSenders(mediaType);
- if (senders == null || senders == SendersEnum.both ||
- (isInitiator() && senders == SendersEnum.initiator) ||
- (!isInitiator() && senders == SendersEnum.responder))
- direction = direction.or(MediaDirection.RECVONLY);
-
- // If we are the focus of a conference and we are receiving media from
- // another CallPeer in the same Call, the direction should allow sending
- CallJabberImpl call = getCall();
- if (call != null && call.isConferenceFocus())
- {
- for (CallPeerJabberImpl peer : call.getCallPeerList())
- {
- if (peer != this)
- {
- senders = peer.getSenders(mediaType);
- if (senders == null || senders == SendersEnum.both ||
- (peer.isInitiator()
- && senders == SendersEnum.initiator) ||
- (!peer.isInitiator()
- && senders == SendersEnum.responder))
- {
- direction = direction.or(MediaDirection.SENDONLY);
- break;
- }
- }
- }
- }
-
- return direction;
- }
-
- /**
- * Send, if necessary, a jingle <tt>content</tt> message to reflect change
- * in video setup. Whether the jingle session should have a video content,
- * and if so, the value of the <tt>senders</tt> field is determined
- * based on whether we are streaming local video and, if we are the focus
- * of a conference, on the other peers in the conference.
- * The message can be content-modify if video content exists (and the
- * <tt>senders</tt> field changes), content-add or content-remove.
- *
- * @return <tt>true</tt> if a jingle <tt>content</tt> message was sent.
- */
- public boolean sendModifyVideoContent()
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- MediaDirection direction = getDirectionForJingle(MediaType.VIDEO);
-
- ContentPacketExtension remoteContent
- = mediaHandler.getLocalContent(MediaType.VIDEO.toString());
-
- if (remoteContent == null)
- {
- if (direction == MediaDirection.INACTIVE)
- {
- // no video content, none needed
- return false;
- }
- else
- {
- if (getState() == CallPeerState.CONNECTED)
- {
- if (logger.isInfoEnabled())
- logger.info("Adding video content for " + this);
- sendAddVideoContent();
- return true;
- }
- return false;
- }
- }
- else
- {
- if (direction == MediaDirection.INACTIVE)
- {
- sendRemoveVideoContent();
- return true;
- }
- }
-
- SendersEnum senders = getSenders(MediaType.VIDEO);
- if (senders == null)
- senders = SendersEnum.both;
-
- SendersEnum newSenders = SendersEnum.none;
- if (MediaDirection.SENDRECV == direction)
- newSenders = SendersEnum.both;
- else if (MediaDirection.RECVONLY == direction)
- newSenders = isInitiator()
- ? SendersEnum.initiator : SendersEnum.responder;
- else if (MediaDirection.SENDONLY == direction)
- newSenders = isInitiator()
- ? SendersEnum.responder : SendersEnum.initiator;
-
- /*
- * Send Content-Modify
- */
- ContentPacketExtension ext = new ContentPacketExtension();
- String remoteContentName = remoteContent.getName();
-
- ext.setSenders(newSenders);
- ext.setCreator(remoteContent.getCreator());
- ext.setName(remoteContentName);
-
- if (newSenders != senders)
- {
- if (logger.isInfoEnabled())
- logger.info("Sending content modify, senders: "
- + senders + "->" + newSenders);
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentModify(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- ext);
-
- protocolProvider.getConnection().sendPacket(contentIQ);
- }
-
- try
- {
- mediaHandler.reinitContent(remoteContentName, ext, false);
- mediaHandler.start();
- }
- catch(Exception e)
- {
- logger.warn("Exception occurred during media reinitialization", e);
- }
-
- return (newSenders != senders);
- }
-
- /**
- * Send a <tt>content</tt> message to reflect change in video setup (start
- * or stop).
- */
- public void sendModifyVideoResolutionContent()
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- ContentPacketExtension remoteContent
- = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
- ContentPacketExtension content;
-
- logger.info("send modify-content to change resolution");
-
- // send content-modify with RTP description
-
- // create content list with resolution
- try
- {
- content = mediaHandler.createContentForMedia(MediaType.VIDEO);
- }
- catch (Exception e)
- {
- logger.warn("Failed to gather content for video type", e);
- return;
- }
-
- // if we are only receiving video senders is null
- SendersEnum senders = remoteContent.getSenders();
-
- if (senders != null)
- content.setSenders(senders);
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentModify(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- content);
-
- protocolProvider.getConnection().sendPacket(contentIQ);
-
- try
- {
- mediaHandler.reinitContent(remoteContent.getName(), content, false);
- mediaHandler.start();
- }
- catch(Exception e)
- {
- logger.warn("Exception occurred when media reinitialization", e);
- }
- }
-
- /**
- * Send a <tt>content-remove</tt> to remove video setup.
- */
- private void sendRemoveVideoContent()
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- ContentPacketExtension content = new ContentPacketExtension();
- ContentPacketExtension remoteContent
- = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
- if (remoteContent == null)
- return;
- String remoteContentName = remoteContent.getName();
-
- content.setName(remoteContentName);
- content.setCreator(remoteContent.getCreator());
- content.setSenders(remoteContent.getSenders());
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentRemove(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- Arrays.asList(content));
-
- protocolProvider.getConnection().sendPacket(contentIQ);
- mediaHandler.removeContent(remoteContentName);
- setSenders(MediaType.VIDEO, SendersEnum.none);
- }
-
- /**
- * Sends local candidate addresses from the local peer to the remote peer
- * using the <tt>transport-info</tt> {@link JingleIQ}.
- *
- * @param contents the local candidate addresses to be sent from the local
- * peer to the remote peer using the <tt>transport-info</tt>
- * {@link JingleIQ}
- */
- protected void sendTransportInfo(Iterable<ContentPacketExtension> contents)
- {
- // if the call is canceled, do not start sending candidates in
- // transport-info
- if(cancelled)
- return;
-
- JingleIQ transportInfo = new JingleIQ();
-
- for (ContentPacketExtension content : contents)
- transportInfo.addContent(content);
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
-
- transportInfo.setAction(JingleAction.TRANSPORT_INFO);
- transportInfo.setFrom(protocolProvider.getOurJID());
- transportInfo.setSID(getSID());
- transportInfo.setTo(getAddress());
- transportInfo.setType(IQ.Type.SET);
-
- PacketCollector collector
- = protocolProvider.getConnection().createPacketCollector(
- new PacketIDFilter(transportInfo.getPacketID()));
-
- protocolProvider.getConnection().sendPacket(transportInfo);
- collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
- collector.cancel();
- }
-
- @Override
- public void setState(CallPeerState newState, String reason, int reasonCode)
- {
- CallPeerState oldState = getState();
- try
- {
- /*
- * We need to dispose of the transport manager before the
- * 'call' field is set to null, because if Jitsi Videobridge is in
- * use, it (the call) is needed in order to expire the
- * Videobridge channels.
- */
- if (CallPeerState.DISCONNECTED.equals(newState)
- || CallPeerState.FAILED.equals(newState))
- getMediaHandler().getTransportManager().close();
- }
- finally
- {
- super.setState(newState, reason, reasonCode);
- }
-
- if (CallPeerState.isOnHold(oldState)
- && CallPeerState.CONNECTED.equals(newState))
- {
- try
- {
- getCall().modifyVideoContent();
- }
- catch (OperationFailedException ofe)
- {
- logger.error("Failed to update call video state after " +
- "'hold' status removed for "+this);
- }
- }
- }
-
- /**
- * Transfer (in the sense of call transfer) this <tt>CallPeer</tt> to a
- * specific callee address which may optionally be participating in an
- * active <tt>Call</tt>.
- *
- * @param to the address of the callee to transfer this <tt>CallPeer</tt> to
- * @param sid the Jingle session ID of the active <tt>Call</tt> between the
- * local peer and the callee in the case of attended transfer; <tt>null</tt>
- * in the case of unattended transfer
- * @throws OperationFailedException if something goes wrong
- */
- protected void transfer(String to, String sid)
- throws OperationFailedException
- {
- JingleIQ transferSessionInfo = new JingleIQ();
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
-
- transferSessionInfo.setAction(JingleAction.SESSION_INFO);
- transferSessionInfo.setFrom(protocolProvider.getOurJID());
- transferSessionInfo.setSID(getSID());
- transferSessionInfo.setTo(getAddress());
- transferSessionInfo.setType(IQ.Type.SET);
-
- TransferPacketExtension transfer = new TransferPacketExtension();
-
- // Attended transfer.
- if (sid != null)
- {
- /*
- * Not really sure what the value of the "from" attribute of the
- * "transfer" element should be but the examples in "XEP-0251:
- * Jingle Session Transfer" has it in the case of attended transfer.
- */
- transfer.setFrom(protocolProvider.getOurJID());
- transfer.setSID(sid);
-
- // Puts on hold the 2 calls before making the attended transfer.
- OperationSetBasicTelephonyJabberImpl basicTelephony
- = (OperationSetBasicTelephonyJabberImpl)
- protocolProvider.getOperationSet(
- OperationSetBasicTelephony.class);
- CallPeerJabberImpl callPeer = basicTelephony.getActiveCallPeer(sid);
- if(callPeer != null)
- {
- if(!CallPeerState.isOnHold(callPeer.getState()))
- {
- callPeer.putOnHold(true);
- }
- }
-
- if(!CallPeerState.isOnHold(this.getState()))
- {
- this.putOnHold(true);
- }
- }
- transfer.setTo(to);
-
- transferSessionInfo.addExtension(transfer);
-
- Connection connection = protocolProvider.getConnection();
- PacketCollector collector = connection.createPacketCollector(
- new PacketIDFilter(transferSessionInfo.getPacketID()));
- protocolProvider.getConnection().sendPacket(transferSessionInfo);
-
- Packet result
- = collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
-
- if(result == null)
- {
- // Log the failed transfer call and notify the user.
- throw new OperationFailedException(
- "No response to the \"transfer\" request.",
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
- else if (((IQ) result).getType() != IQ.Type.RESULT)
- {
- // Log the failed transfer call and notify the user.
- throw new OperationFailedException(
- "Remote peer does not manage call \"transfer\"."
- + "Response to the \"transfer\" request is: "
- + ((IQ) result).getType(),
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
- else
- {
- String message = ((sid == null) ? "Unattended" : "Attended")
- + " transfer to: "
- + to;
- // Implements the SIP behavior: once the transfer is accepted, the
- // current call is closed.
- hangup(
- false,
- message,
- new ReasonPacketExtension(Reason.SUCCESS,
- message,
- new TransferredPacketExtension()));
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public String getEntity()
- {
- return getAddress();
- }
-
- /**
- * {@inheritDoc}
- *
- * In Jingle there isn't an actual "direction" parameter. We use the
- * <tt>senders</tt> field to calculate the direction.
- */
- @Override
- public MediaDirection getDirection(MediaType mediaType)
- {
- SendersEnum senders = getSenders(mediaType);
-
- if (senders == SendersEnum.none)
- {
- return MediaDirection.INACTIVE;
- }
- else if (senders == null || senders == SendersEnum.both)
- {
- return MediaDirection.SENDRECV;
- }
- else if (senders == SendersEnum.initiator)
- {
- return
- isInitiator()
- ? MediaDirection.RECVONLY
- : MediaDirection.SENDONLY;
- }
- else //senders == SendersEnum.responder
- {
- return
- isInitiator()
- ? MediaDirection.SENDONLY
- : MediaDirection.RECVONLY;
- }
- }
-
- /**
- * Gets the current value of the <tt>senders</tt> field of the content with
- * name <tt>mediaType</tt> in the Jingle session with this
- * <tt>CallPeer</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> for which to get the current
- * value of the <tt>senders</tt> field.
- * @return the current value of the <tt>senders</tt> field of the content
- * with name <tt>mediaType</tt> in the Jingle session with this
- * <tt>CallPeer</tt>.
- */
- public SendersEnum getSenders(MediaType mediaType)
- {
- switch (mediaType)
- {
- case AUDIO:
- return audioSenders;
- case DATA:
- /*
- * FIXME DATA has been introduced as a MediaType but explicit
- * support for DATA content has not been added yet.
- */
- return SendersEnum.none;
- case VIDEO:
- return videoSenders;
- default:
- throw new IllegalArgumentException("mediaType");
- }
- }
-
- /**
- * Set the current value of the <tt>senders</tt> field of the content with
- * name <tt>mediaType</tt> in the Jingle session with this <tt>CallPeer</tt>
- * @param mediaType the <tt>MediaType</tt> for which to get the current
- * value of the <tt>senders</tt> field.
- * @param senders the value to set
- */
- public void setSenders(MediaType mediaType, SendersEnum senders)
- {
- if (mediaType == null)
- return;
- else if (MediaType.AUDIO.equals(mediaType))
- this.audioSenders = senders;
- else if (MediaType.VIDEO.equals(mediaType))
- this.videoSenders = senders;
- else
- throw new IllegalArgumentException("mediaType");
- }
-
- /**
- * Gets the <tt>MediaType</tt> of <tt>content</tt>. If <tt>content</tt>
- * does not have a <tt>description</tt> child and therefore not
- * <tt>MediaType</tt> can be associated with it, tries to take the
- * <tt>MediaType</tt> from the session's already established contents with
- * the same name as <tt>content</tt>
- * @param content the <tt>ContentPacketExtention</tt> for which to get the
- * <tt>MediaType</tt>
- * @return the <tt>MediaType</tt> of <tt>content</tt>.
- */
- public MediaType getMediaType(ContentPacketExtension content)
- {
- String contentName = content.getName();
- if (contentName == null)
- return null;
-
- MediaType mediaType = JingleUtils.getMediaType(content);
- if (mediaType == null)
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- for (MediaType m : MediaType.values())
- {
- ContentPacketExtension sessionContent
- = mediaHandler.getRemoteContent(m.toString());
- if (sessionContent == null)
- sessionContent = mediaHandler.getLocalContent(m.toString());
-
- if (sessionContent != null
- && contentName.equals(sessionContent.getName()))
- {
- mediaType = m;
- break;
- }
- }
- }
-
- return mediaType;
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber; + +import java.lang.reflect.*; +import java.util.*; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ContentPacketExtension.SendersEnum; +import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +import org.jitsi.service.neomedia.*; +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.util.*; +import org.jivesoftware.smackx.packet.*; + +/** + * Implements a Jabber <tt>CallPeer</tt>. + * + * @author Emil Ivov + * @author Lyubomir Marinov + * @author Boris Grozev + */ +public class CallPeerJabberImpl + extends AbstractCallPeerJabberGTalkImpl + <CallJabberImpl, CallPeerMediaHandlerJabberImpl, JingleIQ> +{ + /** + * The <tt>Logger</tt> used by the <tt>CallPeerJabberImpl</tt> class and its + * instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(CallPeerJabberImpl.class); + + /** + * If the call is cancelled before session-initiate is sent. + */ + private boolean cancelled = false; + + /** + * Synchronization object for candidates available. + */ + private final Object candSyncRoot = new Object(); + + /** + * If the content-add does not contains candidates. + */ + private boolean contentAddWithNoCands = false; + + /** + * If we have processed the session initiate. + */ + private boolean sessionInitiateProcessed = false; + + /** + * Synchronization object. Synchronization object? Wow, who would have + * thought! ;) Would be great to have a word on what we are syncing with it + */ + private final Object sessionInitiateSyncRoot = new Object(); + + /** + * Synchronization object for SID. + */ + private final Object sidSyncRoot = new Object(); + + /** + * The current value of the 'senders' field of the audio content in the + * Jingle session with this <tt>CallPeer</tt>. + * <tt>null</tt> should be interpreted as 'both', which is the default in + * Jingle if the XML attribute is missing. + */ + private SendersEnum audioSenders = SendersEnum.none; + + /** + * The current value of the 'senders' field of the video content in the + * Jingle session with this <tt>CallPeer</tt>. + * <tt>null</tt> should be interpreted as 'both', which is the default in + * Jingle if the XML attribute is missing. + */ + private SendersEnum videoSenders = SendersEnum.none; + + /** + * Creates a new call peer with address <tt>peerAddress</tt>. + * + * @param peerAddress the Jabber address of the new call peer. + * @param owningCall the call that contains this call peer. + */ + public CallPeerJabberImpl(String peerAddress, + CallJabberImpl owningCall) + { + super(peerAddress, owningCall); + + setMediaHandler(new CallPeerMediaHandlerJabberImpl(this)); + } + + /** + * Creates a new call peer with address <tt>peerAddress</tt>. + * + * @param peerAddress the Jabber address of the new call peer. + * @param owningCall the call that contains this call peer. + * @param sessionIQ The session-initiate <tt>JingleIQ</tt> which was + * received from <tt>peerAddress</tt> and caused the creation of this + * <tt>CallPeerJabberImpl</tt> + */ + public CallPeerJabberImpl(String peerAddress, + CallJabberImpl owningCall, + JingleIQ sessionIQ) + { + this(peerAddress, owningCall); + this.sessionInitIQ = sessionIQ; + } + + /** + * Send a session-accept <tt>JingleIQ</tt> to this <tt>CallPeer</tt> + * @throws OperationFailedException if we fail to create or send the + * response. + */ + public synchronized void answer() + throws OperationFailedException + { + Iterable<ContentPacketExtension> answer; + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + try + { + mediaHandler + .getTransportManager() + .wrapupConnectivityEstablishment(); + answer = mediaHandler.generateSessionAccept(); + for (ContentPacketExtension c : answer) + setSenders(getMediaType(c), c.getSenders()); + } + catch(Exception exc) + { + logger.info("Failed to answer an incoming call", exc); + + //send an error response + String reasonText = "Error: " + exc.getMessage(); + JingleIQ errResp + = JinglePacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), + Reason.FAILED_APPLICATION, + reasonText); + + setState(CallPeerState.FAILED, reasonText); + getProtocolProvider().getConnection().sendPacket(errResp); + return; + } + + JingleIQ response + = JinglePacketFactory.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. + getProtocolProvider().getConnection().sendPacket(response); + + try + { + mediaHandler.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(); + JingleIQ errResp + = JinglePacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), + Reason.GENERAL_ERROR, + reasonText); + + setState(CallPeerState.FAILED, reasonText); + getProtocolProvider().getConnection().sendPacket(errResp); + return; + } + + //tell everyone we are connected 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.getSID() : null; + } + + /** + * Returns the IQ ID of the Jingle session-initiate packet associated with + * this call. + * + * @return the IQ ID of the Jingle session-initiate packet associated with + * this call. + */ + public JingleIQ getSessionIQ() + { + return sessionInitIQ; + } + + /** + * Ends the call with 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) + { + CallPeerState prevPeerState = getState(); + + // do nothing if the call is already ended + if (CallPeerState.DISCONNECTED.equals(prevPeerState) + || CallPeerState.FAILED.equals(prevPeerState)) + { + if (logger.isDebugEnabled()) + logger.debug("Ignoring a request to hangup a call peer " + + "that is already DISCONNECTED"); + return; + } + + setState( + failed ? CallPeerState.FAILED : CallPeerState.DISCONNECTED, + reasonText); + + JingleIQ responseIQ = null; + + if (prevPeerState.equals(CallPeerState.CONNECTED) + || CallPeerState.isOnHold(prevPeerState)) + { + responseIQ = JinglePacketFactory.createBye( + getProtocolProvider().getOurJID(), peerJID, getSID()); + } + else if (CallPeerState.CONNECTING.equals(prevPeerState) + || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState) + || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState)) + { + String jingleSID = getSID(); + + if(jingleSID == null) + { + synchronized(sidSyncRoot) + { + // we cancelled the call too early because the jingleSID + // is null (i.e. the session-initiate has not been created) + // and no need to send the session-terminate + cancelled = true; + return; + } + } + + responseIQ = JinglePacketFactory.createCancel( + getProtocolProvider().getOurJID(), peerJID, getSID()); + } + else if (prevPeerState.equals(CallPeerState.INCOMING_CALL)) + { + responseIQ = JinglePacketFactory.createBusy( + getProtocolProvider().getOurJID(), peerJID, getSID()); + } + 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) + { + reason.setOtherExtension(reasonOtherExtension); + } + else if(reasonOtherExtension instanceof ReasonPacketExtension) + { + responseIQ.setReason( + (ReasonPacketExtension)reasonOtherExtension); + } + } + + getProtocolProvider().getConnection().sendPacket(responseIQ); + } + } + + /** + * Creates and sends a session-initiate {@link JingleIQ}. + * + * @param sessionInitiateExtensions a collection of additional and optional + * <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt> + * {@link JingleIQ} which is to initiate the session with this + * <tt>CallPeerJabberImpl</tt> + * @throws OperationFailedException exception + */ + protected synchronized void initiateSession( + Iterable<PacketExtension> sessionInitiateExtensions) + throws OperationFailedException + { + initiator = false; + + //Create the media description that we'd like to send to the other side. + List<ContentPacketExtension> offer + = getMediaHandler().createContentList(); + + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + + synchronized(sidSyncRoot) + { + sessionInitIQ + = JinglePacketFactory.createSessionInitiate( + protocolProvider.getOurJID(), + this.peerJID, + JingleIQ.generateSID(), + offer); + + if(cancelled) + { + // we cancelled the call too early so no need to send the + // session-initiate to peer + getMediaHandler().getTransportManager().close(); + return; + } + } + + if (sessionInitiateExtensions != null) + { + for (PacketExtension sessionInitiateExtension + : sessionInitiateExtensions) + { + sessionInitIQ.addExtension(sessionInitiateExtension); + } + } + + protocolProvider.getConnection().sendPacket(sessionInitIQ); + } + + /** + * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has + * been received. This <tt>CallPeerJabberImpl</tt> uses the part of the + * information provided in the specified <tt>conferenceIQ</tt> which + * concerns it only. + * + * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been + * received + */ + void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ) + { + /* + * CallPeerJabberImpl does not itself/directly know the specifics + * related to the channels allocated on the Jitsi Videobridge server. + * The channels contain transport and media-related information so + * forward the notification to CallPeerMediaHandlerJabberImpl. + */ + getMediaHandler().processColibriConferenceIQ(conferenceIQ); + } + + /** + * Processes the content-accept {@link JingleIQ}. + * + * @param content The {@link JingleIQ} that contains content that remote + * peer has accepted + */ + public void processContentAccept(JingleIQ content) + { + List<ContentPacketExtension> contents = content.getContentList(); + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + try + { + mediaHandler + .getTransportManager() + .wrapupConnectivityEstablishment(); + mediaHandler.processAnswer(contents); + for (ContentPacketExtension c : contents) + setSenders(getMediaType(c), c.getSenders()); + } + catch (Exception e) + { + logger.warn("Failed to process a content-accept", e); + + // Send an error response. + String reason = "Error: " + e.getMessage(); + JingleIQ errResp + = JinglePacketFactory.createSessionTerminate( + getProtocolProvider().getOurJID(), + peerJID, + sessionInitIQ.getSID(), + Reason.INCOMPATIBLE_PARAMETERS, + reason); + + setState(CallPeerState.FAILED, reason); + getProtocolProvider().getConnection().sendPacket(errResp); + return; + } + + mediaHandler.start(); + } + + /** + * Processes the content-add {@link JingleIQ}. + * + * @param content The {@link JingleIQ} that contains content that remote + * peer wants to be added + */ + public void processContentAdd(final JingleIQ content) + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + List<ContentPacketExtension> contents = content.getContentList(); + Iterable<ContentPacketExtension> answerContents; + JingleIQ contentIQ; + boolean noCands = false; + MediaStream oldVideoStream = mediaHandler.getStream(MediaType.VIDEO); + + if(logger.isInfoEnabled()) + logger.info("Looking for candidates in content-add."); + try + { + if(!contentAddWithNoCands) + { + mediaHandler.processOffer(contents); + + /* + * Gingle transport will not put candidate in session-initiate + * and content-add. + */ + for(ContentPacketExtension c : contents) + { + if(JingleUtils.getFirstCandidate(c, 1) == null) + { + contentAddWithNoCands = true; + noCands = true; + } + } + } + + // if no candidates are present, launch a new Thread which will + // process and wait for the connectivity establishment (otherwise + // the existing thread will be blocked and thus cannot receive + // transport-info with candidates + if(noCands) + { + new Thread() + { + @Override + public void run() + { + try + { + synchronized(candSyncRoot) + { + candSyncRoot.wait(); + } + } + catch(InterruptedException e) + { + } + + processContentAdd(content); + contentAddWithNoCands = false; + } + }.start(); + if(logger.isInfoEnabled()) + logger.info("No candidates found in content-add, started " + + "new thread."); + return; + } + + mediaHandler + .getTransportManager() + .wrapupConnectivityEstablishment(); + if(logger.isInfoEnabled()) + logger.info("Wrapping up connectivity establishment"); + answerContents = mediaHandler.generateSessionAccept(); + contentIQ = null; + } + catch(Exception e) + { + logger.warn("Exception occurred", e); + + answerContents = null; + contentIQ + = JinglePacketFactory.createContentReject( + getProtocolProvider().getOurJID(), + this.peerJID, + getSID(), + answerContents); + } + + if(contentIQ == null) + { + /* send content-accept */ + contentIQ + = JinglePacketFactory.createContentAccept( + getProtocolProvider().getOurJID(), + this.peerJID, + getSID(), + answerContents); + for (ContentPacketExtension c : answerContents) + setSenders(getMediaType(c), c.getSenders()); + } + + getProtocolProvider().getConnection().sendPacket(contentIQ); + mediaHandler.start(); + + /* + * If a remote peer turns her video on in a conference which is hosted + * by the local peer and the local peer is not streaming her local + * video, reinvite the other remote peers to enable RTP translation. + */ + if (oldVideoStream == null) + { + MediaStream newVideoStream + = mediaHandler.getStream(MediaType.VIDEO); + + if ((newVideoStream != null) + && mediaHandler.isRTPTranslationEnabled(MediaType.VIDEO)) + { + try + { + getCall().modifyVideoContent(); + } + catch (OperationFailedException ofe) + { + logger.error("Failed to enable RTP translation", ofe); + } + } + } + } + + /** + * Processes the content-modify {@link JingleIQ}. + * + * @param content The {@link JingleIQ} that contains content that remote + * peer wants to be modified + */ + public void processContentModify(JingleIQ content) + { + ContentPacketExtension ext = content.getContentList().get(0); + MediaType mediaType = getMediaType(ext); + + try + { + boolean modify + = (ext.getFirstChildOfType(RtpDescriptionPacketExtension.class) + != null); + + getMediaHandler().reinitContent(ext.getName(), ext, modify); + + setSenders(mediaType, ext.getSenders()); + + if (MediaType.VIDEO.equals(mediaType)) + getCall().modifyVideoContent(); + } + catch(Exception e) + { + logger.info("Failed to process an incoming content-modify", e); + + // Send an error response. + String reason = "Error: " + e.getMessage(); + JingleIQ errResp + = JinglePacketFactory.createSessionTerminate( + getProtocolProvider().getOurJID(), + peerJID, + sessionInitIQ.getSID(), + Reason.INCOMPATIBLE_PARAMETERS, + reason); + + setState(CallPeerState.FAILED, reason); + getProtocolProvider().getConnection().sendPacket(errResp); + return; + } + } + + /** + * Processes the content-reject {@link JingleIQ}. + * + * @param content The {@link JingleIQ} + */ + public void processContentReject(JingleIQ content) + { + if(content.getContentList().isEmpty()) + { + //send an error response; + JingleIQ errResp = JinglePacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, + "Error: content rejected"); + + setState(CallPeerState.FAILED, "Error: content rejected"); + getProtocolProvider().getConnection().sendPacket(errResp); + return; + } + } + + /** + * Processes the content-remove {@link JingleIQ}. + * + * @param content The {@link JingleIQ} that contains content that remote + * peer wants to be removed + */ + public void processContentRemove(JingleIQ content) + { + List<ContentPacketExtension> contents = content.getContentList(); + boolean videoContentRemoved = false; + + if (!contents.isEmpty()) + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + for(ContentPacketExtension c : contents) + { + mediaHandler.removeContent(c.getName()); + + MediaType mediaType = getMediaType(c); + setSenders(mediaType, SendersEnum.none); + + if (MediaType.VIDEO.equals(mediaType)) + videoContentRemoved = true; + } + + /* + * TODO XEP-0166: Jingle says: If the content-remove results in zero + * content definitions for the session, the entity that receives the + * content-remove SHOULD send a session-terminate action to the + * other party (since a session with no content definitions is + * void). + */ + } + + if (videoContentRemoved) + { + // removing of the video content might affect the other sessions + // in the call + try + { + getCall().modifyVideoContent(); + } + catch (Exception e) + { + logger.warn("Failed to update Jingle sessions"); + } + } + } + + /** + * Processes a session-accept {@link JingleIQ}. + * + * @param sessionInitIQ The session-accept {@link JingleIQ} to process. + */ + public void processSessionAccept(JingleIQ sessionInitIQ) + { + this.sessionInitIQ = sessionInitIQ; + + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + List<ContentPacketExtension> answer = sessionInitIQ.getContentList(); + + try + { + mediaHandler + .getTransportManager() + .wrapupConnectivityEstablishment(); + mediaHandler.processAnswer(answer); + for (ContentPacketExtension c : answer) + setSenders(getMediaType(c), c.getSenders()); + } + catch(Exception exc) + { + if (logger.isInfoEnabled()) + logger.info("Failed to process a session-accept", exc); + + //send an error response; + JingleIQ errResp = JinglePacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, + exc.getClass().getName() + ": " + exc.getMessage()); + + setState(CallPeerState.FAILED, "Error: " + exc.getMessage()); + getProtocolProvider().getConnection().sendPacket(errResp); + return; + } + + //tell everyone we are connected so that the audio notifications would + //stop + setState(CallPeerState.CONNECTED); + + mediaHandler.start(); + + /* + * If video was added to the call after we sent the session-initiate + * to this peer, it needs to be added to this peer's session with a + * content-add. + */ + sendModifyVideoContent(); + } + + /** + * Handles the specified session <tt>info</tt> packet according to its + * content. + * + * @param info the {@link SessionInfoPacketExtension} that we just received. + */ + public void processSessionInfo(SessionInfoPacketExtension info) + { + switch (info.getType()) + { + case ringing: + setState(CallPeerState.ALERTING_REMOTE_SIDE); + break; + case hold: + getMediaHandler().setRemotelyOnHold(true); + reevalRemoteHoldStatus(); + break; + case unhold: + case active: + getMediaHandler().setRemotelyOnHold(false); + reevalRemoteHoldStatus(); + break; + default: + logger.warn("Received SessionInfoPacketExtension of unknown type"); + } + } + + /** + * Processes the session initiation {@link JingleIQ} that we were created + * with, passing its content to the media handler and then sends either a + * "session-info/ringing" or a "session-terminate" response. + * + * @param sessionInitIQ The {@link JingleIQ} that created the session that + * we are handling here. + */ + protected synchronized void processSessionInitiate(JingleIQ sessionInitIQ) + { + // Do initiate the session. + this.sessionInitIQ = sessionInitIQ; + this.initiator = true; + + // This is the SDP offer that came from the initial session-initiate. + // Contrary to SIP, we are guaranteed to have content because XEP-0166 + // says: "A session consists of at least one content type at a time." + List<ContentPacketExtension> offer = sessionInitIQ.getContentList(); + + try + { + getMediaHandler().processOffer(offer); + + CoinPacketExtension coin = null; + + for(PacketExtension ext : sessionInitIQ.getExtensions()) + { + if(ext.getElementName().equals( + CoinPacketExtension.ELEMENT_NAME)) + { + coin = (CoinPacketExtension)ext; + break; + } + } + + /* does the call peer acts as a conference focus ? */ + if(coin != null) + { + setConferenceFocus(Boolean.parseBoolean( + (String)coin.getAttribute("isfocus"))); + } + } + catch(Exception ex) + { + logger.info("Failed to process an incoming session initiate", ex); + + //send an error response; + String reasonText = "Error: " + ex.getMessage(); + JingleIQ errResp + = JinglePacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), + 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); + } + + //send a ringing response + if (logger.isTraceEnabled()) + logger.trace("will send ringing response: "); + + getProtocolProvider().getConnection().sendPacket( + JinglePacketFactory.createRinging(sessionInitIQ)); + + synchronized(sessionInitiateSyncRoot) + { + sessionInitiateProcessed = true; + sessionInitiateSyncRoot.notify(); + } + + //if this is a 3264 initiator, let's give them an early peek at our + //answer so that they could start ICE (SIP-2-Jingle gateways won't + //be able to send their candidates unless they have this) + DiscoverInfo discoverInfo = getDiscoveryInfo(); + if ((discoverInfo != null) + && discoverInfo.containsFeature( + ProtocolProviderServiceJabberImpl.URN_IETF_RFC_3264)) + { + getProtocolProvider().getConnection().sendPacket( + JinglePacketFactory.createDescriptionInfo( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), + getMediaHandler().getLocalContentList())); + } + } + + /** + * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a + * reason to the user, if there is one. + * + * @param jingleIQ the {@link JingleIQ} that's terminating our session. + */ + public void processSessionTerminate(JingleIQ jingleIQ) + { + String reasonStr = "Call ended by remote side."; + ReasonPacketExtension reasonExt = jingleIQ.getReason(); + + if(reasonExt != null) + { + Reason reason = reasonExt.getReason(); + + if(reason != null) + reasonStr += " Reason: " + reason.toString() + "."; + + String text = reasonExt.getText(); + + if(text != null) + reasonStr += " " + text; + } + + setState(CallPeerState.DISCONNECTED, reasonStr); + } + + /** + * Processes a specific "XEP-0251: Jingle Session Transfer" + * <tt>transfer</tt> packet (extension). + * + * @param transfer the "XEP-0251: Jingle Session Transfer" transfer packet + * (extension) to process + * @throws OperationFailedException if anything goes wrong while processing + * the specified <tt>transfer</tt> packet (extension) + */ + public void processTransfer(TransferPacketExtension transfer) + throws OperationFailedException + { + String attendantAddress = transfer.getFrom(); + + if (attendantAddress == null) + { + throw new OperationFailedException( + "Session transfer must contain a \'from\' attribute value.", + OperationFailedException.ILLEGAL_ARGUMENT); + } + + String calleeAddress = transfer.getTo(); + + if (calleeAddress == null) + { + throw new OperationFailedException( + "Session transfer must contain a \'to\' attribute value.", + OperationFailedException.ILLEGAL_ARGUMENT); + } + + // Checks if the transfer remote peer is contained by the roster of this + // account. + Roster roster = getProtocolProvider().getConnection().getRoster(); + if(!roster.contains(StringUtils.parseBareAddress(calleeAddress))) + { + String failedMessage = + "Transfer impossible:\n" + + "Account roster does not contain transfer peer: " + + StringUtils.parseBareAddress(calleeAddress); + setState(CallPeerState.FAILED, failedMessage); + logger.info(failedMessage); + } + + OperationSetBasicTelephonyJabberImpl basicTelephony + = (OperationSetBasicTelephonyJabberImpl) + getProtocolProvider() + .getOperationSet(OperationSetBasicTelephony.class); + CallJabberImpl calleeCall = new CallJabberImpl(basicTelephony); + TransferPacketExtension calleeTransfer = new TransferPacketExtension(); + String sid = transfer.getSID(); + + calleeTransfer.setFrom(attendantAddress); + if (sid != null) + { + calleeTransfer.setSID(sid); + calleeTransfer.setTo(calleeAddress); + } + basicTelephony.createOutgoingCall( + calleeCall, + calleeAddress, + Arrays.asList(new PacketExtension[] { calleeTransfer })); + } + + /** + * Processes the <tt>transport-info</tt> {@link JingleIQ}. + * + * @param jingleIQ the <tt>transport-info</tt> {@link JingleIQ} to process + */ + public void processTransportInfo(JingleIQ jingleIQ) + { + /* + * The transport-info action is used to exchange transport candidates so + * it only concerns the mediaHandler. + */ + try + { + if(isInitiator()) + { + synchronized(sessionInitiateSyncRoot) + { + if(!sessionInitiateProcessed) + { + try + { + sessionInitiateSyncRoot.wait(); + } + catch(InterruptedException e) + { + } + } + } + } + + getMediaHandler().processTransportInfo( + jingleIQ.getContentList()); + } + catch (OperationFailedException ofe) + { + logger.warn("Failed to process an incoming transport-info", ofe); + + //send an error response + String reasonText = "Error: " + ofe.getMessage(); + JingleIQ errResp + = JinglePacketFactory.createSessionTerminate( + getProtocolProvider().getOurJID(), + peerJID, + sessionInitIQ.getSID(), + Reason.GENERAL_ERROR, + reasonText); + + setState(CallPeerState.FAILED, reasonText); + getProtocolProvider().getConnection().sendPacket(errResp); + + return; + } + + synchronized(candSyncRoot) + { + candSyncRoot.notify(); + } + } + + /** + * Puts the <tt>CallPeer</tt> represented by this instance on or off hold. + * + * @param onHold <tt>true</tt> to have the <tt>CallPeer</tt> put on hold; + * <tt>false</tt>, otherwise + * + * @throws OperationFailedException if we fail to construct or send the + * INVITE request putting the remote side on/off hold. + */ + public void putOnHold(boolean onHold) + throws OperationFailedException + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + mediaHandler.setLocallyOnHold(onHold); + + SessionInfoType type; + + if(onHold) + type = SessionInfoType.hold; + else + { + type = SessionInfoType.unhold; + getMediaHandler().reinitAllContents(); + } + + //we are now on hold and need to realize this before potentially + //spoiling it all with an exception while sending the packet :). + reevalLocalHoldStatus(); + + JingleIQ onHoldIQ = JinglePacketFactory.createSessionInfo( + getProtocolProvider().getOurJID(), + peerJID, + getSID(), + type); + + getProtocolProvider().getConnection().sendPacket(onHoldIQ); + } + + /** + * Send a <tt>content-add</tt> to add video setup. + */ + private void sendAddVideoContent() + { + List<ContentPacketExtension> contents; + + try + { + contents = getMediaHandler().createContentList(MediaType.VIDEO); + } + catch(Exception exc) + { + logger.warn("Failed to gather content for video type", exc); + return; + } + + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + JingleIQ contentIQ + = JinglePacketFactory.createContentAdd( + protocolProvider.getOurJID(), + this.peerJID, + getSID(), + contents); + + protocolProvider.getConnection().sendPacket(contentIQ); + } + + /** + * Sends a <tt>content</tt> message to reflect changes in the setup such as + * the local peer/user becoming a conference focus. + */ + public void sendCoinSessionInfo() + { + JingleIQ sessionInfoIQ + = JinglePacketFactory.createSessionInfo( + getProtocolProvider().getOurJID(), + this.peerJID, + getSID()); + CoinPacketExtension coinExt + = new CoinPacketExtension(getCall().isConferenceFocus()); + + sessionInfoIQ.addExtension(coinExt); + getProtocolProvider().getConnection().sendPacket(sessionInfoIQ); + } + + /** + * Returns the <tt>MediaDirection</tt> that should be set for the content + * of type <tt>mediaType</tt> in the Jingle session for this + * <tt>CallPeer</tt>. + * If we are the focus of a conference and are doing RTP translation, + * takes into account the other <tt>CallPeer</tt>s in the <tt>Call</tt>. + * + * @param mediaType the <tt>MediaType</tt> for which to return the + * <tt>MediaDirection</tt> + * @return the <tt>MediaDirection</tt> that should be used for the content + * of type <tt>mediaType</tt> in the Jingle session for this + * <tt>CallPeer</tt>. + */ + private MediaDirection getDirectionForJingle(MediaType mediaType) + { + MediaDirection direction = MediaDirection.INACTIVE; + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + // If we are streaming media, the direction should allow sending + if ( (MediaType.AUDIO == mediaType && + mediaHandler.isLocalAudioTransmissionEnabled()) || + (MediaType.VIDEO == mediaType && + isLocalVideoStreaming())) + direction = direction.or(MediaDirection.SENDONLY); + + // If we are receiving media from this CallPeer, the direction should + // allow receiving + SendersEnum senders = getSenders(mediaType); + if (senders == null || senders == SendersEnum.both || + (isInitiator() && senders == SendersEnum.initiator) || + (!isInitiator() && senders == SendersEnum.responder)) + direction = direction.or(MediaDirection.RECVONLY); + + // If we are the focus of a conference and we are receiving media from + // another CallPeer in the same Call, the direction should allow sending + CallJabberImpl call = getCall(); + if (call != null && call.isConferenceFocus()) + { + for (CallPeerJabberImpl peer : call.getCallPeerList()) + { + if (peer != this) + { + senders = peer.getSenders(mediaType); + if (senders == null || senders == SendersEnum.both || + (peer.isInitiator() + && senders == SendersEnum.initiator) || + (!peer.isInitiator() + && senders == SendersEnum.responder)) + { + direction = direction.or(MediaDirection.SENDONLY); + break; + } + } + } + } + + return direction; + } + + /** + * Send, if necessary, a jingle <tt>content</tt> message to reflect change + * in video setup. Whether the jingle session should have a video content, + * and if so, the value of the <tt>senders</tt> field is determined + * based on whether we are streaming local video and, if we are the focus + * of a conference, on the other peers in the conference. + * The message can be content-modify if video content exists (and the + * <tt>senders</tt> field changes), content-add or content-remove. + * + * @return <tt>true</tt> if a jingle <tt>content</tt> message was sent. + */ + public boolean sendModifyVideoContent() + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + MediaDirection direction = getDirectionForJingle(MediaType.VIDEO); + + ContentPacketExtension remoteContent + = mediaHandler.getLocalContent(MediaType.VIDEO.toString()); + + if (remoteContent == null) + { + if (direction == MediaDirection.INACTIVE) + { + // no video content, none needed + return false; + } + else + { + if (getState() == CallPeerState.CONNECTED) + { + if (logger.isInfoEnabled()) + logger.info("Adding video content for " + this); + sendAddVideoContent(); + return true; + } + return false; + } + } + else + { + if (direction == MediaDirection.INACTIVE) + { + sendRemoveVideoContent(); + return true; + } + } + + SendersEnum senders = getSenders(MediaType.VIDEO); + if (senders == null) + senders = SendersEnum.both; + + SendersEnum newSenders = SendersEnum.none; + if (MediaDirection.SENDRECV == direction) + newSenders = SendersEnum.both; + else if (MediaDirection.RECVONLY == direction) + newSenders = isInitiator() + ? SendersEnum.initiator : SendersEnum.responder; + else if (MediaDirection.SENDONLY == direction) + newSenders = isInitiator() + ? SendersEnum.responder : SendersEnum.initiator; + + /* + * Send Content-Modify + */ + ContentPacketExtension ext = new ContentPacketExtension(); + String remoteContentName = remoteContent.getName(); + + ext.setSenders(newSenders); + ext.setCreator(remoteContent.getCreator()); + ext.setName(remoteContentName); + + if (newSenders != senders) + { + if (logger.isInfoEnabled()) + logger.info("Sending content modify, senders: " + + senders + "->" + newSenders); + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + JingleIQ contentIQ + = JinglePacketFactory.createContentModify( + protocolProvider.getOurJID(), + this.peerJID, + getSID(), + ext); + + protocolProvider.getConnection().sendPacket(contentIQ); + } + + try + { + mediaHandler.reinitContent(remoteContentName, ext, false); + mediaHandler.start(); + } + catch(Exception e) + { + logger.warn("Exception occurred during media reinitialization", e); + } + + return (newSenders != senders); + } + + /** + * Send a <tt>content</tt> message to reflect change in video setup (start + * or stop). + */ + public void sendModifyVideoResolutionContent() + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + ContentPacketExtension remoteContent + = mediaHandler.getRemoteContent(MediaType.VIDEO.toString()); + ContentPacketExtension content; + + logger.info("send modify-content to change resolution"); + + // send content-modify with RTP description + + // create content list with resolution + try + { + content = mediaHandler.createContentForMedia(MediaType.VIDEO); + } + catch (Exception e) + { + logger.warn("Failed to gather content for video type", e); + return; + } + + // if we are only receiving video senders is null + SendersEnum senders = remoteContent.getSenders(); + + if (senders != null) + content.setSenders(senders); + + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + JingleIQ contentIQ + = JinglePacketFactory.createContentModify( + protocolProvider.getOurJID(), + this.peerJID, + getSID(), + content); + + protocolProvider.getConnection().sendPacket(contentIQ); + + try + { + mediaHandler.reinitContent(remoteContent.getName(), content, false); + mediaHandler.start(); + } + catch(Exception e) + { + logger.warn("Exception occurred when media reinitialization", e); + } + } + + /** + * Send a <tt>content-remove</tt> to remove video setup. + */ + private void sendRemoveVideoContent() + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + ContentPacketExtension content = new ContentPacketExtension(); + ContentPacketExtension remoteContent + = mediaHandler.getRemoteContent(MediaType.VIDEO.toString()); + if (remoteContent == null) + return; + String remoteContentName = remoteContent.getName(); + + content.setName(remoteContentName); + content.setCreator(remoteContent.getCreator()); + content.setSenders(remoteContent.getSenders()); + + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + JingleIQ contentIQ + = JinglePacketFactory.createContentRemove( + protocolProvider.getOurJID(), + this.peerJID, + getSID(), + Arrays.asList(content)); + + protocolProvider.getConnection().sendPacket(contentIQ); + mediaHandler.removeContent(remoteContentName); + setSenders(MediaType.VIDEO, SendersEnum.none); + } + + /** + * Sends local candidate addresses from the local peer to the remote peer + * using the <tt>transport-info</tt> {@link JingleIQ}. + * + * @param contents the local candidate addresses to be sent from the local + * peer to the remote peer using the <tt>transport-info</tt> + * {@link JingleIQ} + */ + protected void sendTransportInfo(Iterable<ContentPacketExtension> contents) + { + // if the call is canceled, do not start sending candidates in + // transport-info + if(cancelled) + return; + + JingleIQ transportInfo = new JingleIQ(); + + for (ContentPacketExtension content : contents) + transportInfo.addContent(content); + + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + + transportInfo.setAction(JingleAction.TRANSPORT_INFO); + transportInfo.setFrom(protocolProvider.getOurJID()); + transportInfo.setSID(getSID()); + transportInfo.setTo(getAddress()); + transportInfo.setType(IQ.Type.SET); + + PacketCollector collector + = protocolProvider.getConnection().createPacketCollector( + new PacketIDFilter(transportInfo.getPacketID())); + + protocolProvider.getConnection().sendPacket(transportInfo); + collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + } + + @Override + public void setState(CallPeerState newState, String reason, int reasonCode) + { + CallPeerState oldState = getState(); + try + { + /* + * We need to dispose of the transport manager before the + * 'call' field is set to null, because if Jitsi Videobridge is in + * use, it (the call) is needed in order to expire the + * Videobridge channels. + */ + if (CallPeerState.DISCONNECTED.equals(newState) + || CallPeerState.FAILED.equals(newState)) + getMediaHandler().getTransportManager().close(); + } + finally + { + super.setState(newState, reason, reasonCode); + } + + if (CallPeerState.isOnHold(oldState) + && CallPeerState.CONNECTED.equals(newState)) + { + try + { + getCall().modifyVideoContent(); + } + catch (OperationFailedException ofe) + { + logger.error("Failed to update call video state after " + + "'hold' status removed for "+this); + } + } + } + + /** + * Transfer (in the sense of call transfer) this <tt>CallPeer</tt> to a + * specific callee address which may optionally be participating in an + * active <tt>Call</tt>. + * + * @param to the address of the callee to transfer this <tt>CallPeer</tt> to + * @param sid the Jingle session ID of the active <tt>Call</tt> between the + * local peer and the callee in the case of attended transfer; <tt>null</tt> + * in the case of unattended transfer + * @throws OperationFailedException if something goes wrong + */ + protected void transfer(String to, String sid) + throws OperationFailedException + { + JingleIQ transferSessionInfo = new JingleIQ(); + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + + transferSessionInfo.setAction(JingleAction.SESSION_INFO); + transferSessionInfo.setFrom(protocolProvider.getOurJID()); + transferSessionInfo.setSID(getSID()); + transferSessionInfo.setTo(getAddress()); + transferSessionInfo.setType(IQ.Type.SET); + + TransferPacketExtension transfer = new TransferPacketExtension(); + + // Attended transfer. + if (sid != null) + { + /* + * Not really sure what the value of the "from" attribute of the + * "transfer" element should be but the examples in "XEP-0251: + * Jingle Session Transfer" has it in the case of attended transfer. + */ + transfer.setFrom(protocolProvider.getOurJID()); + transfer.setSID(sid); + + // Puts on hold the 2 calls before making the attended transfer. + OperationSetBasicTelephonyJabberImpl basicTelephony + = (OperationSetBasicTelephonyJabberImpl) + protocolProvider.getOperationSet( + OperationSetBasicTelephony.class); + CallPeerJabberImpl callPeer = basicTelephony.getActiveCallPeer(sid); + if(callPeer != null) + { + if(!CallPeerState.isOnHold(callPeer.getState())) + { + callPeer.putOnHold(true); + } + } + + if(!CallPeerState.isOnHold(this.getState())) + { + this.putOnHold(true); + } + } + transfer.setTo(to); + + transferSessionInfo.addExtension(transfer); + + Connection connection = protocolProvider.getConnection(); + PacketCollector collector = connection.createPacketCollector( + new PacketIDFilter(transferSessionInfo.getPacketID())); + protocolProvider.getConnection().sendPacket(transferSessionInfo); + + Packet result + = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + if(result == null) + { + // Log the failed transfer call and notify the user. + throw new OperationFailedException( + "No response to the \"transfer\" request.", + OperationFailedException.ILLEGAL_ARGUMENT); + } + else if (((IQ) result).getType() != IQ.Type.RESULT) + { + // Log the failed transfer call and notify the user. + throw new OperationFailedException( + "Remote peer does not manage call \"transfer\"." + + "Response to the \"transfer\" request is: " + + ((IQ) result).getType(), + OperationFailedException.ILLEGAL_ARGUMENT); + } + else + { + String message = ((sid == null) ? "Unattended" : "Attended") + + " transfer to: " + + to; + // Implements the SIP behavior: once the transfer is accepted, the + // current call is closed. + hangup( + false, + message, + new ReasonPacketExtension(Reason.SUCCESS, + message, + new TransferredPacketExtension())); + } + } + + /** + * {@inheritDoc} + */ + public String getEntity() + { + return getAddress(); + } + + /** + * {@inheritDoc} + * + * In Jingle there isn't an actual "direction" parameter. We use the + * <tt>senders</tt> field to calculate the direction. + */ + @Override + public MediaDirection getDirection(MediaType mediaType) + { + SendersEnum senders = getSenders(mediaType); + + if (senders == SendersEnum.none) + { + return MediaDirection.INACTIVE; + } + else if (senders == null || senders == SendersEnum.both) + { + return MediaDirection.SENDRECV; + } + else if (senders == SendersEnum.initiator) + { + return + isInitiator() + ? MediaDirection.RECVONLY + : MediaDirection.SENDONLY; + } + else //senders == SendersEnum.responder + { + return + isInitiator() + ? MediaDirection.SENDONLY + : MediaDirection.RECVONLY; + } + } + + /** + * Gets the current value of the <tt>senders</tt> field of the content with + * name <tt>mediaType</tt> in the Jingle session with this + * <tt>CallPeer</tt>. + * + * @param mediaType the <tt>MediaType</tt> for which to get the current + * value of the <tt>senders</tt> field. + * @return the current value of the <tt>senders</tt> field of the content + * with name <tt>mediaType</tt> in the Jingle session with this + * <tt>CallPeer</tt>. + */ + public SendersEnum getSenders(MediaType mediaType) + { + switch (mediaType) + { + case AUDIO: + return audioSenders; + case VIDEO: + return videoSenders; + default: + return SendersEnum.none; + } + } + + /** + * Set the current value of the <tt>senders</tt> field of the content with + * name <tt>mediaType</tt> in the Jingle session with this <tt>CallPeer</tt> + * @param mediaType the <tt>MediaType</tt> for which to get the current + * value of the <tt>senders</tt> field. + * @param senders the value to set + */ + public void setSenders(MediaType mediaType, SendersEnum senders) + { + switch(mediaType) + { + case AUDIO: + this.audioSenders = senders; + break; + case VIDEO: + this.videoSenders = senders; + break; + default: + throw new IllegalArgumentException("mediaType"); + } + } + + /** + * Gets the <tt>MediaType</tt> of <tt>content</tt>. If <tt>content</tt> + * does not have a <tt>description</tt> child and therefore not + * <tt>MediaType</tt> can be associated with it, tries to take the + * <tt>MediaType</tt> from the session's already established contents with + * the same name as <tt>content</tt> + * @param content the <tt>ContentPacketExtention</tt> for which to get the + * <tt>MediaType</tt> + * @return the <tt>MediaType</tt> of <tt>content</tt>. + */ + public MediaType getMediaType(ContentPacketExtension content) + { + String contentName = content.getName(); + if (contentName == null) + return null; + + MediaType mediaType = JingleUtils.getMediaType(content); + if (mediaType == null) + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + for (MediaType m : MediaType.values()) + { + ContentPacketExtension sessionContent + = mediaHandler.getRemoteContent(m.toString()); + if (sessionContent == null) + sessionContent = mediaHandler.getLocalContent(m.toString()); + + if (sessionContent != null + && contentName.equals(sessionContent.getName())) + { + mediaType = m; + break; + } + } + } + + return mediaType; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java index 2c8845d..979d696 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java @@ -937,19 +937,18 @@ public class CallPeerMediaHandlerJabberImpl if (supportedTransports != null && supportedTransports.length > 0) { - for (int i = 0; i < supportedTransports.length; i++) + for(String supportedTransport : supportedTransports) { if (ProtocolProviderServiceJabberImpl. URN_XMPP_JINGLE_ICE_UDP_1. - equals(supportedTransports[i])) + equals(supportedTransport)) { - transportManager - = new IceUdpTransportManager(peer); + transportManager = new IceUdpTransportManager(peer); break; } else if (ProtocolProviderServiceJabberImpl. - URN_XMPP_JINGLE_RAW_UDP_0. - equals(supportedTransports[i])) + URN_XMPP_JINGLE_RAW_UDP_0. + equals(supportedTransport)) { transportManager = new RawUdpTransportManager(peer); @@ -1117,12 +1116,11 @@ public class CallPeerMediaHandlerJabberImpl List<Component> visualComponents = new LinkedList<Component>(); - for (int i = 0; i < remoteSSRCs.length; i++) + for(int remoteSSRC : remoteSSRCs) { - int remoteSSRC = remoteSSRCs[i]; Component visualComponent - = videoStream.getVisualComponent( - 0xFFFFFFFFL & remoteSSRC); + = videoStream.getVisualComponent( + 0xFFFFFFFFL & remoteSSRC); if (visualComponent != null) visualComponents.add(visualComponent); @@ -1605,7 +1603,7 @@ public class CallPeerMediaHandlerJabberImpl { List<MediaFormat> fmts = supportedFormats; - if(fmts.size() > 0) + if(!fmts.isEmpty()) { MediaFormat fmt = fmts.get(0); @@ -2108,21 +2106,17 @@ public class CallPeerMediaHandlerJabberImpl * TODO The transportManager is going to be changed so it may need to be * disposed of prior to the change. */ - - if (xmlns.equals( - ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1)) + switch (xmlns) { - transportManager = new IceUdpTransportManager(peer); - } - else if (xmlns.equals( - ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0)) - { - transportManager = new RawUdpTransportManager(peer); - } - else - { - throw new IllegalArgumentException( - "Unsupported Jingle transport " + xmlns); + case ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1: + transportManager = new IceUdpTransportManager(peer); + break; + case ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0: + transportManager = new RawUdpTransportManager(peer); + break; + default: + throw new IllegalArgumentException("Unsupported Jingle " + + "transport " + xmlns); } synchronized(transportManagerSyncRoot) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java index 8a6f3ed..6c4c358 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java @@ -609,7 +609,7 @@ public class ChatRoomJabberImpl this.provider.getConnection().addPacketListener( presenceListener, new AndFilter( - new FromMatchesFilter(multiUserChat.getRoom()), + FromMatchesFilter.create(multiUserChat.getRoom()), new PacketTypeFilter( org.jivesoftware.smack.packet.Presence.class))); if(password == null) @@ -868,7 +868,7 @@ public class ChatRoomJabberImpl clearCachedConferenceDescriptionList(); - XMPPConnection connection = this.provider.getConnection(); + Connection connection = this.provider.getConnection(); try { // if we are already disconnected @@ -1896,6 +1896,23 @@ public class ChatRoomJabberImpl } /** + * Removes given <tt>PacketExtension</tt> from the MUC presence and + * publishes it immediately. + * @param extension the <tt>PacketExtension</tt> to be removed from the MUC + * presence. + */ + public void removePresenceExtension(PacketExtension extension) + { + if (lastPresenceSent != null) + { + setPacketExtension( + lastPresenceSent, null, extension.getNamespace()); + + provider.getConnection().sendPacket(lastPresenceSent); + } + } + + /** * Returns the ids of the users that has the member role in the room. * When the room is member only, this are the users allowed to join. * @return the ids of the users that has the member role in the room. diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java index 1f4e3d8..c3c852c 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java @@ -58,11 +58,21 @@ public class IceUdpTransportManager = Logger.getLogger(IceUdpTransportManager.class); /** + * Default STUN server address. + */ + protected static final String DEFAULT_STUN_SERVER_ADDRESS = "stun.jitsi.net"; + + /** + * Default STUN server port. + */ + protected static final int DEFAULT_STUN_SERVER_PORT = 3478; + + /** * The ICE <tt>Component</tt> IDs in their common order used, for example, * by <tt>DefaultStreamConnector</tt>, <tt>MediaStreamTarget</tt>. */ private static final int[] COMPONENT_IDS - = new int[] { Component.RTP, Component.RTCP }; + = new int[] { Component.RTP, Component.RTCP }; /** * This is where we keep our answer between the time we get the offer and @@ -76,15 +86,6 @@ public class IceUdpTransportManager */ protected final Agent iceAgent; - /** - * Default STUN server address. - */ - protected static final String DEFAULT_STUN_SERVER_ADDRESS = "stun.jitsi.net"; - - /** - * Default STUN server port. - */ - protected static final int DEFAULT_STUN_SERVER_PORT = 3478; /** * Creates a new instance of this transport manager, binding it to the @@ -156,7 +157,10 @@ public class IceUdpTransportManager // in case user has canceled the login window if(credentials == null) + { + logger.info("Credentials were null. User has most likely canceled the login operation"); return null; + } //extract the password the user passed us. char[] pass = credentials.getPassword(); @@ -164,7 +168,10 @@ public class IceUdpTransportManager // the user didn't provide us a password (i.e. canceled the // operation) if(pass == null) + { + logger.info("Password was null. User has most likely canceled the login operation"); return null; + } password = new String(pass); if (credentials.isPasswordPersistent()) @@ -390,28 +397,29 @@ public class IceUdpTransportManager for (int i = 0; i < COMPONENT_IDS.length; i++) { Component component = stream.getComponent(COMPONENT_IDS[i]); - - if (component != null) + if (component == null) { - CandidatePair selectedPair = component.getSelectedPair(); - - if (selectedPair != null) - { - DatagramSocket streamConnectorSocket - = selectedPair.getLocalCandidate(). - getDatagramSocket(); + continue; + } - if (streamConnectorSocket != null) - { - streamConnectorSockets[i] = streamConnectorSocket; - streamConnectorSocketCount++; - } - } + DatagramSocket streamConnectorSocket = component.getSocket(); + if (streamConnectorSocket != null) + { + streamConnectorSockets[i] = streamConnectorSocket; + streamConnectorSocketCount++; + logger.trace("Added a streamConnectorSocket to the array " + + "StreamConnectorSocket and increased " + + "the count of streamConnectorSocketCount by one to " + + streamConnectorSocketCount); } } + if (streamConnectorSocketCount > 0) + { return streamConnectorSockets; + } } + return null; } @@ -742,20 +750,25 @@ public class IceUdpTransportManager ex); } - //let's now update the next port var as best we can: we would assume - //that all local candidates are bound on the same port and set it - //to the one just above. if the assumption is wrong the next bind - //would simply include one more bind retry. + // Attempt to minimize subsequent bind retries: see if we have allocated + // any ports from the dynamic range, and if so update the port tracker. + // Do NOT update the port tracker with non-dynamic ports (e.g. 4443 + // coming from TCP) because this will force it to revert back it its + // configured min port. When maxPort is reached, allocation will begin + // from minPort again, so we don't have to worry about wraps. try { - portTracker.setNextPort( - 1 - + stream - .getComponent(Component.RTCP) - .getLocalCandidates() - .get(0) - .getTransportAddress() - .getPort()); + int maxAllocatedPort = getMaxAllocatedPort( + stream, + portTracker.getMinPort(), + portTracker.getMaxPort()); + + if(maxAllocatedPort > 0) + { + int nextPort = 1 + maxAllocatedPort; + portTracker.setNextPort(nextPort); + logger.debug("Updating the port tracker min port: " + nextPort); + } } catch(Throwable t) { @@ -768,6 +781,48 @@ public class IceUdpTransportManager } /** + * @return the highest local port used by any of the local candidates of + * {@code iceStream}, which falls in the range [{@code min}, {@code max}]. + */ + private int getMaxAllocatedPort(IceMediaStream iceStream, int min, int max) + { + return + Math.max( + getMaxAllocatedPort( + iceStream.getComponent(Component.RTP), + min, max), + getMaxAllocatedPort( + iceStream.getComponent(Component.RTCP), + min, max)); + } + + /** + * @return the highest local port used by any of the local candidates of + * {@code component}, which falls in the range [{@code min}, {@code max}]. + */ + private int getMaxAllocatedPort(Component component, int min, int max) + { + int maxAllocatedPort = -1; + + if (component != null) + { + for (LocalCandidate candidate : component.getLocalCandidates()) + { + int candidatePort = candidate.getTransportAddress().getPort(); + + if (min <= candidatePort + && candidatePort <= max + && maxAllocatedPort < candidatePort) + { + maxAllocatedPort = candidatePort; + } + } + } + + return maxAllocatedPort; + } + + /** * Simply returns the list of local candidates that we gathered during the * harvest. * @@ -898,8 +953,12 @@ public class IceUdpTransportManager = transport.getChildExtensionsOfType( CandidatePacketExtension.class); - if (iceAgentStateIsRunning && (candidates.size() == 0)) + if (iceAgentStateIsRunning && candidates.isEmpty()) + { + logger.info("connectivity establishment has not been started " + + "because candidate list is empty"); return false; + } String media = e.getKey(); IceMediaStream stream = iceAgent.getStream(media); @@ -938,6 +997,12 @@ public class IceUdpTransportManager if (candidate.getGeneration() != generation) continue; + if (candidate.getIP() == null || "".equals(candidate.getIP())) + { + logger.warn("Skipped ICE candidate with empty IP"); + continue; + } + Component component = stream.getComponent(candidate.getComponent()); String relAddr; diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java b/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java index a0387df..c3137ad 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java @@ -151,7 +151,7 @@ public class InfoRetreiver List<GenericDetail> result = new LinkedList<GenericDetail>(); try { - XMPPConnection connection = jabberProvider.getConnection(); + Connection connection = jabberProvider.getConnection(); if(connection == null || !connection.isAuthenticated()) return null; diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JabberLoginStrategy.java b/src/net/java/sip/communicator/impl/protocol/jabber/JabberLoginStrategy.java index 438ea1b..de7578c 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/JabberLoginStrategy.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JabberLoginStrategy.java @@ -61,7 +61,7 @@ public interface JabberLoginStrategy * @param resource the XMPP resource * @return true to continue connecting, false to abort */ - public boolean login(XMPPConnection connection, String userName, + public boolean login(Connection connection, String userName, String resource) throws XMPPException; diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java index 0ea9a54..f2473dd 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java @@ -87,7 +87,7 @@ public class JingleNodesCandidate * @return the <tt>RelayedCandidateDatagramSocket</tt> of this * <tt>RelayedCandidate</tt> */ - public synchronized JingleNodesCandidateDatagramSocket + private synchronized JingleNodesCandidateDatagramSocket getRelayedCandidateDatagramSocket() { if (jingleNodesCandidateDatagramSocket == null) @@ -113,7 +113,7 @@ public class JingleNodesCandidate * <tt>Candidate</tt> */ @Override - public IceSocketWrapper getIceSocketWrapper() + protected IceSocketWrapper getCandidateIceSocketWrapper() { if (socket == null) { diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java index bb7d31b..d29be03 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java @@ -36,7 +36,7 @@ import org.xmpp.jnodes.smack.*; * @author Sebastien Vincent */ public class JingleNodesHarvester - extends CandidateHarvester + extends AbstractCandidateHarvester { /** * The <tt>Logger</tt> used by the <tt>JingleNodesHarvester</tt> class and @@ -125,7 +125,7 @@ public class JingleNodesHarvester } } - if (ciq != null && ciq.getRemoteport() > 0) + if (ciq != null) { ip = ciq.getHost(); port = ciq.getRemoteport(); @@ -136,6 +136,22 @@ public class JingleNodesHarvester " 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); @@ -160,6 +176,7 @@ public class JingleNodesHarvester candidates.add(local); } } + return candidates; } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/LoginByClientCertificateStrategy.java b/src/net/java/sip/communicator/impl/protocol/jabber/LoginByClientCertificateStrategy.java index 09c9462..4825e01 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/LoginByClientCertificateStrategy.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/LoginByClientCertificateStrategy.java @@ -116,7 +116,7 @@ class LoginByClientCertificateStrategy * accepted. * @throws XMPPException */ - public boolean login(XMPPConnection connection, String userName, + public boolean login(Connection connection, String userName, String resource) throws XMPPException { diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/LoginByPasswordStrategy.java b/src/net/java/sip/communicator/impl/protocol/jabber/LoginByPasswordStrategy.java index 43fc8a4..7034221 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/LoginByPasswordStrategy.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/LoginByPasswordStrategy.java @@ -115,7 +115,7 @@ public class LoginByPasswordStrategy * @return always true. * @throws XMPPException */ - public boolean login(XMPPConnection connection, String userName, + public boolean login(Connection connection, String userName, String resource) throws XMPPException { diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/MobileIndicator.java b/src/net/java/sip/communicator/impl/protocol/jabber/MobileIndicator.java index 926848c..8c801c6 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/MobileIndicator.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/MobileIndicator.java @@ -222,11 +222,13 @@ public class MobileIndicator /** * Caps for user has been changed. * @param user the user (full JID) + * @param fullJids a list of all resources of the user (full JIDs) * @param node the entity caps node#ver * @param online indicates if the user for which we're notified is online */ @Override - public void userCapsNodeAdded(String user, String node, boolean online) + public void userCapsNodeAdded(String user, ArrayList<String> fullJids, + String node, boolean online) { updateMobileIndicatorUsingCaps(user); } @@ -234,11 +236,13 @@ public class MobileIndicator /** * Caps for user has been changed. * @param user the user (full JID) + * @param fullJids a list of all resources of the user (full JIDs) * @param node the entity caps node#ver * @param online indicates if the user for which we're notified is online */ @Override - public void userCapsNodeRemoved(String user, String node, boolean online) + public void userCapsNodeRemoved(String user, ArrayList<String> fullJids, + String node, boolean online) { updateMobileIndicatorUsingCaps(user); } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java index 42a3916..2f35fef 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java @@ -815,6 +815,17 @@ public class OperationSetBasicInstantMessagingJabberImpl ForwardedPacketExtension.class); if(extensions.isEmpty()) return; + + // according to xep-0280 all carbons should come from + // our bare jid + if (!msg.getFrom().equals( + StringUtils.parseBareAddress( + jabberProvider.getOurJID()))) + { + logger.info("Received a carbon copy with wrong from!"); + return; + } + ForwardedPacketExtension forwardedExt = extensions.get(0); msg = forwardedExt.getMessage(); if(msg == null || msg.getBody() == null) @@ -1109,7 +1120,7 @@ public class OperationSetBasicInstantMessagingJabberImpl NewMailNotificationIQ.NAMESPACE, new NewMailNotificationProvider()); - XMPPConnection connection = jabberProvider.getConnection(); + Connection connection = jabberProvider.getConnection(); connection.addPacketListener( new MailboxIQListener(), new PacketTypeFilter(MailboxIQ.class)); diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java index cc36936..78027c0 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java @@ -280,7 +280,10 @@ public class OperationSetBasicTelephonyJabberImpl Iterable<PacketExtension> sessionInitiateExtensions) throws OperationFailedException { - return createOutgoingCall(call, calleeAddress, null, null); + if (calleeAddress.contains("/")) + return createOutgoingCall(call, calleeAddress, calleeAddress, null); + else + return createOutgoingCall(call, calleeAddress, null, null); } /** @@ -774,7 +777,7 @@ public class OperationSetBasicTelephonyJabberImpl */ private void unsubscribeForJinglePackets() { - XMPPConnection connection = protocolProvider.getConnection(); + Connection connection = protocolProvider.getConnection(); if(connection != null) connection.removePacketListener(this); diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetContactCapabilitiesJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetContactCapabilitiesJabberImpl.java index 0a6edfe..59cc17c 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetContactCapabilitiesJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetContactCapabilitiesJabberImpl.java @@ -26,7 +26,7 @@ import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jivesoftware.smack.packet.*; -import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.*; /** * Represents an <tt>OperationSet</tt> to query the <tt>OperationSet</tt>s @@ -52,6 +52,19 @@ public class OperationSetContactCapabilitiesJabberImpl = Logger.getLogger(OperationSetContactCapabilitiesJabberImpl.class); /** + * The name of the property used to control whether to use + * all resources to show capabilities + */ + public static final String PROP_XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES = + "net.java.sip.communicator.XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES"; + + /** + * The default value for the capabilities setting + */ + public static final boolean USE_ALL_RESOURCES_FOR_CAPABILITIES_DEFAULT = + true; + + /** * The list of <tt>OperationSet</tt> capabilities presumed to be supported * by a <tt>Contact</tt> when it is offline. */ @@ -276,6 +289,42 @@ public class OperationSetContactCapabilitiesJabberImpl } /** + * Gets the largest set of <tt>OperationSet</tt>s supported from a + * list of full JIDs. The returned <tt>OperationSet</tt>s are considered + * by the associated protocol provider to capabilities possessed by the + * specified <tt>contact</tt>. + * + * @param fullJids a list of full JIDs in which to find the resource with + * the most capabilities. + * @return the <tt>Map</tt> listing the most <tt>OperationSet</tt>s + * considered by the associated protocol provider to be supported by the + * specified <tt>contact</tt> (i.e. to be possessed as capabilities). + * Each supported <tt>OperationSet</tt> capability is represented by a + * <tt>Map.Entry</tt> with key equal to the <tt>OperationSet</tt> class + * name and value equal to the respective <tt>OperationSet</tt> instance + */ + protected Map<String, OperationSet> getLargestSupportedOperationSet( + ArrayList<String> fullJids) + { + Map<String, OperationSet> supportedOperationSets = + new HashMap<String, OperationSet>(); + if (fullJids!=null) + { + for (String fullJid : fullJids) + { + Map<String, OperationSet> newSupportedOperationSets= + getSupportedOperationSets(fullJid, true); + if (newSupportedOperationSets.size()> + supportedOperationSets.size()) + { + supportedOperationSets = newSupportedOperationSets; + } + } + } + return supportedOperationSets; + } + + /** * Gets the <tt>OperationSet</tt> corresponding to the specified * <tt>Class</tt> and supported by the specified <tt>Contact</tt>. If the * returned value is non-<tt>null</tt>, it indicates that the @@ -387,17 +436,19 @@ public class OperationSetContactCapabilitiesJabberImpl * record for a specific user about the caps node the user has. * * @param user the user (full JID) + * @param fullJids a list of all resources of the user (full JIDs) * @param node the entity caps node#ver * @param online indicates if the user is currently online * @see UserCapsNodeListener#userCapsNodeAdded(String, String, boolean) */ - public void userCapsNodeAdded(String user, String node, boolean online) + public void userCapsNodeAdded(String user, ArrayList<String> fullJids, + String node, boolean online) { /* * It doesn't matter to us whether a caps node has been added or removed * for the specified user because we report all changes. */ - userCapsNodeRemoved(user, node, online); + userCapsNodeChanged(user, fullJids, node, online); } /** @@ -405,45 +456,86 @@ public class OperationSetContactCapabilitiesJabberImpl * record for a specific user about the caps node the user has. * * @param user the user (full JID) + * @param fullJids a list of all resources of the user (full JIDs) + * @param node the entity caps node#ver + * @param online indicates if the user is currently online + * @see UserCapsNodeListener#userCapsNodeAdded(String, String, boolean) + */ + public void userCapsNodeRemoved(String user, ArrayList<String> fullJids, + String node, boolean online) + { + /* + * It doesn't matter to us whether a caps node has been added or removed + * for the specified user because we report all changes. + */ + userCapsNodeChanged(user, fullJids, node, online); + } + + /** + * Notifies this listener that an <tt>EntityCapsManager</tt> has changed a + * record for a specific user about the caps node the user has. + * + * @param user the user (full JID) + * @param fullJids a list of all resources of the user (full JIDs) * @param node the entity caps node#ver * @param online indicates if the given user is online - * @see UserCapsNodeListener#userCapsNodeRemoved(String, String, boolean) */ - public void userCapsNodeRemoved(String user, String node, boolean online) + public void userCapsNodeChanged(String user, ArrayList<String> fullJids, + String node, boolean online) { OperationSetPresence opsetPresence - = parentProvider.getOperationSet(OperationSetPresence.class); - - if (opsetPresence != null) - { - String jid = StringUtils.parseBareAddress(user); - Contact contact = opsetPresence.findContactByID(jid); - - // If the contact isn't null and is online we try to discover the - // new set of operation sets and to notify interested parties. - // Otherwise we ignore the event. - if (contact != null) + = parentProvider.getOperationSet(OperationSetPresence.class); + if (opsetPresence != null) { + if(JabberActivator.getConfigurationService() + .getBoolean( + PROP_XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES, + USE_ALL_RESOURCES_FOR_CAPABILITIES_DEFAULT) + && !fullJids.isEmpty()) { - if(online) + String bareJid = StringUtils.parseBareAddress(user); + Contact contact = opsetPresence.findContactByID(bareJid); + if (contact != null) { - // when going online we have received a presence - // and make sure we discover this particular jid - // for getSupportedOperationSets fireContactCapabilitiesEvent( contact, - ContactCapabilitiesEvent.SUPPORTED_OPERATION_SETS_CHANGED, - getSupportedOperationSets(user, - online)); + ContactCapabilitiesEvent. + SUPPORTED_OPERATION_SETS_CHANGED, + getLargestSupportedOperationSet(fullJids)); } - else + } + else + { + String jid = StringUtils.parseBareAddress(user); + Contact contact = opsetPresence.findContactByID(jid); + + // If the contact isn't null and is online we try to discover + // the new set of operation sets and to notify interested + // parties. Otherwise we ignore the event. + if (contact != null) { - // when offline, we use the contact, and selecting - // the most connected jid - // for getSupportedOperationSets - fireContactCapabilitiesEvent( - contact, - ContactCapabilitiesEvent.SUPPORTED_OPERATION_SETS_CHANGED, - getSupportedOperationSets(contact)); + if(online) + { + // when going online we have received a presence + // and make sure we discover this particular jid + // for getSupportedOperationSets + fireContactCapabilitiesEvent( + contact, + ContactCapabilitiesEvent. + SUPPORTED_OPERATION_SETS_CHANGED, + getSupportedOperationSets(user, + online)); + } + else + { + // when offline, we use the contact, and selecting + // the most connected jid + // for getSupportedOperationSets + fireContactCapabilitiesEvent( + contact, + ContactCapabilitiesEvent. + SUPPORTED_OPERATION_SETS_CHANGED, + getSupportedOperationSets(contact)); + } } } } @@ -460,7 +552,8 @@ public class OperationSetContactCapabilitiesJabberImpl { // If the user goes offline we ensure to remove the caps node. if (capsManager != null - && evt.getNewStatus().getStatus() < PresenceStatus.ONLINE_THRESHOLD) + && evt.getNewStatus().getStatus() < PresenceStatus.ONLINE_THRESHOLD + && !evt.isResourceChanged()) { capsManager.removeContactCapsNode(evt.getSourceContact()); } @@ -469,31 +562,59 @@ public class OperationSetContactCapabilitiesJabberImpl /** * Fires event that contact capabilities has changed. * @param user the user to search for its contact. + * @param fullJids a list of all resources of the user (full JIDs) */ - public void fireContactCapabilitiesChanged(String user) + public void fireContactCapabilitiesChanged(String user, + ArrayList<String> fullJids) { - OperationSetPresence opsetPresence + if(!JabberActivator.getConfigurationService() + .getBoolean( + PROP_XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES, + USE_ALL_RESOURCES_FOR_CAPABILITIES_DEFAULT) + || fullJids.isEmpty()) + { + OperationSetPresence opsetPresence = parentProvider.getOperationSet(OperationSetPresence.class); - if (opsetPresence != null) + if (opsetPresence != null) + { + String userID = StringUtils.parseBareAddress(user); + Contact contact = opsetPresence.findContactByID(userID); + + // this called by received discovery info for particular jid + // so we use its online and opsets for this particular jid + boolean online = false; + Presence presence = parentProvider.getConnection().getRoster() + .getPresence(user); + if(presence != null) + online = presence.isAvailable(); + + if(contact != null) + { + fireContactCapabilitiesEvent( + contact, + ContactCapabilitiesEvent. + SUPPORTED_OPERATION_SETS_CHANGED, + getSupportedOperationSets(user, online)); + } + } + } + else { - String userID = StringUtils.parseBareAddress(user); - Contact contact = opsetPresence.findContactByID(userID); - - // this called by received discovery info for particular jid - // so we use its online and opsets for this particular jid - boolean online = false; - Presence presence = parentProvider.getConnection().getRoster() - .getPresence(user); - if(presence != null) - online = presence.isAvailable(); - - if(contact != null) + OperationSetPresence opsetPresence + = parentProvider.getOperationSet(OperationSetPresence.class); + if (opsetPresence != null) { - fireContactCapabilitiesEvent( - contact, - ContactCapabilitiesEvent.SUPPORTED_OPERATION_SETS_CHANGED, - getSupportedOperationSets(user, online)); + String bareJid = StringUtils.parseBareAddress(user); + Contact contact = opsetPresence.findContactByID(bareJid); + if(contact != null) + { + fireContactCapabilitiesEvent( + contact, + ContactCapabilitiesEvent. + SUPPORTED_OPERATION_SETS_CHANGED, + getLargestSupportedOperationSet(fullJids)); + } } } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetJitsiMeetToolsJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetJitsiMeetToolsJabberImpl.java index 0fb6979..7ea4453 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetJitsiMeetToolsJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetJitsiMeetToolsJabberImpl.java @@ -72,6 +72,16 @@ public class OperationSetJitsiMeetToolsJabberImpl * {@inheritDoc} */ @Override + public void removePresenceExtension(ChatRoom chatRoom, + PacketExtension extension) + { + ((ChatRoomJabberImpl)chatRoom).removePresenceExtension(extension); + } + + /** + * {@inheritDoc} + */ + @Override public void setPresenceStatus(ChatRoom chatRoom, String statusMessage) { ((ChatRoomJabberImpl)chatRoom).publishPresenceStatus(statusMessage); diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java index 80bbb4e..cf53906 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java @@ -471,10 +471,10 @@ public class OperationSetMultiUserChatJabberImpl * Almost all <tt>MultiUserChat</tt> methods require an xmpp connection * param so I added this method only for the sake of utility. * - * @return the XMPPConnection currently in use by the jabber provider or + * @return the XMPP connection currently in use by the jabber provider or * null if jabber provider has yet to be initialized. */ - private XMPPConnection getXmppConnection() + private Connection getXmppConnection() { return (jabberProvider == null) ? null diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java index 69c168c..c502824 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java @@ -691,7 +691,7 @@ public class OperationSetPersistentPresenceJabberImpl */ assertConnected(); - XMPPConnection xmppConnection = parentProvider.getConnection(); + Connection xmppConnection = parentProvider.getConnection(); if (xmppConnection == null) { @@ -1124,7 +1124,7 @@ public class OperationSetPersistentPresenceJabberImpl ssContactList.cleanup(); - XMPPConnection connection = parentProvider.getConnection(); + Connection connection = parentProvider.getConnection(); if(connection != null) { connection.removePacketListener(subscribtionPacketListener); @@ -1528,6 +1528,19 @@ public class OperationSetPersistentPresenceJabberImpl o2, parentProvider).getStatus() - jabberStatusToPresenceStatus( o1, parentProvider).getStatus(); + // We have run out of "logical" ways to order + // the presences inside the TreeSet. We have + // make sure we are consinstent with equals. + // We do this by comparing the unique resource + // names. If this evaluates to 0 again, then we + // can safely assume this presence object + // represents the same resource and by that the + // same client. + if(res == 0) + { + res = o1.getFrom().compareTo( + o2.getFrom()); + } } return res; diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java index e81b43a..e5b83e7 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,585 +15,585 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.service.protocol.media.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.util.xml.*;
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.filter.*;
-import org.jivesoftware.smack.packet.*;
-import org.jivesoftware.smack.packet.IQ.Type;
-import org.jivesoftware.smack.util.*;
-import org.jivesoftware.smackx.packet.*;
-
-/**
- * Implements <tt>OperationSetTelephonyConferencing</tt> for Jabber.
- *
- * @author Lyubomir Marinov
- * @author Sebastien Vincent
- * @author Boris Grozev
- * @author Pawel Domas
- */
-public class OperationSetTelephonyConferencingJabberImpl
- extends AbstractOperationSetTelephonyConferencing<
- ProtocolProviderServiceJabberImpl,
- OperationSetBasicTelephonyJabberImpl,
- CallJabberImpl,
- CallPeerJabberImpl,
- String>
- implements RegistrationStateChangeListener,
- PacketListener,
- PacketFilter
-
-{
- /**
- * The <tt>Logger</tt> used by the
- * <tt>OperationSetTelephonyConferencingJabberImpl</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(OperationSetTelephonyConferencingJabberImpl.class);
-
- /**
- * The minimum interval in milliseconds between COINs sent to a single
- * <tt>CallPeer</tt>.
- */
- private static final int COIN_MIN_INTERVAL = 200;
-
- /**
- * Property used to disable COIN notifications.
- */
- public static final String DISABLE_COIN_PROP_NAME
- = "net.java.sip.communicator.impl.protocol.jabber.DISABLE_COIN";
-
- /**
- * Synchronization object.
- */
- private final Object lock = new Object();
-
- /**
- * Field indicates whether COIN notification are disabled or not.
- */
- private boolean isCoinDisabled = false;
-
- /**
- * Initializes a new <tt>OperationSetTelephonyConferencingJabberImpl</tt>
- * instance which is to provide telephony conferencing services for the
- * specified Jabber <tt>ProtocolProviderService</tt> implementation.
- *
- * @param parentProvider the Jabber <tt>ProtocolProviderService</tt>
- * implementation which has requested the creation of the new instance and
- * for which the new instance is to provide telephony conferencing services
- */
- public OperationSetTelephonyConferencingJabberImpl(
- ProtocolProviderServiceJabberImpl parentProvider)
- {
- super(parentProvider);
-
- this.isCoinDisabled
- = JabberActivator.getConfigurationService()
- .getBoolean(DISABLE_COIN_PROP_NAME, false);
- }
-
- /**
- * Notifies all <tt>CallPeer</tt>s associated with a specific <tt>Call</tt>
- * about changes in the telephony conference-related information. In
- * contrast, {@link #notifyAll()} notifies all <tt>CallPeer</tt>s associated
- * with the telephony conference in which a specific <tt>Call</tt> is
- * participating.
- *
- * @param call the <tt>Call</tt> whose <tt>CallPeer</tt>s are to be notified
- * about changes in the telephony conference-related information
- */
- @Override
- protected void notifyCallPeers(Call call)
- {
- if (!isCoinDisabled && call.isConferenceFocus())
- {
- synchronized (lock)
- {
- // send conference-info to all CallPeers of the specified call.
- for (Iterator<? extends CallPeer> i = call.getCallPeers();
- i.hasNext();)
- {
- notify(i.next());
- }
- }
- }
- }
-
- /**
- * Notifies a specific <tt>CallPeer</tt> about changes in the telephony
- * conference-related information.
- *
- * @param callPeer the <tt>CallPeer</tt> to notify.
- */
- private void notify(CallPeer callPeer)
- {
- if(!(callPeer instanceof CallPeerJabberImpl))
- return;
-
- //Don't send COINs to peers with might not be ready to accept COINs yet
- CallPeerState peerState = callPeer.getState();
- if (peerState == CallPeerState.CONNECTING
- || peerState == CallPeerState.UNKNOWN
- || peerState == CallPeerState.INITIATING_CALL
- || peerState == CallPeerState.DISCONNECTED
- || peerState == CallPeerState.FAILED)
- return;
-
- final CallPeerJabberImpl callPeerJabber = (CallPeerJabberImpl)callPeer;
-
- final long timeSinceLastCoin = System.currentTimeMillis()
- - callPeerJabber.getLastConferenceInfoSentTimestamp();
- if (timeSinceLastCoin < COIN_MIN_INTERVAL)
- {
- if (callPeerJabber.isConfInfoScheduled())
- return;
-
- logger.info("Scheduling to send a COIN to " + callPeerJabber);
- callPeerJabber.setConfInfoScheduled(true);
- new Thread(new Runnable(){
- @Override
- public void run()
- {
- try
- {
- Thread.sleep(1 + COIN_MIN_INTERVAL - timeSinceLastCoin);
- }
- catch (InterruptedException ie) {}
-
- OperationSetTelephonyConferencingJabberImpl.this
- .notify(callPeerJabber);
- }
- }).start();
-
- return;
- }
-
- // check that callPeer supports COIN before sending him a
- // conference-info
- String to = getBasicTelephony().getFullCalleeURI(callPeer.getAddress());
-
- // XXX if this generates actual disco#info requests we might want to
- // cache it.
- try
- {
- DiscoverInfo discoverInfo
- = parentProvider.getDiscoveryManager().discoverInfo(to);
-
- if (!discoverInfo.containsFeature(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_COIN))
- {
- logger.info(callPeer.getAddress() + " does not support COIN");
- callPeerJabber.setConfInfoScheduled(false);
- return;
- }
- }
- catch (XMPPException xmppe)
- {
- logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe);
- }
-
- ConferenceInfoDocument currentConfInfo
- = getCurrentConferenceInfo(callPeerJabber);
- ConferenceInfoDocument lastSentConfInfo
- = callPeerJabber.getLastConferenceInfoSent();
-
- ConferenceInfoDocument diff;
-
- if (lastSentConfInfo == null)
- diff = currentConfInfo;
- else
- diff = getConferenceInfoDiff(lastSentConfInfo, currentConfInfo);
-
- if (diff != null)
- {
- int newVersion
- = lastSentConfInfo == null
- ? 1
- : lastSentConfInfo.getVersion() + 1;
- diff.setVersion(newVersion);
-
- IQ iq = getConferenceInfo(callPeerJabber, diff);
-
- if (iq != null)
- {
- parentProvider.getConnection().sendPacket(iq);
-
- // We save currentConfInfo, because it is of state "full", while
- // diff could be a partial
- currentConfInfo.setVersion(newVersion);
- callPeerJabber.setLastConferenceInfoSent(currentConfInfo);
- callPeerJabber.setLastConferenceInfoSentTimestamp(
- System.currentTimeMillis());
- }
- }
- callPeerJabber.setConfInfoScheduled(false);
- }
-
- /**
- * Generates the conference-info IQ to be sent to a specific
- * <tt>CallPeer</tt> in order to notify it of the current state of the
- * conference managed by the local peer.
- *
- * @param callPeer the <tt>CallPeer</tt> to generate conference-info XML for
- * @param confInfo the <tt>ConferenceInformationDocument</tt> which is to be
- * included in the IQ
- * @return the conference-info IQ to be sent to the specified
- * <tt>callPeer</tt> in order to notify it of the current state of the
- * conference managed by the local peer
- */
- private IQ getConferenceInfo(CallPeerJabberImpl callPeer,
- final ConferenceInfoDocument confInfo)
- {
- String callPeerSID = callPeer.getSID();
-
- if (callPeerSID == null)
- return null;
-
- IQ iq = new IQ(){
- @Override
- public String getChildElementXML()
- {
- return confInfo.toXml();
- }
- };
-
- CallJabberImpl call = callPeer.getCall();
-
- iq.setFrom(call.getProtocolProvider().getOurJID());
- iq.setTo(callPeer.getAddress());
- iq.setType(Type.SET);
-
- return iq;
- }
-
- /**
- * Implementation of method <tt>registrationStateChange</tt> from
- * interface RegistrationStateChangeListener for setting up (or down)
- * our <tt>JingleManager</tt> when an <tt>XMPPConnection</tt> is available
- *
- * @param evt the event received
- */
- @Override
- public void registrationStateChanged(RegistrationStateChangeEvent evt)
- {
- super.registrationStateChanged(evt);
-
- RegistrationState registrationState = evt.getNewState();
-
- if (RegistrationState.REGISTERED.equals(registrationState))
- {
- if(logger.isDebugEnabled())
- logger.debug("Subscribes to Coin packets");
- subscribeForCoinPackets();
- }
- else if (RegistrationState.UNREGISTERED.equals(registrationState))
- {
- if(logger.isDebugEnabled())
- logger.debug("Unsubscribes to Coin packets");
- unsubscribeForCoinPackets();
- }
- }
-
- /**
- * Creates a new outgoing <tt>Call</tt> into which conference callees are to
- * be invited by this <tt>OperationSetTelephonyConferencing</tt>.
- *
- * @return a new outgoing <tt>Call</tt> into which conference callees are to
- * be invited by this <tt>OperationSetTelephonyConferencing</tt>
- * @throws OperationFailedException if anything goes wrong
- */
- @Override
- protected CallJabberImpl createOutgoingCall()
- throws OperationFailedException
- {
- return new CallJabberImpl(getBasicTelephony());
- }
-
- /**
- * {@inheritDoc}
- *
- * Implements the protocol-dependent part of the logic of inviting a callee
- * to a <tt>Call</tt>. The protocol-independent part of that logic is
- * implemented by
- * {@link AbstractOperationSetTelephonyConferencing#inviteCalleeToCall(String,Call)}.
- */
- @Override
- protected CallPeer doInviteCalleeToCall(
- String calleeAddress,
- CallJabberImpl call)
- throws OperationFailedException
- {
- return
- getBasicTelephony().createOutgoingCall(
- call,
- calleeAddress,
- Arrays.asList(
- new PacketExtension[]
- {
- new CoinPacketExtension(true)
- }));
- }
-
- /**
- * Parses a <tt>String</tt> value which represents a callee address
- * specified by the user into an object which is to actually represent the
- * callee during the invitation to a conference <tt>Call</tt>.
- *
- * @param calleeAddressString a <tt>String</tt> value which represents a
- * callee address to be parsed into an object which is to actually represent
- * the callee during the invitation to a conference <tt>Call</tt>
- * @return an object which is to actually represent the specified
- * <tt>calleeAddressString</tt> during the invitation to a conference
- * <tt>Call</tt>
- * @throws OperationFailedException if parsing the specified
- * <tt>calleeAddressString</tt> fails
- */
- @Override
- protected String parseAddressString(String calleeAddressString)
- throws OperationFailedException
- {
- return getBasicTelephony().getFullCalleeURI(calleeAddressString);
- }
-
- /**
- * Subscribes us to notifications about incoming Coin packets.
- */
- private void subscribeForCoinPackets()
- {
- parentProvider.getConnection().addPacketListener(this, this);
- }
-
- /**
- * Unsubscribes us from notifications about incoming Coin packets.
- */
- private void unsubscribeForCoinPackets()
- {
- XMPPConnection connection = parentProvider.getConnection();
-
- if (connection != null)
- connection.removePacketListener(this);
- }
-
- /**
- * Tests whether or not the specified packet should be handled by this
- * operation set. This method is called by smack prior to packet delivery
- * and it would only accept <tt>CoinIQ</tt>s.
- *
- * @param packet the packet to test.
- * @return true if and only if <tt>packet</tt> passes the filter.
- */
- public boolean accept(Packet packet)
- {
- return (packet instanceof CoinIQ);
- }
-
- /**
- * Handles incoming jingle packets and passes them to the corresponding
- * method based on their action.
- *
- * @param packet the packet to process.
- */
- public void processPacket(Packet packet)
- {
- CoinIQ coinIQ = (CoinIQ) packet;
- String errorMessage = null;
-
- //first ack all "set" requests.
- IQ.Type type = coinIQ.getType();
- if (type == IQ.Type.SET)
- {
- IQ ack = IQ.createResultIQ(coinIQ);
-
- parentProvider.getConnection().sendPacket(ack);
- }
- else if(type == IQ.Type.ERROR)
- {
- XMPPError error = coinIQ.getError();
- if(error != null)
- {
- String msg = error.getMessage();
- errorMessage = ((msg != null)? (msg + " ") : "")
- + "Error code: " + error.getCode();
- }
-
- logger.error("Received error in COIN packet. "+errorMessage);
- }
-
- String sid = coinIQ.getSID();
-
- if (sid != null)
- {
- CallPeerJabberImpl callPeer
- = getBasicTelephony().getActiveCallsRepository().findCallPeer(
- sid);
-
-
- if (callPeer != null)
- {
- if(type == IQ.Type.ERROR)
- {
- callPeer.fireConferenceMemberErrorEvent(errorMessage);
- return;
- }
-
- if (logger.isDebugEnabled())
- logger.debug("Processing COIN from " + coinIQ.getFrom()
- + " (version=" + coinIQ.getVersion() + ")");
-
- handleCoin(callPeer, coinIQ);
- }
- }
- }
-
- /**
- * Handles a specific <tt>CoinIQ</tt> sent from a specific
- * <tt>CallPeer</tt>.
- *
- * @param callPeer the <tt>CallPeer</tt> from which the specified
- * <tt>CoinIQ</tt> was sent
- * @param coinIQ the <tt>CoinIQ</tt> which was sent from the specified
- * <tt>callPeer</tt>
- */
- private void handleCoin(CallPeerJabberImpl callPeer, CoinIQ coinIQ)
- {
- try
- {
- setConferenceInfoXML(callPeer, coinIQ.getChildElementXML());
- }
- catch (XMLException e)
- {
- logger.error("Could not handle received COIN from " + callPeer
- + ": " + coinIQ);
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * For COINs (XEP-0298), we use the attributes of the
- * <tt>conference-info</tt> element to piggyback a Jingle SID. This is
- * temporary and should be removed once we choose a better way to pass the
- * SID.
- */
- @Override
- protected ConferenceInfoDocument getCurrentConferenceInfo(
- MediaAwareCallPeer<?,?,?> callPeer)
- {
- ConferenceInfoDocument confInfo
- = super.getCurrentConferenceInfo(callPeer);
-
- if (callPeer instanceof CallPeerJabberImpl
- && confInfo != null)
- {
- confInfo.setSid(((CallPeerJabberImpl)callPeer).getSID());
- }
- return confInfo;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected String getLocalEntity(CallPeer callPeer)
- {
- JingleIQ sessionIQ = ((CallPeerJabberImpl)callPeer).getSessionIQ();
- String from = sessionIQ.getFrom();
- String chatRoomName = StringUtils.parseBareAddress(from);
- OperationSetMultiUserChatJabberImpl opSetMUC
- = (OperationSetMultiUserChatJabberImpl)
- parentProvider.getOperationSet(OperationSetMultiUserChat.class);
- ChatRoom room = null;
- if(opSetMUC != null)
- room = opSetMUC.getChatRoom(chatRoomName);
-
- if(room != null)
- return "xmpp:" + chatRoomName + "/" + room.getUserNickname();
-
- return "xmpp:" + parentProvider.getOurJID();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected String getLocalDisplayName()
- {
- return null;
- }
-
- /**
- * {@inheritDoc}
- *
- * The URI of the returned <tt>ConferenceDescription</tt> is the occupant
- * JID with which we have joined the room.
- *
- * If a Videobridge is available for our <tt>ProtocolProviderService</tt>
- * we use it. TODO: this should be relaxed when we refactor the Videobridge
- * implementation, so that any Videobridge (on any protocol provider) can
- * be used.
- */
- @Override
- public ConferenceDescription setupConference(final ChatRoom chatRoom)
- {
- OperationSetVideoBridge videoBridge
- = parentProvider.getOperationSet(OperationSetVideoBridge.class);
- boolean isVideobridge = (videoBridge != null) && videoBridge.isActive();
-
- CallJabberImpl call = new CallJabberImpl(getBasicTelephony());
- call.setAutoAnswer(true);
-
- String uri = "xmpp:" + chatRoom.getIdentifier() +
- "/" + chatRoom.getUserNickname();
-
- ConferenceDescription cd
- = new ConferenceDescription(uri, call.getCallID());
-
- call.addCallChangeListener(new CallChangeListener()
- {
- @Override
- public void callStateChanged(CallChangeEvent ev)
- {
- if(CallState.CALL_ENDED.equals(ev.getNewValue()))
- chatRoom.publishConference(null, null);
- }
-
- @Override
- public void callPeerRemoved(CallPeerEvent ev)
- {
- }
-
- @Override
- public void callPeerAdded(CallPeerEvent ev)
- {
- }
- });
- if (isVideobridge)
- {
- call.setConference(new MediaAwareCallConference(true));
-
- //For Jitsi Videobridge we set the transports to RAW-UDP, otherwise
- //we leave them empty (meaning both RAW-UDP and ICE could be used)
- cd.addTransport(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0);
- }
-
- if (logger.isInfoEnabled())
- {
- logger.info("Setup a conference with uri=" + uri + " and callid=" +
- call.getCallID() + ". Videobridge in use: " + isVideobridge);
- }
-
- return cd;
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber; + +import java.util.*; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.media.*; +import net.java.sip.communicator.util.*; + +import org.jitsi.util.xml.*; +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.packet.IQ.Type; +import org.jivesoftware.smack.util.*; +import org.jivesoftware.smackx.packet.*; + +/** + * Implements <tt>OperationSetTelephonyConferencing</tt> for Jabber. + * + * @author Lyubomir Marinov + * @author Sebastien Vincent + * @author Boris Grozev + * @author Pawel Domas + */ +public class OperationSetTelephonyConferencingJabberImpl + extends AbstractOperationSetTelephonyConferencing< + ProtocolProviderServiceJabberImpl, + OperationSetBasicTelephonyJabberImpl, + CallJabberImpl, + CallPeerJabberImpl, + String> + implements RegistrationStateChangeListener, + PacketListener, + PacketFilter + +{ + /** + * The <tt>Logger</tt> used by the + * <tt>OperationSetTelephonyConferencingJabberImpl</tt> class and its + * instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(OperationSetTelephonyConferencingJabberImpl.class); + + /** + * The minimum interval in milliseconds between COINs sent to a single + * <tt>CallPeer</tt>. + */ + private static final int COIN_MIN_INTERVAL = 200; + + /** + * Property used to disable COIN notifications. + */ + public static final String DISABLE_COIN_PROP_NAME + = "net.java.sip.communicator.impl.protocol.jabber.DISABLE_COIN"; + + /** + * Synchronization object. + */ + private final Object lock = new Object(); + + /** + * Field indicates whether COIN notification are disabled or not. + */ + private boolean isCoinDisabled = false; + + /** + * Initializes a new <tt>OperationSetTelephonyConferencingJabberImpl</tt> + * instance which is to provide telephony conferencing services for the + * specified Jabber <tt>ProtocolProviderService</tt> implementation. + * + * @param parentProvider the Jabber <tt>ProtocolProviderService</tt> + * implementation which has requested the creation of the new instance and + * for which the new instance is to provide telephony conferencing services + */ + public OperationSetTelephonyConferencingJabberImpl( + ProtocolProviderServiceJabberImpl parentProvider) + { + super(parentProvider); + + this.isCoinDisabled + = JabberActivator.getConfigurationService() + .getBoolean(DISABLE_COIN_PROP_NAME, false); + } + + /** + * Notifies all <tt>CallPeer</tt>s associated with a specific <tt>Call</tt> + * about changes in the telephony conference-related information. In + * contrast, {@link #notifyAll()} notifies all <tt>CallPeer</tt>s associated + * with the telephony conference in which a specific <tt>Call</tt> is + * participating. + * + * @param call the <tt>Call</tt> whose <tt>CallPeer</tt>s are to be notified + * about changes in the telephony conference-related information + */ + @Override + protected void notifyCallPeers(Call call) + { + if (!isCoinDisabled && call.isConferenceFocus()) + { + synchronized (lock) + { + // send conference-info to all CallPeers of the specified call. + for (Iterator<? extends CallPeer> i = call.getCallPeers(); + i.hasNext();) + { + notify(i.next()); + } + } + } + } + + /** + * Notifies a specific <tt>CallPeer</tt> about changes in the telephony + * conference-related information. + * + * @param callPeer the <tt>CallPeer</tt> to notify. + */ + private void notify(CallPeer callPeer) + { + if(!(callPeer instanceof CallPeerJabberImpl)) + return; + + //Don't send COINs to peers with might not be ready to accept COINs yet + CallPeerState peerState = callPeer.getState(); + if (peerState == CallPeerState.CONNECTING + || peerState == CallPeerState.UNKNOWN + || peerState == CallPeerState.INITIATING_CALL + || peerState == CallPeerState.DISCONNECTED + || peerState == CallPeerState.FAILED) + return; + + final CallPeerJabberImpl callPeerJabber = (CallPeerJabberImpl)callPeer; + + final long timeSinceLastCoin = System.currentTimeMillis() + - callPeerJabber.getLastConferenceInfoSentTimestamp(); + if (timeSinceLastCoin < COIN_MIN_INTERVAL) + { + if (callPeerJabber.isConfInfoScheduled()) + return; + + logger.info("Scheduling to send a COIN to " + callPeerJabber); + callPeerJabber.setConfInfoScheduled(true); + new Thread(new Runnable(){ + @Override + public void run() + { + try + { + Thread.sleep(1 + COIN_MIN_INTERVAL - timeSinceLastCoin); + } + catch (InterruptedException ie) {} + + OperationSetTelephonyConferencingJabberImpl.this + .notify(callPeerJabber); + } + }).start(); + + return; + } + + // check that callPeer supports COIN before sending him a + // conference-info + String to = getBasicTelephony().getFullCalleeURI(callPeer.getAddress()); + + // XXX if this generates actual disco#info requests we might want to + // cache it. + try + { + DiscoverInfo discoverInfo + = parentProvider.getDiscoveryManager().discoverInfo(to); + + if (!discoverInfo.containsFeature( + ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_COIN)) + { + logger.info(callPeer.getAddress() + " does not support COIN"); + callPeerJabber.setConfInfoScheduled(false); + return; + } + } + catch (XMPPException xmppe) + { + logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe); + } + + ConferenceInfoDocument currentConfInfo + = getCurrentConferenceInfo(callPeerJabber); + ConferenceInfoDocument lastSentConfInfo + = callPeerJabber.getLastConferenceInfoSent(); + + ConferenceInfoDocument diff; + + if (lastSentConfInfo == null) + diff = currentConfInfo; + else + diff = getConferenceInfoDiff(lastSentConfInfo, currentConfInfo); + + if (diff != null) + { + int newVersion + = lastSentConfInfo == null + ? 1 + : lastSentConfInfo.getVersion() + 1; + diff.setVersion(newVersion); + + IQ iq = getConferenceInfo(callPeerJabber, diff); + + if (iq != null) + { + parentProvider.getConnection().sendPacket(iq); + + // We save currentConfInfo, because it is of state "full", while + // diff could be a partial + currentConfInfo.setVersion(newVersion); + callPeerJabber.setLastConferenceInfoSent(currentConfInfo); + callPeerJabber.setLastConferenceInfoSentTimestamp( + System.currentTimeMillis()); + } + } + callPeerJabber.setConfInfoScheduled(false); + } + + /** + * Generates the conference-info IQ to be sent to a specific + * <tt>CallPeer</tt> in order to notify it of the current state of the + * conference managed by the local peer. + * + * @param callPeer the <tt>CallPeer</tt> to generate conference-info XML for + * @param confInfo the <tt>ConferenceInformationDocument</tt> which is to be + * included in the IQ + * @return the conference-info IQ to be sent to the specified + * <tt>callPeer</tt> in order to notify it of the current state of the + * conference managed by the local peer + */ + private IQ getConferenceInfo(CallPeerJabberImpl callPeer, + final ConferenceInfoDocument confInfo) + { + String callPeerSID = callPeer.getSID(); + + if (callPeerSID == null) + return null; + + IQ iq = new IQ(){ + @Override + public String getChildElementXML() + { + return confInfo.toXml(); + } + }; + + CallJabberImpl call = callPeer.getCall(); + + iq.setFrom(call.getProtocolProvider().getOurJID()); + iq.setTo(callPeer.getAddress()); + iq.setType(Type.SET); + + return iq; + } + + /** + * Implementation of method <tt>registrationStateChange</tt> from + * interface RegistrationStateChangeListener for setting up (or down) + * our <tt>JingleManager</tt> when an <tt>XMPPConnection</tt> is available + * + * @param evt the event received + */ + @Override + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + super.registrationStateChanged(evt); + + RegistrationState registrationState = evt.getNewState(); + + if (RegistrationState.REGISTERED.equals(registrationState)) + { + if(logger.isDebugEnabled()) + logger.debug("Subscribes to Coin packets"); + subscribeForCoinPackets(); + } + else if (RegistrationState.UNREGISTERED.equals(registrationState)) + { + if(logger.isDebugEnabled()) + logger.debug("Unsubscribes to Coin packets"); + unsubscribeForCoinPackets(); + } + } + + /** + * Creates a new outgoing <tt>Call</tt> into which conference callees are to + * be invited by this <tt>OperationSetTelephonyConferencing</tt>. + * + * @return a new outgoing <tt>Call</tt> into which conference callees are to + * be invited by this <tt>OperationSetTelephonyConferencing</tt> + * @throws OperationFailedException if anything goes wrong + */ + @Override + protected CallJabberImpl createOutgoingCall() + throws OperationFailedException + { + return new CallJabberImpl(getBasicTelephony()); + } + + /** + * {@inheritDoc} + * + * Implements the protocol-dependent part of the logic of inviting a callee + * to a <tt>Call</tt>. The protocol-independent part of that logic is + * implemented by + * {@link AbstractOperationSetTelephonyConferencing#inviteCalleeToCall(String,Call)}. + */ + @Override + protected CallPeer doInviteCalleeToCall( + String calleeAddress, + CallJabberImpl call) + throws OperationFailedException + { + return + getBasicTelephony().createOutgoingCall( + call, + calleeAddress, + Arrays.asList( + new PacketExtension[] + { + new CoinPacketExtension(true) + })); + } + + /** + * Parses a <tt>String</tt> value which represents a callee address + * specified by the user into an object which is to actually represent the + * callee during the invitation to a conference <tt>Call</tt>. + * + * @param calleeAddressString a <tt>String</tt> value which represents a + * callee address to be parsed into an object which is to actually represent + * the callee during the invitation to a conference <tt>Call</tt> + * @return an object which is to actually represent the specified + * <tt>calleeAddressString</tt> during the invitation to a conference + * <tt>Call</tt> + * @throws OperationFailedException if parsing the specified + * <tt>calleeAddressString</tt> fails + */ + @Override + protected String parseAddressString(String calleeAddressString) + throws OperationFailedException + { + return getBasicTelephony().getFullCalleeURI(calleeAddressString); + } + + /** + * Subscribes us to notifications about incoming Coin packets. + */ + private void subscribeForCoinPackets() + { + parentProvider.getConnection().addPacketListener(this, this); + } + + /** + * Unsubscribes us from notifications about incoming Coin packets. + */ + private void unsubscribeForCoinPackets() + { + Connection connection = parentProvider.getConnection(); + + if (connection != null) + connection.removePacketListener(this); + } + + /** + * Tests whether or not the specified packet should be handled by this + * operation set. This method is called by smack prior to packet delivery + * and it would only accept <tt>CoinIQ</tt>s. + * + * @param packet the packet to test. + * @return true if and only if <tt>packet</tt> passes the filter. + */ + public boolean accept(Packet packet) + { + return (packet instanceof CoinIQ); + } + + /** + * Handles incoming jingle packets and passes them to the corresponding + * method based on their action. + * + * @param packet the packet to process. + */ + public void processPacket(Packet packet) + { + CoinIQ coinIQ = (CoinIQ) packet; + String errorMessage = null; + + //first ack all "set" requests. + IQ.Type type = coinIQ.getType(); + if (type == IQ.Type.SET) + { + IQ ack = IQ.createResultIQ(coinIQ); + + parentProvider.getConnection().sendPacket(ack); + } + else if(type == IQ.Type.ERROR) + { + XMPPError error = coinIQ.getError(); + if(error != null) + { + String msg = error.getMessage(); + errorMessage = ((msg != null)? (msg + " ") : "") + + "Error code: " + error.getCode(); + } + + logger.error("Received error in COIN packet. "+errorMessage); + } + + String sid = coinIQ.getSID(); + + if (sid != null) + { + CallPeerJabberImpl callPeer + = getBasicTelephony().getActiveCallsRepository().findCallPeer( + sid); + + + if (callPeer != null) + { + if(type == IQ.Type.ERROR) + { + callPeer.fireConferenceMemberErrorEvent(errorMessage); + return; + } + + if (logger.isDebugEnabled()) + logger.debug("Processing COIN from " + coinIQ.getFrom() + + " (version=" + coinIQ.getVersion() + ")"); + + handleCoin(callPeer, coinIQ); + } + } + } + + /** + * Handles a specific <tt>CoinIQ</tt> sent from a specific + * <tt>CallPeer</tt>. + * + * @param callPeer the <tt>CallPeer</tt> from which the specified + * <tt>CoinIQ</tt> was sent + * @param coinIQ the <tt>CoinIQ</tt> which was sent from the specified + * <tt>callPeer</tt> + */ + private void handleCoin(CallPeerJabberImpl callPeer, CoinIQ coinIQ) + { + try + { + setConferenceInfoXML(callPeer, coinIQ.getChildElementXML()); + } + catch (XMLException e) + { + logger.error("Could not handle received COIN from " + callPeer + + ": " + coinIQ); + } + } + + /** + * {@inheritDoc} + * + * For COINs (XEP-0298), we use the attributes of the + * <tt>conference-info</tt> element to piggyback a Jingle SID. This is + * temporary and should be removed once we choose a better way to pass the + * SID. + */ + @Override + protected ConferenceInfoDocument getCurrentConferenceInfo( + MediaAwareCallPeer<?,?,?> callPeer) + { + ConferenceInfoDocument confInfo + = super.getCurrentConferenceInfo(callPeer); + + if (callPeer instanceof CallPeerJabberImpl + && confInfo != null) + { + confInfo.setSid(((CallPeerJabberImpl)callPeer).getSID()); + } + return confInfo; + } + + /** + * {@inheritDoc} + */ + @Override + protected String getLocalEntity(CallPeer callPeer) + { + JingleIQ sessionIQ = ((CallPeerJabberImpl)callPeer).getSessionIQ(); + String from = sessionIQ.getFrom(); + String chatRoomName = StringUtils.parseBareAddress(from); + OperationSetMultiUserChatJabberImpl opSetMUC + = (OperationSetMultiUserChatJabberImpl) + parentProvider.getOperationSet(OperationSetMultiUserChat.class); + ChatRoom room = null; + if(opSetMUC != null) + room = opSetMUC.getChatRoom(chatRoomName); + + if(room != null) + return "xmpp:" + chatRoomName + "/" + room.getUserNickname(); + + return "xmpp:" + parentProvider.getOurJID(); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getLocalDisplayName() + { + return null; + } + + /** + * {@inheritDoc} + * + * The URI of the returned <tt>ConferenceDescription</tt> is the occupant + * JID with which we have joined the room. + * + * If a Videobridge is available for our <tt>ProtocolProviderService</tt> + * we use it. TODO: this should be relaxed when we refactor the Videobridge + * implementation, so that any Videobridge (on any protocol provider) can + * be used. + */ + @Override + public ConferenceDescription setupConference(final ChatRoom chatRoom) + { + OperationSetVideoBridge videoBridge + = parentProvider.getOperationSet(OperationSetVideoBridge.class); + boolean isVideobridge = (videoBridge != null) && videoBridge.isActive(); + + CallJabberImpl call = new CallJabberImpl(getBasicTelephony()); + call.setAutoAnswer(true); + + String uri = "xmpp:" + chatRoom.getIdentifier() + + "/" + chatRoom.getUserNickname(); + + ConferenceDescription cd + = new ConferenceDescription(uri, call.getCallID()); + + call.addCallChangeListener(new CallChangeListener() + { + @Override + public void callStateChanged(CallChangeEvent ev) + { + if(CallState.CALL_ENDED.equals(ev.getNewValue())) + chatRoom.publishConference(null, null); + } + + @Override + public void callPeerRemoved(CallPeerEvent ev) + { + } + + @Override + public void callPeerAdded(CallPeerEvent ev) + { + } + }); + if (isVideobridge) + { + call.setConference(new MediaAwareCallConference(true)); + + //For Jitsi Videobridge we set the transports to RAW-UDP, otherwise + //we leave them empty (meaning both RAW-UDP and ICE could be used) + cd.addTransport( + ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0); + } + + if (logger.isInfoEnabled()) + { + logger.info("Setup a conference with uri=" + uri + " and callid=" + + call.getCallID() + ". Videobridge in use: " + isVideobridge); + } + + return cd; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java index b7e13ea..5d3dd8b 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,282 +15,282 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.service.protocol.media.*;
-import net.java.sip.communicator.util.*;
-
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.filter.*;
-import org.jivesoftware.smack.packet.*;
-
-/**
- * Implements <tt>OperationSetVideoBridge</tt> for Jabber.
- *
- * @author Yana Stamcheva
- * @author Lyubomir Marinov
- */
-public class OperationSetVideoBridgeImpl
- implements OperationSetVideoBridge,
- PacketFilter,
- PacketListener,
- RegistrationStateChangeListener
-{
- /**
- * The <tt>Logger</tt> used by the <tt>OperationSetVideoBridgeImpl</tt>
- * class and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(OperationSetVideoBridgeImpl.class);
-
- /**
- * The <tt>ProtocolProviderService</tt> implementation which initialized
- * this instance, owns it and is often referred to as its parent.
- */
- private final ProtocolProviderServiceJabberImpl protocolProvider;
-
- /**
- * Creates an instance of <tt>OperationSetVideoBridgeImpl</tt> by
- * specifying the parent <tt>ProtocolProviderService</tt> announcing this
- * operation set.
- *
- * @param protocolProvider the parent Jabber protocol provider
- */
- public OperationSetVideoBridgeImpl(
- ProtocolProviderServiceJabberImpl protocolProvider)
- {
- this.protocolProvider = protocolProvider;
- this.protocolProvider.addRegistrationStateChangeListener(this);
- }
-
- /**
- * Implements {@link PacketFilter}. Determines whether this instance is
- * interested in a specific {@link Packet}.
- * <tt>OperationSetVideoBridgeImpl</tt> returns <tt>true</tt> if the
- * specified <tt>packet</tt> is a {@link ColibriConferenceIQ}; otherwise,
- * <tt>false</tt>.
- *
- * @param packet the <tt>Packet</tt> to be determined whether this instance
- * is interested in it
- * @return <tt>true</tt> if the specified <tt>packet</tt> is a
- * <tt>ColibriConferenceIQ</tt>; otherwise, <tt>false</tt>
- */
- public boolean accept(Packet packet)
- {
- return (packet instanceof ColibriConferenceIQ);
- }
-
- /**
- * Creates a conference call with the specified callees as call peers via a
- * video bridge provided by the parent Jabber provider.
- *
- * @param callees the list of addresses that we should call
- * @return the newly created conference call containing all CallPeers
- * @throws OperationFailedException if establishing the conference call
- * fails
- * @throws OperationNotSupportedException if the provider does not have any
- * conferencing features.
- */
- public Call createConfCall(String[] callees)
- throws OperationFailedException,
- OperationNotSupportedException
- {
- return
- protocolProvider
- .getOperationSet(OperationSetTelephonyConferencing.class)
- .createConfCall(
- callees,
- new MediaAwareCallConference(true));
- }
-
- /**
- * Invites the callee represented by the specified uri to an already
- * existing call using a video bridge provided by the parent Jabber provider.
- * The difference between this method and createConfCall is that
- * inviteCalleeToCall allows a user to add new peers to an already
- * established conference.
- *
- * @param uri the callee to invite to an existing conf call.
- * @param call the call that we should invite the callee to.
- * @return the CallPeer object corresponding to the callee represented by
- * the specified uri.
- * @throws OperationFailedException if inviting the specified callee to the
- * specified call fails
- * @throws OperationNotSupportedException if allowing additional callees to
- * a pre-established call is not supported.
- */
- public CallPeer inviteCalleeToCall(String uri, Call call)
- throws OperationFailedException,
- OperationNotSupportedException
- {
- return
- protocolProvider
- .getOperationSet(OperationSetTelephonyConferencing.class)
- .inviteCalleeToCall(uri, call);
- }
-
- /**
- * Indicates if there's an active video bridge available at this moment. The
- * Jabber provider may announce support for video bridge, but it should not
- * be used for calling until it becomes actually active.
- *
- * @return <tt>true</tt> to indicate that there's currently an active
- * available video bridge, <tt>false</tt> - otherwise
- */
- public boolean isActive()
- {
- String jitsiVideobridge = protocolProvider.getJitsiVideobridge();
-
- return ((jitsiVideobridge != null) && (jitsiVideobridge.length() > 0));
- }
-
- /**
- * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
- * been received.
- *
- * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
- * received
- */
- private void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
- {
- /*
- * The application is not a Jitsi Videobridge server, it is a client.
- * Consequently, the specified ColibriConferenceIQ is sent to it in
- * relation to the part of the application's functionality which makes
- * requests to a Jitsi Videobridge server i.e. CallJabberImpl.
- *
- * Additionally, the method processColibriConferenceIQ is presently tasked
- * with processing ColibriConferenceIQ requests only. They are SET IQs
- * sent by the Jitsi Videobridge server to notify the application about
- * updates in the states of (colibri) conferences organized by the
- * application.
- */
- if (IQ.Type.SET.equals(conferenceIQ.getType())
- && conferenceIQ.getID() != null)
- {
- OperationSetBasicTelephony<?> basicTelephony
- = protocolProvider.getOperationSet(
- OperationSetBasicTelephony.class);
-
- if (basicTelephony != null)
- {
- Iterator<? extends Call> i = basicTelephony.getActiveCalls();
-
- while (i.hasNext())
- {
- Call call = i.next();
-
- if (call instanceof CallJabberImpl)
- {
- CallJabberImpl callJabberImpl = (CallJabberImpl) call;
- MediaAwareCallConference conference
- = callJabberImpl.getConference();
-
- if ((conference != null)
- && conference.isJitsiVideobridge())
- {
- /*
- * TODO We may want to disallow rogue CallJabberImpl
- * instances which may throw an exception to prevent
- * the conferenceIQ from reaching the CallJabberImpl
- * instance which it was meant for.
- */
- if (callJabberImpl.processColibriConferenceIQ(
- conferenceIQ))
- break;
- }
- }
- }
- }
- }
- }
-
- /**
- * Implements {@link PacketListener}. Notifies this instance that a specific
- * {@link Packet} (which this instance has already expressed interest into
- * by returning <tt>true</tt> from {@link #accept(Packet)}) has been
- * received.
- *
- * @param packet the <tt>Packet</tt> which has been received and which this
- * instance is given a chance to process
- */
- public void processPacket(Packet packet)
- {
- /*
- * As we do elsewhere, acknowledge the receipt of the Packet first and
- * then go about our business with it.
- */
- IQ iq = (IQ) packet;
-
- if (iq.getType() == IQ.Type.SET)
- protocolProvider.getConnection().sendPacket(IQ.createResultIQ(iq));
-
- /*
- * Now that the acknowledging is out of the way, do go about our
- * business with the Packet.
- */
- ColibriConferenceIQ conferenceIQ = (ColibriConferenceIQ) iq;
- boolean interrupted = false;
-
- try
- {
- processColibriConferenceIQ(conferenceIQ);
- }
- catch (Throwable t)
- {
- logger.error(
- "An error occurred during the processing of a "
- + packet.getClass().getName() + " packet",
- t);
-
- if (t instanceof InterruptedException)
- {
- /*
- * We cleared the interrupted state of the current Thread by
- * catching the InterruptedException. However, we do not really
- * care whether the current Thread has been interrupted - we
- * caught the InterruptedException because we want to swallow
- * any Throwable. Consequently, we should better restore the
- * interrupted state.
- */
- interrupted = true;
- }
- else if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
- }
- if (interrupted)
- Thread.currentThread().interrupt();
- }
-
- /**
- * {@inheritDoc}
- *
- * Implements {@link RegistrationStateChangeListener}. Notifies this
- * instance that there has been a change in the <tt>RegistrationState</tt>
- * of {@link #protocolProvider}. Subscribes this instance to
- * {@link ColibriConferenceIQ}s as soon as <tt>protocolProvider</tt> is
- * registered and unsubscribes it as soon as <tt>protocolProvider</tt> is
- * unregistered.
- */
- public void registrationStateChanged(RegistrationStateChangeEvent ev)
- {
- RegistrationState registrationState = ev.getNewState();
-
- if (RegistrationState.REGISTERED.equals(registrationState))
- {
- protocolProvider.getConnection().addPacketListener(this, this);
- }
- else if (RegistrationState.UNREGISTERED.equals(registrationState))
- {
- XMPPConnection connection = protocolProvider.getConnection();
-
- if (connection != null)
- connection.removePacketListener(this);
- }
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber; + +import java.util.*; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.media.*; +import net.java.sip.communicator.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; + +/** + * Implements <tt>OperationSetVideoBridge</tt> for Jabber. + * + * @author Yana Stamcheva + * @author Lyubomir Marinov + */ +public class OperationSetVideoBridgeImpl + implements OperationSetVideoBridge, + PacketFilter, + PacketListener, + RegistrationStateChangeListener +{ + /** + * The <tt>Logger</tt> used by the <tt>OperationSetVideoBridgeImpl</tt> + * class and its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(OperationSetVideoBridgeImpl.class); + + /** + * The <tt>ProtocolProviderService</tt> implementation which initialized + * this instance, owns it and is often referred to as its parent. + */ + private final ProtocolProviderServiceJabberImpl protocolProvider; + + /** + * Creates an instance of <tt>OperationSetVideoBridgeImpl</tt> by + * specifying the parent <tt>ProtocolProviderService</tt> announcing this + * operation set. + * + * @param protocolProvider the parent Jabber protocol provider + */ + public OperationSetVideoBridgeImpl( + ProtocolProviderServiceJabberImpl protocolProvider) + { + this.protocolProvider = protocolProvider; + this.protocolProvider.addRegistrationStateChangeListener(this); + } + + /** + * Implements {@link PacketFilter}. Determines whether this instance is + * interested in a specific {@link Packet}. + * <tt>OperationSetVideoBridgeImpl</tt> returns <tt>true</tt> if the + * specified <tt>packet</tt> is a {@link ColibriConferenceIQ}; otherwise, + * <tt>false</tt>. + * + * @param packet the <tt>Packet</tt> to be determined whether this instance + * is interested in it + * @return <tt>true</tt> if the specified <tt>packet</tt> is a + * <tt>ColibriConferenceIQ</tt>; otherwise, <tt>false</tt> + */ + public boolean accept(Packet packet) + { + return (packet instanceof ColibriConferenceIQ); + } + + /** + * Creates a conference call with the specified callees as call peers via a + * video bridge provided by the parent Jabber provider. + * + * @param callees the list of addresses that we should call + * @return the newly created conference call containing all CallPeers + * @throws OperationFailedException if establishing the conference call + * fails + * @throws OperationNotSupportedException if the provider does not have any + * conferencing features. + */ + public Call createConfCall(String[] callees) + throws OperationFailedException, + OperationNotSupportedException + { + return + protocolProvider + .getOperationSet(OperationSetTelephonyConferencing.class) + .createConfCall( + callees, + new MediaAwareCallConference(true)); + } + + /** + * Invites the callee represented by the specified uri to an already + * existing call using a video bridge provided by the parent Jabber provider. + * The difference between this method and createConfCall is that + * inviteCalleeToCall allows a user to add new peers to an already + * established conference. + * + * @param uri the callee to invite to an existing conf call. + * @param call the call that we should invite the callee to. + * @return the CallPeer object corresponding to the callee represented by + * the specified uri. + * @throws OperationFailedException if inviting the specified callee to the + * specified call fails + * @throws OperationNotSupportedException if allowing additional callees to + * a pre-established call is not supported. + */ + public CallPeer inviteCalleeToCall(String uri, Call call) + throws OperationFailedException, + OperationNotSupportedException + { + return + protocolProvider + .getOperationSet(OperationSetTelephonyConferencing.class) + .inviteCalleeToCall(uri, call); + } + + /** + * Indicates if there's an active video bridge available at this moment. The + * Jabber provider may announce support for video bridge, but it should not + * be used for calling until it becomes actually active. + * + * @return <tt>true</tt> to indicate that there's currently an active + * available video bridge, <tt>false</tt> - otherwise + */ + public boolean isActive() + { + String jitsiVideobridge = protocolProvider.getJitsiVideobridge(); + + return ((jitsiVideobridge != null) && (jitsiVideobridge.length() > 0)); + } + + /** + * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has + * been received. + * + * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been + * received + */ + private void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ) + { + /* + * The application is not a Jitsi Videobridge server, it is a client. + * Consequently, the specified ColibriConferenceIQ is sent to it in + * relation to the part of the application's functionality which makes + * requests to a Jitsi Videobridge server i.e. CallJabberImpl. + * + * Additionally, the method processColibriConferenceIQ is presently tasked + * with processing ColibriConferenceIQ requests only. They are SET IQs + * sent by the Jitsi Videobridge server to notify the application about + * updates in the states of (colibri) conferences organized by the + * application. + */ + if (IQ.Type.SET.equals(conferenceIQ.getType()) + && conferenceIQ.getID() != null) + { + OperationSetBasicTelephony<?> basicTelephony + = protocolProvider.getOperationSet( + OperationSetBasicTelephony.class); + + if (basicTelephony != null) + { + Iterator<? extends Call> i = basicTelephony.getActiveCalls(); + + while (i.hasNext()) + { + Call call = i.next(); + + if (call instanceof CallJabberImpl) + { + CallJabberImpl callJabberImpl = (CallJabberImpl) call; + MediaAwareCallConference conference + = callJabberImpl.getConference(); + + if ((conference != null) + && conference.isJitsiVideobridge()) + { + /* + * TODO We may want to disallow rogue CallJabberImpl + * instances which may throw an exception to prevent + * the conferenceIQ from reaching the CallJabberImpl + * instance which it was meant for. + */ + if (callJabberImpl.processColibriConferenceIQ( + conferenceIQ)) + break; + } + } + } + } + } + } + + /** + * Implements {@link PacketListener}. Notifies this instance that a specific + * {@link Packet} (which this instance has already expressed interest into + * by returning <tt>true</tt> from {@link #accept(Packet)}) has been + * received. + * + * @param packet the <tt>Packet</tt> which has been received and which this + * instance is given a chance to process + */ + public void processPacket(Packet packet) + { + /* + * As we do elsewhere, acknowledge the receipt of the Packet first and + * then go about our business with it. + */ + IQ iq = (IQ) packet; + + if (iq.getType() == IQ.Type.SET) + protocolProvider.getConnection().sendPacket(IQ.createResultIQ(iq)); + + /* + * Now that the acknowledging is out of the way, do go about our + * business with the Packet. + */ + ColibriConferenceIQ conferenceIQ = (ColibriConferenceIQ) iq; + boolean interrupted = false; + + try + { + processColibriConferenceIQ(conferenceIQ); + } + catch (Throwable t) + { + logger.error( + "An error occurred during the processing of a " + + packet.getClass().getName() + " packet", + t); + + if (t instanceof InterruptedException) + { + /* + * We cleared the interrupted state of the current Thread by + * catching the InterruptedException. However, we do not really + * care whether the current Thread has been interrupted - we + * caught the InterruptedException because we want to swallow + * any Throwable. Consequently, we should better restore the + * interrupted state. + */ + interrupted = true; + } + else if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + } + if (interrupted) + Thread.currentThread().interrupt(); + } + + /** + * {@inheritDoc} + * + * Implements {@link RegistrationStateChangeListener}. Notifies this + * instance that there has been a change in the <tt>RegistrationState</tt> + * of {@link #protocolProvider}. Subscribes this instance to + * {@link ColibriConferenceIQ}s as soon as <tt>protocolProvider</tt> is + * registered and unsubscribes it as soon as <tt>protocolProvider</tt> is + * unregistered. + */ + public void registrationStateChanged(RegistrationStateChangeEvent ev) + { + RegistrationState registrationState = ev.getNewState(); + + if (RegistrationState.REGISTERED.equals(registrationState)) + { + protocolProvider.getConnection().addPacketListener(this, this); + } + else if (RegistrationState.UNREGISTERED.equals(registrationState)) + { + Connection connection = protocolProvider.getConnection(); + + if (connection != null) + connection.removePacketListener(this); + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java index f38d0bc..b219d68 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java @@ -248,7 +248,7 @@ public class OutgoingFileTransferJabberImpl ThumbnailIQ thumbnailIQ = (ThumbnailIQ) packet; String thumbnailIQCid = thumbnailIQ.getCid(); - XMPPConnection connection = protocolProvider.getConnection(); + Connection connection = protocolProvider.getConnection(); if ((thumbnailIQCid != null) && thumbnailIQCid.equals(thumbnailElement.getCid())) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java index 4f9fc5f..86666f3 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java @@ -21,6 +21,7 @@ import java.util.*; import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.jabber.*; import org.jivesoftware.smack.provider.*; import org.jivesoftware.smack.util.*; import org.osgi.framework.*; @@ -46,7 +47,14 @@ public class ProtocolProviderFactoryJabberImpl { try { + + // Set the extension provider manager for classes that use + // it directly ProviderManager.setInstance(new ProviderManagerExt()); + // Set the Smack interop implementation for the classes that need + // to support Smackv4 interoperation + AbstractSmackInteroperabilityLayer.setImplementationClass( + SmackV3InteroperabilityLayer.class); } catch(Throwable t) { @@ -179,7 +187,7 @@ public class ProtocolProviderFactoryJabberImpl ProtocolProviderServiceJabberImpl service = new ProtocolProviderServiceJabberImpl(); - service.initialize(userID, accountID); + service.initialize(userID, (JabberAccountID) accountID); return service; } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index 2ea5c9c..47b9b75 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -32,6 +32,7 @@ import net.java.sip.communicator.impl.protocol.jabber.extensions.carbon.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jibri.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingleinfo.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive.*; @@ -41,6 +42,7 @@ import net.java.sip.communicator.service.certificate.*; import net.java.sip.communicator.service.dns.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.jabber.*; import net.java.sip.communicator.service.protocol.jabberconstants.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.Logger; @@ -152,6 +154,14 @@ public class ProtocolProviderServiceJabberImpl public static final String URN_IETF_RFC_3264 = "urn:ietf:rfc:3264"; /** + * http://xmpp.org/extensions/xep-0092.html Software Version. + * + */ + // Used in JVB + @SuppressWarnings("unused") + public static final String URN_XMPP_IQ_VERSION = "jabber:iq:version"; + + /** * Jingle's Discovery Info URN for "XEP-0294: Jingle RTP Header Extensions * Negotiation" support. */ @@ -211,7 +221,7 @@ public class ProtocolProviderServiceJabberImpl /** * Used to connect to a XMPP server. */ - private XMPPConnection connection; + private Connection connection; /** * The socket address of the XMPP server. @@ -231,7 +241,7 @@ public class ProtocolProviderServiceJabberImpl /** * The identifier of the account that this provider represents. */ - private AccountID accountID = null; + private JabberAccountID accountID = null; /** * Used when we need to re-register @@ -598,7 +608,7 @@ public class ProtocolProviderServiceJabberImpl */ public boolean isSignalingTransportSecure() { - return connection != null && connection.isUsingTLS(); + return connection.isSecureConnection(); } /** @@ -613,7 +623,7 @@ public class ProtocolProviderServiceJabberImpl if(connection != null && connection.isConnected()) { // Transport using a secure connection. - if(connection.isUsingTLS()) + if(isSignalingTransportSecure()) { return TransportProtocol.TLS; } @@ -1112,11 +1122,24 @@ public class ProtocolProviderServiceJabberImpl JabberLoginStrategy loginStrategy) throws XMPPException { - ConnectionConfiguration confConn = new ConnectionConfiguration( - address.getAddress().getHostAddress(), - address.getPort(), - serviceName, proxy - ); + // BOSH or TCP ? + ConnectionConfiguration confConn; + String boshURL = accountID.getBoshUrl(); + boolean isBosh = !org.jitsi.util.StringUtils.isNullOrEmpty(boshURL); + + if (isBosh) + { + confConn = new BOSHConfiguration(serviceName); + ((BOSHConfiguration)confConn).setBoshUrl(boshURL); + } + else + { + confConn + = new ConnectionConfiguration( + address.getAddress().getHostAddress(), + address.getPort(), + serviceName, proxy); + } // if we have OperationSetPersistentPresence skip sending initial // presence while login is executed, the OperationSet will take care @@ -1144,7 +1167,11 @@ public class ProtocolProviderServiceJabberImpl disconnectAndCleanConnection(); } - connection = new XMPPConnection(confConn); + connection + = isBosh + ? new XMPPBOSHConnection((BOSHConfiguration)confConn) + : new XMPPConnection(confConn); + this.address = address; try @@ -1194,13 +1221,16 @@ public class ProtocolProviderServiceJabberImpl throw new XMPPException("Error creating custom trust manager", e); } - if(debugger == null) + // FIXME rework debugger to work with Connection if possible + if(debugger == null && connection instanceof XMPPConnection) + { debugger = new SmackPacketDebugger(); - // sets the debugger - debugger.setConnection(connection); - connection.addPacketListener(debugger, null); - connection.addPacketInterceptor(debugger, null); + // sets the debugger + debugger.setConnection((XMPPConnection) connection); + connection.addPacketListener(debugger, null); + connection.addPacketInterceptor(debugger, null); + } connection.connect(); @@ -1247,9 +1277,10 @@ public class ProtocolProviderServiceJabberImpl } else { - if (connection.getSocket() instanceof SSLSocket) + final SSLSocket sslSocket = getSSLSocket(); + + if (sslSocket != null) { - final SSLSocket sslSocket = (SSLSocket) connection.getSocket(); StringBuilder buff = new StringBuilder(); buff.append("Chosen TLS protocol and algorithm:\n") .append("Protocol: ").append(sslSocket.getSession() @@ -1538,7 +1569,7 @@ public class ProtocolProviderServiceJabberImpl * @see net.java.sip.communicator.service.protocol.AccountID */ protected void initialize(String screenname, - AccountID accountID) + JabberAccountID accountID) { synchronized(initializationLock) { @@ -1732,6 +1763,12 @@ public class ProtocolProviderServiceJabberImpl ColibriConferenceIQ.NAMESPACE, new ColibriIQProvider()); + providerManager.addIQProvider( + JibriIq.ELEMENT_NAME, + JibriIq.NAMESPACE, + new JibriIqProvider() + ); + providerManager.addExtensionProvider( ConferenceDescriptionPacketExtension.ELEMENT_NAME, ConferenceDescriptionPacketExtension.NAMESPACE, @@ -2025,11 +2062,98 @@ public class ProtocolProviderServiceJabberImpl } /** - * Returns the <tt>XMPPConnection</tt>opened by this provider - * @return a reference to the <tt>XMPPConnection</tt> last opened by this + * Validates the node part of a JID and returns an error message if + * applicable and a suggested correction. + * + * @param contactId the contact identifier to validate + * @param result Must be supplied as an empty a list. Implementors add + * items: + * <ol> + * <li>is the error message if applicable + * <li>a suggested correction. Index 1 is optional and can only + * be present if there was a validation failure. + * </ol> + * @return true if the contact id is valid, false otherwise + */ + @Override + public boolean validateContactAddress(String contactId, List<String> result) + { + if (result == null) + { + throw new IllegalArgumentException("result must be an empty list"); + } + + result.clear(); + try + { + contactId = contactId.trim(); + if (contactId.length() == 0) + { + result.add(JabberActivator.getResources().getI18NString( + "impl.protocol.jabber.INVALID_ADDRESS", new String[] + { contactId })); + // no suggestion for an empty id + return false; + } + + String user = contactId; + String remainder = ""; + int at = contactId.indexOf('@'); + if (at > -1) + { + user = contactId.substring(0, at); + remainder = contactId.substring(at); + } + + // <conforming-char> ::= #x21 | [#x23-#x25] | [#x28-#x2E] | + // [#x30-#x39] | #x3B | #x3D | #x3F | + // [#x41-#x7E] | [#x80-#xD7FF] | + // [#xE000-#xFFFD] | [#x10000-#x10FFFF] + boolean valid = true; + String suggestion = ""; + for (char c : user.toCharArray()) + { + if (!(c == 0x21 || (c >= 0x23 && c <= 0x25) + || (c >= 0x28 && c <= 0x2e) || (c >= 0x30 && c <= 0x39) + || c == 0x3b || c == 0x3d || c == 0x3f + || (c >= 0x41 && c <= 0x7e) || (c >= 0x80 && c <= 0xd7ff) + || (c >= 0xe000 && c <= 0xfffd))) + { + valid = false; + } + else + { + suggestion += c; + } + } + + if (!valid) + { + result.add(JabberActivator.getResources().getI18NString( + "impl.protocol.jabber.INVALID_ADDRESS", new String[] + { contactId })); + result.add(suggestion + remainder); + return false; + } + + return true; + } + catch (Exception ex) + { + result.add(JabberActivator.getResources().getI18NString( + "impl.protocol.jabber.INVALID_ADDRESS", new String[] + { contactId })); + } + + return false; + } + + /** + * Returns the <tt>Connection</tt>opened by this provider + * @return a reference to the <tt>Connection</tt> last opened by this * provider. */ - public XMPPConnection getConnection() + public Connection getConnection() { return connection; } @@ -2315,18 +2439,16 @@ public class ProtocolProviderServiceJabberImpl */ public boolean isFeatureListSupported(String jid, String... features) { - boolean isFeatureListSupported = true; - try { if(discoveryManager == null) - return isFeatureListSupported; + return false; DiscoverInfo featureInfo = discoveryManager.discoverInfoNonBlocking(jid); if(featureInfo == null) - return isFeatureListSupported; + return false; for (String feature : features) { @@ -2334,17 +2456,19 @@ public class ProtocolProviderServiceJabberImpl { // If one is not supported we return false and don't check // the others. - isFeatureListSupported = false; - break; + return false; } } + + return true; } catch (XMPPException e) { if (logger.isDebugEnabled()) logger.debug("Failed to retrive discovery info.", e); } - return isFeatureListSupported; + + return false; } /** @@ -2386,7 +2510,7 @@ public class ProtocolProviderServiceJabberImpl */ public String getFullJid(String bareJid) { - XMPPConnection connection = getConnection(); + Connection connection = getConnection(); // when we are not connected there is no full jid if (connection != null && connection.isConnected()) @@ -2590,12 +2714,21 @@ public class ProtocolProviderServiceJabberImpl */ public void startJingleNodesDiscovery() { + if (!(connection instanceof XMPPConnection)) + { + logger.warn( + "Jingle node discovery currently will work only with " + + "TCP XMPP connection"); + return; + } + // Jingle Nodes Service Initialization + final XMPPConnection xmppConnection = (XMPPConnection) connection; final JabberAccountIDImpl accID = (JabberAccountIDImpl)getAccountID(); - final SmackServiceNode service = new SmackServiceNode(connection, - 60000); + final SmackServiceNode service + = new SmackServiceNode(xmppConnection, 60000); // make sure SmackServiceNode will clean up when connection is closed - connection.addConnectionListener(service); + xmppConnection.addConnectionListener(service); for(JingleNodeDescriptor desc : accID.getJingleNodes()) { @@ -2611,7 +2744,7 @@ public class ProtocolProviderServiceJabberImpl new Thread(new JingleNodesServiceDiscovery( service, - connection, + xmppConnection, accID, jingleNodesSyncRoot)) .start(); @@ -2739,7 +2872,7 @@ public class ProtocolProviderServiceJabberImpl */ private void setTrafficClass() { - Socket s = connection.getSocket(); + Socket s = getSocket(); if(s != null) { @@ -2774,7 +2907,7 @@ public class ProtocolProviderServiceJabberImpl */ public String getJitsiVideobridge() { - XMPPConnection connection = getConnection(); + Connection connection = getConnection(); if (connection != null) { @@ -2865,21 +2998,23 @@ public class ProtocolProviderServiceJabberImpl } /** + * Obtains XMPP connection's socket. + * @return <tt>Socket</tt> instance used by the underlying XMPP connection + * or <tt>null</tt> if "non socket" type of transport is currently used. + */ + private Socket getSocket() + { + return connection != null ? connection.getSocket() : null; + } + + /** * Return the SSL socket (if TLS used). * @return The SSL socket or null if not used */ - public SSLSocket getSSLSocket() + SSLSocket getSSLSocket() { - final SSLSocket result; - final Socket socket = connection.getSocket(); - if (socket instanceof SSLSocket) - { - result = (SSLSocket) socket; - } - else - { - result = null; - } - return result; + final Socket socket = getSocket(); + + return (socket instanceof SSLSocket) ? (SSLSocket) socket : null; } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java index 0aa4fb6..4bd49a9 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,529 +15,529 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.net.*;
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
-import net.java.sip.communicator.service.protocol.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jivesoftware.smack.packet.*;
-
-/**
- * A {@link TransportManagerJabberImpl} implementation that would only gather a
- * single candidate pair (i.e. RTP and RTCP).
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Hristo Terezov
- */
-public class RawUdpTransportManager
- extends TransportManagerJabberImpl
-{
- /**
- * The list of <tt>ContentPacketExtension</tt>s which represents the local
- * counterpart of the negotiation between the local and the remote peers.
- */
- private List<ContentPacketExtension> local;
-
- /**
- * The collection of <tt>ContentPacketExtension</tt>s which represents the
- * remote counterpart of the negotiation between the local and the remote
- * peers.
- */
- private final List<Iterable<ContentPacketExtension>> remotes
- = new LinkedList<Iterable<ContentPacketExtension>>();
-
- /**
- * Creates a new instance of this transport manager, binding it to the
- * specified peer.
- *
- * @param callPeer the {@link CallPeer} whose traffic we will be taking
- * care of.
- */
- public RawUdpTransportManager(CallPeerJabberImpl callPeer)
- {
- super(callPeer);
- }
-
- /**
- * {@inheritDoc}
- */
- protected PacketExtension createTransport(String media)
- throws OperationFailedException
- {
- MediaType mediaType = MediaType.parseString(media);
-
- return createTransport(mediaType, getStreamConnector(mediaType));
- }
-
- /**
- * Creates a raw UDP transport element according to a specific
- * <tt>StreamConnector</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
- * uses the specified <tt>connector</tt> or <tt>channel</tt>
- * @param connector the <tt>StreamConnector</tt> to be described within the
- * transport element
- * @return a {@link RawUdpTransportPacketExtension} containing the RTP and
- * RTCP candidates of the specified <tt>connector</tt>
- */
- private RawUdpTransportPacketExtension createTransport(
- MediaType mediaType,
- StreamConnector connector)
- {
- RawUdpTransportPacketExtension ourTransport
- = new RawUdpTransportPacketExtension();
- int generation = getCurrentGeneration();
-
- // create and add candidates that correspond to the stream connector
- // RTP
- CandidatePacketExtension rtpCand = new CandidatePacketExtension();
-
- rtpCand.setComponent(CandidatePacketExtension.RTP_COMPONENT_ID);
- rtpCand.setGeneration(generation);
- rtpCand.setID(getNextID());
- rtpCand.setType(CandidateType.host);
-
- DatagramSocket dataSocket = connector.getDataSocket();
-
- rtpCand.setIP(dataSocket.getLocalAddress().getHostAddress());
- rtpCand.setPort(dataSocket.getLocalPort());
-
- ourTransport.addCandidate(rtpCand);
-
- // RTCP
- CandidatePacketExtension rtcpCand = new CandidatePacketExtension();
-
- rtcpCand.setComponent(CandidatePacketExtension.RTCP_COMPONENT_ID);
- rtcpCand.setGeneration(generation);
- rtcpCand.setID(getNextID());
- rtcpCand.setType(CandidateType.host);
-
- DatagramSocket controlSocket = connector.getControlSocket();
-
- rtcpCand.setIP(controlSocket.getLocalAddress().getHostAddress());
- rtcpCand.setPort(controlSocket.getLocalPort());
-
- ourTransport.addCandidate(rtcpCand);
-
- return ourTransport;
- }
-
- /**
- * {@inheritDoc}
- */
- protected PacketExtension createTransportPacketExtension()
- {
- return new RawUdpTransportPacketExtension();
- }
-
- /**
- * Implements {@link TransportManagerJabberImpl#getStreamTarget(MediaType)}.
- * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of
- * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
- * is to have its <tt>target</tt> set to the returned
- * <tt>MediaStreamTarget</tt>
- * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt>
- * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt>
- * @see TransportManagerJabberImpl#getStreamTarget(MediaType)
- */
- @Override
- public MediaStreamTarget getStreamTarget(MediaType mediaType)
- {
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, true /* local */);
- MediaStreamTarget streamTarget = null;
-
- if (channel == null)
- {
- String media = mediaType.toString();
-
- for (Iterable<ContentPacketExtension> remote : remotes)
- {
- for (ContentPacketExtension content : remote)
- {
- RtpDescriptionPacketExtension rtpDescription
- = content.getFirstChildOfType(
- RtpDescriptionPacketExtension.class);
-
- if (media.equals(rtpDescription.getMedia()))
- {
- streamTarget
- = JingleUtils.extractDefaultTarget(content);
- break;
- }
- }
- }
- }
- else
- {
- IceUdpTransportPacketExtension transport = channel.getTransport();
-
- if (transport != null)
- streamTarget = JingleUtils.extractDefaultTarget(transport);
- if (streamTarget == null)
- {
- /*
- * For the purposes of compatibility with legacy Jitsi
- * Videobridge, support the channel attributes host, rtpPort and
- * rtcpPort.
- */
- @SuppressWarnings("deprecation")
- String host = channel.getHost();
-
- if (host != null)
- {
- @SuppressWarnings("deprecation")
- int rtpPort = channel.getRTPPort();
- @SuppressWarnings("deprecation")
- int rtcpPort = channel.getRTCPPort();
-
- streamTarget
- = new MediaStreamTarget(
- new InetSocketAddress(host, rtpPort),
- new InetSocketAddress(host, rtcpPort));
- }
- }
- }
- return streamTarget;
- }
-
- /**
- * Implements {@link TransportManagerJabberImpl#getXmlNamespace()}. Gets the
- * XML namespace of the Jingle transport implemented by this
- * <tt>TransportManagerJabberImpl</tt>.
- *
- * @return the XML namespace of the Jingle transport implemented by this
- * <tt>TransportManagerJabberImpl</tt>
- * @see TransportManagerJabberImpl#getXmlNamespace()
- */
- @Override
- public String getXmlNamespace()
- {
- return ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0;
- }
-
- /**
- * Removes a content with a specific name from the transport-related part of
- * the session represented by this <tt>TransportManagerJabberImpl</tt> which
- * may have been reported through previous calls to the
- * <tt>startCandidateHarvest</tt> and
- * <tt>startConnectivityEstablishment</tt> methods.
- *
- * @param name the name of the content to be removed from the
- * transport-related part of the session represented by this
- * <tt>TransportManagerJabberImpl</tt>
- * @see TransportManagerJabberImpl#removeContent(String)
- */
- @Override
- public void removeContent(String name)
- {
- if (local != null)
- removeContent(local, name);
-
- removeRemoteContent(name);
- }
-
- /**
- * Removes a content with a specific name from the remote counterpart of the
- * negotiation between the local and the remote peers.
- *
- * @param name the name of the content to be removed from the remote
- * counterpart of the negotiation between the local and the remote peers
- */
- private void removeRemoteContent(String name)
- {
- for (Iterator<Iterable<ContentPacketExtension>> remoteIter
- = remotes.iterator();
- remoteIter.hasNext();)
- {
- Iterable<ContentPacketExtension> remote = remoteIter.next();
-
- /*
- * Once the remote content is removed, make sure that we are not
- * retaining sets which do not have any contents.
- */
- if ((removeContent(remote, name) != null)
- && !remote.iterator().hasNext())
- {
- remoteIter.remove();
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- protected PacketExtension startCandidateHarvest(
- ContentPacketExtension theirContent,
- ContentPacketExtension ourContent,
- TransportInfoSender transportInfoSender,
- String media)
- throws OperationFailedException
- {
- return createTransportForStartCandidateHarvest(media);
- }
-
- /**
- * Starts transport candidate harvest. This method should complete rapidly
- * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
- * are necessary, they should be executed in a separate thread. Candidate
- * harvest would then need to be concluded in the
- * {@link #wrapupCandidateHarvest()} method which would be called once we
- * absolutely need the candidates.
- *
- * @param theirOffer a media description offer that we've received from the
- * remote party and that we should use in case we need to know what
- * transports our peer is using.
- * @param ourAnswer the content descriptions that we should be adding our
- * transport lists to (although not necessarily in this very instance).
- * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by
- * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt>
- * <tt>JingleIQ</tt>s from the local peer to the remote peer if this
- * <tt>TransportManagerJabberImpl</tt> wishes to utilize
- * <tt>transport-info</tt>. Local candidate addresses sent by this
- * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are
- * expected to not be included in the result of
- * {@link #wrapupCandidateHarvest()}.
- *
- * @throws OperationFailedException if we fail to allocate a port number.
- * @see TransportManagerJabberImpl#startCandidateHarvest(List, List,
- * TransportInfoSender)
- */
- @Override
- public void startCandidateHarvest(
- List<ContentPacketExtension> theirOffer,
- List<ContentPacketExtension> ourAnswer,
- TransportInfoSender transportInfoSender)
- throws OperationFailedException
- {
- this.local = ourAnswer;
-
- super.startCandidateHarvest(theirOffer, ourAnswer, transportInfoSender);
- }
-
- /**
- * Overrides the super implementation in order to remember the remote
- * counterpart of the negotiation between the local and the remote peer for
- * subsequent calls to {@link #getStreamTarget(MediaType)}.
- *
- * @param remote the collection of <tt>ContentPacketExtension</tt>s which
- * represents the remote counterpart of the negotiation between the local
- * and the remote peer
- * @return <tt>true</tt> because <tt>RawUdpTransportManager</tt> does not
- * perform connectivity checks
- * @see TransportManagerJabberImpl#startConnectivityEstablishment(Iterable)
- */
- @Override
- public boolean startConnectivityEstablishment(
- Iterable<ContentPacketExtension> remote)
- {
- if ((remote != null) && !remotes.contains(remote))
- {
- /*
- * The state of the session in Jingle is maintained by each peer and
- * is modified by content-add and content-remove. The remotes field
- * of this RawUdpTransportManager represents the state of the
- * session with respect to the remote peer. When the remote peer
- * tells us about a specific set of contents, make sure that it is
- * the only record we will have with respect to the specified set of
- * contents.
- */
- for (ContentPacketExtension content : remote)
- removeRemoteContent(content.getName());
-
- remotes.add(remote);
- }
-
- return super.startConnectivityEstablishment(remote);
- }
-
- /**
- * Simply returns the list of local candidates that we gathered during the
- * harvest. This is a raw UDP transport manager so there's no real wrapping
- * up to do.
- *
- * @return the list of local candidates that we gathered during the harvest
- * @see TransportManagerJabberImpl#wrapupCandidateHarvest()
- */
- @Override
- public List<ContentPacketExtension> wrapupCandidateHarvest()
- {
- return local;
- }
-
- /**
- * Returns the extended type of the candidate selected if this transport
- * manager is using ICE.
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return The extended type of the candidate selected if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- @Override
- public String getICECandidateExtendedType(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the current state of ICE processing.
- *
- * @return the current state of ICE processing.
- */
- @Override
- public String getICEState()
- {
- return null;
- }
-
- /**
- * Returns the ICE local host address.
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE local host address if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- @Override
- public InetSocketAddress getICELocalHostAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE remote host address.
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE remote host address if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- @Override
- public InetSocketAddress getICERemoteHostAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE local reflexive address (server or peer reflexive).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE local reflexive address. May be null if this transport
- * manager is not using ICE or if there is no reflexive address for the
- * local candidate used.
- */
- @Override
- public InetSocketAddress getICELocalReflexiveAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE remote reflexive address (server or peer reflexive).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE remote reflexive address. May be null if this transport
- * manager is not using ICE or if there is no reflexive address for the
- * remote candidate used.
- */
- @Override
- public InetSocketAddress getICERemoteReflexiveAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE local relayed address (server or peer relayed).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE local relayed address. May be null if this transport
- * manager is not using ICE or if there is no relayed address for the
- * local candidate used.
- */
- @Override
- public InetSocketAddress getICELocalRelayedAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE remote relayed address (server or peer relayed).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE remote relayed address. May be null if this transport
- * manager is not using ICE or if there is no relayed address for the
- * remote candidate used.
- */
- @Override
- public InetSocketAddress getICERemoteRelayedAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the total harvesting time (in ms) for all harvesters.
- *
- * @return The total harvesting time (in ms) for all the harvesters. 0 if
- * the ICE agent is null, or if the agent has nevers harvested.
- */
- @Override
- public long getTotalHarvestingTime()
- {
- return 0;
- }
-
- /**
- * Returns the harvesting time (in ms) for the harvester given in parameter.
- *
- * @param harvesterName The class name if the harvester.
- *
- * @return The harvesting time (in ms) for the harvester given in parameter.
- * 0 if this harvester does not exists, if the ICE agent is null, or if the
- * agent has never harvested with this harvester.
- */
- @Override
- public long getHarvestingTime(String harvesterName)
- {
- return 0;
- }
-
- /**
- * Returns the number of harvesting for this agent.
- *
- * @return The number of harvesting for this agent.
- */
- @Override
- public int getNbHarvesting()
- {
- return 0;
- }
-
- /**
- * Returns the number of harvesting time for the harvester given in
- * parameter.
- *
- * @param harvesterName The class name if the harvester.
- *
- * @return The number of harvesting time for the harvester given in
- * parameter.
- */
- @Override
- public int getNbHarvesting(String harvesterName)
- {
- return 0;
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber; + +import java.net.*; +import java.util.*; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; +import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*; +import net.java.sip.communicator.service.protocol.*; + +import org.jitsi.service.neomedia.*; +import org.jivesoftware.smack.packet.*; + +/** + * A {@link TransportManagerJabberImpl} implementation that would only gather a + * single candidate pair (i.e. RTP and RTCP). + * + * @author Emil Ivov + * @author Lyubomir Marinov + * @author Hristo Terezov + */ +public class RawUdpTransportManager + extends TransportManagerJabberImpl +{ + /** + * The list of <tt>ContentPacketExtension</tt>s which represents the local + * counterpart of the negotiation between the local and the remote peers. + */ + private List<ContentPacketExtension> local; + + /** + * The collection of <tt>ContentPacketExtension</tt>s which represents the + * remote counterpart of the negotiation between the local and the remote + * peers. + */ + private final List<Iterable<ContentPacketExtension>> remotes + = new LinkedList<Iterable<ContentPacketExtension>>(); + + /** + * Creates a new instance of this transport manager, binding it to the + * specified peer. + * + * @param callPeer the {@link CallPeer} whose traffic we will be taking + * care of. + */ + public RawUdpTransportManager(CallPeerJabberImpl callPeer) + { + super(callPeer); + } + + /** + * {@inheritDoc} + */ + protected PacketExtension createTransport(String media) + throws OperationFailedException + { + MediaType mediaType = MediaType.parseString(media); + + return createTransport(mediaType, getStreamConnector(mediaType)); + } + + /** + * Creates a raw UDP transport element according to a specific + * <tt>StreamConnector</tt>. + * + * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which + * uses the specified <tt>connector</tt> or <tt>channel</tt> + * @param connector the <tt>StreamConnector</tt> to be described within the + * transport element + * @return a {@link RawUdpTransportPacketExtension} containing the RTP and + * RTCP candidates of the specified <tt>connector</tt> + */ + private RawUdpTransportPacketExtension createTransport( + MediaType mediaType, + StreamConnector connector) + { + RawUdpTransportPacketExtension ourTransport + = new RawUdpTransportPacketExtension(); + int generation = getCurrentGeneration(); + + // create and add candidates that correspond to the stream connector + // RTP + CandidatePacketExtension rtpCand = new CandidatePacketExtension(); + + rtpCand.setComponent(CandidatePacketExtension.RTP_COMPONENT_ID); + rtpCand.setGeneration(generation); + rtpCand.setID(getNextID()); + rtpCand.setType(CandidateType.host); + + DatagramSocket dataSocket = connector.getDataSocket(); + + rtpCand.setIP(dataSocket.getLocalAddress().getHostAddress()); + rtpCand.setPort(dataSocket.getLocalPort()); + + ourTransport.addCandidate(rtpCand); + + // RTCP + CandidatePacketExtension rtcpCand = new CandidatePacketExtension(); + + rtcpCand.setComponent(CandidatePacketExtension.RTCP_COMPONENT_ID); + rtcpCand.setGeneration(generation); + rtcpCand.setID(getNextID()); + rtcpCand.setType(CandidateType.host); + + DatagramSocket controlSocket = connector.getControlSocket(); + + rtcpCand.setIP(controlSocket.getLocalAddress().getHostAddress()); + rtcpCand.setPort(controlSocket.getLocalPort()); + + ourTransport.addCandidate(rtcpCand); + + return ourTransport; + } + + /** + * {@inheritDoc} + */ + protected PacketExtension createTransportPacketExtension() + { + return new RawUdpTransportPacketExtension(); + } + + /** + * Implements {@link TransportManagerJabberImpl#getStreamTarget(MediaType)}. + * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of + * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>. + * + * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which + * is to have its <tt>target</tt> set to the returned + * <tt>MediaStreamTarget</tt> + * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> + * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt> + * @see TransportManagerJabberImpl#getStreamTarget(MediaType) + */ + @Override + public MediaStreamTarget getStreamTarget(MediaType mediaType) + { + ColibriConferenceIQ.Channel channel + = getColibriChannel(mediaType, true /* local */); + MediaStreamTarget streamTarget = null; + + if (channel == null) + { + String media = mediaType.toString(); + + for (Iterable<ContentPacketExtension> remote : remotes) + { + for (ContentPacketExtension content : remote) + { + RtpDescriptionPacketExtension rtpDescription + = content.getFirstChildOfType( + RtpDescriptionPacketExtension.class); + + if (media.equals(rtpDescription.getMedia())) + { + streamTarget + = JingleUtils.extractDefaultTarget(content); + break; + } + } + } + } + else + { + IceUdpTransportPacketExtension transport = channel.getTransport(); + + if (transport != null) + streamTarget = JingleUtils.extractDefaultTarget(transport); + if (streamTarget == null) + { + /* + * For the purposes of compatibility with legacy Jitsi + * Videobridge, support the channel attributes host, rtpPort and + * rtcpPort. + */ + @SuppressWarnings("deprecation") + String host = channel.getHost(); + + if (host != null) + { + @SuppressWarnings("deprecation") + int rtpPort = channel.getRTPPort(); + @SuppressWarnings("deprecation") + int rtcpPort = channel.getRTCPPort(); + + streamTarget + = new MediaStreamTarget( + new InetSocketAddress(host, rtpPort), + new InetSocketAddress(host, rtcpPort)); + } + } + } + return streamTarget; + } + + /** + * Implements {@link TransportManagerJabberImpl#getXmlNamespace()}. Gets the + * XML namespace of the Jingle transport implemented by this + * <tt>TransportManagerJabberImpl</tt>. + * + * @return the XML namespace of the Jingle transport implemented by this + * <tt>TransportManagerJabberImpl</tt> + * @see TransportManagerJabberImpl#getXmlNamespace() + */ + @Override + public String getXmlNamespace() + { + return ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0; + } + + /** + * Removes a content with a specific name from the transport-related part of + * the session represented by this <tt>TransportManagerJabberImpl</tt> which + * may have been reported through previous calls to the + * <tt>startCandidateHarvest</tt> and + * <tt>startConnectivityEstablishment</tt> methods. + * + * @param name the name of the content to be removed from the + * transport-related part of the session represented by this + * <tt>TransportManagerJabberImpl</tt> + * @see TransportManagerJabberImpl#removeContent(String) + */ + @Override + public void removeContent(String name) + { + if (local != null) + removeContent(local, name); + + removeRemoteContent(name); + } + + /** + * Removes a content with a specific name from the remote counterpart of the + * negotiation between the local and the remote peers. + * + * @param name the name of the content to be removed from the remote + * counterpart of the negotiation between the local and the remote peers + */ + private void removeRemoteContent(String name) + { + for (Iterator<Iterable<ContentPacketExtension>> remoteIter + = remotes.iterator(); + remoteIter.hasNext();) + { + Iterable<ContentPacketExtension> remote = remoteIter.next(); + + /* + * Once the remote content is removed, make sure that we are not + * retaining sets which do not have any contents. + */ + if ((removeContent(remote, name) != null) + && !remote.iterator().hasNext()) + { + remoteIter.remove(); + } + } + } + + /** + * {@inheritDoc} + */ + protected PacketExtension startCandidateHarvest( + ContentPacketExtension theirContent, + ContentPacketExtension ourContent, + TransportInfoSender transportInfoSender, + String media) + throws OperationFailedException + { + return createTransportForStartCandidateHarvest(media); + } + + /** + * Starts transport candidate harvest. This method should complete rapidly + * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests + * are necessary, they should be executed in a separate thread. Candidate + * harvest would then need to be concluded in the + * {@link #wrapupCandidateHarvest()} method which would be called once we + * absolutely need the candidates. + * + * @param theirOffer a media description offer that we've received from the + * remote party and that we should use in case we need to know what + * transports our peer is using. + * @param ourAnswer the content descriptions that we should be adding our + * transport lists to (although not necessarily in this very instance). + * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by + * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt> + * <tt>JingleIQ</tt>s from the local peer to the remote peer if this + * <tt>TransportManagerJabberImpl</tt> wishes to utilize + * <tt>transport-info</tt>. Local candidate addresses sent by this + * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are + * expected to not be included in the result of + * {@link #wrapupCandidateHarvest()}. + * + * @throws OperationFailedException if we fail to allocate a port number. + * @see TransportManagerJabberImpl#startCandidateHarvest(List, List, + * TransportInfoSender) + */ + @Override + public void startCandidateHarvest( + List<ContentPacketExtension> theirOffer, + List<ContentPacketExtension> ourAnswer, + TransportInfoSender transportInfoSender) + throws OperationFailedException + { + this.local = ourAnswer; + + super.startCandidateHarvest(theirOffer, ourAnswer, transportInfoSender); + } + + /** + * Overrides the super implementation in order to remember the remote + * counterpart of the negotiation between the local and the remote peer for + * subsequent calls to {@link #getStreamTarget(MediaType)}. + * + * @param remote the collection of <tt>ContentPacketExtension</tt>s which + * represents the remote counterpart of the negotiation between the local + * and the remote peer + * @return <tt>true</tt> because <tt>RawUdpTransportManager</tt> does not + * perform connectivity checks + * @see TransportManagerJabberImpl#startConnectivityEstablishment(Iterable) + */ + @Override + public boolean startConnectivityEstablishment( + Iterable<ContentPacketExtension> remote) + { + if ((remote != null) && !remotes.contains(remote)) + { + /* + * The state of the session in Jingle is maintained by each peer and + * is modified by content-add and content-remove. The remotes field + * of this RawUdpTransportManager represents the state of the + * session with respect to the remote peer. When the remote peer + * tells us about a specific set of contents, make sure that it is + * the only record we will have with respect to the specified set of + * contents. + */ + for (ContentPacketExtension content : remote) + removeRemoteContent(content.getName()); + + remotes.add(remote); + } + + return super.startConnectivityEstablishment(remote); + } + + /** + * Simply returns the list of local candidates that we gathered during the + * harvest. This is a raw UDP transport manager so there's no real wrapping + * up to do. + * + * @return the list of local candidates that we gathered during the harvest + * @see TransportManagerJabberImpl#wrapupCandidateHarvest() + */ + @Override + public List<ContentPacketExtension> wrapupCandidateHarvest() + { + return local; + } + + /** + * Returns the extended type of the candidate selected if this transport + * manager is using ICE. + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return The extended type of the candidate selected if this transport + * manager is using ICE. Otherwise, returns null. + */ + @Override + public String getICECandidateExtendedType(String streamName) + { + return null; + } + + /** + * Returns the current state of ICE processing. + * + * @return the current state of ICE processing. + */ + @Override + public String getICEState() + { + return null; + } + + /** + * Returns the ICE local host address. + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE local host address if this transport + * manager is using ICE. Otherwise, returns null. + */ + @Override + public InetSocketAddress getICELocalHostAddress(String streamName) + { + return null; + } + + /** + * Returns the ICE remote host address. + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE remote host address if this transport + * manager is using ICE. Otherwise, returns null. + */ + @Override + public InetSocketAddress getICERemoteHostAddress(String streamName) + { + return null; + } + + /** + * Returns the ICE local reflexive address (server or peer reflexive). + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE local reflexive address. May be null if this transport + * manager is not using ICE or if there is no reflexive address for the + * local candidate used. + */ + @Override + public InetSocketAddress getICELocalReflexiveAddress(String streamName) + { + return null; + } + + /** + * Returns the ICE remote reflexive address (server or peer reflexive). + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE remote reflexive address. May be null if this transport + * manager is not using ICE or if there is no reflexive address for the + * remote candidate used. + */ + @Override + public InetSocketAddress getICERemoteReflexiveAddress(String streamName) + { + return null; + } + + /** + * Returns the ICE local relayed address (server or peer relayed). + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE local relayed address. May be null if this transport + * manager is not using ICE or if there is no relayed address for the + * local candidate used. + */ + @Override + public InetSocketAddress getICELocalRelayedAddress(String streamName) + { + return null; + } + + /** + * Returns the ICE remote relayed address (server or peer relayed). + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE remote relayed address. May be null if this transport + * manager is not using ICE or if there is no relayed address for the + * remote candidate used. + */ + @Override + public InetSocketAddress getICERemoteRelayedAddress(String streamName) + { + return null; + } + + /** + * Returns the total harvesting time (in ms) for all harvesters. + * + * @return The total harvesting time (in ms) for all the harvesters. 0 if + * the ICE agent is null, or if the agent has nevers harvested. + */ + @Override + public long getTotalHarvestingTime() + { + return 0; + } + + /** + * Returns the harvesting time (in ms) for the harvester given in parameter. + * + * @param harvesterName The class name if the harvester. + * + * @return The harvesting time (in ms) for the harvester given in parameter. + * 0 if this harvester does not exists, if the ICE agent is null, or if the + * agent has never harvested with this harvester. + */ + @Override + public long getHarvestingTime(String harvesterName) + { + return 0; + } + + /** + * Returns the number of harvesting for this agent. + * + * @return The number of harvesting for this agent. + */ + @Override + public int getNbHarvesting() + { + return 0; + } + + /** + * Returns the number of harvesting time for the harvester given in + * parameter. + * + * @param harvesterName The class name if the harvester. + * + * @return The number of harvesting time for the harvester given in + * parameter. + */ + @Override + public int getNbHarvesting(String harvesterName) + { + return 0; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java index 9fdbea5..de2ce3e 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java @@ -27,6 +27,7 @@ import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; import org.jivesoftware.smack.filter.*; import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.util.*; import org.jivesoftware.smackx.*; import org.jivesoftware.smackx.packet.*; @@ -79,9 +80,9 @@ public class ScServiceDiscoveryManager private final ProtocolProviderService parentProvider; /** - * The {@link XMPPConnection} that this manager is responsible for. + * The {@link Connection} that this manager is responsible for. */ - private final XMPPConnection connection; + private final Connection connection; /** * A local copy that we keep in sync with {@link ServiceDiscoveryManager}'s @@ -129,7 +130,7 @@ public class ScServiceDiscoveryManager */ public ScServiceDiscoveryManager( ProtocolProviderService parentProvider, - XMPPConnection connection, + Connection connection, String[] featuresToRemove, String[] featuresToAdd, boolean cacheNonCaps) @@ -791,7 +792,11 @@ public class ScServiceDiscoveryManager // fire event if(fireEvent && capabilitiesOpSet != null) { - capabilitiesOpSet.fireContactCapabilitiesChanged(entityID); + capabilitiesOpSet.fireContactCapabilitiesChanged( + entityID, + capsManager.getFullJidsByBareJid( + StringUtils.parseBareAddress(entityID)) + ); } } catch(XMPPException ex) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/SmackV3InteroperabilityLayer.java b/src/net/java/sip/communicator/impl/protocol/jabber/SmackV3InteroperabilityLayer.java new file mode 100644 index 0000000..d5af26f --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/SmackV3InteroperabilityLayer.java @@ -0,0 +1,91 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import net.java.sip.communicator.service.protocol.jabber.*; +import org.jivesoftware.smack.provider.*; + +/** + * Smack v3 interoperation layer + * + * @author Maksym Kulish + */ +public class SmackV3InteroperabilityLayer + extends AbstractSmackInteroperabilityLayer +{ + + /** + * A SmackV3 ProviderManager instance + */ + private ProviderManager providerManager = ProviderManager.getInstance(); + + /** + * A default constructor + */ + public SmackV3InteroperabilityLayer() {} + + /** + * Add <tt>PacketExtensionProvider</tt> to the list of known + * providers + * + * @param elementName The element name where the matching is happening + * @param namespace The XML namespace used in that element + * @param provider <tt>PacketExtensionProvider</tt> implementation to be + * used + */ + @Override + public void addExtensionProvider( + String elementName, String namespace, Object provider) + { + providerManager.addExtensionProvider(elementName, namespace, provider); + } + + /** + * Add <tt>IQProvider</tt> to the list of known + * providers + * + * @param elementName The element name where the matching is happening + * @param namespace The XML namespace used in that element + * @param provider <tt>IQProvider</tt> implementation to be + * used + */ + @Override + public void addIQProvider( + String elementName, String namespace, Object provider) + { + providerManager.addIQProvider(elementName, namespace, provider); + } + + /** + * Get the <tt>PacketExtensionProvider</tt> for given element name and XML + * namespace + * + * @param elementName The element name where the matching is happening + * @param namespace The XML namespace used in that element + * @return <tt>PacketExtensionProvider</tt> implementation to be + * used + */ + @Override + public PacketExtensionProvider getExtensionProvider( + String elementName, String namespace) + { + return (PacketExtensionProvider)providerManager + .getExtensionProvider(elementName, namespace); + } + +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java index f7f47c6..41e8c05 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,963 +15,963 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.net.*;
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.media.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jivesoftware.smack.packet.*;
-
-/**
- * <tt>TransportManager</tt>s gather local candidates for incoming and outgoing
- * calls. Their work starts by calling a start method which, using the remote
- * peer's session description, would start the harvest. Calling a second wrapup
- * method would deliver the candidate harvest, possibly after blocking if it has
- * not yet completed.
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- */
-public abstract class TransportManagerJabberImpl
- extends TransportManager<CallPeerJabberImpl>
-{
- /**
- * The <tt>Logger</tt> used by the <tt>TransportManagerJabberImpl</tt> class
- * and its instances to print debug messages.
- */
- private static final Logger logger
- = Logger.getLogger(TransportManagerJabberImpl.class);
-
- /**
- * The ID that we will be assigning to our next candidate. We use
- * <tt>int</tt>s for interoperability reasons (Emil: I believe that GTalk
- * uses <tt>int</tt>s. If that turns out not to be the case we can stop
- * using <tt>int</tt>s here if that's an issue).
- */
- private static int nextID = 1;
-
- /**
- * The information pertaining to the Jisti Videobridge conference which the
- * local peer represented by this instance is a focus of. It gives a view of
- * the whole Jitsi Videobridge conference managed by the associated
- * <tt>CallJabberImpl</tt> which provides information specific to this
- * <tt>TransportManager</tt> only.
- */
- private ColibriConferenceIQ colibri;
-
- /**
- * The generation of the candidates we are currently generating
- */
- private int currentGeneration = 0;
-
- /**
- * The indicator which determines whether this <tt>TransportManager</tt>
- * instance is responsible to establish the connectivity with the associated
- * Jitsi Videobridge (in case it is being employed at all).
- */
- boolean isEstablishingConnectivityWithJitsiVideobridge = false;
-
- /**
- * The indicator which determines whether this <tt>TransportManager</tt>
- * instance is yet to start establishing the connectivity with the
- * associated Jitsi Videobridge (in case it is being employed at all).
- */
- boolean startConnectivityEstablishmentWithJitsiVideobridge = false;
-
- /**
- * Creates a new instance of this transport manager, binding it to the
- * specified peer.
- *
- * @param callPeer the {@link CallPeer} whose traffic we will be taking
- * care of.
- */
- protected TransportManagerJabberImpl(CallPeerJabberImpl callPeer)
- {
- super(callPeer);
- }
-
- /**
- * Returns the <tt>InetAddress</tt> that is most likely to be to be used
- * as a next hop when contacting the specified <tt>destination</tt>. This is
- * an utility method that is used whenever we have to choose one of our
- * local addresses to put in the Via, Contact or (in the case of no
- * registrar accounts) From headers.
- *
- * @param peer the CallPeer that we would contact.
- *
- * @return the <tt>InetAddress</tt> that is most likely to be to be used
- * as a next hop when contacting the specified <tt>destination</tt>.
- *
- * @throws IllegalArgumentException if <tt>destination</tt> is not a valid
- * host/IP/FQDN
- */
- @Override
- protected InetAddress getIntendedDestination(CallPeerJabberImpl peer)
- {
- return peer.getProtocolProvider().getNextHop();
- }
-
- /**
- * Returns the ID that we will be assigning to the next candidate we create.
- *
- * @return the next ID to use with a candidate.
- */
- protected String getNextID()
- {
- int nextID;
-
- synchronized (TransportManagerJabberImpl.class)
- {
- nextID = TransportManagerJabberImpl.nextID++;
- }
- return Integer.toString(nextID);
- }
-
- /**
- * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of
- * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
- * is to have its <tt>target</tt> set to the returned
- * <tt>MediaStreamTarget</tt>
- * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt>
- * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt>
- */
- public abstract MediaStreamTarget getStreamTarget(MediaType mediaType);
-
- /**
- * Gets the XML namespace of the Jingle transport implemented by this
- * <tt>TransportManagerJabberImpl</tt>.
- *
- * @return the XML namespace of the Jingle transport implemented by this
- * <tt>TransportManagerJabberImpl</tt>
- */
- public abstract String getXmlNamespace();
-
- /**
- * Returns the generation that our current candidates belong to.
- *
- * @return the generation that we should assign to candidates that we are
- * currently advertising.
- */
- protected int getCurrentGeneration()
- {
- return currentGeneration;
- }
-
- /**
- * Increments the generation that we are assigning candidates.
- */
- protected void incrementGeneration()
- {
- currentGeneration++;
- }
-
- /**
- * Sends transport-related information received from the remote peer to the
- * associated Jiitsi Videobridge in order to update the (remote)
- * <tt>ColibriConferenceIQ.Channel</tt> associated with this
- * <tt>TransportManager</tt> instance.
- *
- * @param map a <tt>Map</tt> of media-IceUdpTransportPacketExtension pairs
- * which represents the transport-related information which has been
- * received from the remote peer and which is to be sent to the associated
- * Jitsi Videobridge
- */
- protected void sendTransportInfoToJitsiVideobridge(
- Map<String,IceUdpTransportPacketExtension> map)
- {
- CallPeerJabberImpl peer = getCallPeer();
- boolean initiator = !peer.isInitiator();
- ColibriConferenceIQ conferenceRequest = null;
-
- for (Map.Entry<String,IceUdpTransportPacketExtension> e
- : map.entrySet())
- {
- String media = e.getKey();
- MediaType mediaType = MediaType.parseString(media);
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, false /* remote */);
-
- if (channel != null)
- {
- IceUdpTransportPacketExtension transport;
-
- try
- {
- transport = cloneTransportAndCandidates(e.getValue());
- }
- catch (OperationFailedException ofe)
- {
- transport = null;
- }
- if (transport == null)
- continue;
-
- ColibriConferenceIQ.Channel channelRequest
- = new ColibriConferenceIQ.Channel();
-
- channelRequest.setID(channel.getID());
- channelRequest.setInitiator(initiator);
- channelRequest.setTransport(transport);
-
- if (conferenceRequest == null)
- {
- if (colibri == null)
- break;
- else
- {
- String id = colibri.getID();
-
- if ((id == null) || (id.length() == 0))
- break;
- else
- {
- conferenceRequest = new ColibriConferenceIQ();
- conferenceRequest.setID(id);
- conferenceRequest.setTo(colibri.getFrom());
- conferenceRequest.setType(IQ.Type.SET);
- }
- }
- }
- conferenceRequest.getOrCreateContent(media).addChannel(
- channelRequest);
- }
- }
- if (conferenceRequest != null)
- {
- peer.getProtocolProvider().getConnection().sendPacket(
- conferenceRequest);
- }
- }
-
- /**
- * Starts transport candidate harvest for a specific
- * <tt>ContentPacketExtension</tt> that we are going to offer or answer
- * with.
- *
- * @param theirContent the <tt>ContentPacketExtension</tt> offered by the
- * remote peer to which we are going to answer with <tt>ourContent</tt> or
- * <tt>null</tt> if <tt>ourContent</tt> will be an offer to the remote peer
- * @param ourContent the <tt>ContentPacketExtension</tt> for which transport
- * candidate harvest is to be started
- * @param transportInfoSender a <tt>TransportInfoSender</tt> if the
- * harvested transport candidates are to be sent in a
- * <tt>transport-info</tt> rather than in <tt>ourContent</tt>; otherwise,
- * <tt>null</tt>
- * @param media the media of the <tt>RtpDescriptionPacketExtension</tt>
- * child of <tt>ourContent</tt>
- * @return a <tt>PacketExtension</tt> to be added as a child to
- * <tt>ourContent</tt>; otherwise, <tt>null</tt>
- * @throws OperationFailedException if anything goes wrong while starting
- * transport candidate harvest for the specified <tt>ourContent</tt>
- */
- protected abstract PacketExtension startCandidateHarvest(
- ContentPacketExtension theirContent,
- ContentPacketExtension ourContent,
- TransportInfoSender transportInfoSender,
- String media)
- throws OperationFailedException;
-
- /**
- * Starts transport candidate harvest. This method should complete rapidly
- * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
- * are necessary, they should be executed in a separate thread. Candidate
- * harvest would then need to be concluded in the
- * {@link #wrapupCandidateHarvest()} method which would be called once we
- * absolutely need the candidates.
- *
- * @param theirOffer a media description offer that we've received from the
- * remote party and that we should use in case we need to know what
- * transports our peer is using.
- * @param ourAnswer the content descriptions that we should be adding our
- * transport lists to (although not necessarily in this very instance).
- * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by
- * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt>
- * <tt>JingleIQ</tt>s from the local peer to the remote peer if this
- * <tt>TransportManagerJabberImpl</tt> wishes to utilize
- * <tt>transport-info</tt>. Local candidate addresses sent by this
- * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are
- * expected to not be included in the result of
- * {@link #wrapupCandidateHarvest()}.
- *
- * @throws OperationFailedException if we fail to allocate a port number.
- */
- public void startCandidateHarvest(
- List<ContentPacketExtension> theirOffer,
- List<ContentPacketExtension> ourAnswer,
- TransportInfoSender transportInfoSender)
- throws OperationFailedException
- {
- CallPeerJabberImpl peer = getCallPeer();
- CallJabberImpl call = peer.getCall();
- boolean isJitsiVideobridge = call.getConference().isJitsiVideobridge();
- List<ContentPacketExtension> cpes
- = (theirOffer == null) ? ourAnswer : theirOffer;
-
- /*
- * If Jitsi Videobridge is to be used, determine which channels are to
- * be allocated and attempt to allocate them now.
- */
- if (isJitsiVideobridge)
- {
- Map<ContentPacketExtension,ContentPacketExtension> contentMap
- = new LinkedHashMap
- <ContentPacketExtension,ContentPacketExtension>();
-
- for (ContentPacketExtension cpe : cpes)
- {
- MediaType mediaType = JingleUtils.getMediaType(cpe);
-
- /*
- * The existence of a content for the mediaType and regardless
- * of the existence of channels in it signals that a channel
- * allocation request has already been sent for that mediaType.
- */
- if ((colibri == null)
- || (colibri.getContent(mediaType.toString()) == null))
- {
- ContentPacketExtension local, remote;
-
- if (cpes == ourAnswer)
- {
- local = cpe;
- remote
- = (theirOffer == null)
- ? null
- : findContentByName(theirOffer, cpe.getName());
- }
- else
- {
- local = findContentByName(ourAnswer, cpe.getName());
- remote = cpe;
- }
- contentMap.put(local, remote);
- }
- }
- if (!contentMap.isEmpty())
- {
- /*
- * We are about to request the channel allocations for the media
- * types found in contentMap. Regardless of the response, we do
- * not want to repeat these requests.
- */
- if (colibri == null)
- colibri = new ColibriConferenceIQ();
- for (Map.Entry<ContentPacketExtension,ContentPacketExtension> e
- : contentMap.entrySet())
- {
- ContentPacketExtension cpe = e.getValue();
-
- if (cpe == null)
- cpe = e.getKey();
-
- colibri.getOrCreateContent(
- JingleUtils.getMediaType(cpe).toString());
- }
-
- ColibriConferenceIQ conferenceResult
- = call.createColibriChannels(peer, contentMap);
-
- if (conferenceResult != null)
- {
- String videobridgeID = colibri.getID();
- String conferenceResultID = conferenceResult.getID();
-
- if (videobridgeID == null)
- colibri.setID(conferenceResultID);
- else if (!videobridgeID.equals(conferenceResultID))
- throw new IllegalStateException("conference.id");
-
- String videobridgeFrom = conferenceResult.getFrom();
-
- if ((videobridgeFrom != null)
- && (videobridgeFrom.length() != 0))
- {
- colibri.setFrom(videobridgeFrom);
- }
-
- for (ColibriConferenceIQ.Content contentResult
- : conferenceResult.getContents())
- {
- ColibriConferenceIQ.Content content
- = colibri.getOrCreateContent(
- contentResult.getName());
-
- for (ColibriConferenceIQ.Channel channelResult
- : contentResult.getChannels())
- {
- if (content.getChannel(channelResult.getID())
- == null)
- {
- content.addChannel(channelResult);
- }
- }
- }
- }
- else
- {
- /*
- * The call fails if the createColibriChannels method fails
- * which may happen if the conference packet times out or it
- * can't be built.
- */
- ProtocolProviderServiceJabberImpl
- .throwOperationFailedException(
- "Failed to allocate colibri channel.",
- OperationFailedException.GENERAL_ERROR,
- null,
- logger);
- }
- }
- }
-
- for (ContentPacketExtension cpe : cpes)
- {
- String contentName = cpe.getName();
- ContentPacketExtension ourContent
- = findContentByName(ourAnswer, contentName);
-
- //it might be that we decided not to reply to this content
- if (ourContent != null)
- {
- ContentPacketExtension theirContent
- = (theirOffer == null)
- ? null
- : findContentByName(theirOffer, contentName);
- RtpDescriptionPacketExtension rtpDesc
- = ourContent.getFirstChildOfType(
- RtpDescriptionPacketExtension.class);
- String media = rtpDesc.getMedia();
- PacketExtension pe
- = startCandidateHarvest(
- theirContent,
- ourContent,
- transportInfoSender,
- media);
-
- if (pe != null)
- ourContent.addChildExtension(pe);
- }
- }
- }
-
- /**
- * Starts transport candidate harvest. This method should complete rapidly
- * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
- * are necessary, they should be executed in a separate thread. Candidate
- * harvest would then need to be concluded in the
- * {@link #wrapupCandidateHarvest()} method which would be called once we
- * absolutely need the candidates.
- *
- * @param ourOffer the content descriptions that we should be adding our
- * transport lists to (although not necessarily in this very instance).
- * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by
- * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt>
- * <tt>JingleIQ</tt>s from the local peer to the remote peer if this
- * <tt>TransportManagerJabberImpl</tt> wishes to utilize
- * <tt>transport-info</tt>. Local candidate addresses sent by this
- * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are
- * expected to not be included in the result of
- * {@link #wrapupCandidateHarvest()}.
- * @throws OperationFailedException if we fail to allocate a port number.
- */
- public void startCandidateHarvest(
- List<ContentPacketExtension> ourOffer,
- TransportInfoSender transportInfoSender)
- throws OperationFailedException
- {
- startCandidateHarvest(
- /* theirOffer */ null,
- ourOffer,
- transportInfoSender);
- }
-
- /**
- * Notifies the transport manager that it should conclude candidate
- * harvesting as soon as possible and return the lists of candidates
- * gathered so far.
- *
- * @return the content list that we received earlier (possibly cloned into
- * a new instance) and that we have updated with transport lists.
- */
- public abstract List<ContentPacketExtension> wrapupCandidateHarvest();
-
- /**
- * Looks through the <tt>cpExtList</tt> and returns the {@link
- * ContentPacketExtension} with the specified name.
- *
- * @param cpExtList the list that we will be searching for a specific
- * content.
- * @param name the name of the content element we are looking for.
- * @return the {@link ContentPacketExtension} with the specified name or
- * <tt>null</tt> if no such content element exists.
- */
- public static ContentPacketExtension findContentByName(
- Iterable<ContentPacketExtension> cpExtList,
- String name)
- {
- for(ContentPacketExtension cpExt : cpExtList)
- {
- if(cpExt.getName().equals(name))
- return cpExt;
- }
- return null;
- }
-
- /**
- * Starts the connectivity establishment of this
- * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between
- * the local and the remote peers given the remote counterpart of the
- * negotiation between them.
- *
- * @param remote the collection of <tt>ContentPacketExtension</tt>s which
- * represents the remote counterpart of the negotiation between the local
- * and the remote peer
- * @return <tt>true</tt> if connectivity establishment has been started in
- * response to the call; otherwise, <tt>false</tt>.
- * <tt>TransportManagerJabberImpl</tt> implementations which do not perform
- * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The
- * default implementation does not perform connectivity checks and always
- * returns <tt>true</tt>.
- */
- public boolean startConnectivityEstablishment(
- Iterable<ContentPacketExtension> remote)
- {
- return true;
- }
-
- /**
- * Starts the connectivity establishment of this
- * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between
- * the local and the remote peers given the remote counterpart of the
- * negotiation between them.
- *
- * @param remote a <tt>Map</tt> of
- * media-<tt>IceUdpTransportPacketExtension</tt> pairs which represents the
- * remote counterpart of the negotiation between the local and the remote
- * peers
- * @return <tt>true</tt> if connectivity establishment has been started in
- * response to the call; otherwise, <tt>false</tt>.
- * <tt>TransportManagerJabberImpl</tt> implementations which do not perform
- * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The
- * default implementation does not perform connectivity checks and always
- * returns <tt>true</tt>.
- */
- protected boolean startConnectivityEstablishment(
- Map<String,IceUdpTransportPacketExtension> remote)
- {
- return true;
- }
-
- /**
- * Notifies this <tt>TransportManagerJabberImpl</tt> that it should conclude
- * any started connectivity establishment.
- *
- * @throws OperationFailedException if anything goes wrong with connectivity
- * establishment (i.e. ICE failed, ...)
- */
- public void wrapupConnectivityEstablishment()
- throws OperationFailedException
- {
- }
-
- /**
- * Removes a content with a specific name from the transport-related part of
- * the session represented by this <tt>TransportManagerJabberImpl</tt> which
- * may have been reported through previous calls to the
- * <tt>startCandidateHarvest</tt> and
- * <tt>startConnectivityEstablishment</tt> methods.
- * <p>
- * <b>Note</b>: Because <tt>TransportManager</tt> deals with
- * <tt>MediaType</tt>s, not content names and
- * <tt>TransportManagerJabberImpl</tt> does not implement translating from
- * content name to <tt>MediaType</tt>, implementers are expected to call
- * {@link TransportManager#closeStreamConnector(MediaType)}.
- * </p>
- *
- * @param name the name of the content to be removed from the
- * transport-related part of the session represented by this
- * <tt>TransportManagerJabberImpl</tt>
- */
- public abstract void removeContent(String name);
-
- /**
- * Removes a content with a specific name from a specific collection of
- * contents and closes any associated <tt>StreamConnector</tt>.
- *
- * @param contents the collection of contents to remove the content with the
- * specified name from
- * @param name the name of the content to remove
- * @return the removed <tt>ContentPacketExtension</tt> if any; otherwise,
- * <tt>null</tt>
- */
- protected ContentPacketExtension removeContent(
- Iterable<ContentPacketExtension> contents,
- String name)
- {
- for (Iterator<ContentPacketExtension> contentIter = contents.iterator();
- contentIter.hasNext();)
- {
- ContentPacketExtension content = contentIter.next();
-
- if (name.equals(content.getName()))
- {
- contentIter.remove();
-
- // closeStreamConnector
- MediaType mediaType = JingleUtils.getMediaType(content);
- if (mediaType != null)
- {
- closeStreamConnector(mediaType);
- }
-
- return content;
- }
- }
- return null;
- }
-
- /**
- * Clones a specific <tt>IceUdpTransportPacketExtension</tt> and its
- * candidates.
- *
- * @param src the <tt>IceUdpTransportPacketExtension</tt> to be cloned
- * @return a new <tt>IceUdpTransportPacketExtension</tt> instance which has
- * the same run-time type, attributes, namespace, text and candidates as the
- * specified <tt>src</tt>
- * @throws OperationFailedException if an error occurs during the cloing of
- * the specified <tt>src</tt> and its candidates
- */
- static IceUdpTransportPacketExtension cloneTransportAndCandidates(
- IceUdpTransportPacketExtension src)
- throws OperationFailedException
- {
- try
- {
- return IceUdpTransportPacketExtension
- .cloneTransportAndCandidates(src);
- }
- catch (Exception e)
- {
- ProtocolProviderServiceJabberImpl
- .throwOperationFailedException(
- "Failed to close transport and candidates.",
- OperationFailedException.GENERAL_ERROR,
- e,
- logger);
-
- }
- return null;
- }
-
- /**
- * Releases the resources acquired by this <tt>TransportManager</tt> and
- * prepares it for garbage collection.
- */
- public void close()
- {
- for (MediaType mediaType : MediaType.values())
- closeStreamConnector(mediaType);
- }
-
- /**
- * Closes a specific <tt>StreamConnector</tt> associated with a specific
- * <tt>MediaType</tt>. If this <tt>TransportManager</tt> has a reference to
- * the specified <tt>streamConnector</tt>, it remains.
- * Also expires the <tt>ColibriConferenceIQ.Channel</tt> associated with
- * the closed <tt>StreamConnector</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> associated with the specified
- * <tt>streamConnector</tt>
- * @param streamConnector the <tt>StreamConnector</tt> to be closed
- */
- @Override
- protected void closeStreamConnector(
- MediaType mediaType,
- StreamConnector streamConnector)
- {
- try
- {
- boolean superCloseStreamConnector = true;
-
- if (streamConnector instanceof ColibriStreamConnector)
- {
- CallPeerJabberImpl peer = getCallPeer();
-
- if (peer != null)
- {
- CallJabberImpl call = peer.getCall();
-
- if (call != null)
- {
- superCloseStreamConnector = false;
- call.closeColibriStreamConnector(
- peer,
- mediaType,
- (ColibriStreamConnector) streamConnector);
- }
- }
- }
- if (superCloseStreamConnector)
- super.closeStreamConnector(mediaType, streamConnector);
- }
- finally
- {
- /*
- * Expire the ColibriConferenceIQ.Channel associated with the closed
- * StreamConnector.
- */
- if (colibri != null)
- {
- ColibriConferenceIQ.Content content
- = colibri.getContent(mediaType.toString());
-
- if (content != null)
- {
- List<ColibriConferenceIQ.Channel> channels
- = content.getChannels();
-
- if (channels.size() == 2)
- {
- ColibriConferenceIQ requestConferenceIQ
- = new ColibriConferenceIQ();
-
- requestConferenceIQ.setID(colibri.getID());
-
- ColibriConferenceIQ.Content requestContent
- = requestConferenceIQ.getOrCreateContent(
- content.getName());
-
- requestContent.addChannel(channels.get(1 /* remote */));
-
- /*
- * Regardless of whether the request to expire the
- * Channel associated with mediaType succeeds, consider
- * the Channel in question expired. Since
- * RawUdpTransportManager allocates a single channel per
- * MediaType, consider the whole Content expired.
- */
- colibri.removeContent(content);
-
- CallPeerJabberImpl peer = getCallPeer();
-
- if (peer != null)
- {
- CallJabberImpl call = peer.getCall();
-
- if (call != null)
- {
- call.expireColibriChannels(
- peer,
- requestConferenceIQ);
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * Adds support for telephony conferences utilizing the Jitsi Videobridge
- * server-side technology.
- *
- * @see #doCreateStreamConnector(MediaType)
- */
- @Override
- protected StreamConnector createStreamConnector(final MediaType mediaType)
- throws OperationFailedException
- {
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, true /* local */);
-
- if (channel != null)
- {
- CallPeerJabberImpl peer = getCallPeer();
- CallJabberImpl call = peer.getCall();
- StreamConnector streamConnector
- = call.createColibriStreamConnector(
- peer,
- mediaType,
- channel,
- new StreamConnectorFactory()
- {
- public StreamConnector createStreamConnector()
- {
- try
- {
- return doCreateStreamConnector(mediaType);
- }
- catch (OperationFailedException ofe)
- {
- return null;
- }
- }
- });
-
- if (streamConnector != null)
- return streamConnector;
- }
-
- return doCreateStreamConnector(mediaType);
- }
-
- protected abstract PacketExtension createTransport(String media)
- throws OperationFailedException;
-
- protected PacketExtension createTransportForStartCandidateHarvest(
- String media)
- throws OperationFailedException
- {
- PacketExtension pe = null;
-
- if (getCallPeer().isJitsiVideobridge())
- {
- MediaType mediaType = MediaType.parseString(media);
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, false /* remote */);
-
- if (channel != null)
- pe = cloneTransportAndCandidates(channel.getTransport());
- }
- else
- pe = createTransport(media);
- return pe;
- }
-
- /**
- * Initializes a new <tt>PacketExtension</tt> instance appropriate to the
- * type of Jingle transport represented by this <tt>TransportManager</tt>.
- * The new instance is not initialized with any attributes or child
- * extensions.
- *
- * @return a new <tt>PacketExtension</tt> instance appropriate to the type
- * of Jingle transport represented by this <tt>TransportManager</tt>
- */
- protected abstract PacketExtension createTransportPacketExtension();
-
- /**
- * Creates a media <tt>StreamConnector</tt> for a stream of a specific
- * <tt>MediaType</tt>. The minimum and maximum of the media port boundaries
- * are taken into account.
- *
- * @param mediaType the <tt>MediaType</tt> of the stream for which a
- * <tt>StreamConnector</tt> is to be created
- * @return a <tt>StreamConnector</tt> for the stream of the specified
- * <tt>mediaType</tt>
- * @throws OperationFailedException if the binding of the sockets fails
- */
- protected StreamConnector doCreateStreamConnector(MediaType mediaType)
- throws OperationFailedException
- {
- return super.createStreamConnector(mediaType);
- }
-
- /**
- * Finds a <tt>TransportManagerJabberImpl</tt> participating in a telephony
- * conference utilizing the Jitsi Videobridge server-side technology that
- * this instance is participating in which is establishing the connectivity
- * with the Jitsi Videobridge server (as opposed to a <tt>CallPeer</tt>).
- *
- * @return a <tt>TransportManagerJabberImpl</tt> which is participating in
- * a telephony conference utilizing the Jitsi Videobridge server-side
- * technology that this instance is participating in which is establishing
- * the connectivity with the Jitsi Videobridge server (as opposed to a
- * <tt>CallPeer</tt>).
- */
- TransportManagerJabberImpl
- findTransportManagerEstablishingConnectivityWithJitsiVideobridge()
- {
- Call call = getCallPeer().getCall();
- TransportManagerJabberImpl transportManager = null;
-
- if (call != null)
- {
- CallConference conference = call.getConference();
-
- if ((conference != null) && conference.isJitsiVideobridge())
- {
- for (Call aCall : conference.getCalls())
- {
- Iterator<? extends CallPeer> callPeerIter
- = aCall.getCallPeers();
-
- while (callPeerIter.hasNext())
- {
- CallPeer aCallPeer = callPeerIter.next();
-
- if (aCallPeer instanceof CallPeerJabberImpl)
- {
- TransportManagerJabberImpl aTransportManager
- = ((CallPeerJabberImpl) aCallPeer)
- .getMediaHandler()
- .getTransportManager();
-
- if (aTransportManager
- .isEstablishingConnectivityWithJitsiVideobridge)
- {
- transportManager = aTransportManager;
- break;
- }
- }
- }
- }
- }
- }
- return transportManager;
- }
-
- /**
- * Gets the {@link ColibriConferenceIQ.Channel} which belongs to a content
- * associated with a specific <tt>MediaType</tt> and is to be either locally
- * or remotely used.
- * <p>
- * <b>Note</b>: Modifications to the <tt>ColibriConferenceIQ.Channel</tt>
- * instance returned by the method propagate to (the state of) this
- * instance.
- * </p>
- *
- * @param mediaType the <tt>MediaType</tt> associated with the content which
- * contains the <tt>ColibriConferenceIQ.Channel</tt> to get
- * @param local <tt>true</tt> if the <tt>ColibriConferenceIQ.Channel</tt>
- * which is to be used locally is to be returned or <tt>false</tt> for the
- * one which is to be used remotely
- * @return the <tt>ColibriConferenceIQ.Channel</tt> which belongs to a
- * content associated with the specified <tt>mediaType</tt> and which is to
- * be used in accord with the specified <tt>local</tt> indicator if such a
- * channel exists; otherwise, <tt>null</tt>
- */
- ColibriConferenceIQ.Channel getColibriChannel(
- MediaType mediaType,
- boolean local)
- {
- ColibriConferenceIQ.Channel channel = null;
-
- if (colibri != null)
- {
- ColibriConferenceIQ.Content content
- = colibri.getContent(mediaType.toString());
-
- if (content != null)
- {
- List<ColibriConferenceIQ.Channel> channels
- = content.getChannels();
-
- if (channels.size() == 2)
- channel = channels.get(local ? 0 : 1);
- }
- }
- return channel;
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber; + +import java.net.*; +import java.util.*; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; +import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.media.*; +import net.java.sip.communicator.util.*; + +import org.jitsi.service.neomedia.*; +import org.jivesoftware.smack.packet.*; + +/** + * <tt>TransportManager</tt>s gather local candidates for incoming and outgoing + * calls. Their work starts by calling a start method which, using the remote + * peer's session description, would start the harvest. Calling a second wrapup + * method would deliver the candidate harvest, possibly after blocking if it has + * not yet completed. + * + * @author Emil Ivov + * @author Lyubomir Marinov + */ +public abstract class TransportManagerJabberImpl + extends TransportManager<CallPeerJabberImpl> +{ + /** + * The <tt>Logger</tt> used by the <tt>TransportManagerJabberImpl</tt> class + * and its instances to print debug messages. + */ + private static final Logger logger + = Logger.getLogger(TransportManagerJabberImpl.class); + + /** + * The ID that we will be assigning to our next candidate. We use + * <tt>int</tt>s for interoperability reasons (Emil: I believe that GTalk + * uses <tt>int</tt>s. If that turns out not to be the case we can stop + * using <tt>int</tt>s here if that's an issue). + */ + private static int nextID = 1; + + /** + * The information pertaining to the Jisti Videobridge conference which the + * local peer represented by this instance is a focus of. It gives a view of + * the whole Jitsi Videobridge conference managed by the associated + * <tt>CallJabberImpl</tt> which provides information specific to this + * <tt>TransportManager</tt> only. + */ + private ColibriConferenceIQ colibri; + + /** + * The generation of the candidates we are currently generating + */ + private int currentGeneration = 0; + + /** + * The indicator which determines whether this <tt>TransportManager</tt> + * instance is responsible to establish the connectivity with the associated + * Jitsi Videobridge (in case it is being employed at all). + */ + boolean isEstablishingConnectivityWithJitsiVideobridge = false; + + /** + * The indicator which determines whether this <tt>TransportManager</tt> + * instance is yet to start establishing the connectivity with the + * associated Jitsi Videobridge (in case it is being employed at all). + */ + boolean startConnectivityEstablishmentWithJitsiVideobridge = false; + + /** + * Creates a new instance of this transport manager, binding it to the + * specified peer. + * + * @param callPeer the {@link CallPeer} whose traffic we will be taking + * care of. + */ + protected TransportManagerJabberImpl(CallPeerJabberImpl callPeer) + { + super(callPeer); + } + + /** + * Returns the <tt>InetAddress</tt> that is most likely to be to be used + * as a next hop when contacting the specified <tt>destination</tt>. This is + * an utility method that is used whenever we have to choose one of our + * local addresses to put in the Via, Contact or (in the case of no + * registrar accounts) From headers. + * + * @param peer the CallPeer that we would contact. + * + * @return the <tt>InetAddress</tt> that is most likely to be to be used + * as a next hop when contacting the specified <tt>destination</tt>. + * + * @throws IllegalArgumentException if <tt>destination</tt> is not a valid + * host/IP/FQDN + */ + @Override + protected InetAddress getIntendedDestination(CallPeerJabberImpl peer) + { + return peer.getProtocolProvider().getNextHop(); + } + + /** + * Returns the ID that we will be assigning to the next candidate we create. + * + * @return the next ID to use with a candidate. + */ + protected String getNextID() + { + int nextID; + + synchronized (TransportManagerJabberImpl.class) + { + nextID = TransportManagerJabberImpl.nextID++; + } + return Integer.toString(nextID); + } + + /** + * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of + * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>. + * + * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which + * is to have its <tt>target</tt> set to the returned + * <tt>MediaStreamTarget</tt> + * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> + * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt> + */ + public abstract MediaStreamTarget getStreamTarget(MediaType mediaType); + + /** + * Gets the XML namespace of the Jingle transport implemented by this + * <tt>TransportManagerJabberImpl</tt>. + * + * @return the XML namespace of the Jingle transport implemented by this + * <tt>TransportManagerJabberImpl</tt> + */ + public abstract String getXmlNamespace(); + + /** + * Returns the generation that our current candidates belong to. + * + * @return the generation that we should assign to candidates that we are + * currently advertising. + */ + protected int getCurrentGeneration() + { + return currentGeneration; + } + + /** + * Increments the generation that we are assigning candidates. + */ + protected void incrementGeneration() + { + currentGeneration++; + } + + /** + * Sends transport-related information received from the remote peer to the + * associated Jiitsi Videobridge in order to update the (remote) + * <tt>ColibriConferenceIQ.Channel</tt> associated with this + * <tt>TransportManager</tt> instance. + * + * @param map a <tt>Map</tt> of media-IceUdpTransportPacketExtension pairs + * which represents the transport-related information which has been + * received from the remote peer and which is to be sent to the associated + * Jitsi Videobridge + */ + protected void sendTransportInfoToJitsiVideobridge( + Map<String,IceUdpTransportPacketExtension> map) + { + CallPeerJabberImpl peer = getCallPeer(); + boolean initiator = !peer.isInitiator(); + ColibriConferenceIQ conferenceRequest = null; + + for (Map.Entry<String,IceUdpTransportPacketExtension> e + : map.entrySet()) + { + String media = e.getKey(); + MediaType mediaType = MediaType.parseString(media); + ColibriConferenceIQ.Channel channel + = getColibriChannel(mediaType, false /* remote */); + + if (channel != null) + { + IceUdpTransportPacketExtension transport; + + try + { + transport = cloneTransportAndCandidates(e.getValue()); + } + catch (OperationFailedException ofe) + { + transport = null; + } + if (transport == null) + continue; + + ColibriConferenceIQ.Channel channelRequest + = new ColibriConferenceIQ.Channel(); + + channelRequest.setID(channel.getID()); + channelRequest.setInitiator(initiator); + channelRequest.setTransport(transport); + + if (conferenceRequest == null) + { + if (colibri == null) + break; + else + { + String id = colibri.getID(); + + if ((id == null) || (id.length() == 0)) + break; + else + { + conferenceRequest = new ColibriConferenceIQ(); + conferenceRequest.setID(id); + conferenceRequest.setTo(colibri.getFrom()); + conferenceRequest.setType(IQ.Type.SET); + } + } + } + conferenceRequest.getOrCreateContent(media).addChannel( + channelRequest); + } + } + if (conferenceRequest != null) + { + peer.getProtocolProvider().getConnection().sendPacket( + conferenceRequest); + } + } + + /** + * Starts transport candidate harvest for a specific + * <tt>ContentPacketExtension</tt> that we are going to offer or answer + * with. + * + * @param theirContent the <tt>ContentPacketExtension</tt> offered by the + * remote peer to which we are going to answer with <tt>ourContent</tt> or + * <tt>null</tt> if <tt>ourContent</tt> will be an offer to the remote peer + * @param ourContent the <tt>ContentPacketExtension</tt> for which transport + * candidate harvest is to be started + * @param transportInfoSender a <tt>TransportInfoSender</tt> if the + * harvested transport candidates are to be sent in a + * <tt>transport-info</tt> rather than in <tt>ourContent</tt>; otherwise, + * <tt>null</tt> + * @param media the media of the <tt>RtpDescriptionPacketExtension</tt> + * child of <tt>ourContent</tt> + * @return a <tt>PacketExtension</tt> to be added as a child to + * <tt>ourContent</tt>; otherwise, <tt>null</tt> + * @throws OperationFailedException if anything goes wrong while starting + * transport candidate harvest for the specified <tt>ourContent</tt> + */ + protected abstract PacketExtension startCandidateHarvest( + ContentPacketExtension theirContent, + ContentPacketExtension ourContent, + TransportInfoSender transportInfoSender, + String media) + throws OperationFailedException; + + /** + * Starts transport candidate harvest. This method should complete rapidly + * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests + * are necessary, they should be executed in a separate thread. Candidate + * harvest would then need to be concluded in the + * {@link #wrapupCandidateHarvest()} method which would be called once we + * absolutely need the candidates. + * + * @param theirOffer a media description offer that we've received from the + * remote party and that we should use in case we need to know what + * transports our peer is using. + * @param ourAnswer the content descriptions that we should be adding our + * transport lists to (although not necessarily in this very instance). + * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by + * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt> + * <tt>JingleIQ</tt>s from the local peer to the remote peer if this + * <tt>TransportManagerJabberImpl</tt> wishes to utilize + * <tt>transport-info</tt>. Local candidate addresses sent by this + * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are + * expected to not be included in the result of + * {@link #wrapupCandidateHarvest()}. + * + * @throws OperationFailedException if we fail to allocate a port number. + */ + public void startCandidateHarvest( + List<ContentPacketExtension> theirOffer, + List<ContentPacketExtension> ourAnswer, + TransportInfoSender transportInfoSender) + throws OperationFailedException + { + CallPeerJabberImpl peer = getCallPeer(); + CallJabberImpl call = peer.getCall(); + boolean isJitsiVideobridge = call.getConference().isJitsiVideobridge(); + List<ContentPacketExtension> cpes + = (theirOffer == null) ? ourAnswer : theirOffer; + + /* + * If Jitsi Videobridge is to be used, determine which channels are to + * be allocated and attempt to allocate them now. + */ + if (isJitsiVideobridge) + { + Map<ContentPacketExtension,ContentPacketExtension> contentMap + = new LinkedHashMap + <ContentPacketExtension,ContentPacketExtension>(); + + for (ContentPacketExtension cpe : cpes) + { + MediaType mediaType = JingleUtils.getMediaType(cpe); + + /* + * The existence of a content for the mediaType and regardless + * of the existence of channels in it signals that a channel + * allocation request has already been sent for that mediaType. + */ + if ((colibri == null) + || (colibri.getContent(mediaType.toString()) == null)) + { + ContentPacketExtension local, remote; + + if (cpes == ourAnswer) + { + local = cpe; + remote + = (theirOffer == null) + ? null + : findContentByName(theirOffer, cpe.getName()); + } + else + { + local = findContentByName(ourAnswer, cpe.getName()); + remote = cpe; + } + contentMap.put(local, remote); + } + } + if (!contentMap.isEmpty()) + { + /* + * We are about to request the channel allocations for the media + * types found in contentMap. Regardless of the response, we do + * not want to repeat these requests. + */ + if (colibri == null) + colibri = new ColibriConferenceIQ(); + for (Map.Entry<ContentPacketExtension,ContentPacketExtension> e + : contentMap.entrySet()) + { + ContentPacketExtension cpe = e.getValue(); + + if (cpe == null) + cpe = e.getKey(); + + colibri.getOrCreateContent( + JingleUtils.getMediaType(cpe).toString()); + } + + ColibriConferenceIQ conferenceResult + = call.createColibriChannels(peer, contentMap); + + if (conferenceResult != null) + { + String videobridgeID = colibri.getID(); + String conferenceResultID = conferenceResult.getID(); + + if (videobridgeID == null) + colibri.setID(conferenceResultID); + else if (!videobridgeID.equals(conferenceResultID)) + throw new IllegalStateException("conference.id"); + + String videobridgeFrom = conferenceResult.getFrom(); + + if ((videobridgeFrom != null) + && (videobridgeFrom.length() != 0)) + { + colibri.setFrom(videobridgeFrom); + } + + for (ColibriConferenceIQ.Content contentResult + : conferenceResult.getContents()) + { + ColibriConferenceIQ.Content content + = colibri.getOrCreateContent( + contentResult.getName()); + + for (ColibriConferenceIQ.Channel channelResult + : contentResult.getChannels()) + { + if (content.getChannel(channelResult.getID()) + == null) + { + content.addChannel(channelResult); + } + } + } + } + else + { + /* + * The call fails if the createColibriChannels method fails + * which may happen if the conference packet times out or it + * can't be built. + */ + ProtocolProviderServiceJabberImpl + .throwOperationFailedException( + "Failed to allocate colibri channel.", + OperationFailedException.GENERAL_ERROR, + null, + logger); + } + } + } + + for (ContentPacketExtension cpe : cpes) + { + String contentName = cpe.getName(); + ContentPacketExtension ourContent + = findContentByName(ourAnswer, contentName); + + //it might be that we decided not to reply to this content + if (ourContent != null) + { + ContentPacketExtension theirContent + = (theirOffer == null) + ? null + : findContentByName(theirOffer, contentName); + RtpDescriptionPacketExtension rtpDesc + = ourContent.getFirstChildOfType( + RtpDescriptionPacketExtension.class); + String media = rtpDesc.getMedia(); + PacketExtension pe + = startCandidateHarvest( + theirContent, + ourContent, + transportInfoSender, + media); + + if (pe != null) + ourContent.addChildExtension(pe); + } + } + } + + /** + * Starts transport candidate harvest. This method should complete rapidly + * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests + * are necessary, they should be executed in a separate thread. Candidate + * harvest would then need to be concluded in the + * {@link #wrapupCandidateHarvest()} method which would be called once we + * absolutely need the candidates. + * + * @param ourOffer the content descriptions that we should be adding our + * transport lists to (although not necessarily in this very instance). + * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by + * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt> + * <tt>JingleIQ</tt>s from the local peer to the remote peer if this + * <tt>TransportManagerJabberImpl</tt> wishes to utilize + * <tt>transport-info</tt>. Local candidate addresses sent by this + * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are + * expected to not be included in the result of + * {@link #wrapupCandidateHarvest()}. + * @throws OperationFailedException if we fail to allocate a port number. + */ + public void startCandidateHarvest( + List<ContentPacketExtension> ourOffer, + TransportInfoSender transportInfoSender) + throws OperationFailedException + { + startCandidateHarvest( + /* theirOffer */ null, + ourOffer, + transportInfoSender); + } + + /** + * Notifies the transport manager that it should conclude candidate + * harvesting as soon as possible and return the lists of candidates + * gathered so far. + * + * @return the content list that we received earlier (possibly cloned into + * a new instance) and that we have updated with transport lists. + */ + public abstract List<ContentPacketExtension> wrapupCandidateHarvest(); + + /** + * Looks through the <tt>cpExtList</tt> and returns the {@link + * ContentPacketExtension} with the specified name. + * + * @param cpExtList the list that we will be searching for a specific + * content. + * @param name the name of the content element we are looking for. + * @return the {@link ContentPacketExtension} with the specified name or + * <tt>null</tt> if no such content element exists. + */ + public static ContentPacketExtension findContentByName( + Iterable<ContentPacketExtension> cpExtList, + String name) + { + for(ContentPacketExtension cpExt : cpExtList) + { + if(cpExt.getName().equals(name)) + return cpExt; + } + return null; + } + + /** + * Starts the connectivity establishment of this + * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between + * the local and the remote peers given the remote counterpart of the + * negotiation between them. + * + * @param remote the collection of <tt>ContentPacketExtension</tt>s which + * represents the remote counterpart of the negotiation between the local + * and the remote peer + * @return <tt>true</tt> if connectivity establishment has been started in + * response to the call; otherwise, <tt>false</tt>. + * <tt>TransportManagerJabberImpl</tt> implementations which do not perform + * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The + * default implementation does not perform connectivity checks and always + * returns <tt>true</tt>. + */ + public boolean startConnectivityEstablishment( + Iterable<ContentPacketExtension> remote) + { + return true; + } + + /** + * Starts the connectivity establishment of this + * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between + * the local and the remote peers given the remote counterpart of the + * negotiation between them. + * + * @param remote a <tt>Map</tt> of + * media-<tt>IceUdpTransportPacketExtension</tt> pairs which represents the + * remote counterpart of the negotiation between the local and the remote + * peers + * @return <tt>true</tt> if connectivity establishment has been started in + * response to the call; otherwise, <tt>false</tt>. + * <tt>TransportManagerJabberImpl</tt> implementations which do not perform + * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The + * default implementation does not perform connectivity checks and always + * returns <tt>true</tt>. + */ + protected boolean startConnectivityEstablishment( + Map<String,IceUdpTransportPacketExtension> remote) + { + return true; + } + + /** + * Notifies this <tt>TransportManagerJabberImpl</tt> that it should conclude + * any started connectivity establishment. + * + * @throws OperationFailedException if anything goes wrong with connectivity + * establishment (i.e. ICE failed, ...) + */ + public void wrapupConnectivityEstablishment() + throws OperationFailedException + { + } + + /** + * Removes a content with a specific name from the transport-related part of + * the session represented by this <tt>TransportManagerJabberImpl</tt> which + * may have been reported through previous calls to the + * <tt>startCandidateHarvest</tt> and + * <tt>startConnectivityEstablishment</tt> methods. + * <p> + * <b>Note</b>: Because <tt>TransportManager</tt> deals with + * <tt>MediaType</tt>s, not content names and + * <tt>TransportManagerJabberImpl</tt> does not implement translating from + * content name to <tt>MediaType</tt>, implementers are expected to call + * {@link TransportManager#closeStreamConnector(MediaType)}. + * </p> + * + * @param name the name of the content to be removed from the + * transport-related part of the session represented by this + * <tt>TransportManagerJabberImpl</tt> + */ + public abstract void removeContent(String name); + + /** + * Removes a content with a specific name from a specific collection of + * contents and closes any associated <tt>StreamConnector</tt>. + * + * @param contents the collection of contents to remove the content with the + * specified name from + * @param name the name of the content to remove + * @return the removed <tt>ContentPacketExtension</tt> if any; otherwise, + * <tt>null</tt> + */ + protected ContentPacketExtension removeContent( + Iterable<ContentPacketExtension> contents, + String name) + { + for (Iterator<ContentPacketExtension> contentIter = contents.iterator(); + contentIter.hasNext();) + { + ContentPacketExtension content = contentIter.next(); + + if (name.equals(content.getName())) + { + contentIter.remove(); + + // closeStreamConnector + MediaType mediaType = JingleUtils.getMediaType(content); + if (mediaType != null) + { + closeStreamConnector(mediaType); + } + + return content; + } + } + return null; + } + + /** + * Clones a specific <tt>IceUdpTransportPacketExtension</tt> and its + * candidates. + * + * @param src the <tt>IceUdpTransportPacketExtension</tt> to be cloned + * @return a new <tt>IceUdpTransportPacketExtension</tt> instance which has + * the same run-time type, attributes, namespace, text and candidates as the + * specified <tt>src</tt> + * @throws OperationFailedException if an error occurs during the cloing of + * the specified <tt>src</tt> and its candidates + */ + static IceUdpTransportPacketExtension cloneTransportAndCandidates( + IceUdpTransportPacketExtension src) + throws OperationFailedException + { + try + { + return IceUdpTransportPacketExtension + .cloneTransportAndCandidates(src); + } + catch (Exception e) + { + ProtocolProviderServiceJabberImpl + .throwOperationFailedException( + "Failed to close transport and candidates.", + OperationFailedException.GENERAL_ERROR, + e, + logger); + + } + return null; + } + + /** + * Releases the resources acquired by this <tt>TransportManager</tt> and + * prepares it for garbage collection. + */ + public void close() + { + for (MediaType mediaType : MediaType.values()) + closeStreamConnector(mediaType); + } + + /** + * Closes a specific <tt>StreamConnector</tt> associated with a specific + * <tt>MediaType</tt>. If this <tt>TransportManager</tt> has a reference to + * the specified <tt>streamConnector</tt>, it remains. + * Also expires the <tt>ColibriConferenceIQ.Channel</tt> associated with + * the closed <tt>StreamConnector</tt>. + * + * @param mediaType the <tt>MediaType</tt> associated with the specified + * <tt>streamConnector</tt> + * @param streamConnector the <tt>StreamConnector</tt> to be closed + */ + @Override + protected void closeStreamConnector( + MediaType mediaType, + StreamConnector streamConnector) + { + try + { + boolean superCloseStreamConnector = true; + + if (streamConnector instanceof ColibriStreamConnector) + { + CallPeerJabberImpl peer = getCallPeer(); + + if (peer != null) + { + CallJabberImpl call = peer.getCall(); + + if (call != null) + { + superCloseStreamConnector = false; + call.closeColibriStreamConnector( + peer, + mediaType, + (ColibriStreamConnector) streamConnector); + } + } + } + if (superCloseStreamConnector) + super.closeStreamConnector(mediaType, streamConnector); + } + finally + { + /* + * Expire the ColibriConferenceIQ.Channel associated with the closed + * StreamConnector. + */ + if (colibri != null) + { + ColibriConferenceIQ.Content content + = colibri.getContent(mediaType.toString()); + + if (content != null) + { + List<ColibriConferenceIQ.Channel> channels + = content.getChannels(); + + if (channels.size() == 2) + { + ColibriConferenceIQ requestConferenceIQ + = new ColibriConferenceIQ(); + + requestConferenceIQ.setID(colibri.getID()); + + ColibriConferenceIQ.Content requestContent + = requestConferenceIQ.getOrCreateContent( + content.getName()); + + requestContent.addChannel(channels.get(1 /* remote */)); + + /* + * Regardless of whether the request to expire the + * Channel associated with mediaType succeeds, consider + * the Channel in question expired. Since + * RawUdpTransportManager allocates a single channel per + * MediaType, consider the whole Content expired. + */ + colibri.removeContent(content); + + CallPeerJabberImpl peer = getCallPeer(); + + if (peer != null) + { + CallJabberImpl call = peer.getCall(); + + if (call != null) + { + call.expireColibriChannels( + peer, + requestConferenceIQ); + } + } + } + } + } + } + } + + /** + * {@inheritDoc} + * + * Adds support for telephony conferences utilizing the Jitsi Videobridge + * server-side technology. + * + * @see #doCreateStreamConnector(MediaType) + */ + @Override + protected StreamConnector createStreamConnector(final MediaType mediaType) + throws OperationFailedException + { + ColibriConferenceIQ.Channel channel + = getColibriChannel(mediaType, true /* local */); + + if (channel != null) + { + CallPeerJabberImpl peer = getCallPeer(); + CallJabberImpl call = peer.getCall(); + StreamConnector streamConnector + = call.createColibriStreamConnector( + peer, + mediaType, + channel, + new StreamConnectorFactory() + { + public StreamConnector createStreamConnector() + { + try + { + return doCreateStreamConnector(mediaType); + } + catch (OperationFailedException ofe) + { + return null; + } + } + }); + + if (streamConnector != null) + return streamConnector; + } + + return doCreateStreamConnector(mediaType); + } + + protected abstract PacketExtension createTransport(String media) + throws OperationFailedException; + + protected PacketExtension createTransportForStartCandidateHarvest( + String media) + throws OperationFailedException + { + PacketExtension pe = null; + + if (getCallPeer().isJitsiVideobridge()) + { + MediaType mediaType = MediaType.parseString(media); + ColibriConferenceIQ.Channel channel + = getColibriChannel(mediaType, false /* remote */); + + if (channel != null) + pe = cloneTransportAndCandidates(channel.getTransport()); + } + else + pe = createTransport(media); + return pe; + } + + /** + * Initializes a new <tt>PacketExtension</tt> instance appropriate to the + * type of Jingle transport represented by this <tt>TransportManager</tt>. + * The new instance is not initialized with any attributes or child + * extensions. + * + * @return a new <tt>PacketExtension</tt> instance appropriate to the type + * of Jingle transport represented by this <tt>TransportManager</tt> + */ + protected abstract PacketExtension createTransportPacketExtension(); + + /** + * Creates a media <tt>StreamConnector</tt> for a stream of a specific + * <tt>MediaType</tt>. The minimum and maximum of the media port boundaries + * are taken into account. + * + * @param mediaType the <tt>MediaType</tt> of the stream for which a + * <tt>StreamConnector</tt> is to be created + * @return a <tt>StreamConnector</tt> for the stream of the specified + * <tt>mediaType</tt> + * @throws OperationFailedException if the binding of the sockets fails + */ + protected StreamConnector doCreateStreamConnector(MediaType mediaType) + throws OperationFailedException + { + return super.createStreamConnector(mediaType); + } + + /** + * Finds a <tt>TransportManagerJabberImpl</tt> participating in a telephony + * conference utilizing the Jitsi Videobridge server-side technology that + * this instance is participating in which is establishing the connectivity + * with the Jitsi Videobridge server (as opposed to a <tt>CallPeer</tt>). + * + * @return a <tt>TransportManagerJabberImpl</tt> which is participating in + * a telephony conference utilizing the Jitsi Videobridge server-side + * technology that this instance is participating in which is establishing + * the connectivity with the Jitsi Videobridge server (as opposed to a + * <tt>CallPeer</tt>). + */ + TransportManagerJabberImpl + findTransportManagerEstablishingConnectivityWithJitsiVideobridge() + { + Call call = getCallPeer().getCall(); + TransportManagerJabberImpl transportManager = null; + + if (call != null) + { + CallConference conference = call.getConference(); + + if ((conference != null) && conference.isJitsiVideobridge()) + { + for (Call aCall : conference.getCalls()) + { + Iterator<? extends CallPeer> callPeerIter + = aCall.getCallPeers(); + + while (callPeerIter.hasNext()) + { + CallPeer aCallPeer = callPeerIter.next(); + + if (aCallPeer instanceof CallPeerJabberImpl) + { + TransportManagerJabberImpl aTransportManager + = ((CallPeerJabberImpl) aCallPeer) + .getMediaHandler() + .getTransportManager(); + + if (aTransportManager + .isEstablishingConnectivityWithJitsiVideobridge) + { + transportManager = aTransportManager; + break; + } + } + } + } + } + } + return transportManager; + } + + /** + * Gets the {@link ColibriConferenceIQ.Channel} which belongs to a content + * associated with a specific <tt>MediaType</tt> and is to be either locally + * or remotely used. + * <p> + * <b>Note</b>: Modifications to the <tt>ColibriConferenceIQ.Channel</tt> + * instance returned by the method propagate to (the state of) this + * instance. + * </p> + * + * @param mediaType the <tt>MediaType</tt> associated with the content which + * contains the <tt>ColibriConferenceIQ.Channel</tt> to get + * @param local <tt>true</tt> if the <tt>ColibriConferenceIQ.Channel</tt> + * which is to be used locally is to be returned or <tt>false</tt> for the + * one which is to be used remotely + * @return the <tt>ColibriConferenceIQ.Channel</tt> which belongs to a + * content associated with the specified <tt>mediaType</tt> and which is to + * be used in accord with the specified <tt>local</tt> indicator if such a + * channel exists; otherwise, <tt>null</tt> + */ + ColibriConferenceIQ.Channel getColibriChannel( + MediaType mediaType, + boolean local) + { + ColibriConferenceIQ.Channel channel = null; + + if (colibri != null) + { + ColibriConferenceIQ.Content content + = colibri.getContent(mediaType.toString()); + + if (content != null) + { + List<ColibriConferenceIQ.Channel> channels + = content.getChannels(); + + if (channels.size() == 2) + channel = channels.get(local ? 0 : 1); + } + } + return channel; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/DefaultPacketExtensionProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/DefaultPacketExtensionProvider.java index 1285581..778d086 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/DefaultPacketExtensionProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/DefaultPacketExtensionProvider.java @@ -19,6 +19,7 @@ package net.java.sip.communicator.impl.protocol.jabber.extensions; import java.util.logging.*; +import net.java.sip.communicator.service.protocol.jabber.*; import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.provider.*; import org.xmlpull.v1.*; @@ -41,6 +42,13 @@ public class DefaultPacketExtensionProvider<C extends AbstractPacketExtension> .getLogger(DefaultPacketExtensionProvider.class.getName()); /** + * The <tt>AbstractSmackInteroperabilityLayer</tt> instance implementing + * necessary methods + */ + private AbstractSmackInteroperabilityLayer smackInteroperabilityLayer = + AbstractSmackInteroperabilityLayer.getInstance(); + + /** * The {@link Class} that the packets we will be parsing here belong to. */ private final Class<C> packetClass; @@ -100,8 +108,7 @@ public class DefaultPacketExtensionProvider<C extends AbstractPacketExtension> if (eventType == XmlPullParser.START_TAG) { - PacketExtensionProvider provider - = (PacketExtensionProvider)ProviderManager.getInstance() + PacketExtensionProvider provider = smackInteroperabilityLayer .getExtensionProvider( elementName, namespace ); if(provider == null) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/EntityCapsManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/EntityCapsManager.java index ebdcbb0..97d9ff8 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/EntityCapsManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/EntityCapsManager.java @@ -243,6 +243,7 @@ public class EntityCapsManager if ((user != null) && (node != null) && (hash != null) && (ver != null)) { Caps caps = userCaps.get(user); + String bareJid=StringUtils.parseBareAddress(user); if ((caps == null) || !caps.node.equals(node) @@ -270,7 +271,9 @@ public class EntityCapsManager String nodeVer = caps.getNodeVer(); for (UserCapsNodeListener listener : listeners) - listener.userCapsNodeAdded(user, nodeVer, online); + listener.userCapsNodeAdded(user, + getFullJidsByBareJid(bareJid), + nodeVer, online); } } } @@ -305,6 +308,8 @@ public class EntityCapsManager { Caps caps = null; String lastRemovedJid = null; + String bareJid=StringUtils.parseBareAddress( + contact.getAddress()); Iterator<String> iter = userCaps.keySet().iterator(); while(iter.hasNext()) @@ -337,7 +342,9 @@ public class EntityCapsManager for (UserCapsNodeListener listener : listeners) listener.userCapsNodeRemoved( - lastRemovedJid, nodeVer, false); + lastRemovedJid, + getFullJidsByBareJid(bareJid), + nodeVer, false); } } } @@ -350,6 +357,7 @@ public class EntityCapsManager public void removeUserCapsNode(String user) { Caps caps = userCaps.remove(user); + String bareJid=StringUtils.parseBareAddress(user); // Fire userCapsNodeRemoved. if (caps != null) @@ -367,7 +375,9 @@ public class EntityCapsManager String nodeVer = caps.getNodeVer(); for (UserCapsNodeListener listener : listeners) - listener.userCapsNodeRemoved(user, nodeVer, false); + listener.userCapsNodeRemoved(user, + getFullJidsByBareJid(bareJid), + nodeVer, false); } } } @@ -404,6 +414,24 @@ public class EntityCapsManager { return userCaps.get(user); } + + /** + * Gets the full Jids (with resources) as Strings. + * + * @param the bare Jid + * @return the full Jids as an ArrayList <tt>user</tt> + */ + public ArrayList<String> getFullJidsByBareJid(String bareJid) + { + ArrayList<String> jids = new ArrayList<String>(); + for(String jid: userCaps.keySet()) + { + if(bareJid.equals(StringUtils.parseBareAddress(jid))){ + jids.add(jid); + } + } + return jids; + } /** * Get the discover info given a user name. The discover info is returned if @@ -605,14 +633,9 @@ public class EntityCapsManager * @param connection the connection that we'd like this manager to register * with. */ - public void addPacketListener(XMPPConnection connection) + public void addPacketListener(Connection connection) { - PacketFilter filter - = new AndFilter( - new PacketTypeFilter(Presence.class), - new PacketExtensionFilter( - CapsPacketExtension.ELEMENT_NAME, - CapsPacketExtension.NAMESPACE)); + PacketFilter filter = new PacketTypeFilter(Presence.class); connection.addPacketListener(new CapsPacketListener(), filter); } @@ -913,48 +936,48 @@ public class EntityCapsManager */ public void processPacket(Packet packet) { + // Check it the packet indicates that the user is online. We + // will use this information to decide if we're going to send + // the discover info request. + boolean online + = (packet instanceof Presence) + && ((Presence) packet).isAvailable(); + CapsPacketExtension ext = (CapsPacketExtension) packet.getExtension( CapsPacketExtension.ELEMENT_NAME, CapsPacketExtension.NAMESPACE); - /* - * Before Version 1.4 of XEP-0115: Entity Capabilities, the 'ver' - * attribute was generated differently and the 'hash' attribute was - * absent. The 'ver' attribute in Version 1.3 represents the - * specific version of the client and thus does not provide a way to - * validate the DiscoverInfo sent by the client. If - * EntityCapsManager receives no 'hash' attribute, it will assume - * the legacy format and will not cache it because the DiscoverInfo - * to be received from the client later on will not be trustworthy. - */ - String hash = ext.getHash(); - - /* Google Talk web does not set hash but we need it to be cached */ - if(hash == null) - hash = ""; - - if (hash != null) + if(ext != null && online) { - // Check it the packet indicates that the user is online. We - // will use this information to decide if we're going to send - // the discover info request. - boolean online - = (packet instanceof Presence) - && ((Presence) packet).isAvailable(); - - if(online) - { - addUserCapsNode( - packet.getFrom(), - ext.getNode(), hash, ext.getVersion(), - ext.getExtensions(), online); - } - else - { - removeUserCapsNode(packet.getFrom()); - } + /* + * Before Version 1.4 of XEP-0115: Entity Capabilities, + * the 'ver' attribute was generated differently and the 'hash' + * attribute was absent. The 'ver' attribute in Version 1.3 + * represents the specific version of the client and thus does + * not provide a way to validate the DiscoverInfo sent by + * the client. If EntityCapsManager receives no 'hash' + * attribute, it will assume the legacy format and will not + * cache it because the DiscoverInfo to be received from + * the client later on will not be trustworthy. + */ + String hash = ext.getHash(); + + /* Google Talk web does not set hash, but we need it to + * be cached + */ + if (hash == null) + hash = ""; + + addUserCapsNode( + packet.getFrom(), + ext.getNode(), hash, ext.getVersion(), + ext.getExtensions(), online); + } + else if (!online) + { + removeUserCapsNode(packet.getFrom()); } } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/UserCapsNodeListener.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/UserCapsNodeListener.java index 5ee38b0..eda921f 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/UserCapsNodeListener.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/UserCapsNodeListener.java @@ -17,6 +17,8 @@ */ package net.java.sip.communicator.impl.protocol.jabber.extensions.caps; +import java.util.ArrayList; + /** * Represents a listener of events notifying about changes in the list of user * caps nodes of <tt>EntityCapsManager</tt>. @@ -30,18 +32,22 @@ public interface UserCapsNodeListener * record for a specific user about the caps node the user has. * * @param user the user (full JID) + * @param fullJids a list of all resources of the user (full JIDs) * @param node the entity caps node#ver * @param online indicates if the user for which we're notified is online */ - public void userCapsNodeAdded(String user, String node, boolean online); + public void userCapsNodeAdded(String user, ArrayList<String> fullJids, + String node, boolean online); /** * Notifies this listener that an <tt>EntityCapsManager</tt> has removed a * record for a specific user about the caps node the user has. * * @param user the user (full JID) + * @param fullJids a list of all resources of the user (full JIDs) * @param node the entity caps node#ver * @param online indicates if the user for which we're notified is online */ - public void userCapsNodeRemoved(String user, String node, boolean online); + public void userCapsNodeRemoved(String user, ArrayList<String> fullJids, + String node, boolean online); } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriBuilder.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriBuilder.java index 547ebe8..48207c2 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriBuilder.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriBuilder.java @@ -37,9 +37,9 @@ import java.util.*; * Add one or multiple requests of the same type by calling * {@link #addAllocateChannelsReq(boolean, String, boolean, java.util.List)}} * or {@link #addExpireChannelsReq(ColibriConferenceIQ)} - * or {@link #addTransportUpdateReq(boolean, java.util.Map, ColibriConferenceIQ)} - * or {@link #addBundleTransportUpdateReq( - * boolean, IceUdpTransportPacketExtension, ColibriConferenceIQ)}. + * or {@link #addRtpDescription(Map, ColibriConferenceIQ)} + * and {@link #addSSSRCGroupsInfo(Map, ColibriConferenceIQ)} + * and {@link #addSSSRCInfo(Map, ColibriConferenceIQ)}. * </li> * <li> * Compile the request by calling {@link #getRequest(String)}. Then send it to @@ -119,6 +119,20 @@ public class ColibriBuilder private SimulcastMode simulcastMode; /** + * Specifies the audio packet delay that will be set on all created audio + * channels. When set to <tt>null</tt> the builder will clear the attribute + * which stands for 'undefined'. + **/ + private Integer audioPacketDelay; + + /** + * Channel 'rtp-level-relay-type' option that will be used with all created + * audio channel. Possible values: mixer or translator (default). + * + */ + private RTPLevelRelayType rtpLevelRelayType; + + /** * Creates new instance of {@link ColibriBuilder} for given * <tt>conferenceState</tt>. * @@ -161,18 +175,26 @@ public class ColibriBuilder * @param contents the list of {@link ContentPacketExtension} describing * channels media. * - * @return this instance fo calls chaining purpose. + * @return <tt>true</tt> if the request yields any changes in Colibri + * channels state on the bridge or <tt>false</tt> otherwise. + * In general when <tt>false</tt> is returned for all + * combined requests it makes no sense to send it. */ - public ColibriBuilder addAllocateChannelsReq( - boolean useBundle, - String endpointName, - boolean peerIsInitiator, + public boolean addAllocateChannelsReq( + boolean useBundle, + String endpointName, + boolean peerIsInitiator, List<ContentPacketExtension> contents) { + Objects.requireNonNull(endpointName, "endpointName"); + Objects.requireNonNull(contents, "contents"); + assertRequestType(RequestType.ALLOCATE_CHANNELS); request.setType(IQ.Type.GET); + boolean hasAnyChanges = false; + for (ContentPacketExtension cpe : contents) { MediaType mediaType = JingleUtils.getMediaType(cpe); @@ -193,7 +215,7 @@ public class ColibriBuilder remoteChannelRequest.setChannelBundleId(endpointName); } - if (mediaType != MediaType.DATA) + if (remoteChannelRequest instanceof ColibriConferenceIQ.Channel) { RtpDescriptionPacketExtension rdpe = cpe.getFirstChildOfType( @@ -213,6 +235,13 @@ public class ColibriBuilder remoteRtpChannelRequest.setAdaptiveLastN(adaptiveLastN); remoteRtpChannelRequest.setAdaptiveSimulcast(adaptiveSimulcast); remoteRtpChannelRequest.setSimulcastMode(simulcastMode); + if (MediaType.AUDIO.equals(mediaType)) + { + // When audioPacketDelay is null it will clear the attribute + remoteRtpChannelRequest.setPacketDelay(audioPacketDelay); + // Set rtp packet relay type for this channel + remoteRtpChannelRequest.setRTPLevelRelayType(rtpLevelRelayType); + } } // Copy transport @@ -221,13 +250,17 @@ public class ColibriBuilder copyTransportOnChannel(cpe, remoteChannelRequest); } - if (mediaType != MediaType.DATA) + if (remoteChannelRequest instanceof ColibriConferenceIQ.Channel) { + hasAnyChanges = true; + contentRequest.addChannel( (ColibriConferenceIQ.Channel) remoteChannelRequest); } else { + hasAnyChanges = true; + contentRequest.addSctpConnection( (ColibriConferenceIQ.SctpConnection) remoteChannelRequest); } @@ -246,6 +279,8 @@ public class ColibriBuilder IceUdpTransportPacketExtension.class); if (transport != null) { + hasAnyChanges = true; + bundle.setTransport( IceUdpTransportPacketExtension .cloneTransportAndCandidates(transport, true)); @@ -254,106 +289,38 @@ public class ColibriBuilder request.addChannelBundle(bundle); } - return this; - } - - /** - * Adds next ICE transport update request to - * {@link RequestType#TRANSPORT_UPDATE} query currently being built. - * - * @param initiator the value that will be set in 'initiator' - * attribute({@link ColibriConferenceIQ.Channel#initiator}). - * @param map the map of content name to transport extensions. Maps - * transport to media types. - * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about - * Colibri channels to be updated. - * - * @return this instance fo calls chaining purpose. - */ - public ColibriBuilder addTransportUpdateReq( - boolean initiator, - Map<String, IceUdpTransportPacketExtension> map, - ColibriConferenceIQ localChannelsInfo) - { - if (conferenceState == null - || StringUtils.isNullOrEmpty(conferenceState.getID())) - { - // We are not initialized yet - return null; - } - - assertRequestType(RequestType.TRANSPORT_UPDATE); - - request.setType(IQ.Type.SET); - - for (Map.Entry<String,IceUdpTransportPacketExtension> e - : map.entrySet()) - { - String contentName = e.getKey(); - ColibriConferenceIQ.ChannelCommon channel - = getColibriChannel(localChannelsInfo, contentName); - - if (channel != null) - { - IceUdpTransportPacketExtension transport - = IceUdpTransportPacketExtension - .cloneTransportAndCandidates(e.getValue(), true); - - ColibriConferenceIQ.ChannelCommon channelRequest - = channel instanceof ColibriConferenceIQ.Channel - ? new ColibriConferenceIQ.Channel() - : new ColibriConferenceIQ.SctpConnection(); - - channelRequest.setID(channel.getID()); - channelRequest.setEndpoint(channel.getEndpoint()); - channelRequest.setInitiator(initiator); - channelRequest.setTransport(transport); - - if (channelRequest instanceof ColibriConferenceIQ.Channel) - { - request.getOrCreateContent(contentName) - .addChannel( - (ColibriConferenceIQ.Channel) channelRequest); - } - else - { - request.getOrCreateContent(contentName) - .addSctpConnection( - (ColibriConferenceIQ.SctpConnection) channelRequest); - } - } - } - return this; + return hasAnyChanges; } /** - * Adds next request to {@link RequestType#BUNDLE_TRANSPORT_UPDATE} query. - * @param initiator the value that will be set in 'initiator' - * attribute({@link ColibriConferenceIQ.Channel#initiator}). + * Adds next request to {@link RequestType#CHANNEL_INFO_UPDATE} query. * @param localChannelsInfo the {@link ColibriConferenceIQ} instance that * describes the channel for which bundle transport will be updated. * It should contain the description of only one "channel bundle". * If it contains more than one then the first one will be used. - * @return this instance for calls chaining purpose. + * @return <tt>true</tt> if the request yields any changes in Colibri + * channels state on the bridge or <tt>false</tt> otherwise. + * In general when <tt>false</tt> is returned for all + * combined requests it makes no sense to send it. * @throws IllegalArgumentException if <tt>localChannelsInfo</tt> does not * describe any channel bundles. */ - public ColibriBuilder addBundleTransportUpdateReq( - boolean initiator, - IceUdpTransportPacketExtension transport, - ColibriConferenceIQ localChannelsInfo) + public boolean addBundleTransportUpdateReq( + IceUdpTransportPacketExtension transport, + ColibriConferenceIQ localChannelsInfo) throws IllegalArgumentException { - // FIXME:'initiator' not used on bundle transport update ? + Objects.requireNonNull(transport, "transport"); + Objects.requireNonNull(localChannelsInfo, "localChannelsInfo"); if (conferenceState == null || StringUtils.isNullOrEmpty(conferenceState.getID())) { // We are not initialized yet - return null; + return false; } - assertRequestType(RequestType.BUNDLE_TRANSPORT_UPDATE); + assertRequestType(RequestType.CHANNEL_INFO_UPDATE); request.setType(IQ.Type.SET); @@ -371,7 +338,7 @@ public class ColibriBuilder else { throw new IllegalArgumentException( - "Expected ChannelBundle as not found"); + "Expected ChannelBundle as not found"); } ColibriConferenceIQ.ChannelBundle bundleUpdate @@ -387,7 +354,7 @@ public class ColibriBuilder request.addChannelBundle(bundleUpdate); - return this; + return true; } /** @@ -395,15 +362,20 @@ public class ColibriBuilder * {@link RequestType#EXPIRE_CHANNELS} query currently being built. * @param channelInfo the {@link ColibriConferenceIQ} instance that contains * info about the channels to be expired. - * @return this instance for the purpose of calls chaining. + * @return <tt>true</tt> if the request yields any changes in Colibri + * channels state on the bridge or <tt>false</tt> otherwise. + * In general when <tt>false</tt> is returned for all + * combined requests it makes no sense to send it. */ - public ColibriBuilder addExpireChannelsReq(ColibriConferenceIQ channelInfo) + public boolean addExpireChannelsReq(ColibriConferenceIQ channelInfo) { + Objects.requireNonNull(channelInfo, "channelInfo"); + // Formulate the ColibriConferenceIQ request which is to be sent. if (conferenceState == null || StringUtils.isNullOrEmpty(conferenceState.getID())) { - return null; + return false; } assertRequestType(RequestType.EXPIRE_CHANNELS); @@ -411,7 +383,7 @@ public class ColibriBuilder request.setType(IQ.Type.SET); for (ColibriConferenceIQ.Content expiredContent - : channelInfo.getContents()) + : channelInfo.getContents()) { ColibriConferenceIQ.Content stateContent = conferenceState.getContent(expiredContent.getName()); @@ -420,7 +392,7 @@ public class ColibriBuilder { ColibriConferenceIQ.Content requestContent = request.getOrCreateContent( - stateContent.getName()); + stateContent.getName()); for (ColibriConferenceIQ.Channel expiredChannel : expiredContent.getChannels()) @@ -490,7 +462,7 @@ public class ColibriBuilder */ /*if (stateContent.getChannelCount() == 1) { - stateChannel = stateContent.getChannel(0); + stateChannel = stateContent.getRtpChannel(0); ColibriConferenceIQ.Channel channelRequest = new ColibriConferenceIQ.Channel(); @@ -537,7 +509,294 @@ public class ColibriBuilder } } - return this; + return hasAnyChannelsToExpire; + } + + /** + * Adds next payload type information update request to + * {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built. + * + * @param map the map of content name to RTP description packet extension. + * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about + * Colibri channels to be updated. + * + * @return <tt>true</tt> if the request yields any changes in Colibri + * channels state on the bridge or <tt>false</tt> otherwise. + * In general when <tt>false</tt> is returned for all + * combined requests it makes no sense to send it. + */ + public boolean addRtpDescription( + Map<String, RtpDescriptionPacketExtension> map, + ColibriConferenceIQ localChannelsInfo) + { + Objects.requireNonNull(map, "map"); + Objects.requireNonNull(localChannelsInfo, "localChannelsInfo"); + + if (conferenceState == null + || StringUtils.isNullOrEmpty(conferenceState.getID())) + { + // We are not initialized yet + return false; + } + + assertRequestType(RequestType.CHANNEL_INFO_UPDATE); + + request.setType(IQ.Type.SET); + + boolean anyUpdates = false; + + for (Map.Entry<String, RtpDescriptionPacketExtension> e + : map.entrySet()) + { + String contentName = e.getKey(); + ColibriConferenceIQ.ChannelCommon channel + = getColibriChannel(localChannelsInfo, contentName); + + if (channel != null + && channel instanceof ColibriConferenceIQ.Channel) + { + RtpDescriptionPacketExtension rtpPE = e.getValue(); + if (rtpPE == null) + { + continue; + } + + List<PayloadTypePacketExtension> pts = rtpPE.getPayloadTypes(); + if (pts == null || pts.isEmpty()) + { + continue; + } + + anyUpdates = true; + + ColibriConferenceIQ.Channel channelRequest + = (ColibriConferenceIQ.Channel) getRequestChannel( + request.getOrCreateContent(contentName), + channel); + if (channelRequest == null) + { + channelRequest = new ColibriConferenceIQ.Channel(); + channelRequest.setID(channel.getID()); + } + + for (PayloadTypePacketExtension ptPE : pts) + { + channelRequest.addPayloadType(ptPE); + } + } + } + + return anyUpdates; + } + + /** + * Adds next SSRC information update request to + * {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built. + * + * @param ssrcMap the map of content name to the list of + * <tt>SourcePacketExtension</tt>. + * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about + * Colibri channels to be updated. + * + * @return <tt>true</tt> if the request yields any changes in Colibri + * channels state on the bridge or <tt>false</tt> otherwise. + * In general when <tt>false</tt> is returned for all + * combined requests it makes no sense to send it. + */ + public boolean addSSSRCInfo( + Map<String, List<SourcePacketExtension>> ssrcMap, + ColibriConferenceIQ localChannelsInfo) + { + Objects.requireNonNull(ssrcMap, "ssrcMap"); + Objects.requireNonNull(localChannelsInfo, "localChannelsInfo"); + + if (conferenceState == null + || StringUtils.isNullOrEmpty(conferenceState.getID())) + { + // We are not initialized yet + return false; + } + + assertRequestType(RequestType.CHANNEL_INFO_UPDATE); + + request.setType(IQ.Type.SET); + + boolean anyUpdates = false; + + // Go over SSRCs + for (String contentName : ssrcMap.keySet()) + { + // Get channel from local channel info + ColibriConferenceIQ.ChannelCommon rtpChanel + = getRtpChannel(localChannelsInfo, contentName); + if (rtpChanel == null) + { + // There's no channel for this content name in localChannelsInfo + continue; + } + + anyUpdates = true; + + // Ok we have channel for this content, let's add SSRCs + ColibriConferenceIQ.Channel reqChannel + = (ColibriConferenceIQ.Channel) getRequestChannel( + request.getOrCreateContent(contentName), rtpChanel); + + for (SourcePacketExtension ssrc : ssrcMap.get(contentName)) + { + reqChannel.addSource(ssrc.copy()); + } + + if (reqChannel.getSources() == null + || reqChannel.getSources().isEmpty()) + { + // Put an empty source to remove all sources + SourcePacketExtension emptySource = new SourcePacketExtension(); + emptySource.setSSRC(-1L); + reqChannel.addSource(emptySource); + } + } + + return anyUpdates; + } + + /** + * Adds next SSRC group information update request to + * {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built. + * + * @param ssrcGroupMap the map of content name to the list of + * <tt>SourceGroupPacketExtension</tt>. + * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about + * Colibri channels to be updated. + * + * @return <tt>true</tt> if the request yields any changes in Colibri + * channels state on the bridge or <tt>false</tt> otherwise. + * In general when <tt>false</tt> is returned for all + * combined requests it makes no sense to send it. + */ + public boolean addSSSRCGroupsInfo( + Map<String, List<SourceGroupPacketExtension>> ssrcGroupMap, + ColibriConferenceIQ localChannelsInfo) + { + Objects.requireNonNull(ssrcGroupMap, "ssrcGroupMap"); + Objects.requireNonNull(localChannelsInfo, "localChannelsInfo"); + + if (conferenceState == null + || StringUtils.isNullOrEmpty(conferenceState.getID())) + { + // We are not initialized yet + return false; + } + + assertRequestType(RequestType.CHANNEL_INFO_UPDATE); + + request.setType(IQ.Type.SET); + + boolean anyUpdates = false; + + // Go over SSRC groups + for (String contentName : ssrcGroupMap.keySet()) + { + // Get channel from local channel info + ColibriConferenceIQ.Channel rtpChannel + = getRtpChannel(localChannelsInfo, contentName); + if (rtpChannel == null) + { + // There's no channel for this content name in localChannelsInfo + continue; + } + + List<SourceGroupPacketExtension> groups + = ssrcGroupMap.get(contentName); + + // Ok we have channel for this content, let's add SSRCs + ColibriConferenceIQ.Channel reqChannel + = (ColibriConferenceIQ.Channel) getRequestChannel( + request.getOrCreateContent(contentName), rtpChannel); + + if (groups.isEmpty() && "video".equalsIgnoreCase(contentName)) + { + anyUpdates = true; + + // Put empty source group to turn off simulcast layers + reqChannel.addSourceGroup( + SourceGroupPacketExtension.createSimulcastGroup()); + } + + for (SourceGroupPacketExtension group : groups) + { + anyUpdates = true; + + reqChannel.addSourceGroup(group); + } + } + + return anyUpdates; + } + + /** + * Adds next ICE transport update request to + * {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built. + * + * @param map the map of content name to transport extensions. Maps + * transport to media types. + * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about + * Colibri channels to be updated. + * + * @return <tt>true</tt> if the request yields any changes in Colibri + * channels state on the bridge or <tt>false</tt> otherwise. + * In general when <tt>false</tt> is returned for all + * combined requests it makes no sense to send it. + */ + public boolean addTransportUpdateReq( + Map<String, IceUdpTransportPacketExtension> map, + ColibriConferenceIQ localChannelsInfo) + { + Objects.requireNonNull(map, "map"); + Objects.requireNonNull(localChannelsInfo, "localChannelsInfo"); + + if (conferenceState == null + || StringUtils.isNullOrEmpty(conferenceState.getID())) + { + // We are not initialized yet + return false; + } + + boolean hasAnyChanges = false; + + assertRequestType(RequestType.CHANNEL_INFO_UPDATE); + + request.setType(IQ.Type.SET); + + for (Map.Entry<String,IceUdpTransportPacketExtension> e + : map.entrySet()) + { + String contentName = e.getKey(); + ColibriConferenceIQ.ChannelCommon channel + = getColibriChannel(localChannelsInfo, contentName); + + if (channel != null) + { + IceUdpTransportPacketExtension transport + = IceUdpTransportPacketExtension + .cloneTransportAndCandidates(e.getValue(), true); + + ColibriConferenceIQ.ChannelCommon channelRequest + = channel instanceof ColibriConferenceIQ.Channel + ? new ColibriConferenceIQ.Channel() + : new ColibriConferenceIQ.SctpConnection(); + + channelRequest.setID(channel.getID()); + channelRequest.setEndpoint(channel.getEndpoint()); + channelRequest.setTransport(transport); + + request.getOrCreateContent(contentName) + .addChannelCommon(channelRequest); + + hasAnyChanges = true; + } + } + return hasAnyChanges; } /** @@ -583,10 +842,12 @@ public class ColibriBuilder request.setTo(videobridge); - if (requestType == RequestType.EXPIRE_CHANNELS - && !hasAnyChannelsToExpire) + if (requestType == RequestType.EXPIRE_CHANNELS) { - return null; + if (!hasAnyChannelsToExpire) + return null; + + hasAnyChannelsToExpire = false; } return request; @@ -720,6 +981,28 @@ public class ColibriBuilder } /** + * Returns an <tt>Integer</tt> which stands for the audio packet delay + * that will be set on all created audio channels or <tt>null</tt> if + * the builder should leave not include the XML attribute at all. + */ + public Integer getAudioPacketDelay() + { + return audioPacketDelay; + } + + /** + * Configures audio channels packet delay. + * @param audioPacketDelay an <tt>Integer</tt> value which stands for + * the audio packet delay that will be set on all created audio channels or + * <tt>null</tt> if the builder should not set that channel property to any + * value. + */ + public void setAudioPacketDelay(Integer audioPacketDelay) + { + this.audioPacketDelay = audioPacketDelay; + } + + /** * Sets channel 'simulcast-mode' option that will be added to the * request when channels are created. * @param simulcastMode a <tt>SimulcastMode</tt> value to specify @@ -732,69 +1015,64 @@ public class ColibriBuilder } /** - * Adds next payload type information update request to - * {@link RequestType#RTP_DESCRIPTION_UPDATE} query currently being built. + * Creates a new instance of <tt>localChannelInfo</tt> and initializes only + * the fields required to identify particular Colibri channel on the bridge. + * This instance is meant to be used in Colibri + * {@link RequestType#CHANNEL_INFO_UPDATE} requests. This instance is also + * added to given <tt>requestContent</tt> which used to construct current + * request. * - * @param map the map of content name to RTP description packet extension. - * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about - * Colibri channels to be updated. + * @param requestContent <tt>Content</tt> of Colibri update request to which + * new instance wil be automatically added after has been created. + * @param localChannelInfo the original channel for which "update request" + * equivalent is to be created with this call. * - * @return this instance for calls chaining purpose. + * @return new instance of <tt>localChannelInfo</tt> and initialized with + * only those fields required to identify particular Colibri channel on + * the bridge. */ - public ColibriBuilder addRtpDescription( - Map<String, RtpDescriptionPacketExtension> map, - ColibriConferenceIQ localChannelsInfo) { - - if (conferenceState == null - || StringUtils.isNullOrEmpty(conferenceState.getID())) - { - // We are not initialized yet - return null; - } - - assertRequestType(RequestType.RTP_DESCRIPTION_UPDATE); - - request.setType(IQ.Type.SET); - - for (Map.Entry<String, RtpDescriptionPacketExtension> e - : map.entrySet()) + private ColibriConferenceIQ.ChannelCommon getRequestChannel( + ColibriConferenceIQ.Content requestContent, + ColibriConferenceIQ.ChannelCommon localChannelInfo) + { + ColibriConferenceIQ.ChannelCommon reqChannel + = requestContent.getChannel(localChannelInfo.getID()); + if (reqChannel == null) { - String contentName = e.getKey(); - ColibriConferenceIQ.ChannelCommon channel - = getColibriChannel(localChannelsInfo, contentName); - - if (channel != null - && channel instanceof ColibriConferenceIQ.Channel) + if (localChannelInfo instanceof ColibriConferenceIQ.Channel) { - RtpDescriptionPacketExtension rtpPE = e.getValue(); - if (rtpPE == null) - { - continue; - } - - List<PayloadTypePacketExtension> pts = rtpPE.getPayloadTypes(); - if (pts == null || pts.isEmpty()) - { - continue; - } - - ColibriConferenceIQ.Channel channelRequest - = new ColibriConferenceIQ.Channel(); + reqChannel = new ColibriConferenceIQ.Channel(); + } + else if ( + localChannelInfo instanceof ColibriConferenceIQ.SctpConnection) + { + reqChannel = new ColibriConferenceIQ.SctpConnection(); + } + else + { + throw new RuntimeException( + "Unsupported ChannelCommon class: " + + localChannelInfo.getClass()); + } - channelRequest.setID(channel.getID()); + reqChannel.setID(localChannelInfo.getID()); - for (PayloadTypePacketExtension ptPE : rtpPE.getPayloadTypes()) - { - channelRequest.addPayloadType(ptPE); - } + requestContent.addChannelCommon(reqChannel); + } + return reqChannel; + } - request.getOrCreateContent(contentName) - .addChannel(channelRequest); - } + private ColibriConferenceIQ.Channel getRtpChannel( + ColibriConferenceIQ localChannelsInfo, + String contentName) + { + ColibriConferenceIQ.Content content + = localChannelsInfo.getContent(contentName); - } + if (content == null) + return null; - return this; + return content.getChannelCount() > 0 ? content.getChannel(0) : null; } /** @@ -808,19 +1086,10 @@ public class ColibriBuilder ALLOCATE_CHANNELS, /** - * Updates transport information for channels that use RTP bundle. - */ - BUNDLE_TRANSPORT_UPDATE, - - /** - * Updates channel transport information(ICE transport candidates). + * An update request which is meant to modify some values of existing + * Colibri channels on the bridge. */ - TRANSPORT_UPDATE, - - /** - * Updates the RTP description of a channel (payload types). - */ - RTP_DESCRIPTION_UPDATE, + CHANNEL_INFO_UPDATE, /** * Expires specified Colibri channels. @@ -833,4 +1102,28 @@ public class ColibriBuilder */ UNDEFINED; } + + /** + * Configures RTP-level relay (RFC 3550, section 2.3). + * @param rtpLevelRelayType an <tt>RTPLevelRelayType</tt> value which + * stands for the rtp level relay type that will be set on all created + * audio channels. + */ + public void setRTPLevelRelayType(RTPLevelRelayType rtpLevelRelayType) + { + this.rtpLevelRelayType = rtpLevelRelayType; + } + + /** + * Configures RTP-level relay (RFC 3550, section 2.3). + * @param rtpLevelRelayType a <tt>String</tt> value which + * stands for the rtp level relay type that will be set on all created + * audio channels. + */ + public void setRTPLevelRelayType(String rtpLevelRelayType) + { + setRTPLevelRelayType + (RTPLevelRelayType.parseRTPLevelRelayType(rtpLevelRelayType)); + } + } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java index 52368bf..d5cf175 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java @@ -161,7 +161,7 @@ public class ColibriConferenceIQ throw new NullPointerException("channelBundle"); return - channelBundles.contains(channelBundles) + channelBundles.contains(channelBundle) ? false : channelBundles.add(channelBundle); } @@ -543,6 +543,14 @@ public class ColibriConferenceIQ = "receive-simulcast-layer"; /** + * The XML name of the <tt>packet-delay</tt> attribute of + * a <tt>channel</tt> of a <tt>content</tt> of a <tt>conference</tt> IQ + * which represents the value of the {@link #packetDelay} property of + * <tt>ColibriConferenceIQ.Channel</tt>. + */ + public static final String PACKET_DELAY_ATTR_NAME = "packet-delay"; + + /** * The XML name of the <tt>rtcpport</tt> attribute of a <tt>channel</tt> * of a <tt>content</tt> of a <tt>conference</tt> IQ which represents * the value of the <tt>rtcpPort</tt> property of @@ -613,6 +621,11 @@ public class ColibriConferenceIQ private SimulcastMode simulcastMode; /** + * The amount of delay added to the RTP stream in a number of packets. + */ + private Integer packetDelay; + + /** * The <tt>payload-type</tt> elements defined by XEP-0167: Jingle RTP * Sessions associated with this <tt>channel</tt>. */ @@ -893,6 +906,18 @@ public class ColibriConferenceIQ } /** + * Returns an <tt>Integer</tt> which stands for the amount of delay + * added to the RTP stream in a number of packets. + * + * @return <tt>Integer</tt> with the value or <tt>null</tt> if + * unspecified. + */ + public Integer getPacketDelay() + { + return packetDelay; + } + + /** * Gets a list of <tt>payload-type</tt> elements defined by XEP-0167: * Jingle RTP Sessions added to this <tt>channel</tt>. * @@ -1077,8 +1102,16 @@ public class ColibriConferenceIQ if (adaptiveSimulcast != null) { - xml.append(' ').append(adaptiveSimulcast).append("='") - .append(adaptiveSimulcast).append('\''); + xml.append(' ').append(ADAPTIVE_SIMULCAST_ATTR_NAME) + .append("='").append(adaptiveSimulcast).append('\''); + } + + // packet-delay + Integer packetDelay = getPacketDelay(); + if (packetDelay != null) + { + xml.append(' ').append(PACKET_DELAY_ATTR_NAME).append("='") + .append(packetDelay).append('\''); } // simulcastMode @@ -1314,6 +1347,17 @@ public class ColibriConferenceIQ } /** + * Configures channel's packet delay which tells by how many packets + * the RTP streams will be delayed. + * @param packetDelay an <tt>Integer</tt> value which stands for + * the packet delay that will be set or <tt>null</tt> to leave undefined + */ + public void setPacketDelay(Integer packetDelay) + { + this.packetDelay = packetDelay; + } + + /** * Sets the value of the 'simulcast-mode' flag. * @param simulcastMode the value to set. */ @@ -1925,6 +1969,25 @@ public class ColibriConferenceIQ } /** + * Adds <tt>ChannelCommon</tt> to this <tt>Content</tt>. + * @param channelCommon {@link ChannelCommon} instance to be added to + * this content. + * @return <tt>true</tt> if given <tt>channelCommon</tt> has been + * actually added to this <tt>Content</tt> instance. + */ + public boolean addChannelCommon(ChannelCommon channelCommon) + { + if (channelCommon instanceof Channel) + { + return addChannel((Channel) channelCommon); + } + else + { + return addSctpConnection((SctpConnection) channelCommon); + } + } + + /** * Adds a specific <tt>SctpConnection</tt> to the list of * <tt>SctpConnection</tt>s included into this <tt>Content</tt>. * diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java index 39e299f..ce676ef 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java @@ -20,6 +20,7 @@ package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri; import net.java.sip.communicator.impl.protocol.jabber.extensions.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; +import net.java.sip.communicator.service.protocol.jabber.*; import org.jitsi.service.neomedia.*; import org.jitsi.util.*; import org.jivesoftware.smack.packet.*; @@ -36,89 +37,101 @@ import org.xmlpull.v1.*; public class ColibriIQProvider implements IQProvider { + + /** + * Smack interoperation layer + */ + private AbstractSmackInteroperabilityLayer smackInteroperabilityLayer = + AbstractSmackInteroperabilityLayer.getInstance(); + /** Initializes a new <tt>ColibriIQProvider</tt> instance. */ public ColibriIQProvider() { - ProviderManager providerManager = ProviderManager.getInstance(); - - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( PayloadTypePacketExtension.ELEMENT_NAME, ColibriConferenceIQ.NAMESPACE, new DefaultPacketExtensionProvider<PayloadTypePacketExtension>( PayloadTypePacketExtension.class)); - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( RtcpFbPacketExtension.ELEMENT_NAME, RtcpFbPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<RtcpFbPacketExtension>( RtcpFbPacketExtension.class)); - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( RTPHdrExtPacketExtension.ELEMENT_NAME, ColibriConferenceIQ.NAMESPACE, new DefaultPacketExtensionProvider<RTPHdrExtPacketExtension>( RTPHdrExtPacketExtension.class)); - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( SourcePacketExtension.ELEMENT_NAME, SourcePacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<SourcePacketExtension>( SourcePacketExtension.class)); - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( SourceGroupPacketExtension.ELEMENT_NAME, SourceGroupPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<SourceGroupPacketExtension>( SourceGroupPacketExtension.class)); PacketExtensionProvider parameterProvider - = new DefaultPacketExtensionProvider<ParameterPacketExtension>( - ParameterPacketExtension.class); + = new DefaultPacketExtensionProvider<ParameterPacketExtension>( + ParameterPacketExtension.class); - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( ParameterPacketExtension.ELEMENT_NAME, ColibriConferenceIQ.NAMESPACE, parameterProvider); - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( ParameterPacketExtension.ELEMENT_NAME, SourcePacketExtension.NAMESPACE, parameterProvider); // Shutdown IQ - providerManager.addIQProvider( - GracefulShutdownIQ.ELEMENT_NAME, - GracefulShutdownIQ.NAMESPACE, + smackInteroperabilityLayer.addIQProvider( + ShutdownIQ.GRACEFUL_ELEMENT_NAME, + ShutdownIQ.NAMESPACE, + this); + smackInteroperabilityLayer.addIQProvider( + ShutdownIQ.FORCE_ELEMENT_NAME, + ShutdownIQ.NAMESPACE, this); // Shutdown extension PacketExtensionProvider shutdownProvider - = new DefaultPacketExtensionProvider - <ColibriConferenceIQ.GracefulShutdown>( - ColibriConferenceIQ.GracefulShutdown.class); + = new DefaultPacketExtensionProvider + <ColibriConferenceIQ.GracefulShutdown>( + ColibriConferenceIQ.GracefulShutdown.class); - providerManager.addExtensionProvider( - ColibriConferenceIQ.GracefulShutdown.ELEMENT_NAME, - ColibriConferenceIQ.GracefulShutdown.NAMESPACE, - shutdownProvider); + smackInteroperabilityLayer.addExtensionProvider( + ColibriConferenceIQ.GracefulShutdown.ELEMENT_NAME, + ColibriConferenceIQ.GracefulShutdown.NAMESPACE, + shutdownProvider); // ColibriStatsIQ - providerManager.addIQProvider( - ColibriStatsIQ.ELEMENT_NAME, - ColibriStatsIQ.NAMESPACE, - this); + smackInteroperabilityLayer.addIQProvider( + ColibriStatsIQ.ELEMENT_NAME, + ColibriStatsIQ.NAMESPACE, + this); // ColibriStatsExtension PacketExtensionProvider statsProvider - = new DefaultPacketExtensionProvider<ColibriStatsExtension>( - ColibriStatsExtension.class); + = new DefaultPacketExtensionProvider<ColibriStatsExtension>( + ColibriStatsExtension.class); - providerManager.addExtensionProvider( - ColibriStatsExtension.ELEMENT_NAME, - ColibriStatsExtension.NAMESPACE, - statsProvider); + smackInteroperabilityLayer.addExtensionProvider( + ColibriStatsExtension.ELEMENT_NAME, + ColibriStatsExtension.NAMESPACE, + statsProvider); // ColibriStatsExtension.Stat PacketExtensionProvider statProvider - = new DefaultPacketExtensionProvider<ColibriStatsExtension.Stat>( - ColibriStatsExtension.Stat.class); - - providerManager.addExtensionProvider( - ColibriStatsExtension.Stat.ELEMENT_NAME, - ColibriStatsExtension.NAMESPACE, - statProvider); + = new DefaultPacketExtensionProvider + <ColibriStatsExtension.Stat>( + ColibriStatsExtension.Stat.class); + + smackInteroperabilityLayer.addExtensionProvider( + ColibriStatsExtension.Stat.ELEMENT_NAME, + ColibriStatsExtension.NAMESPACE, + statProvider); + + } private void addChildExtension( @@ -199,8 +212,7 @@ public class ColibriIQProvider throws Exception { PacketExtensionProvider extensionProvider - = (PacketExtensionProvider) - ProviderManager.getInstance().getExtensionProvider( + = smackInteroperabilityLayer.getExtensionProvider( name, namespace); PacketExtension extension; @@ -416,6 +428,15 @@ public class ColibriIQProvider if ((expire != null) && (expire.length() != 0)) channel.setExpire(Integer.parseInt(expire)); + String packetDelay + = parser.getAttributeValue( + "", + ColibriConferenceIQ.Channel + .PACKET_DELAY_ATTR_NAME); + if (!StringUtils.isNullOrEmpty(packetDelay)) + channel.setPacketDelay( + Integer.parseInt(packetDelay)); + // host String host = parser.getAttributeValue( @@ -464,6 +485,18 @@ public class ColibriIQProvider channel.setAdaptiveLastN( Boolean.parseBoolean(adaptiveLastN)); + String adaptiveSimulcast + = parser.getAttributeValue( + "", + ColibriConferenceIQ.Channel + .ADAPTIVE_SIMULCAST_ATTR_NAME); + + if (!StringUtils.isNullOrEmpty(adaptiveSimulcast)) + { + channel.setAdaptiveSimulcast( + Boolean.parseBoolean(adaptiveSimulcast)); + } + // simulcastMode String simulcastMode = parser.getAttributeValue( @@ -802,12 +835,12 @@ public class ColibriIQProvider iq = conference; } - else if (GracefulShutdownIQ.ELEMENT_NAME.equals(parser.getName()) - && GracefulShutdownIQ.NAMESPACE.equals(namespace)) + else if (ShutdownIQ.NAMESPACE.equals(namespace) && + ShutdownIQ.isValidElementName(parser.getName())) { String rootElement = parser.getName(); - iq = new GracefulShutdownIQ(); + iq = ShutdownIQ.createShutdownIQ(rootElement); boolean done = false; @@ -825,12 +858,6 @@ public class ColibriIQProvider } break; } - - case XmlPullParser.TEXT: - { - // Parse some text here - break; - } } } } @@ -891,12 +918,6 @@ public class ColibriIQProvider } break; } - - case XmlPullParser.TEXT: - { - // Parse some text here - break; - } } } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java index d5a6ce1..ef80392 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,74 +15,74 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri;
-
-import org.jitsi.service.neomedia.*;
-
-/**
- * Implements a <tt>StreamConnector</tt> which allows sharing a specific
- * <tt>StreamConnector</tt> instance among multiple <tt>TransportManager</tt>s
- * for the purposes of the Jitsi Videobridge.
- *
- * @author Lyubomir Marinov
- */
-public class ColibriStreamConnector
- extends StreamConnectorDelegate<StreamConnector>
-{
- /**
- * Initializes a new <tt>ColibriStreamConnector</tt> instance which is to
- * share a specific <tt>StreamConnector</tt> instance among multiple
- * <tt>TransportManager</tt>s for the purposes of the Jitsi Videobridge.
- *
- * @param streamConnector the <tt>StreamConnector</tt> instance to be shared
- * by the new instance among multiple <tt>TransportManager</tt>s for the
- * purposes of the Jitsi Videobridge
- */
- public ColibriStreamConnector(StreamConnector streamConnector)
- {
- super(streamConnector);
- }
-
- /**
- * {@inheritDoc}
- *
- * Overrides {@link StreamConnectorDelegate#close()} in order to prevent the
- * closing of the <tt>StreamConnector</tt> wrapped by this instance because
- * the latter is shared and it is not clear whether no
- * <tt>TransportManager</tt> is using it.
- */
- @Override
- public void close()
- {
- /*
- * Do not close the shared StreamConnector because it is not clear
- * whether no TransportManager is using it.
- */
- }
-
- /**
- * {@inheritDoc}
- *
- * Invokes {@link #close()} on this instance when it is clear that no
- * <tt>TransportManager</tt> is using it in order to release the resources
- * allocated by this instance throughout its life time (that need explicit
- * disposal).
- */
- @Override
- protected void finalize()
- throws Throwable
- {
- try
- {
- /*
- * Close the shared StreamConnector because it is clear that no
- * TrasportManager is using it.
- */
- super.close();
- }
- finally
- {
- super.finalize();
- }
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri; + +import org.jitsi.service.neomedia.*; + +/** + * Implements a <tt>StreamConnector</tt> which allows sharing a specific + * <tt>StreamConnector</tt> instance among multiple <tt>TransportManager</tt>s + * for the purposes of the Jitsi Videobridge. + * + * @author Lyubomir Marinov + */ +public class ColibriStreamConnector + extends StreamConnectorDelegate<StreamConnector> +{ + /** + * Initializes a new <tt>ColibriStreamConnector</tt> instance which is to + * share a specific <tt>StreamConnector</tt> instance among multiple + * <tt>TransportManager</tt>s for the purposes of the Jitsi Videobridge. + * + * @param streamConnector the <tt>StreamConnector</tt> instance to be shared + * by the new instance among multiple <tt>TransportManager</tt>s for the + * purposes of the Jitsi Videobridge + */ + public ColibriStreamConnector(StreamConnector streamConnector) + { + super(streamConnector); + } + + /** + * {@inheritDoc} + * + * Overrides {@link StreamConnectorDelegate#close()} in order to prevent the + * closing of the <tt>StreamConnector</tt> wrapped by this instance because + * the latter is shared and it is not clear whether no + * <tt>TransportManager</tt> is using it. + */ + @Override + public void close() + { + /* + * Do not close the shared StreamConnector because it is not clear + * whether no TransportManager is using it. + */ + } + + /** + * {@inheritDoc} + * + * Invokes {@link #close()} on this instance when it is clear that no + * <tt>TransportManager</tt> is using it in order to release the resources + * allocated by this instance throughout its life time (that need explicit + * disposal). + */ + @Override + protected void finalize() + throws Throwable + { + try + { + /* + * Close the shared StreamConnector because it is clear that no + * TrasportManager is using it. + */ + super.close(); + } + finally + { + super.finalize(); + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ShutdownIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ShutdownIQ.java new file mode 100644 index 0000000..4df251b --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ShutdownIQ.java @@ -0,0 +1,134 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri; + +import org.jivesoftware.smack.packet.*; + +/** + * The IQ used to trigger the graceful shutdown mode of the videobridge or force + * shutdown the one which receives the stanza(given that source JID is + * authorized to do so). + * + * @author Pawel Domas + */ +public class ShutdownIQ + extends IQ +{ + /** + * XML namespace name for shutdown IQs. + */ + final static public String NAMESPACE = ColibriConferenceIQ.NAMESPACE; + + /** + * Force shutdown IQ element name. + */ + final static public String FORCE_ELEMENT_NAME = "force-shutdown"; + + /** + * Graceful shutdown IQ element name. + */ + final static public String GRACEFUL_ELEMENT_NAME = "graceful-shutdown"; + + /** + * The element name of this IQ. Either {@link #FORCE_ELEMENT_NAME} or + * {@link #GRACEFUL_ELEMENT_NAME}. + */ + private final String elementName; + + /** + * Checks if given element is a valid one for <tt>ShutdownIQ</tt>. + * + * @param elementName the name if XML element name inside of the IQ. + * + * @return <tt>true</tt> if given <tt>elementName</tt> is correct for + * <tt>ShutdownIQ</tt>. + */ + public static boolean isValidElementName(String elementName) + { + return GRACEFUL_ELEMENT_NAME.equals(elementName) + || FORCE_ELEMENT_NAME.equals(elementName); + } + + /** + * Creates shutdown IQ for given element name. + * + * @param elementName can be {@link #FORCE_ELEMENT_NAME} or + * {@link #GRACEFUL_ELEMENT_NAME} + * + * @return new <tt>ShutdownIQ</tt> instance for given element name. + * + * @throws IllegalArgumentException if given element name is neither + * {@link #FORCE_ELEMENT_NAME} nor {@link #GRACEFUL_ELEMENT_NAME}. + */ + public static ShutdownIQ createShutdownIQ(String elementName) + { + if (!isValidElementName(elementName)) + { + throw new IllegalArgumentException( + "Invalid element name: " + elementName); + } + + if (GRACEFUL_ELEMENT_NAME.equals(elementName)) + { + return createGracefulShutdownIQ(); + } + else + { + return createForceShutdownIQ(); + } + } + + /** + * Creates and returns new instance of graceful shutdown IQ. + */ + public static ShutdownIQ createGracefulShutdownIQ() + { + return new ShutdownIQ(GRACEFUL_ELEMENT_NAME); + } + + /** + * Creates and returns new instance of force shutdown IQ. + */ + public static ShutdownIQ createForceShutdownIQ() + { + return new ShutdownIQ(FORCE_ELEMENT_NAME); + } + + private ShutdownIQ(String elementName) + { + this.elementName = elementName; + } + + /** + * Returns <tt>true</tt> if this IQ instance is a "graceful shutdown" one. + * Otherwise it is a force shutdown IQ. + */ + public boolean isGracefulShutdown() + { + return elementName.equals(GRACEFUL_ELEMENT_NAME); + } + + /** + * {@inheritDoc} + */ + @Override + public String getChildElementXML() + { + return "<" + elementName + " xmlns='" + NAMESPACE + "' />"; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/GracefulShutdownIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQ.java index 9808b11..661bb1b 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/GracefulShutdownIQ.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQ.java @@ -15,23 +15,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri; +package net.java.sip.communicator.impl.protocol.jabber.extensions.health; import org.jivesoftware.smack.packet.*; /** - * The IQ used to trigger the graceful shutdown mode of the videobridge which - * receives the stanza(given that source JID is authorized to start it). + * The health check IQ used to trigger health checks on the Jitsi Videobridge. * * @author Pawel Domas */ -public class GracefulShutdownIQ +public class HealthCheckIQ extends IQ { - public static final String NAMESPACE = ColibriConferenceIQ.NAMESPACE; + /** + * Health check IQ element name. + */ + final static public String ELEMENT_NAME = "healthcheck"; - public static final String ELEMENT_NAME = "graceful-shutdown"; + /** + * XML namespace name for health check IQs. + */ + final static public String NAMESPACE + = "http://jitsi.org/protocol/healthcheck"; + /** + * {@inheritDoc} + */ @Override public String getChildElementXML() { diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQProvider.java new file mode 100644 index 0000000..9c2903d --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQProvider.java @@ -0,0 +1,94 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.health; + +import net.java.sip.communicator.service.protocol.jabber.*; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; + +import org.xmlpull.v1.*; + +/** + * The <tt>IQProvider</tt> for {@link HealthCheckIQ}. + * + * @author Pawel Domas + */ +public class HealthCheckIQProvider + implements IQProvider +{ + /** + * Registers <tt>HealthCheckIQProvider</tt> as an <tt>IQProvider</tt> + * in {@link AbstractSmackInteroperabilityLayer}. + */ + public static void registerIQProvider() + { + AbstractSmackInteroperabilityLayer smackInteropLayer = + AbstractSmackInteroperabilityLayer.getInstance(); + + // ColibriStatsIQ + smackInteropLayer.addIQProvider( + HealthCheckIQ.ELEMENT_NAME, + HealthCheckIQ.NAMESPACE, + new HealthCheckIQProvider()); + } + + /** + * Parses <tt>HealthCheckIQ</tt>. + * + * {@inheritDoc} + */ + @Override + public IQ parseIQ(XmlPullParser parser) + throws Exception + { + String namespace = parser.getNamespace(); + IQ iq; + + if (HealthCheckIQ.ELEMENT_NAME.equals(parser.getName()) + && HealthCheckIQ.NAMESPACE.equals(namespace)) + { + String rootElement = parser.getName(); + + iq = new HealthCheckIQ(); + + boolean done = false; + + while (!done) + { + switch (parser.next()) + { + case XmlPullParser.END_TAG: + { + String name = parser.getName(); + + if (rootElement.equals(name)) + { + done = true; + } + break; + } + } + } + } + else + iq = null; + + return iq; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java new file mode 100644 index 0000000..8b964af --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java @@ -0,0 +1,413 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri; + +import org.jitsi.util.*; + +import org.jivesoftware.smack.packet.*; + +import java.util.*; + +/** + * The IQ used to control conference recording with Jibri component. + * + * Start the recording: + * + * 1. Send Jibri IQ with {@link Action#START} to Jibri. + * 2. Jibri replies with RESULT and status {@link Status#PENDING}. + * 3. Jibri sends SET IQ with status {@link Status#ON} once recording actually + * starts. + * + * Stop the recording: + * + * 1. Send Jibri IQ with {@link Action#STOP} to Jibri. + * 2. Jibri replies with {@link Status#OFF} immediately if the recording has + * been stopped already or sends separate Jibri SET IQ later on if it takes + * more time. + * + * @author lishunyang + * @author Pawel Domas + */ +public class JibriIq + extends IQ +{ + /** + * Attribute name of "action". + */ + public static final String ACTION_ATTR_NAME = "action"; + + /** + * XML element name of the Jibri IQ. + */ + public static final String ELEMENT_NAME = "jibri"; + + /** + * XML namespace of the Jibri IQ. + */ + public static final String NAMESPACE = "http://jitsi.org/protocol/jibri"; + + /** + * The name of XML attribute which stores the recording status. + */ + static final String STATUS_ATTR_NAME = "status"; + + /** + * The name of XML attribute which stores the stream id. + */ + static final String STREAM_ID_ATTR_NAME = "streamid"; + + /** + * The name of XML attribute which stores the name of the conference room to + * be recorded. + */ + static final String ROOM_ATTR_NAME = "room"; + + /** + * Holds the action. + */ + private Action action = Action.UNDEFINED; + + /** + * XMPPError stores error details for {@link Status#FAILED}. + */ + private XMPPError error; + + /** + * Holds recording status. + */ + private Status status = Status.UNDEFINED; + + /** + * The ID of the stream which will be used to record the conference. The + * value depends on recording service provider. + */ + private String streamId = null; + + /** + * The name of the conference room to be recorded. + */ + private String room = null; + + /** + * Returns the value of {@link #STREAM_ID_ATTR_NAME} attribute. + * @return a <tt>String</tt> which contains the value of "stream id" + * attribute or <tt>null</tt> if empty. + */ + public String getStreamId() + { + return streamId; + } + + /** + * Sets the value for {@link #STREAM_ID_ATTR_NAME} attribute. + * @param streamId a <tt>String</tt> for the stream id attribute or + * <tt>null</tt> to remove it from XML element. + */ + public void setStreamId(String streamId) + { + this.streamId = streamId; + } + + /** + * Returns the value of {@link #ROOM_ATTR_NAME} attribute. + * @return a <tt>String</tt> which contains the value of the room attribute + * or <tt>null</tt> if empty. + * @see #room + */ + public String getRoom() + { + return room; + } + + /** + * Sets the value for {@link #ROOM_ATTR_NAME} attribute. + * @param room a <tt>String</tt> for the room attribute or <tt>null</tt> to + * remove it from XML element. + * @see #room + */ + public void setRoom(String room) + { + this.room = room; + } + + /** + * {@inheritDoc} + */ + @Override + public String getChildElementXML() + { + StringBuilder xml = new StringBuilder(); + + xml.append('<').append(ELEMENT_NAME); + xml.append(" xmlns='").append(NAMESPACE).append("' "); + + if (action != Action.UNDEFINED) + { + printStringAttribute(xml, ACTION_ATTR_NAME, action.toString()); + } + + if (status != Status.UNDEFINED) + { + printStringAttribute(xml, STATUS_ATTR_NAME, status.toString()); + } + + if (room != null) + { + printStringAttribute(xml, ROOM_ATTR_NAME, room); + } + + if (streamId != null) + { + printStringAttribute(xml, STREAM_ID_ATTR_NAME, streamId); + } + + Collection<PacketExtension> extensions = getExtensions(); + if (extensions.size() > 0) + { + xml.append(">"); + for (PacketExtension extension : extensions) + { + xml.append(extension.toXML()); + } + xml.append("</").append(ELEMENT_NAME).append(">"); + } + else + { + xml.append("/>"); + } + + return xml.toString(); + } + + private void printStringAttribute( + StringBuilder xml, String attrName, String attr) + { + if (!StringUtils.isNullOrEmpty(attr)) + { + attr = org.jivesoftware.smack.util.StringUtils.escapeForXML(attr); + xml.append(attrName).append("='") + .append(attr).append("' "); + } + } + + /** + * Sets the value of 'action' attribute. + * + * @param action the value to be set as 'action' attribute of this IQ. + */ + public void setAction(Action action) + { + this.action = action; + } + + /** + * Returns the value of 'action' attribute. + */ + public Action getAction() + { + return action; + } + + /** + * Sets the value of 'status' attribute. + */ + public void setStatus(Status status) + { + this.status = status; + } + + /** + * Returns the value of 'status' attribute. + */ + public Status getStatus() + { + return status; + } + + /** + * Sets the <tt>XMPPError</tt> which will provide details about Jibri + * failure. It is expected to be set when this IQ's status value is + * {@link Status#FAILED}. + * + * @param error <tt>XMPPError</tt> to be set on this <tt>JibriIq</tt> + * instance. + */ + public void setXMPPError(XMPPError error) + { + this.error = error; + } + + /** + * Returns {@link XMPPError} with Jibri error details when the status is + * {@link Status#FAILED}. + */ + public XMPPError getError() + { + return error; + } + + /** + * Enumerative value of attribute "action" in recording extension. + * + * @author lishunyang + * @author Pawel Domas + * + */ + public enum Action + { + /** + * Start the recording. + */ + START("start"), + /** + * Stop the recording. + */ + STOP("stop"), + /** + * Unknown/uninitialized + */ + UNDEFINED("undefined"); + + private String name; + + Action(String name) + { + this.name = name; + } + + @Override + public String toString() + { + return name; + } + + /** + * Parses <tt>Action</tt> from given string. + * + * @param action the string representation of <tt>Action</tt>. + * + * @return <tt>Action</tt> value for given string or + * {@link #UNDEFINED} if given string does not + * reflect any of valid values. + */ + public static Action parse(String action) + { + if (StringUtils.isNullOrEmpty(action)) + return UNDEFINED; + + try + { + return Action.valueOf(action.toUpperCase()); + } + catch(IllegalArgumentException e) + { + return UNDEFINED; + } + } + } + + /** + * The enumeration of recording status values. + */ + public enum Status + { + /** + * Recording is in progress. + */ + ON("on"), + + /** + * Recording stopped. + */ + OFF("off"), + + /** + * Starting the recording process. + */ + PENDING("pending"), + + /** + * The recorder has failed and the service is retrying on another + * instance. + */ + RETRYING("retrying"), + + /** + * An error occurred any point during startup, recording or shutdown. + */ + FAILED("failed"), + + /** + * There are Jibri instances connected to the system, but all of them + * are currently busy. + */ + BUSY("busy"), + + /** + * Unknown/uninitialized. + */ + UNDEFINED("undefined"); + + /** + * Status name holder. + */ + private String name; + + /** + * Creates new {@link Status} instance. + * @param name a string corresponding to one of {@link Status} values. + */ + Status(String name) + { + this.name = name; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return name; + } + + /** + * Parses <tt>Status</tt> from given string. + * + * @param status the string representation of <tt>Status</tt>. + * + * @return <tt>Status</tt> value for given string or + * {@link #UNDEFINED} if given string does not + * reflect any of valid values. + */ + public static Status parse(String status) + { + if (StringUtils.isNullOrEmpty(status)) + return UNDEFINED; + + try + { + return Status.valueOf(status.toUpperCase()); + } + catch(IllegalArgumentException e) + { + return UNDEFINED; + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java new file mode 100644 index 0000000..155853c --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java @@ -0,0 +1,112 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri; + +import org.jitsi.util.*; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.jivesoftware.smack.util.PacketParserUtils; + +import org.xmlpull.v1.*; + +/** + * Parses {@link JibriIq}. + */ +public class JibriIqProvider + implements IQProvider +{ + /** + * {@inheritDoc} + */ + @Override + public IQ parseIQ(XmlPullParser parser) + throws Exception + { + String namespace = parser.getNamespace(); + + // Check the namespace + if (!JibriIq.NAMESPACE.equals(namespace)) + { + return null; + } + + String rootElement = parser.getName(); + + JibriIq iq; + + if (JibriIq.ELEMENT_NAME.equals(rootElement)) + { + iq = new JibriIq(); + + String action + = parser.getAttributeValue("", JibriIq.ACTION_ATTR_NAME); + iq.setAction(JibriIq.Action.parse(action)); + + String status + = parser.getAttributeValue("", JibriIq.STATUS_ATTR_NAME); + iq.setStatus(JibriIq.Status.parse(status)); + + String room + = parser.getAttributeValue("", JibriIq.ROOM_ATTR_NAME); + if (!StringUtils.isNullOrEmpty(room)) + iq.setRoom(room); + + String streamId + = parser.getAttributeValue("", JibriIq.STREAM_ID_ATTR_NAME); + if (!StringUtils.isNullOrEmpty(streamId)) + iq.setStreamId(streamId); + } + else + { + return null; + } + + boolean done = false; + + while (!done) + { + switch (parser.next()) + { + case XmlPullParser.START_TAG: + { + String name = parser.getName(); + + if ("error".equals(name)) + { + XMPPError error = PacketParserUtils.parseError(parser); + iq.setXMPPError(error); + } + break; + } + case XmlPullParser.END_TAG: + { + String name = parser.getName(); + + if (rootElement.equals(name)) + { + done = true; + } + break; + } + } + } + + return iq; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java new file mode 100644 index 0000000..e046b68 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java @@ -0,0 +1,121 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +import org.jitsi.util.*; + +import org.jivesoftware.smack.provider.*; + +/** + * Status extension included in MUC presence by Jibri to indicate it's status. + * One of: + * <li>idle</li> - the instance is idle and can be used for recording + * <li>busy</li> - the instance is currently recording or doing something very + * important and should not be disturbed + * + * + */ +public class JibriStatusPacketExt + extends AbstractPacketExtension +{ + /** + * The namespace of this packet extension. + */ + public static final String NAMESPACE = JibriIq.NAMESPACE; + + /** + * XML element name of this packet extension. + */ + public static final String ELEMENT_NAME = "jibri-status"; + + private static final String STATUS_ATTRIBUTE = "status"; + + /** + * Creates new instance of <tt>VideoMutedExtension</tt>. + */ + public JibriStatusPacketExt() + { + super(NAMESPACE, ELEMENT_NAME); + } + + static public void registerExtensionProvider() + { + ProviderManager.getInstance().addExtensionProvider( + ELEMENT_NAME, + NAMESPACE, + new DefaultPacketExtensionProvider<JibriStatusPacketExt>( + JibriStatusPacketExt.class) + ); + } + + public Status getStatus() + { + return Status.parse(getAttributeAsString(STATUS_ATTRIBUTE)); + } + + public void setStatus(Status status) + { + setAttribute(STATUS_ATTRIBUTE, String.valueOf(status)); + } + + public enum Status + { + IDLE("idle"), + BUSY("busy"), + UNDEFINED("undefined"); + + private String name; + + Status(String name) + { + this.name = name; + } + + @Override + public String toString() + { + return name; + } + + /** + * Parses <tt>Status</tt> from given string. + * + * @param status the string representation of <tt>Status</tt>. + * + * @return <tt>Status</tt> value for given string or + * {@link #UNDEFINED} if given string does not + * reflect any of valid values. + */ + public static Status parse(String status) + { + if (StringUtils.isNullOrEmpty(status)) + return UNDEFINED; + + try + { + return Status.valueOf(status.toUpperCase()); + } + catch(IllegalArgumentException e) + { + return UNDEFINED; + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java new file mode 100644 index 0000000..13177cf --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java @@ -0,0 +1,126 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +import org.jivesoftware.smack.packet.*; + +import java.util.*; + +/** + * The packet extension added to Jicofo MUC presence to broadcast current + * recording status to all conference participants. + * + * Status meaning: + * <tt>{@link JibriIq.Status#UNDEFINED}</tt> - recording not available + * <tt>{@link JibriIq.Status#OFF}</tt> - recording stopped(available to start) + * <tt>{@link JibriIq.Status#PENDING}</tt> - starting recording + * <tt>{@link JibriIq.Status#ON}</tt> - recording in progress + */ +public class RecordingStatus + extends AbstractPacketExtension +{ + /** + * The namespace of this packet extension. + */ + public static final String NAMESPACE = JibriIq.NAMESPACE; + + /** + * XML element name of this packet extension. + */ + public static final String ELEMENT_NAME = "jibri-recording-status"; + + /** + * The name of XML attribute which holds the recording status. + */ + private static final String STATUS_ATTRIBUTE = "status"; + + public RecordingStatus() + { + super(NAMESPACE, ELEMENT_NAME); + } + + /** + * Returns the value of current recording status stored in it's attribute. + * @return one of {@link JibriIq.Status} + */ + public JibriIq.Status getStatus() + { + String statusAttr = getAttributeAsString(STATUS_ATTRIBUTE); + + return JibriIq.Status.parse(statusAttr); + } + + /** + * Sets new value for the recording status. + * @param status one of {@link JibriIq.Status} + */ + public void setStatus(JibriIq.Status status) + { + setAttribute(STATUS_ATTRIBUTE, String.valueOf(status)); + } + + /** + * Returns <tt>XMPPError</tt> associated with current + * {@link RecordingStatus}. + */ + public XMPPError getError() + { + XMPPErrorPE errorPe = getErrorPE(); + return errorPe != null ? errorPe.getError() : null; + } + + /** + * Gets <tt>{@link XMPPErrorPE}</tt> from the list of child packet + * extensions. + * @return {@link XMPPErrorPE} or <tt>null</tt> if not found. + */ + private XMPPErrorPE getErrorPE() + { + List<? extends PacketExtension> errorPe + = getChildExtensionsOfType(XMPPErrorPE.class); + + return (XMPPErrorPE) (!errorPe.isEmpty() ? errorPe.get(0) : null); + } + + /** + * Sets <tt>XMPPError</tt> on this <tt>RecordingStatus</tt>. + * @param error <tt>XMPPError</tt> to add error details to this + * <tt>RecordingStatus</tt> instance or <tt>null</tt> to have it removed. + */ + public void setError(XMPPError error) + { + if (error != null) + { + // Wrap and add XMPPError as packet extension + XMPPErrorPE errorPe = getErrorPE(); + if (errorPe == null) + { + errorPe = new XMPPErrorPE(error); + addChildExtension(errorPe); + } + errorPe.setError(error); + } + else + { + // Remove error PE + getChildExtensions().remove(getErrorPE()); + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java new file mode 100644 index 0000000..a72f310 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java @@ -0,0 +1,93 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri; + +import org.jivesoftware.smack.packet.*; + +import java.util.*; + +/** + * Wraps Smack's <tt>XMPPError</tt> into <tt>PacketExtension</tt>, so that it + * can be easily inserted into {@link RecordingStatus}. + */ +public class XMPPErrorPE + implements PacketExtension +{ + /** + * <tt>XMPPError</tt> wrapped into this <tt>XMPPErrorPE</tt>. + */ + private XMPPError error; + + /** + * Creates new instance of <tt>XMPPErrorPE</tt>. + * @param xmppError the instance of <tt>XMPPError</tt> that will be wrapped + * by the newly created <tt>XMPPErrorPE</tt>. + */ + public XMPPErrorPE(XMPPError xmppError) + { + setError(xmppError); + } + + /** + * Returns the underlying instance of <tt>XMPPError</tt>. + */ + public XMPPError getError() + { + return error; + } + + /** + * Sets new instance of <tt>XMPPError</tt> to be wrapped by this + * <tt>XMPPErrorPE</tt>. + * @param error <tt>XMPPError</tt> that will be wrapped by this + * <TT>XMPPErrorPE</TT>. + */ + public void setError(XMPPError error) + { + Objects.requireNonNull(error, "error"); + + this.error = error; + } + + /** + * {@inheritDoc} + */ + @Override + public String getElementName() + { + return "error"; + } + + /** + * {@inheritDoc} + */ + @Override + public String getNamespace() + { + return ""; + } + + /** + * {@inheritDoc} + */ + @Override + public String toXML() + { + return error.toXML(); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java index 0c5b190..46b1e77 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,441 +15,441 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
-import org.ice4j.ice.*;
-
-/**
- * @author Emil Ivov
- */
-public class CandidatePacketExtension extends AbstractPacketExtension
- implements Comparable<CandidatePacketExtension>
-{
- /**
- * The name of the "candidate" element.
- */
- public static final String ELEMENT_NAME = "candidate";
-
- /**
- * The name of the "component" element.
- */
- public static final String COMPONENT_ATTR_NAME = "component";
-
- /**
- * The "component" ID for RTP components.
- */
- public static final int RTP_COMPONENT_ID = 1;
-
- /**
- * The "component" ID for RTCP components.
- */
- public static final int RTCP_COMPONENT_ID = 2;
-
- /**
- * The name of the "foundation" element.
- */
- public static final String FOUNDATION_ATTR_NAME = "foundation";
-
- /**
- * The name of the "generation" element.
- */
- public static final String GENERATION_ATTR_NAME = "generation";
-
- /**
- * The name of the "id" element.
- */
- public static final String ID_ATTR_NAME = "id";
-
- /**
- * The name of the "ip" element.
- */
- public static final String IP_ATTR_NAME = "ip";
-
- /**
- * The name of the "network" element.
- */
- public static final String NETWORK_ATTR_NAME = "network";
-
- /**
- * The name of the "port" element.
- */
- public static final String PORT_ATTR_NAME = "port";
-
- /**
- * The name of the "priority" element.
- */
- public static final String PRIORITY_ATTR_NAME = "priority";
-
- /**
- * The name of the "protocol" element.
- */
- public static final String PROTOCOL_ATTR_NAME = "protocol";
-
- /**
- * The name of the "rel-addr" element.
- */
- public static final String REL_ADDR_ATTR_NAME = "rel-addr";
-
- /**
- * The name of the "rel-port" element.
- */
- public static final String REL_PORT_ATTR_NAME = "rel-port";
-
- /**
- * The name of the "type" element.
- */
- public static final String TYPE_ATTR_NAME = "type";
-
- /**
- * The name of the "tcptype" element.
- */
- public static final String TCPTYPE_ATTR_NAME = "tcptype";
-
- /**
- * Creates a new {@link CandidatePacketExtension}
- */
- public CandidatePacketExtension()
- {
- super(null, ELEMENT_NAME);
- }
-
- /**
- * Creates a new {@link CandidatePacketExtension} with the specified
- * <tt>elementName</tt> so that this class would be usable as a
- * <tt>RemoteCandidatePacketExtension</tt> parent.
- *
- * @param elementName the element name that this instance should be using.
- */
- protected CandidatePacketExtension(String elementName)
- {
- super(null, elementName);
- }
-
- /**
- * Sets a component ID as defined in ICE-CORE.
- *
- * @param component a component ID as defined in ICE-CORE.
- */
- public void setComponent(int component)
- {
- super.setAttribute(COMPONENT_ATTR_NAME, component);
- }
-
- /**
- * Returns a component ID as defined in ICE-CORE.
- *
- * @return a component ID as defined in ICE-CORE.
- */
- public int getComponent()
- {
- return super.getAttributeAsInt(COMPONENT_ATTR_NAME);
- }
-
- /**
- * Sets the candidate foundation as defined in ICE-CORE.
- *
- * @param foundation the candidate foundation as defined in ICE-CORE.
- */
- public void setFoundation(String foundation)
- {
- super.setAttribute(FOUNDATION_ATTR_NAME, foundation);
- }
-
- /**
- * Returns the candidate foundation as defined in ICE-CORE.
- *
- * @return the candidate foundation as defined in ICE-CORE.
- */
- public String getFoundation()
- {
- return super.getAttributeAsString(FOUNDATION_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's generation index. A generation is an index,
- * starting at 0, that enables the parties to keep track of updates to the
- * candidate throughout the life of the session. For details, see the ICE
- * Restarts section of XEP-0176.
- *
- * @param generation this candidate's generation index.
- */
- public void setGeneration(int generation)
- {
- super.setAttribute(GENERATION_ATTR_NAME, generation);
- }
-
- /**
- * Returns this candidate's generation. A generation is an index, starting at
- * 0, that enables the parties to keep track of updates to the candidate
- * throughout the life of the session. For details, see the ICE Restarts
- * section of XEP-0176.
- *
- * @return this candidate's generation index.
- */
- public int getGeneration()
- {
- return super.getAttributeAsInt(GENERATION_ATTR_NAME);
- }
-
- /**
- * Sets this candidates's unique identifier <tt>String</tt>.
- *
- * @param id this candidates's unique identifier <tt>String</tt>
- */
- public void setID(String id)
- {
- super.setAttribute(ID_ATTR_NAME, id);
- }
-
- /**
- * Returns this candidates's unique identifier <tt>String</tt>.
- *
- * @return this candidates's unique identifier <tt>String</tt>
- */
- public String getID()
- {
- return super.getAttributeAsString(ID_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's Internet Protocol (IP) address; this can be either
- * an IPv4 address or an IPv6 address.
- *
- * @param ip this candidate's IPv4 or IPv6 address.
- */
- public void setIP(String ip)
- {
- super.setAttribute(IP_ATTR_NAME, ip);
- }
-
- /**
- * Returns this candidate's Internet Protocol (IP) address; this can be
- * either an IPv4 address or an IPv6 address.
- *
- * @return this candidate's IPv4 or IPv6 address.
- */
- public String getIP()
- {
- return super.getAttributeAsString(IP_ATTR_NAME);
- }
-
- /**
- * The network index indicating the interface that the candidate belongs to.
- * The network ID is used for diagnostic purposes only in cases where the
- * calling hardware has more than one Network Interface Card.
- *
- * @param network the network index indicating the interface that the
- * candidate belongs to.
- */
- public void setNetwork(int network)
- {
- super.setAttribute(NETWORK_ATTR_NAME, network);
- }
-
- /**
- * Returns the network index indicating the interface that the candidate
- * belongs to. The network ID is used for diagnostic purposes only in cases
- * where the calling hardware has more than one Network Interface Card.
- *
- * @return the network index indicating the interface that the candidate
- * belongs to.
- */
- public int getNetwork()
- {
- return super.getAttributeAsInt(NETWORK_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's port number.
- *
- * @param port this candidate's port number.
- */
- public void setPort(int port)
- {
- super.setAttribute(PORT_ATTR_NAME, port);
- }
-
- /**
- * Returns this candidate's port number.
- *
- * @return this candidate's port number.
- */
- public int getPort()
- {
- return super.getAttributeAsInt(PORT_ATTR_NAME);
- }
-
- /**
- * This candidate's priority as defined in ICE's RFC 5245
- *
- * @param priority this candidate's priority
- */
- public void setPriority(long priority)
- {
- super.setAttribute(PRIORITY_ATTR_NAME, priority);
- }
-
- /**
- * This candidate's priority as defined in ICE's RFC 5245
- *
- * @return this candidate's priority
- */
- public int getPriority()
- {
- return super.getAttributeAsInt(PRIORITY_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's transport protocol.
- *
- * @param protocol this candidate's transport protocol.
- */
- public void setProtocol(String protocol)
- {
- super.setAttribute(PROTOCOL_ATTR_NAME, protocol);
- }
-
- /**
- * Sets this candidate's transport protocol.
- *
- * @return this candidate's transport protocol.
- */
- public String getProtocol()
- {
- return super.getAttributeAsString(PROTOCOL_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's related address as described by ICE's RFC 5245.
- *
- * @param relAddr this candidate's related address as described by ICE's
- * RFC 5245.
- */
- public void setRelAddr(String relAddr)
- {
- super.setAttribute(REL_ADDR_ATTR_NAME, relAddr);
- }
-
- /**
- * Returns this candidate's related address as described by ICE's RFC 5245.
- *
- * @return this candidate's related address as described by ICE's RFC 5245.
- */
- public String getRelAddr()
- {
- return super.getAttributeAsString(REL_ADDR_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's related port as described by ICE's RFC 5245.
- *
- * @param relPort this candidate's related port as described by ICE's
- * RFC 5245.
- */
- public void setRelPort(int relPort)
- {
- super.setAttribute(REL_PORT_ATTR_NAME, relPort);
- }
-
- /**
- * Returns this candidate's related port as described by ICE's RFC 5245.
- *
- * @return this candidate's related port as described by ICE's RFC 5245.
- */
- public int getRelPort()
- {
- return super.getAttributeAsInt(REL_PORT_ATTR_NAME);
- }
-
- /**
- * Sets a Candidate Type as defined in ICE-CORE. The allowable values are
- * "host" for host candidates, "prflx" for peer reflexive candidates,
- * "relay" for relayed candidates, and "srflx" for server reflexive
- * candidates. All allowable values are enumerated in the {@link
- * CandidateType} enum.
- *
- * @param type this candidates' type as per ICE's RFC 5245.
- */
- public void setType(CandidateType type)
- {
- super.setAttribute(TYPE_ATTR_NAME, type);
- }
-
- /**
- * Returns a Candidate Type as defined in ICE-CORE. The allowable values are
- * "host" for host candidates, "prflx" for peer reflexive candidates,
- * "relay" for relayed candidates, and "srflx" for server reflexive
- * candidates. All allowable values are enumerated in the {@link
- * CandidateType} enum.
- *
- * @return this candidates' type as per ICE's RFC 5245.
- */
- public CandidateType getType()
- {
- return CandidateType.valueOf(getAttributeAsString(TYPE_ATTR_NAME));
- }
-
- /**
- * Compares this instance with another CandidatePacketExtension by
- * preference of type: host < local < prflx < srflx < stun < relay.
- *
- * @return 0 if the type are equal. -1 if this instance type is preferred.
- * Otherwise 1.
- */
- public int compareTo(CandidatePacketExtension candidatePacketExtension)
- {
- // If the types are different.
- if(this.getType() != candidatePacketExtension.getType())
- {
- CandidateType[] types = {
- CandidateType.host,
- CandidateType.local,
- CandidateType.prflx,
- CandidateType.srflx,
- CandidateType.stun,
- CandidateType.relay
- };
- for(int i = 0; i < types.length; ++i)
- {
- // this object is preferred.
- if(types[i] == this.getType())
- {
- return -1;
- }
- // the candidatePacketExtension is preferred.
- else if(types[i] == candidatePacketExtension.getType())
- {
- return 1;
- }
- }
- }
- // If the types are equal.
- return 0;
- }
-
- /**
- * Gets the TCP type for this <tt>CandidatePacketExtension</tt>.
- */
- public CandidateTcpType getTcpType()
- {
- String tcpTypeString = getAttributeAsString(TCPTYPE_ATTR_NAME);
- try
- {
- return CandidateTcpType.parse(tcpTypeString);
- }
- catch (IllegalArgumentException iae)
- {
- return null;
- }
- }
-
- /**
- * Sets the TCP type for this <tt>CandidatePacketExtension</tt>.
- * @param tcpType
- */
- public void setTcpType(CandidateTcpType tcpType)
- {
- setAttribute(TCPTYPE_ATTR_NAME, tcpType.toString());
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; +import org.ice4j.ice.*; + +/** + * @author Emil Ivov + */ +public class CandidatePacketExtension extends AbstractPacketExtension + implements Comparable<CandidatePacketExtension> +{ + /** + * The name of the "candidate" element. + */ + public static final String ELEMENT_NAME = "candidate"; + + /** + * The name of the "component" element. + */ + public static final String COMPONENT_ATTR_NAME = "component"; + + /** + * The "component" ID for RTP components. + */ + public static final int RTP_COMPONENT_ID = 1; + + /** + * The "component" ID for RTCP components. + */ + public static final int RTCP_COMPONENT_ID = 2; + + /** + * The name of the "foundation" element. + */ + public static final String FOUNDATION_ATTR_NAME = "foundation"; + + /** + * The name of the "generation" element. + */ + public static final String GENERATION_ATTR_NAME = "generation"; + + /** + * The name of the "id" element. + */ + public static final String ID_ATTR_NAME = "id"; + + /** + * The name of the "ip" element. + */ + public static final String IP_ATTR_NAME = "ip"; + + /** + * The name of the "network" element. + */ + public static final String NETWORK_ATTR_NAME = "network"; + + /** + * The name of the "port" element. + */ + public static final String PORT_ATTR_NAME = "port"; + + /** + * The name of the "priority" element. + */ + public static final String PRIORITY_ATTR_NAME = "priority"; + + /** + * The name of the "protocol" element. + */ + public static final String PROTOCOL_ATTR_NAME = "protocol"; + + /** + * The name of the "rel-addr" element. + */ + public static final String REL_ADDR_ATTR_NAME = "rel-addr"; + + /** + * The name of the "rel-port" element. + */ + public static final String REL_PORT_ATTR_NAME = "rel-port"; + + /** + * The name of the "type" element. + */ + public static final String TYPE_ATTR_NAME = "type"; + + /** + * The name of the "tcptype" element. + */ + public static final String TCPTYPE_ATTR_NAME = "tcptype"; + + /** + * Creates a new {@link CandidatePacketExtension} + */ + public CandidatePacketExtension() + { + super(null, ELEMENT_NAME); + } + + /** + * Creates a new {@link CandidatePacketExtension} with the specified + * <tt>elementName</tt> so that this class would be usable as a + * <tt>RemoteCandidatePacketExtension</tt> parent. + * + * @param elementName the element name that this instance should be using. + */ + protected CandidatePacketExtension(String elementName) + { + super(null, elementName); + } + + /** + * Sets a component ID as defined in ICE-CORE. + * + * @param component a component ID as defined in ICE-CORE. + */ + public void setComponent(int component) + { + super.setAttribute(COMPONENT_ATTR_NAME, component); + } + + /** + * Returns a component ID as defined in ICE-CORE. + * + * @return a component ID as defined in ICE-CORE. + */ + public int getComponent() + { + return super.getAttributeAsInt(COMPONENT_ATTR_NAME); + } + + /** + * Sets the candidate foundation as defined in ICE-CORE. + * + * @param foundation the candidate foundation as defined in ICE-CORE. + */ + public void setFoundation(String foundation) + { + super.setAttribute(FOUNDATION_ATTR_NAME, foundation); + } + + /** + * Returns the candidate foundation as defined in ICE-CORE. + * + * @return the candidate foundation as defined in ICE-CORE. + */ + public String getFoundation() + { + return super.getAttributeAsString(FOUNDATION_ATTR_NAME); + } + + /** + * Sets this candidate's generation index. A generation is an index, + * starting at 0, that enables the parties to keep track of updates to the + * candidate throughout the life of the session. For details, see the ICE + * Restarts section of XEP-0176. + * + * @param generation this candidate's generation index. + */ + public void setGeneration(int generation) + { + super.setAttribute(GENERATION_ATTR_NAME, generation); + } + + /** + * Returns this candidate's generation. A generation is an index, starting at + * 0, that enables the parties to keep track of updates to the candidate + * throughout the life of the session. For details, see the ICE Restarts + * section of XEP-0176. + * + * @return this candidate's generation index. + */ + public int getGeneration() + { + return super.getAttributeAsInt(GENERATION_ATTR_NAME); + } + + /** + * Sets this candidates's unique identifier <tt>String</tt>. + * + * @param id this candidates's unique identifier <tt>String</tt> + */ + public void setID(String id) + { + super.setAttribute(ID_ATTR_NAME, id); + } + + /** + * Returns this candidates's unique identifier <tt>String</tt>. + * + * @return this candidates's unique identifier <tt>String</tt> + */ + public String getID() + { + return super.getAttributeAsString(ID_ATTR_NAME); + } + + /** + * Sets this candidate's Internet Protocol (IP) address; this can be either + * an IPv4 address or an IPv6 address. + * + * @param ip this candidate's IPv4 or IPv6 address. + */ + public void setIP(String ip) + { + super.setAttribute(IP_ATTR_NAME, ip); + } + + /** + * Returns this candidate's Internet Protocol (IP) address; this can be + * either an IPv4 address or an IPv6 address. + * + * @return this candidate's IPv4 or IPv6 address. + */ + public String getIP() + { + return super.getAttributeAsString(IP_ATTR_NAME); + } + + /** + * The network index indicating the interface that the candidate belongs to. + * The network ID is used for diagnostic purposes only in cases where the + * calling hardware has more than one Network Interface Card. + * + * @param network the network index indicating the interface that the + * candidate belongs to. + */ + public void setNetwork(int network) + { + super.setAttribute(NETWORK_ATTR_NAME, network); + } + + /** + * Returns the network index indicating the interface that the candidate + * belongs to. The network ID is used for diagnostic purposes only in cases + * where the calling hardware has more than one Network Interface Card. + * + * @return the network index indicating the interface that the candidate + * belongs to. + */ + public int getNetwork() + { + return super.getAttributeAsInt(NETWORK_ATTR_NAME); + } + + /** + * Sets this candidate's port number. + * + * @param port this candidate's port number. + */ + public void setPort(int port) + { + super.setAttribute(PORT_ATTR_NAME, port); + } + + /** + * Returns this candidate's port number. + * + * @return this candidate's port number. + */ + public int getPort() + { + return super.getAttributeAsInt(PORT_ATTR_NAME); + } + + /** + * This candidate's priority as defined in ICE's RFC 5245 + * + * @param priority this candidate's priority + */ + public void setPriority(long priority) + { + super.setAttribute(PRIORITY_ATTR_NAME, priority); + } + + /** + * This candidate's priority as defined in ICE's RFC 5245 + * + * @return this candidate's priority + */ + public int getPriority() + { + return super.getAttributeAsInt(PRIORITY_ATTR_NAME); + } + + /** + * Sets this candidate's transport protocol. + * + * @param protocol this candidate's transport protocol. + */ + public void setProtocol(String protocol) + { + super.setAttribute(PROTOCOL_ATTR_NAME, protocol); + } + + /** + * Sets this candidate's transport protocol. + * + * @return this candidate's transport protocol. + */ + public String getProtocol() + { + return super.getAttributeAsString(PROTOCOL_ATTR_NAME); + } + + /** + * Sets this candidate's related address as described by ICE's RFC 5245. + * + * @param relAddr this candidate's related address as described by ICE's + * RFC 5245. + */ + public void setRelAddr(String relAddr) + { + super.setAttribute(REL_ADDR_ATTR_NAME, relAddr); + } + + /** + * Returns this candidate's related address as described by ICE's RFC 5245. + * + * @return this candidate's related address as described by ICE's RFC 5245. + */ + public String getRelAddr() + { + return super.getAttributeAsString(REL_ADDR_ATTR_NAME); + } + + /** + * Sets this candidate's related port as described by ICE's RFC 5245. + * + * @param relPort this candidate's related port as described by ICE's + * RFC 5245. + */ + public void setRelPort(int relPort) + { + super.setAttribute(REL_PORT_ATTR_NAME, relPort); + } + + /** + * Returns this candidate's related port as described by ICE's RFC 5245. + * + * @return this candidate's related port as described by ICE's RFC 5245. + */ + public int getRelPort() + { + return super.getAttributeAsInt(REL_PORT_ATTR_NAME); + } + + /** + * Sets a Candidate Type as defined in ICE-CORE. The allowable values are + * "host" for host candidates, "prflx" for peer reflexive candidates, + * "relay" for relayed candidates, and "srflx" for server reflexive + * candidates. All allowable values are enumerated in the {@link + * CandidateType} enum. + * + * @param type this candidates' type as per ICE's RFC 5245. + */ + public void setType(CandidateType type) + { + super.setAttribute(TYPE_ATTR_NAME, type); + } + + /** + * Returns a Candidate Type as defined in ICE-CORE. The allowable values are + * "host" for host candidates, "prflx" for peer reflexive candidates, + * "relay" for relayed candidates, and "srflx" for server reflexive + * candidates. All allowable values are enumerated in the {@link + * CandidateType} enum. + * + * @return this candidates' type as per ICE's RFC 5245. + */ + public CandidateType getType() + { + return CandidateType.valueOf(getAttributeAsString(TYPE_ATTR_NAME)); + } + + /** + * Compares this instance with another CandidatePacketExtension by + * preference of type: host < local < prflx < srflx < stun < relay. + * + * @return 0 if the type are equal. -1 if this instance type is preferred. + * Otherwise 1. + */ + public int compareTo(CandidatePacketExtension candidatePacketExtension) + { + // If the types are different. + if(this.getType() != candidatePacketExtension.getType()) + { + CandidateType[] types = { + CandidateType.host, + CandidateType.local, + CandidateType.prflx, + CandidateType.srflx, + CandidateType.stun, + CandidateType.relay + }; + for(int i = 0; i < types.length; ++i) + { + // this object is preferred. + if(types[i] == this.getType()) + { + return -1; + } + // the candidatePacketExtension is preferred. + else if(types[i] == candidatePacketExtension.getType()) + { + return 1; + } + } + } + // If the types are equal. + return 0; + } + + /** + * Gets the TCP type for this <tt>CandidatePacketExtension</tt>. + */ + public CandidateTcpType getTcpType() + { + String tcpTypeString = getAttributeAsString(TCPTYPE_ATTR_NAME); + try + { + return CandidateTcpType.parse(tcpTypeString); + } + catch (IllegalArgumentException iae) + { + return null; + } + } + + /** + * Sets the TCP type for this <tt>CandidatePacketExtension</tt>. + * @param tcpType + */ + public void setTcpType(CandidateTcpType tcpType) + { + setAttribute(TCPTYPE_ATTR_NAME, tcpType.toString()); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CryptoPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CryptoPacketExtension.java index 69b2e47..3ccbd72 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CryptoPacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CryptoPacketExtension.java @@ -17,6 +17,8 @@ */ package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; +import java.util.Objects; + import ch.imvs.sdes4j.srtp.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.*; @@ -332,4 +334,14 @@ public class CryptoPacketExtension } return false; } + + @Override + public int hashCode() + { + return Objects.hash( + getCryptoSuite(), + getKeyParams(), + getSessionParams(), + getTag()); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java index 1112c8c..173701f 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java @@ -191,6 +191,27 @@ public class IceUdpTransportPacketExtension } /** + * Removes given <tt>PacketExtension</tt> from the list of child packet + * extensions. <tt>CandidatePacketExtension</tt> are not taken into account + * in this method and {@link #removeCandidate(CandidatePacketExtension)} + * should be used instead. + * + * @param childExtension <tt>PacketExtension</tt> instance to be removed + * from child packet extensions list. + * + * @return <tt>true</tt> if given <tt>childExtension</tt> has been in the + * list and was removed or <tt>false</tt> otherwise. + */ + public boolean removeChildExtension(PacketExtension childExtension) + { + List<? extends PacketExtension> childExtensions + = super.getChildExtensions(); + + return childExtensions != null + && childExtensions.remove(childExtension); + } + + /** * Returns the list of {@link CandidatePacketExtension}s currently * registered with this transport. * diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQ.java index 9183fa0..65b0849 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQ.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQ.java @@ -21,6 +21,7 @@ import java.math.*; import java.security.*; import java.util.*; +import net.java.sip.communicator.service.protocol.jabber.*; import org.jivesoftware.smack.packet.*; /** @@ -62,7 +63,8 @@ public class JingleIQ extends IQ * The name of the argument that contains the session id. */ public static final String SID_ATTR_NAME = "sid"; - + + /** * The <tt>JingleAction</tt> that describes the purpose of this * <tt>jingle</tt> element. @@ -104,8 +106,8 @@ public class JingleIQ extends IQ * The list of "content" elements included in this IQ. */ private final List<ContentPacketExtension> contentList - = new ArrayList<ContentPacketExtension>(); - + = new ArrayList<ContentPacketExtension>(); + /** * Returns the XML string of this Jingle IQ's "section" sub-element. * @@ -124,17 +126,18 @@ public class JingleIQ extends IQ if( initiator != null) bldr.append(" " + INITIATOR_ATTR_NAME - + "='" + getInitiator() + "'"); + + "='" + getInitiator() + "'"); if( responder != null) bldr.append(" " + RESPONDER_ATTR_NAME - + "='" + getResponder() + "'"); + + "='" + getResponder() + "'"); bldr.append(" " + SID_ATTR_NAME - + "='" + getSID() + "'"); - - String extensionsXML = getExtensionsXML(); + + "='" + getSID() + "'"); + CharSequence extensionsXMLSeq = getExtensionsXML(); + String extensionsXML = extensionsXMLSeq.toString(); + if ((contentList.size() == 0) && (reason == null) && (sessionInfo == null) @@ -348,7 +351,7 @@ public class JingleIQ extends IQ * otherwise. */ public boolean containsContentChildOfType( - Class<? extends PacketExtension> contentType) + Class<? extends PacketExtension> contentType) { if(getContentForType(contentType) != null) return true; @@ -369,14 +372,14 @@ public class JingleIQ extends IQ * found. */ public ContentPacketExtension getContentForType( - Class<? extends PacketExtension> contentType) + Class<? extends PacketExtension> contentType) { synchronized(contentList) { for(ContentPacketExtension content : contentList) { PacketExtension child - = content.getFirstChildOfType(contentType); + = content.getFirstChildOfType(contentType); if(child != null) return content; } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java index 11f909a..49934e5 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java @@ -20,6 +20,7 @@ package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; import net.java.sip.communicator.impl.protocol.jabber.extensions.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet.*; +import net.java.sip.communicator.service.protocol.jabber.*; import org.jivesoftware.smack.provider.*; import org.xmlpull.v1.*; @@ -37,174 +38,187 @@ public class JingleIQProvider implements IQProvider */ public JingleIQProvider() { - ProviderManager providerManager = ProviderManager.getInstance(); + + AbstractSmackInteroperabilityLayer smackInteroperabilityLayer = + AbstractSmackInteroperabilityLayer.getInstance(); //<description/> provider - providerManager.addExtensionProvider( - RtpDescriptionPacketExtension.ELEMENT_NAME, - RtpDescriptionPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - <RtpDescriptionPacketExtension>( - RtpDescriptionPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + RtpDescriptionPacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <RtpDescriptionPacketExtension>( + RtpDescriptionPacketExtension.class)); //<payload-type/> provider - providerManager.addExtensionProvider( - PayloadTypePacketExtension.ELEMENT_NAME, - RtpDescriptionPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - <PayloadTypePacketExtension>( - PayloadTypePacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + PayloadTypePacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <PayloadTypePacketExtension>( + PayloadTypePacketExtension.class)); //<parameter/> provider - providerManager.addExtensionProvider( - ParameterPacketExtension.ELEMENT_NAME, - RtpDescriptionPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - <ParameterPacketExtension>(ParameterPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + ParameterPacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <ParameterPacketExtension> + (ParameterPacketExtension.class)); //<rtp-hdrext/> provider - providerManager.addExtensionProvider( - RTPHdrExtPacketExtension.ELEMENT_NAME, - RTPHdrExtPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - <RTPHdrExtPacketExtension>(RTPHdrExtPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + RTPHdrExtPacketExtension.ELEMENT_NAME, + RTPHdrExtPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <RTPHdrExtPacketExtension> + (RTPHdrExtPacketExtension.class)); // <sctpmap/> provider - providerManager.addExtensionProvider( - SctpMapExtension.ELEMENT_NAME, - SctpMapExtension.NAMESPACE, - new SctpMapExtensionProvider()); + smackInteroperabilityLayer.addExtensionProvider( + SctpMapExtension.ELEMENT_NAME, + SctpMapExtension.NAMESPACE, + new SctpMapExtensionProvider()); //<encryption/> provider - providerManager.addExtensionProvider( - EncryptionPacketExtension.ELEMENT_NAME, - RtpDescriptionPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - <EncryptionPacketExtension>(EncryptionPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + EncryptionPacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <EncryptionPacketExtension> + (EncryptionPacketExtension.class)); //<zrtp-hash/> provider - providerManager.addExtensionProvider( - ZrtpHashPacketExtension.ELEMENT_NAME, - ZrtpHashPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - <ZrtpHashPacketExtension>(ZrtpHashPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + ZrtpHashPacketExtension.ELEMENT_NAME, + ZrtpHashPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <ZrtpHashPacketExtension> + (ZrtpHashPacketExtension.class)); //<crypto/> provider - providerManager.addExtensionProvider( - CryptoPacketExtension.ELEMENT_NAME, - RtpDescriptionPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - <CryptoPacketExtension>(CryptoPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + CryptoPacketExtension.ELEMENT_NAME, + RtpDescriptionPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <CryptoPacketExtension> + (CryptoPacketExtension.class)); // <bundle/> provider - providerManager.addExtensionProvider( - BundlePacketExtension.ELEMENT_NAME, - BundlePacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - <BundlePacketExtension>(BundlePacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + BundlePacketExtension.ELEMENT_NAME, + BundlePacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <BundlePacketExtension> + (BundlePacketExtension.class)); // <group/> provider - providerManager.addExtensionProvider( - GroupPacketExtension.ELEMENT_NAME, - GroupPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider - <GroupPacketExtension>(GroupPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + GroupPacketExtension.ELEMENT_NAME, + GroupPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <GroupPacketExtension>(GroupPacketExtension.class)); //ice-udp transport - providerManager.addExtensionProvider( - IceUdpTransportPacketExtension.ELEMENT_NAME, - IceUdpTransportPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider<IceUdpTransportPacketExtension>( - IceUdpTransportPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + IceUdpTransportPacketExtension.ELEMENT_NAME, + IceUdpTransportPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <IceUdpTransportPacketExtension>( + IceUdpTransportPacketExtension.class)); //<raw-udp/> provider - providerManager.addExtensionProvider( - RawUdpTransportPacketExtension.ELEMENT_NAME, - RawUdpTransportPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider<RawUdpTransportPacketExtension>( - RawUdpTransportPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + RawUdpTransportPacketExtension.ELEMENT_NAME, + RawUdpTransportPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <RawUdpTransportPacketExtension>( + RawUdpTransportPacketExtension.class)); //ice-udp <candidate/> provider - providerManager.addExtensionProvider( - CandidatePacketExtension.ELEMENT_NAME, - IceUdpTransportPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider<CandidatePacketExtension>( - CandidatePacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + CandidatePacketExtension.ELEMENT_NAME, + IceUdpTransportPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <CandidatePacketExtension>( + CandidatePacketExtension.class)); //raw-udp <candidate/> provider - providerManager.addExtensionProvider( - CandidatePacketExtension.ELEMENT_NAME, - RawUdpTransportPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider<CandidatePacketExtension>( - CandidatePacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + CandidatePacketExtension.ELEMENT_NAME, + RawUdpTransportPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <CandidatePacketExtension>( + CandidatePacketExtension.class)); //ice-udp <remote-candidate/> provider - providerManager.addExtensionProvider( - RemoteCandidatePacketExtension.ELEMENT_NAME, - IceUdpTransportPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider<RemoteCandidatePacketExtension>( - RemoteCandidatePacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + RemoteCandidatePacketExtension.ELEMENT_NAME, + IceUdpTransportPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider + <RemoteCandidatePacketExtension>( + RemoteCandidatePacketExtension.class)); //inputevt <inputevt/> provider - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( InputEvtPacketExtension.ELEMENT_NAME, InputEvtPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<InputEvtPacketExtension>( InputEvtPacketExtension.class)); //coin <conference-info/> provider - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( CoinPacketExtension.ELEMENT_NAME, CoinPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<CoinPacketExtension>( CoinPacketExtension.class)); // DTLS-SRTP - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( DtlsFingerprintPacketExtension.ELEMENT_NAME, DtlsFingerprintPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider - <DtlsFingerprintPacketExtension>( + <DtlsFingerprintPacketExtension>( DtlsFingerprintPacketExtension.class)); /* * XEP-0251: Jingle Session Transfer <transfer/> and <transferred> * providers */ - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( TransferPacketExtension.ELEMENT_NAME, TransferPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<TransferPacketExtension>( TransferPacketExtension.class)); - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( TransferredPacketExtension.ELEMENT_NAME, TransferredPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<TransferredPacketExtension>( TransferredPacketExtension.class)); //conference description <callid/> provider - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( ConferenceDescriptionPacketExtension.CALLID_ELEM_NAME, ConferenceDescriptionPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<CallIdPacketExtension>( CallIdPacketExtension.class)); //rtcp-fb - providerManager.addExtensionProvider( - RtcpFbPacketExtension.ELEMENT_NAME, - RtcpFbPacketExtension.NAMESPACE, - new DefaultPacketExtensionProvider<RtcpFbPacketExtension>( - RtcpFbPacketExtension.class)); + smackInteroperabilityLayer.addExtensionProvider( + RtcpFbPacketExtension.ELEMENT_NAME, + RtcpFbPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider<RtcpFbPacketExtension>( + RtcpFbPacketExtension.class)); //rtcp-mux - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( RtcpmuxPacketExtension.ELEMENT_NAME, IceUdpTransportPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<RtcpmuxPacketExtension>( RtcpmuxPacketExtension.class)); //ssrcInfo - providerManager.addExtensionProvider( + smackInteroperabilityLayer.addExtensionProvider( SSRCInfoPacketExtension.ELEMENT_NAME, SSRCInfoPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<SSRCInfoPacketExtension>( diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/ComponentVersionsExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/ComponentVersionsExtension.java new file mode 100644 index 0000000..bcc3397 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/ComponentVersionsExtension.java @@ -0,0 +1,135 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +/** + * The packet extension is used by Jicofo to broadcast versions of all video + * conferencing system components. This packets extension is added to jicofo's + * MUC presence. It will contain {@link Component} children which carry each + * component's name and version. + * + * @author Pawel Domas + */ +public class ComponentVersionsExtension + extends AbstractPacketExtension +{ + /** + * The XML element name of {@link ComponentVersionsExtension}. + */ + public static final String ELEMENT_NAME = "versions"; + + /** + * The name of XML sub-elements which carry the info about particular + * component's version. + */ + public static final String COMPONENT_ELEMENT_NAME = "component"; + + /** + * Constant for {@link Component} name used to signal the version of + * conference focus. + */ + public static final String COMPONENT_FOCUS = "focus"; + + /** + * Constant for {@link Component} name used to signal the version of + * XMPP server. + */ + public static final String COMPONENT_XMPP_SERVER = "xmpp"; + + /** + * Constant for {@link Component} name used to signal the version of + * the videobridge. + */ + public static final String COMPONENT_VIDEOBRIDGE = "videobridge"; + + /** + * The XML element namespace of {@link ComponentVersionsExtension}. + */ + public static final String NAMESPACE = "http://jitsi.org/jitmeet"; + + /** + * Creates an {@link AbstractPacketExtension} instance for the specified + * <tt>namespace</tt> and <tt>elementName</tt>. + */ + public ComponentVersionsExtension() + { + super(NAMESPACE, ELEMENT_NAME); + } + + /** + * Adds component's version to this extension. + * + * @param componentName the name of the component for which + * child {@link Component} extension will be added. + * @param versionStr human readable string that describes component's + * version. + */ + public void addComponentVersion(String componentName, String versionStr) + { + Component v = new Component(); + + v.setName(componentName); + v.setText(versionStr); + + addChildExtension(v); + } + + /** + * Component child element of {@link ComponentVersionsExtension}. The name + * of the component is carried in name attribute and the version string is + * the text value. + */ + public class Component + extends AbstractPacketExtension + { + /** + * The name of that attribute that carries component's name. + */ + private final String NAME_ATTR_NAME = "name"; + + /** + * Creates new instance of {@link Component} packet extension. + */ + public Component() + { + super(NAMESPACE, COMPONENT_ELEMENT_NAME); + } + + /** + * Returns the value of the name attribute. + * @return <tt>String</tt> which describes the name of video + * conferencing system component. + */ + public String getName() + { + return getAttributeAsString(NAME_ATTR_NAME); + } + + /** + * Sets new value for the component's name attribute. + * @param name a <tt>String</tt> which describes the name of video + * conferencing system component. + */ + public void setName(String name) + { + setAttribute(NAME_ATTR_NAME, name); + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/SSRCInfoPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/SSRCInfoPacketExtension.java index 239c708..7384c9e 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/SSRCInfoPacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/SSRCInfoPacketExtension.java @@ -1,8 +1,19 @@ /* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * - * Distributable under LGPL license. - * See terms of license at gnu.org. + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet; diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/VideoMutedExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/VideoMutedExtension.java new file mode 100644 index 0000000..cf76d89 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/VideoMutedExtension.java @@ -0,0 +1,70 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +/** + * Video muted extension that is included in users presence in Jitsi-meet + * conferences. It does carry the info about user's video muted status. + * + * @author Pawel Domas + */ +public class VideoMutedExtension + extends AbstractPacketExtension +{ + /** + * The namespace of this packet extension. + */ + public static final String NAMESPACE = "http://jitsi.org/jitmeet/video"; + + /** + * XML element name of this packet extension. + */ + public static final String ELEMENT_NAME = "videomuted"; + + /** + * Creates new instance of <tt>VideoMutedExtension</tt>. + */ + public VideoMutedExtension() + { + super(NAMESPACE, ELEMENT_NAME); + } + + /** + * Check whether or not user's video is in muted status. + * @return <tt>true</tt> if muted, <tt>false</tt> if unmuted or + * <tt>null</tt> if no valid info found in the extension body. + */ + public Boolean isVideoMuted() + { + return Boolean.valueOf(getText()); + } + + /** + * Sets user's video muted status. + * + * @param videoMuted <tt>true</tt> or <tt>false</tt> which indicates video + * muted status of the user. + */ + public void setVideoMuted(Boolean videoMuted) + { + setText( + String.valueOf(videoMuted)); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/jinglesdp/JingleUtils.java b/src/net/java/sip/communicator/impl/protocol/jabber/jinglesdp/JingleUtils.java index 93cdead..8094f68 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/jinglesdp/JingleUtils.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/jinglesdp/JingleUtils.java @@ -179,12 +179,17 @@ public class JingleUtils else paramsMap.put(paramName, param.getValue()); } - - // video-related attributes in payload-type element + for(String attr : payloadType.getAttributeNames()) { + //video-related attributes in payload-type element if(attr.equals("width") || attr.equals("height")) paramsMap.put(attr, payloadType.getAttributeAsString(attr)); + + //update ptime with the actual value from the payload + if (attr.equals(PayloadTypePacketExtension.PTIME_ATTR_NAME)) + advancedMap.put(PayloadTypePacketExtension.PTIME_ATTR_NAME, + Integer.toString(payloadType.getPtime())); } //now create the format. diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockContactGroup.java b/src/net/java/sip/communicator/impl/protocol/mock/MockContactGroup.java index fa4fff1..e6cdd36 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockContactGroup.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockContactGroup.java @@ -506,6 +506,31 @@ public class MockContactGroup return true; } + @Override + public int hashCode() + { + List<Object> objects = new ArrayList<Object>(); + objects.add(getGroupName()); + objects.add(getUID()); + objects.add(countContacts()); + objects.add(countSubgroups()); + + //traverse child contacts + for (Contact c : contacts) + { + objects.add(c.getAddress()); + } + + + //traverse subgroups + for (ContactGroup g : subGroups) + { + objects.add(g.getGroupName()); + } + + return Objects.hash(objects.toArray()); + } + public void setPersistent(boolean isPersistent) { this.isPersistent = isPersistent; diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java b/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java index bcff799..a7b6e5e 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java @@ -294,6 +294,15 @@ public class MockProvider } /** + * Always true. + */ + @Override + public boolean validateContactAddress(String contactId, List<String> result) + { + return true; + } + + /** * Mock implementation of the corresponding ProtocolProviderService method. * We have no icon corresponding to this protocol provider. */ diff --git a/src/net/java/sip/communicator/impl/protocol/sip/AddressResolverImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/AddressResolverImpl.java index eba0da6..4fd1b6f 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/AddressResolverImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/AddressResolverImpl.java @@ -70,18 +70,8 @@ public class AddressResolverImpl // if it is a textual IP address, do no try to resolve it if(NetworkUtils.isValidIPAddress(hostAddress)) { - byte[] addr = null; - - addr = NetworkUtils.strToIPv4(hostAddress); - - // not an IPv4, try IPv6 - if (addr == null) - { - addr = NetworkUtils.strToIPv6(hostAddress); - } - InetSocketAddress hostSocketAddress = new InetSocketAddress( - InetAddress.getByAddress(hostAddress, addr), + NetworkUtils.getInetAddress(hostAddress), inputAddress.getPort()); return new HopImpl(hostSocketAddress.getHostName(), inputAddress.getPort(), diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java index c4193a5..b626dbb 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java @@ -87,7 +87,7 @@ public class CallPeerMediaHandlerSipImpl * Whether other party is able to change video quality settings. * Normally its whether we have detected existence of imageattr in sdp. */ - boolean supportQualityControls; + private boolean supportQualityControls; /** * The current quality controls for this peer media handler if any. @@ -98,7 +98,7 @@ public class CallPeerMediaHandlerSipImpl * The lock we use to make sure that we won't be processing a second * offer/answer exchange while a . */ - private Object offerAnswerLock = new Object(); + private final Object offerAnswerLock = new Object(); /** * Creates a new handler that will be managing media streams for @@ -164,7 +164,7 @@ public class CallPeerMediaHandlerSipImpl throws OperationFailedException { //Audio Media Description - Vector<MediaDescription> mediaDescs = createMediaDescriptions(); + List<MediaDescription> mediaDescs = createMediaDescriptions(); //wrap everything up in a session description String userName @@ -197,7 +197,7 @@ public class CallPeerMediaHandlerSipImpl * for reasons like - problems with device interaction, allocating ports, * etc. */ - private Vector<MediaDescription> createMediaDescriptions() + private List<MediaDescription> createMediaDescriptions() throws OperationFailedException { //Audio Media Description @@ -215,12 +215,16 @@ public class CallPeerMediaHandlerSipImpl receiveQualityPreset = qualityControls.getRemoteSendMaxPreset(); } - for (MediaType mediaType : MediaType.values()) + for (MediaType mediaType : new MediaType[] { MediaType.AUDIO , MediaType.VIDEO}) { MediaDevice dev = getDefaultDevice(mediaType); if (!isDeviceActive(dev, sendQualityPreset, receiveQualityPreset)) + { + logger.warn("No active device for " + mediaType.toString() + + " was found!"); continue; + } MediaDirection direction = dev.getDirection().and(getDirectionUserPreference(mediaType)); @@ -339,7 +343,7 @@ public class CallPeerMediaHandlerSipImpl * it is known that the other endpoint supports this * profile" and "[o]ther profiles MAY also be used." */ - updateMediaDescriptionForZrtp(mediaType, md, null); + updateMediaDescriptionForZrtp(mediaType, md); if (SrtpControl.RTP_SAVP.equals(proto) || SrtpControl.RTP_SAVPF.equals(proto)) { @@ -400,7 +404,7 @@ public class CallPeerMediaHandlerSipImpl { //create the media descriptions reflecting our current state. - Vector<MediaDescription> newMediaDescs = createMediaDescriptions(); + List<MediaDescription> newMediaDescs = createMediaDescriptions(); SessionDescription newOffer = SdpUtils.createSessionUpdateDescription( sdescToUpdate, getTransportManager().getLastUsedLocalHost(), @@ -510,7 +514,7 @@ public class CallPeerMediaHandlerSipImpl throws OperationFailedException, IllegalArgumentException { - Vector<MediaDescription> answerDescriptions + List<MediaDescription> answerDescriptions = createMediaDescriptionsForAnswer(newOffer); // wrap everything up in a session description SessionDescription newAnswer @@ -555,10 +559,11 @@ public class CallPeerMediaHandlerSipImpl boolean rejectedAvpOfferDueToSavpMandatory = false; AccountID accountID = getPeer().getProtocolProvider().getAccountID(); - int savpOption - = accountID.getAccountPropertyBoolean( - ProtocolProviderFactory.DEFAULT_ENCRYPTION, - true) + boolean useDefaultEncryption = + accountID.getAccountPropertyBoolean( + ProtocolProviderFactory.DEFAULT_ENCRYPTION , true); + + int savpOption = useDefaultEncryption ? accountID.getAccountPropertyInt( ProtocolProviderFactory.SAVP_OPTION, ProtocolProviderFactory.SAVP_OFF) @@ -638,7 +643,7 @@ public class CallPeerMediaHandlerSipImpl { mutuallySupportedFormats = null; } - else if(mediaType.equals(MediaType.VIDEO) + else if(MediaType.VIDEO.equals(mediaType) && (qualityControls != null)) { /* @@ -705,18 +710,18 @@ public class CallPeerMediaHandlerSipImpl = getTransportManager().getStreamConnector(mediaType); // check for options from remote party and set them locally - if(mediaType.equals(MediaType.VIDEO)) + if(MediaType.VIDEO.equals(mediaType)) { // update stream MediaStream stream = getStream(MediaType.VIDEO); - if(stream != null && dev != null) + if(stream != null) { List<MediaFormat> fmts = intersectFormats( getLocallySupportedFormats(dev), remoteFormats); - if(fmts.size() > 0) + if(!fmts.isEmpty()) { MediaFormat fmt = fmts.get(0); @@ -809,7 +814,7 @@ public class CallPeerMediaHandlerSipImpl { if(remoteDescriptions.size() > 1) { - if(mediaType.equals(MediaType.AUDIO)) + if(MediaType.AUDIO.equals(mediaType)) { masterStream = true; masterStreamSet = true; @@ -1192,8 +1197,7 @@ public class CallPeerMediaHandlerSipImpl */ private boolean updateMediaDescriptionForZrtp( MediaType mediaType, - MediaDescription localMd, - MediaDescription remoteMd) + MediaDescription localMd) { MediaAwareCallPeer<?, ?, ?> peer = getPeer(); AccountID accountID = peer.getProtocolProvider().getAccountID(); @@ -1499,7 +1503,7 @@ public class CallPeerMediaHandlerSipImpl // check for options from remote party and set // is quality controls supported - if(mediaType.equals(MediaType.VIDEO)) + if(MediaType.VIDEO.equals(mediaType)) { supportQualityControls = SdpUtils.containsAttribute(mediaDescription, "imageattr"); @@ -1544,7 +1548,7 @@ public class CallPeerMediaHandlerSipImpl { if(remoteDescriptions.size() > 1) { - if(mediaType.equals(MediaType.AUDIO)) + if(MediaType.AUDIO.equals(mediaType)) { masterStream = true; masterStreamSet = true; @@ -1882,7 +1886,7 @@ public class CallPeerMediaHandlerSipImpl // ZRTP else if(srtpControlType == SrtpControlType.ZRTP) { - if(updateMediaDescriptionForZrtp(mediaType, localMd, remoteMd)) + if(updateMediaDescriptionForZrtp(mediaType, localMd)) { // Stop once an encryption advertisement has been chosen. return; diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java index fa0e08e..62711c7 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java @@ -61,15 +61,27 @@ public class CallSipImpl * Name of extra INVITE header which specifies name of MUC room that is * hosting the Jitsi Meet conference. */ - public static final String JITSI_MEET_ROOM_HEADER - = "Jitsi-Conference-Room"; + public String JITSI_MEET_ROOM_HEADER = "Jitsi-Conference-Room"; + + /** + * Property name of extra INVITE header which specifies name of MUC room + * that is hosting the Jitsi Meet conference. + */ + private static final String JITSI_MEET_ROOM_HEADER_PROPERTY + = "JITSI_MEET_ROOM_HEADER_NAME"; + + /** + * Property name of extra INVITE header which specifies password required + * to enter MUC room that is hosting the Jitsi Meet conference. + */ + public String JITSI_MEET_ROOM_PASS_HEADER = "Jitsi-Conference-Room-Pass"; /** * Name of extra INVITE header which specifies password required to enter * MUC room that is hosting the Jitsi Meet conference. */ - public static final String JITSI_MEET_ROOM_PASS_HEADER - = "Jitsi-Conference-Room-Pass"; + private static final String JITSI_MEET_ROOM_PASS_HEADER_PROPERTY + = "JITSI_MEET_ROOM_PASS_HEADER_NAME"; /** * Custom header included in initial desktop sharing call creation. @@ -78,16 +90,27 @@ public class CallSipImpl public static final String DS_SHARING_HEADER = "X-Desktop-Share"; /** - * When starting call we may have quality preferences we must use - * for the call. + * Custom header name prefix that can be added to the call instance. + * Several headers can be specified in the form of: + * EXTRA_HEADER_NAME.1=... + * EXTRA_HEADER_NAME.2=... + * Index starting from 1. */ - private QualityPreset initialQualityPreferences; + public static final String EXTRA_HEADER_NAME = "EXTRA_HEADER_NAME"; /** - * A reference to the <tt>SipMessageFactory</tt> instance that we should - * use when creating requests. + * Custom header value prefix that can be added to the call instance. + * Several headers can be specified in the form of: + * EXTRA_HEADER_VALUE.1=... + * EXTRA_HEADER_VALUE.2=... + * Index starting from 1. */ - private final SipMessageFactory messageFactory; + public static final String EXTRA_HEADER_VALUE = "EXTRA_HEADER_VALUE"; + + /** + * Maximum number of retransmissions that will be sent. + */ + private static final int MAX_RETRANSMISSIONS = 3; /** * The name of the property under which the user may specify the number of @@ -95,19 +118,27 @@ public class CallSipImpl * 180. */ private static final String RETRANSMITS_RINGING_INTERVAL - = "net.java.sip.communicator.impl.protocol.sip" - + ".RETRANSMITS_RINGING_INTERVAL"; + = "net.java.sip.communicator.impl.protocol.sip" + + ".RETRANSMITS_RINGING_INTERVAL"; /** - * The default amount of time (in milliseconds) for the initial interval for - * retransmissions of response 180. - */ + * The default amount of time (in milliseconds) for the initial interval for + * retransmissions of response 180. + */ private static final int DEFAULT_RETRANSMITS_RINGING_INTERVAL = 500; /** - * Maximum number of retransmissions that will be sent. + * When starting call we may have quality preferences we must use + * for the call. */ - private static final int MAX_RETRANSMISSIONS = 3; + private QualityPreset initialQualityPreferences; + + /** + * A reference to the <tt>SipMessageFactory</tt> instance that we should + * use when creating requests. + */ + private final SipMessageFactory messageFactory; + /** * The amount of time (in milliseconds) for the initial interval for @@ -140,6 +171,13 @@ public class CallSipImpl } this.retransmitsRingingInterval = retransmitsRingingInterval; + AccountID account = parentOpSet.getProtocolProvider().getAccountID(); + // Specify custom header names + JITSI_MEET_ROOM_HEADER = account.getAccountPropertyString( + JITSI_MEET_ROOM_HEADER_PROPERTY, JITSI_MEET_ROOM_HEADER); + JITSI_MEET_ROOM_PASS_HEADER = account.getAccountPropertyString( + JITSI_MEET_ROOM_PASS_HEADER_PROPERTY, JITSI_MEET_ROOM_PASS_HEADER); + //let's add ourselves to the calls repo. we are doing it ourselves just //to make sure that no one ever forgets. parentOpSet.getActiveCallsRepository().addCall(this); @@ -387,24 +425,21 @@ public class CallSipImpl logger.trace("Looking for peer with dialog: " + dialog + "among " + getCallPeerCount() + " calls"); } - while (callPeers.hasNext()) + for (CallPeerSipImpl callPeer : getCallPeerList()) { - CallPeerSipImpl cp = callPeers.next(); - - if (cp.getDialog() == dialog) + if (callPeer.getDialog() == dialog) { if (logger.isTraceEnabled()) - logger.trace("Returning cp=" + cp); - return cp; + logger.trace("Returning cp=" + callPeer); + return callPeer; } else { if (logger.isTraceEnabled()) - logger.trace("Ignoring cp=" + cp + " because cp.dialog=" - + cp.getDialog() + " while dialog=" + dialog); + logger.trace("Ignoring cp=" + callPeer + " because cp.dialog=" + + callPeer.getDialog() + " while dialog=" + dialog); } } - return null; } @@ -449,7 +484,7 @@ public class CallSipImpl // Transport preference String forceTransport = null; javax.sip.address.URI calleeURI = calleeAddress.getURI(); - if(calleeURI.getScheme().toLowerCase().equals("sips")) + if("sips".equals(calleeURI.getScheme().toLowerCase())) { // MUST use TLS forceTransport = "TLS"; @@ -553,7 +588,7 @@ public class CallSipImpl String alternativeIMPPAddress = null; if (infoHeader != null && infoHeader.getParameter("purpose") != null - && infoHeader.getParameter("purpose").equals("impp")) + && "impp".equals(infoHeader.getParameter("purpose"))) { alternativeIMPPAddress = infoHeader.getInfo().toString(); } @@ -585,7 +620,7 @@ public class CallSipImpl { if (logger.isTraceEnabled()) logger.trace("will send ringing response: "); - if(peer.getState().equals(CallPeerState.INCOMING_CALL)) + if( CallPeerState.INCOMING_CALL.equals(peer.getState()) ) { response = messageFactory.createResponse(Response.RINGING, invite); @@ -680,10 +715,10 @@ public class CallSipImpl */ public void reInvite() throws OperationFailedException { - Iterator<CallPeerSipImpl> peers = getCallPeers(); - - while (peers.hasNext()) - peers.next().sendReInvite(); + for(CallPeerSipImpl peer : getCallPeerList()) + { + peer.sendReInvite(); + } } /** @@ -706,6 +741,22 @@ public class CallSipImpl protected void processExtraHeaders(javax.sip.message.Message message) throws ParseException { + // If there are custom headers added to the call instance, add those + // headers + int extraHeaderIx = 1; + + Object name = getData(EXTRA_HEADER_NAME + "." + extraHeaderIx); + while(name != null) + { + Object value = getData(EXTRA_HEADER_VALUE + "." + extraHeaderIx); + + Header header = getProtocolProvider().getHeaderFactory() + .createHeader((String) name, (String) value); + message.setHeader(header); + + extraHeaderIx++; + name = getData(EXTRA_HEADER_NAME + "." + extraHeaderIx); + } } /** @@ -758,8 +809,8 @@ public class CallSipImpl { try { - if(!peer.getState().equals( - CallPeerState.INCOMING_CALL)) + if( !CallPeerState.INCOMING_CALL.equals( + peer.getState()) ) { timer.cancel(); } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ContactGroupSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/ContactGroupSipImpl.java index f5782f1..3c2aeb5 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ContactGroupSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ContactGroupSipImpl.java @@ -641,5 +641,31 @@ public class ContactGroupSipImpl return true; } + + @Override + public int hashCode() + { + List<Object> objects = new ArrayList<Object>(); + objects.add(getGroupName()); + objects.add(getUID()); + objects.add(countContacts()); + objects.add(countSubgroups()); + objects.add(getProtocolProvider()); + + //traverse child contacts + for (Contact c : contacts) + { + objects.add(c.getAddress()); + } + + + //traverse subgroups + for (ContactGroup g : subGroups) + { + objects.add(g.getGroupName()); + } + + return Objects.hash(objects.toArray()); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ContactSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/ContactSipImpl.java index 0d0b5e8..d4662dd 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ContactSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ContactSipImpl.java @@ -172,7 +172,7 @@ public class ContactSipImpl public String getAddress() { SipURI sipURI = (SipURI) sipAddress.getURI(); - return sipURI.getUser() + "@" + sipURI.getHost(); + return sipURI.toString().substring(sipURI.getScheme().length() + 1); } /** @@ -535,9 +535,7 @@ public class ContactSipImpl if(obj instanceof String) { String sobj = (String)obj; - - if(sobj.startsWith("sip:")) - sobj = sobj.substring(4); + sobj = stripScheme(stripAddress(sobj)); if(getAddress().equalsIgnoreCase(sobj)) return true; @@ -556,6 +554,43 @@ public class ContactSipImpl } /** + * Get rid of any parameters, ports etc. within a sip contact + * @param address the address to strip + * @return [sip[s]:]user@host without any params or port numbers. + */ + static String stripAddress(String address) + { + if (address != null && address.length() > 0) + { + int idx = address.indexOf(':', 5); + if (idx > -1) + address = address.substring(0, idx); + idx = address.indexOf(';'); + if (idx > -1) + address = address.substring(0, idx); + } + return address; + } + + /** + * @param from address to strip + * @return the address, stripped from either "sip:" or "sips:" + */ + public static String stripScheme(String from) + { + if (from.startsWith("sip:")) + { + return from.substring(4); + } + else if (from.startsWith("sips:")) + { + return from.substring(5); + } + + return from; + } + + /** * Returns the presence operation set that this contact belongs * to. * diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java index 5ee32c9..fc99643 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java @@ -434,7 +434,10 @@ public class OperationSetBasicTelephonySipImpl CSeqHeader cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)); if (cseq == null) + { logger.error("An incoming response did not contain a CSeq header"); + return false; + } String method = cseq.getMethod(); @@ -1163,11 +1166,7 @@ public class OperationSetBasicTelephonySipImpl protocolProvider.getAccountID().getService() , 399, reasonText); } - catch(InvalidArgumentException e) - { - logger.error("Cannot create warning header", e); - } - catch(ParseException e) + catch(InvalidArgumentException | ParseException e) { logger.error("Cannot create warning header", e); } @@ -1458,11 +1457,7 @@ public class OperationSetBasicTelephonySipImpl { serverTransaction.sendResponse(accepted); } - catch (InvalidArgumentException ex) - { - failure = ex; - } - catch (SipException ex) + catch (InvalidArgumentException | SipException ex) { failure = ex; } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetJitsiMeetToolsSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetJitsiMeetToolsSipImpl.java index 77a7fae..4997fc1 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetJitsiMeetToolsSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetJitsiMeetToolsSipImpl.java @@ -142,6 +142,16 @@ public class OperationSetJitsiMeetToolsSipImpl * {@inheritDoc} */ @Override + public void removePresenceExtension(ChatRoom chatRoom, + PacketExtension extension) + { + throw new RuntimeException("Not implemented for SIP"); + } + + /** + * {@inheritDoc} + */ + @Override public void setPresenceStatus(ChatRoom chatRoom, String statusMessage) { throw new RuntimeException("Not implemented for SIP"); diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetMessageWaitingSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetMessageWaitingSipImpl.java index 28fce28..7972ee8 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetMessageWaitingSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetMessageWaitingSipImpl.java @@ -209,6 +209,10 @@ public class OperationSetMessageWaitingSipImpl try { subscribeAddress = getSubscribeAddress(); + if (subscribeAddress == null) + { + return; + } } catch (ParseException e) { diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetPresenceSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetPresenceSipImpl.java index 2fce57e..ef0ec4c 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetPresenceSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetPresenceSipImpl.java @@ -2240,6 +2240,40 @@ public class OperationSetPresenceSipImpl updateContactIcon((ContactSipImpl) contact, personStatusIcon); } + // search for a <note> that can define a more precise + // status this is not recommended by RFC3863 but some im + // clients use this. + NodeList presNoteList = getPidfChilds(presence, NOTE_ELEMENT); + if (presNoteList.getLength() >= 1) + { + Node noteNode = presNoteList.item(presNoteList.getLength() - 1); + if (noteNode.getNodeType() == Node.ELEMENT_NODE) + { + String state = getTextContent((Element)noteNode); + if (state != null) + { + switch (state.toLowerCase()) + { + case "ready": + case "available": + personStatus = sipStatusEnum + .getStatus(SipStatusEnum.ONLINE); + break; + case "ringing": + case "on the phone": + case "on hold": + personStatus = sipStatusEnum + .getStatus(SipStatusEnum.ON_THE_PHONE); + break; + case "unavailable": + personStatus = sipStatusEnum + .getStatus(SipStatusEnum.OFFLINE); + break; + } + } + } + } + // Vector containing the list of status to set for each contact in // the presence document ordered by priority (highest first). // <SipContact, Float (priority), SipStatusEnum> diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyBLFSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyBLFSipImpl.java index 644c85a..a9aefbe 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyBLFSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyBLFSipImpl.java @@ -72,6 +72,10 @@ public class OperationSetTelephonyBLFSipImpl * Account property suffix to set/provision monitored line group. */ public static final String BLF_LINE_GROUP_ACC_PROP_PREFIX = "Group"; + /** + * Account property suffix to set/provision monitored line pickup template. + */ + public static final String BLF_LINE_PICKUP_ACC_PROP_PREFIX = "Pickup"; /** * The name of the event package supported by @@ -195,7 +199,7 @@ public class OperationSetTelephonyBLFSipImpl String[] lineValues = lines.get(ix); if(lineValues == null) { - lineValues = new String[3]; + lineValues = new String[4]; lines.put(ix, lineValues); } @@ -211,13 +215,17 @@ public class OperationSetTelephonyBLFSipImpl { lineValues[2] = entryValue; } + else if(pName.contains(BLF_LINE_PICKUP_ACC_PROP_PREFIX)) + { + lineValues[3] = entryValue; + } } for(Map.Entry<String, String[]> en : lines.entrySet()) { String[] vals = en.getValue(); - this.lines.add(new Line(vals[0], vals[1], vals[2], this.provider)); + this.lines.add(new Line(vals[0], vals[1], vals[2], vals[3], this.provider)); } } @@ -263,16 +271,25 @@ public class OperationSetTelephonyBLFSipImpl if(details == null) return; - if(StringUtils.isNullOrEmpty(details.callID) - || StringUtils.isNullOrEmpty(details.localTag) - || StringUtils.isNullOrEmpty(details.remoteTag)) - return; - // replaces Address targetAddress = null; try { - targetAddress = provider.parseAddressString(line.getAddress()); + String address = line.getAddress(); + if(asteriskMode(details)) + { + // broken mode for Asterisk, doesn't provide us with + // the proper call-id, etc. attributes. + // send an unspecified pickup-call if a template is set + if (StringUtils.isNullOrEmpty(line.getPickupTemplate(), true)) + { + return; + } + + address = line.getPickupTemplate().replace("\\1", address); + } + + targetAddress = provider.parseAddressString(address); } catch (ParseException ex) { @@ -281,6 +298,16 @@ public class OperationSetTelephonyBLFSipImpl OperationFailedException.ILLEGAL_ARGUMENT, ex, logger); } + OperationSetBasicTelephonySipImpl telOpSet + = (OperationSetBasicTelephonySipImpl)provider + .getOperationSet(OperationSetBasicTelephony.class); + + if (asteriskMode(details)) + { + telOpSet.createOutgoingCall(targetAddress, null, null); + return; + } + Replaces replacesHeader = null; SipURI sipURI = (SipURI) targetAddress.getURI(); @@ -315,12 +342,14 @@ public class OperationSetTelephonyBLFSipImpl OperationFailedException.INTERNAL_ERROR, ex, logger); } - OperationSetBasicTelephonySipImpl telOpSet - = (OperationSetBasicTelephonySipImpl)provider - .getOperationSet(OperationSetBasicTelephony.class); - - CallSipImpl call - = telOpSet.createOutgoingCall(targetAddress, null, null); + telOpSet.createOutgoingCall(targetAddress, null, null); + } + + private boolean asteriskMode(LineDetails details) + { + return StringUtils.isNullOrEmpty(details.callID) + || StringUtils.isNullOrEmpty(details.localTag) + || StringUtils.isNullOrEmpty(details.remoteTag); } /** @@ -549,6 +578,8 @@ public class OperationSetTelephonyBLFSipImpl Node dialogNode = dialogList.item(i); Element dialogElem = (Element)dialogNode; + details.id = dialogElem.getAttribute("id"); + details.direction = dialogElem.getAttribute("direction"); details.callID = dialogElem.getAttribute("call-id"); details.localTag = dialogElem.getAttribute("local-tag"); details.remoteTag = dialogElem.getAttribute("remote-tag"); @@ -698,12 +729,25 @@ public class OperationSetTelephonyBLFSipImpl */ private class LineDetails { + /** * The current status of the line, the last event fired for it. */ int lastStatusEvent = BLFStatusEvent.STATUS_OFFLINE; /** + * id of the dialog. Mandatory. + */ + String id = null; + + /** + * either initiator or recipient, and indicates whether the observed + * user was the initiator of the dialog, or the recipient of the INVITE + * that created it. + */ + String direction; + + /** * call-id of the dialog if any, used for remote pickup. */ String callID = null; diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java index 0fbcf23..927c0a3 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java @@ -246,6 +246,76 @@ public class ProtocolProviderServiceSipImpl } /** + * Validates the contact identifier and returns an error message if + * applicable and a suggested correction + * + * @param contactId the contact identifier to validate + * @param result Must be supplied as an empty a list. Implementors add + * items: + * <ol> + * <li>is the error message if applicable + * <li>a suggested correction. Index 1 is optional and can only + * be present if there was a validation failure. + * </ol> + * @return true if the contact id is valid, false otherwise + */ + @Override + public boolean validateContactAddress(String contactId, List<String> result) + { + if (result == null) + { + throw new IllegalArgumentException("result must be an empty list"); + } + + result.clear(); + try + { + Address address = parseAddressString(contactId); + if (address.toString().equals(contactId)) + { + return true; + } + else if (((SipUri) address.getURI()).getUser().equals(contactId)) + { + return true; + } + else if (address.toString().equals(address.getURI().getScheme() + ":" + contactId)) + { + return true; + } + else + { + result.add(SipActivator.getResources().getI18NString( + "impl.protocol.sip.INVALID_ADDRESS", new String[] + { contactId })); + result.add(((SipUri) address.getURI()).getUser()); + } + } + catch (Exception ex) + { + logger.error("Validating SIP address failed for " + contactId, ex); + result.add(SipActivator.getResources() + .getI18NString("impl.protocol.sip.INVALID_ADDRESS", new String[] + { contactId })); + + String user = contactId; + String remainder = ""; + int at = contactId.indexOf('@'); + if (at > -1) + { + user = contactId.substring(0, at); + remainder = contactId.substring(at); + } + + // replace invalid characters in user part with hex encoding + String banned = "([^a-z0-9-_.!~*'()&=+$,;?/])+"; + result.add(user.replaceAll(banned, "") + remainder); + } + + return false; + } + + /** * Indicates whether or not this provider must registered * when placing outgoing calls. * @@ -610,50 +680,50 @@ public class ProtocolProviderServiceSipImpl addSupportedOperationSet( OperationSetPresence.class, opSetPersPresence); + } - // Only init messaging and typing if enabled. - boolean isMessagingDisabled - = SipActivator.getConfigurationService() - .getBoolean(IS_MESSAGING_DISABLED, false); + // Only init messaging and typing if enabled. + boolean isMessagingDisabled + = SipActivator.getConfigurationService() + .getBoolean(IS_MESSAGING_DISABLED, false); - if (!isMessagingDisabled) - { - // init instant messaging - this.opSetBasicIM = - new OperationSetBasicInstantMessagingSipImpl(this); + if (!isMessagingDisabled) + { + // init instant messaging + this.opSetBasicIM = + new OperationSetBasicInstantMessagingSipImpl(this); - addSupportedOperationSet( - OperationSetBasicInstantMessaging.class, - opSetBasicIM); + addSupportedOperationSet( + OperationSetBasicInstantMessaging.class, + opSetBasicIM); - // init typing notifications - this.opSetTypingNotif - = new OperationSetTypingNotificationsSipImpl( - this, opSetBasicIM); - addSupportedOperationSet( - OperationSetTypingNotifications.class, - opSetTypingNotif); + // init typing notifications + this.opSetTypingNotif + = new OperationSetTypingNotificationsSipImpl( + this, opSetBasicIM); + addSupportedOperationSet( + OperationSetTypingNotifications.class, + opSetTypingNotif); - addSupportedOperationSet( - OperationSetInstantMessageTransform.class, - new OperationSetInstantMessageTransformImpl()); - } + addSupportedOperationSet( + OperationSetInstantMessageTransform.class, + new OperationSetInstantMessageTransformImpl()); + } - this.opSetSSAccountInfo = - new OperationSetServerStoredAccountInfoSipImpl(this); + this.opSetSSAccountInfo = + new OperationSetServerStoredAccountInfoSipImpl(this); - // Set the display name. - opSetSSAccountInfo.setOurDisplayName(ourDisplayName); + // Set the display name. + opSetSSAccountInfo.setOurDisplayName(ourDisplayName); - // init avatar - addSupportedOperationSet( - OperationSetServerStoredAccountInfo.class, - opSetSSAccountInfo); + // init avatar + addSupportedOperationSet( + OperationSetServerStoredAccountInfo.class, + opSetSSAccountInfo); - addSupportedOperationSet( - OperationSetAvatar.class, - new OperationSetAvatarSipImpl(this, opSetSSAccountInfo)); - } + addSupportedOperationSet( + OperationSetAvatar.class, + new OperationSetAvatarSipImpl(this, opSetSSAccountInfo)); // MWI is enabled by default if(accountID.getAccountPropertyBoolean( @@ -1519,16 +1589,16 @@ public class ProtocolProviderServiceSipImpl Set<ProtocolProviderServiceSipImpl> instances = new HashSet<ProtocolProviderServiceSipImpl>(); BundleContext context = SipActivator.getBundleContext(); - ServiceReference[] references = context.getServiceReferences( - ProtocolProviderService.class.getName(), - null - ); - for(ServiceReference reference : references) + Collection<ServiceReference<ProtocolProviderService>> references = + context.getServiceReferences(ProtocolProviderService.class, + null); + for(ServiceReference<ProtocolProviderService> ref : references) { - Object service = context.getService(reference); + ProtocolProviderService service = context.getService(ref); if(service instanceof ProtocolProviderServiceSipImpl) instances.add((ProtocolProviderServiceSipImpl) service); } + return instances; } catch(InvalidSyntaxException ex) @@ -2390,21 +2460,38 @@ public class ProtocolProviderServiceSipImpl //we don't know how to handle the "tel:" and "callto:" schemes ... or // rather we handle them same as sip so replace: if(uriStr.toLowerCase().startsWith("tel:")) - uriStr = "sip:" + uriStr.substring("tel:".length()); + uriStr = uriStr.substring("tel:".length()); else if(uriStr.toLowerCase().startsWith("callto:")) - uriStr = "sip:" + uriStr.substring("callto:".length()); + uriStr = uriStr.substring("callto:".length()); + else if(uriStr.toLowerCase().startsWith("sips:")) + uriStr = uriStr.substring("sips:".length()); + else if(uriStr.toLowerCase().startsWith("sip:")) + uriStr = uriStr.substring("sip:".length()); + + String user = uriStr; + String remainder = ""; + int at = uriStr.indexOf('@'); + if (at > -1) + { + user = uriStr.substring(0, at); + remainder = uriStr.substring(at); + } + + //replace invalid characters in user part with hex encoding + String banned = "([^a-z0-9-_.!~*'()&=+$,;?/])+"; + user = user.replaceAll(banned, "") + remainder; //Handle default domain name (i.e. transform 1234 -> 1234@sip.com) //assuming that if no domain name is specified then it should be the //same as ours. - if (uriStr.indexOf('@') == -1) + if (at == -1) { //if we have a registrar, then we could append its domain name as //default SipRegistrarConnection src = sipRegistrarConnection; if(src != null && !src.isRegistrarless() ) { - uriStr = uriStr + "@" + uriStr = user + "@" + ((SipURI)src.getAddressOfRecord().getURI()).getHost(); } @@ -2684,6 +2771,11 @@ public class ProtocolProviderServiceSipImpl */ protected void notifyConnectionFailed() { + if (sipRegistrarConnection.isRegistrarless()) + { + return; + } + if(getRegistrationState().equals(RegistrationState.REGISTERED) && sipRegistrarConnection != null) sipRegistrarConnection.setRegistrationState( diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ProxyRouter.java b/src/net/java/sip/communicator/impl/protocol/sip/ProxyRouter.java index 5d60612..b6bf997 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ProxyRouter.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ProxyRouter.java @@ -19,6 +19,7 @@ package net.java.sip.communicator.impl.protocol.sip; import gov.nist.javax.sip.stack.*; +import java.net.*; import java.util.*; import javax.sip.*; @@ -26,6 +27,7 @@ import javax.sip.address.*; import javax.sip.header.*; import javax.sip.message.*; +import net.java.sip.communicator.impl.protocol.sip.net.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; @@ -137,7 +139,9 @@ public class ProxyRouter ProtocolProviderServiceSipImpl sipProvider = ((ProtocolProviderServiceSipImpl) service); - String proxy = sipProvider.getConnection().getOutboundProxyString(); + final ProxyConnection connection = sipProvider.getConnection(); + final String proxy = connection.getOutboundProxyString(); + logger.trace("Router for proxy: " + proxy); boolean forceLooseRouting = sipProvider.getAccountID() @@ -146,13 +150,30 @@ public class ProxyRouter // P2P case if (proxy == null || forceLooseRouting ) + { + logger.info("Returning default SIP router, P2P/loose routing"); return this.getDefaultRouter(); + } // outbound proxy case Router router = routerCache.get(proxy); if (router == null) { - router = new DefaultRouter(stack, proxy); + router = new DefaultRouter(stack, proxy) + { + @Override + public Hop getNextHop(Request request) throws SipException + { + logger.info("Outbound proxy mode, using proxy " + + proxy + " as hop instead of an address resolved" + + " by the SIP router"); + InetSocketAddress sa = connection.getAddress(); + return new HopImpl( + sa.getAddress().getHostAddress(), + sa.getPort(), + connection.getTransport()); + } + }; routerCache.put(proxy, router); } return router; @@ -164,6 +185,7 @@ public class ProxyRouter logger.error("unable to identify the service which created this " + "out-of-dialog request"); + logger.info("Returning default router"); return this.getDefaultRouter(); } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/SipRegistrarConnection.java b/src/net/java/sip/communicator/impl/protocol/sip/SipRegistrarConnection.java index 99cb0f8..2a3ed00 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/SipRegistrarConnection.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/SipRegistrarConnection.java @@ -1288,8 +1288,11 @@ public class SipRegistrarConnection if(registrarPort != ListeningPoint.PORT_5060) registrarURI.setPort(registrarPort); - if(!registrationTransport.equals(ListeningPoint.UDP)) + if(!registrationTransport.equals(ListeningPoint.UDP) + && !registrationTransport.equals(ListeningPoint.TLS)) + { registrarURI.setTransportParam(registrationTransport); + } } return registrarURI; } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/SipStackSharing.java b/src/net/java/sip/communicator/impl/protocol/sip/SipStackSharing.java index 2e714b5..c92782e 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/SipStackSharing.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/SipStackSharing.java @@ -1172,6 +1172,9 @@ public class SipStackSharing String transport) throws IOException { + logger.info("Gettting source address for " + + localAddress + " -> " + dst + ":" + dstPort + + "(" + transport + ")"); if(ListeningPoint.TLS.equalsIgnoreCase(transport)) return (java.net.InetSocketAddress)(((SipStackImpl)this.stack) .getLocalAddressForTlsDst(dst, dstPort, localAddress)); diff --git a/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java index 885455a..ecec5c5 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java @@ -351,7 +351,11 @@ public class UriHandlerSipImpl // Even if not registered after the timeout, try the call // anyway and the error popup will appear to ask the // user if they want to register - handleUri(uri, provider); + if(provider.getRegistrationState() + != RegistrationState.REGISTERED) + { + handleUri(uri, provider); + } } }, initialRegistrationTimeout); } @@ -401,9 +405,34 @@ public class UriHandlerSipImpl OperationSetBasicTelephony<?> telephonyOpSet = provider.getOperationSet(OperationSetBasicTelephony.class); + OperationSetVideoTelephony videoTelephonyOpSet + = provider.getOperationSet(OperationSetVideoTelephony.class); + + boolean videoCall = false; + if(videoTelephonyOpSet != null + && uri.contains("?")) + { + String params = uri.substring(uri.indexOf('?') + 1); + uri = uri.substring(0, uri.indexOf('?')); + + StringTokenizer paramTokens = new StringTokenizer(params, "&"); + while(paramTokens.hasMoreTokens()) + { + String tok = paramTokens.nextToken(); + String[] keyValue = tok.split("\\="); + if (keyValue.length == 2 + && keyValue[0].equalsIgnoreCase("video") + && keyValue[1].equalsIgnoreCase("true")) + videoCall = true; + } + } + try { - telephonyOpSet.createCall(uri); + if(videoCall) + videoTelephonyOpSet.createVideoCall(uri); + else + telephonyOpSet.createCall(uri); } catch (OperationFailedException exc) { diff --git a/src/net/java/sip/communicator/impl/protocol/sip/net/ManualProxyConnection.java b/src/net/java/sip/communicator/impl/protocol/sip/net/ManualProxyConnection.java index 5439d99..ca7faa9 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/net/ManualProxyConnection.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/net/ManualProxyConnection.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,115 +15,115 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.sip.net;
-
-import static javax.sip.ListeningPoint.PORT_5060;
-import static net.java.sip.communicator.service.protocol.ProtocolProviderFactory.PREFERRED_TRANSPORT;
-import static net.java.sip.communicator.service.protocol.ProtocolProviderFactory.PROXY_ADDRESS;
-import static net.java.sip.communicator.service.protocol.ProtocolProviderFactory.PROXY_PORT;
-
-import java.net.*;
-import java.text.*;
-
-import net.java.sip.communicator.impl.protocol.sip.*;
-import net.java.sip.communicator.service.dns.*;
-import net.java.sip.communicator.util.*;
-
-/**
- * Implementation of the manually configured SIP proxy connection. IP Address
- * lookups are performed using the account's proxy address.
- *
- * @author Ingo Bauersachs
- */
-public class ManualProxyConnection
- extends ProxyConnection
-{
- private final static Logger logger
- = Logger.getLogger(ManualProxyConnection.class);
-
- private String address;
- private int port;
-
- private InetSocketAddress[] lookups;
- private int lookupIndex;
-
- /**
- * Creates a new instance of this class. Uses the server from the account.
- *
- * @param account the account of this SIP protocol instance
- */
- public ManualProxyConnection(SipAccountIDImpl account)
- {
- super(account);
- reset();
- }
-
- /*
- * (non-Javadoc)
- *
- * @see net.java.sip.communicator.impl.protocol.sip.net.ProxyConnection#
- * getNextAddress()
- */
- @Override
- public boolean getNextAddressFromDns()
- throws DnssecException
- {
- if(lookups == null)
- {
- try
- {
- lookupIndex = 0;
- lookups = NetworkUtils.getAandAAAARecords(address, port);
-
- //no result found, reset state and indicate "out of addresses"
- if(lookups.length == 0)
- {
- lookups = null;
- return false;
- }
- }
- catch (ParseException e)
- {
- logger.error("Invalid address <" + address + ">", e);
- return false;
- }
- }
-
- //check if the available addresses are exhausted
- if(lookupIndex >= lookups.length)
- {
- if(logger.isDebugEnabled())
- logger.debug("No more addresses for " + account);
- lookups = null;
- return false;
- }
-
- //assign the next address and return lookup success
- if(logger.isDebugEnabled())
- logger.debug("Returning <" + socketAddress
- + "> as next address for " + account);
- socketAddress = lookups[lookupIndex];
- lookupIndex++;
- return true;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * net.java.sip.communicator.impl.protocol.sip.net.ProxyConnection#reset()
- */
- @Override
- public void reset()
- {
- super.reset();
- address = account.getAccountPropertyString(PROXY_ADDRESS);
- port = account.getAccountPropertyInt(PROXY_PORT, PORT_5060);
- transport = account.getAccountPropertyString(PREFERRED_TRANSPORT);
-
- //check property sanity
- if(!ProtocolProviderServiceSipImpl.isValidTransport(transport))
- throw new IllegalArgumentException(
- transport + " is not a valid SIP transport");
- }
-}
+package net.java.sip.communicator.impl.protocol.sip.net; + +import static javax.sip.ListeningPoint.PORT_5060; +import static net.java.sip.communicator.service.protocol.ProtocolProviderFactory.PREFERRED_TRANSPORT; +import static net.java.sip.communicator.service.protocol.ProtocolProviderFactory.PROXY_ADDRESS; +import static net.java.sip.communicator.service.protocol.ProtocolProviderFactory.PROXY_PORT; + +import java.net.*; +import java.text.*; + +import net.java.sip.communicator.impl.protocol.sip.*; +import net.java.sip.communicator.service.dns.*; +import net.java.sip.communicator.util.*; + +/** + * Implementation of the manually configured SIP proxy connection. IP Address + * lookups are performed using the account's proxy address. + * + * @author Ingo Bauersachs + */ +public class ManualProxyConnection + extends ProxyConnection +{ + private final static Logger logger + = Logger.getLogger(ManualProxyConnection.class); + + private String address; + private int port; + + private InetSocketAddress[] lookups; + private int lookupIndex; + + /** + * Creates a new instance of this class. Uses the server from the account. + * + * @param account the account of this SIP protocol instance + */ + public ManualProxyConnection(SipAccountIDImpl account) + { + super(account); + reset(); + } + + /* + * (non-Javadoc) + * + * @see net.java.sip.communicator.impl.protocol.sip.net.ProxyConnection# + * getNextAddress() + */ + @Override + public boolean getNextAddressFromDns() + throws DnssecException + { + if(lookups == null) + { + try + { + lookupIndex = 0; + lookups = NetworkUtils.getAandAAAARecords(address, port); + + //no result found, reset state and indicate "out of addresses" + if(lookups.length == 0) + { + lookups = null; + return false; + } + } + catch (ParseException e) + { + logger.error("Invalid address <" + address + ">", e); + return false; + } + } + + //check if the available addresses are exhausted + if(lookupIndex >= lookups.length) + { + if(logger.isDebugEnabled()) + logger.debug("No more addresses for " + account); + lookups = null; + return false; + } + + //assign the next address and return lookup success + if(logger.isDebugEnabled()) + logger.debug("Returning <" + socketAddress + + "> as next address for " + account); + socketAddress = lookups[lookupIndex]; + lookupIndex++; + return true; + } + + /* + * (non-Javadoc) + * + * @see + * net.java.sip.communicator.impl.protocol.sip.net.ProxyConnection#reset() + */ + @Override + public void reset() + { + super.reset(); + address = account.getAccountPropertyString(PROXY_ADDRESS); + port = account.getAccountPropertyInt(PROXY_PORT, PORT_5060); + transport = account.getAccountPropertyString(PREFERRED_TRANSPORT); + + //check property sanity + if(!ProtocolProviderServiceSipImpl.isValidTransport(transport)) + throw new IllegalArgumentException( + transport + " is not a valid SIP transport"); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/net/ProxyConnection.java b/src/net/java/sip/communicator/impl/protocol/sip/net/ProxyConnection.java index 99fcfb2..f7079f4 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/net/ProxyConnection.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/net/ProxyConnection.java @@ -1,180 +1,180 @@ -/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
- *
- * Copyright @ 2015 Atlassian Pty Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package net.java.sip.communicator.impl.protocol.sip.net;
-
-import static net.java.sip.communicator.service.protocol.ProtocolProviderFactory.PROXY_AUTO_CONFIG;
-
-import java.net.*;
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.sip.*;
-import net.java.sip.communicator.service.dns.*;
-
-/**
- * Abstract class for the determining the address for the SIP proxy.
- *
- * @author Ingo Bauersachs
- */
-public abstract class ProxyConnection
-{
- private List<String> returnedAddresses = new LinkedList<String>();
-
- protected String transport;
- protected InetSocketAddress socketAddress;
- protected final SipAccountIDImpl account;
-
- /**
- * Creates a new instance of this class.
- * @param account the account of this SIP protocol instance
- */
- protected ProxyConnection(SipAccountIDImpl account)
- {
- this.account = account;
- }
-
- /**
- * Gets the address to use for the next connection attempt.
- * @return the address of the last lookup.
- */
- public final InetSocketAddress getAddress()
- {
- return socketAddress;
- }
-
- /**
- * Gets the transport to use for the next connection attempt.
- * @return the transport of the last lookup.
- */
- public final String getTransport()
- {
- return transport;
- }
-
- /**
- * In case we are using an outbound proxy this method returns
- * a suitable string for use with Router.
- * The method returns <tt>null</tt> otherwise.
- *
- * @return the string of our outbound proxy if we are using one and
- * <tt>null</tt> otherwise.
- */
- public final String getOutboundProxyString()
- {
- if(socketAddress == null)
- return null;
-
- InetAddress proxyAddress = socketAddress.getAddress();
- StringBuilder proxyStringBuffer
- = new StringBuilder(proxyAddress.getHostAddress());
-
- if(proxyAddress instanceof Inet6Address)
- {
- proxyStringBuffer.insert(0, '[');
- proxyStringBuffer.append(']');
- }
-
- proxyStringBuffer.append(':');
- proxyStringBuffer.append(socketAddress.getPort());
- proxyStringBuffer.append('/');
- proxyStringBuffer.append(transport);
-
- return proxyStringBuffer.toString();
- }
-
- /**
- * Compares an InetAddress against the active outbound proxy. The comparison
- * is by reference, not equals.
- *
- * @param addressToTest The addres to test.
- * @return True when the InetAddress is the same as the outbound proxy.
- */
- public final boolean isSameInetAddress(InetAddress addressToTest)
- {
- // if the proxy is not yet initialized then this is not the provider
- // that caused this comparison
- if(socketAddress == null)
- return false;
- return addressToTest == socketAddress.getAddress();
- }
-
- /**
- * Retrieves the next address to use from DNS. Duplicate results are
- * suppressed.
- *
- * @return True if a new address is available through {@link #getAddress()},
- * false if the last address was reached. A new lookup from scratch
- * can be started by calling {@link #reset()}.
- * @throws DnssecException if there is a problem related to DNSSEC
- */
- public final boolean getNextAddress() throws DnssecException
- {
- boolean result;
- String key = null;
- do
- {
- result = getNextAddressFromDns();
- if(result && socketAddress != null)
- {
- key = getOutboundProxyString();
- if(!returnedAddresses.contains(key))
- {
- returnedAddresses.add(key);
- break;
- }
- }
- }
- while(result && returnedAddresses.contains(key));
- return result;
- }
-
- /**
- * Implementations must use this method to get the next address, but do not
- * have to care about duplicate addresses.
- *
- * @return True when a further address was available.
- * @throws DnssecException when a DNSSEC validation failure occured.
- */
- protected abstract boolean getNextAddressFromDns()
- throws DnssecException;
-
- /**
- * Resets the lookup to it's initial state. Overriders methods have to call
- * this method through a super-call.
- */
- public void reset()
- {
- returnedAddresses.clear();
- }
-
- /**
- * Factory method to create a proxy connection based on the account settings
- * of the protocol provider.
- *
- * @param pps the protocol provider that needs a SIP server connection.
- * @return An instance of a derived class.
- */
- public static ProxyConnection create(ProtocolProviderServiceSipImpl pps)
- {
- if (pps.getAccountID().getAccountPropertyBoolean(PROXY_AUTO_CONFIG,
- true))
- return new AutoProxyConnection((SipAccountIDImpl) pps.getAccountID(),
- pps.getDefaultTransport());
- else
- return new ManualProxyConnection((SipAccountIDImpl) pps.getAccountID());
- }
-}
+/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.java.sip.communicator.impl.protocol.sip.net; + +import static net.java.sip.communicator.service.protocol.ProtocolProviderFactory.PROXY_AUTO_CONFIG; + +import java.net.*; +import java.util.*; + +import net.java.sip.communicator.impl.protocol.sip.*; +import net.java.sip.communicator.service.dns.*; + +/** + * Abstract class for the determining the address for the SIP proxy. + * + * @author Ingo Bauersachs + */ +public abstract class ProxyConnection +{ + private List<String> returnedAddresses = new LinkedList<String>(); + + protected String transport; + protected InetSocketAddress socketAddress; + protected final SipAccountIDImpl account; + + /** + * Creates a new instance of this class. + * @param account the account of this SIP protocol instance + */ + protected ProxyConnection(SipAccountIDImpl account) + { + this.account = account; + } + + /** + * Gets the address to use for the next connection attempt. + * @return the address of the last lookup. + */ + public final InetSocketAddress getAddress() + { + return socketAddress; + } + + /** + * Gets the transport to use for the next connection attempt. + * @return the transport of the last lookup. + */ + public final String getTransport() + { + return transport; + } + + /** + * In case we are using an outbound proxy this method returns + * a suitable string for use with Router. + * The method returns <tt>null</tt> otherwise. + * + * @return the string of our outbound proxy if we are using one and + * <tt>null</tt> otherwise. + */ + public final String getOutboundProxyString() + { + if(socketAddress == null) + return null; + + InetAddress proxyAddress = socketAddress.getAddress(); + StringBuilder proxyStringBuffer + = new StringBuilder(proxyAddress.getHostAddress()); + + if(proxyAddress instanceof Inet6Address) + { + proxyStringBuffer.insert(0, '['); + proxyStringBuffer.append(']'); + } + + proxyStringBuffer.append(':'); + proxyStringBuffer.append(socketAddress.getPort()); + proxyStringBuffer.append('/'); + proxyStringBuffer.append(transport); + + return proxyStringBuffer.toString(); + } + + /** + * Compares an InetAddress against the active outbound proxy. The comparison + * is by reference, not equals. + * + * @param addressToTest The addres to test. + * @return True when the InetAddress is the same as the outbound proxy. + */ + public final boolean isSameInetAddress(InetAddress addressToTest) + { + // if the proxy is not yet initialized then this is not the provider + // that caused this comparison + if(socketAddress == null) + return false; + return addressToTest == socketAddress.getAddress(); + } + + /** + * Retrieves the next address to use from DNS. Duplicate results are + * suppressed. + * + * @return True if a new address is available through {@link #getAddress()}, + * false if the last address was reached. A new lookup from scratch + * can be started by calling {@link #reset()}. + * @throws DnssecException if there is a problem related to DNSSEC + */ + public final boolean getNextAddress() throws DnssecException + { + boolean result; + String key = null; + do + { + result = getNextAddressFromDns(); + if(result && socketAddress != null) + { + key = getOutboundProxyString(); + if(!returnedAddresses.contains(key)) + { + returnedAddresses.add(key); + break; + } + } + } + while(result && returnedAddresses.contains(key)); + return result; + } + + /** + * Implementations must use this method to get the next address, but do not + * have to care about duplicate addresses. + * + * @return True when a further address was available. + * @throws DnssecException when a DNSSEC validation failure occured. + */ + protected abstract boolean getNextAddressFromDns() + throws DnssecException; + + /** + * Resets the lookup to it's initial state. Overriders methods have to call + * this method through a super-call. + */ + public void reset() + { + returnedAddresses.clear(); + } + + /** + * Factory method to create a proxy connection based on the account settings + * of the protocol provider. + * + * @param pps the protocol provider that needs a SIP server connection. + * @return An instance of a derived class. + */ + public static ProxyConnection create(ProtocolProviderServiceSipImpl pps) + { + if (pps.getAccountID().getAccountPropertyBoolean(PROXY_AUTO_CONFIG, + true)) + return new AutoProxyConnection((SipAccountIDImpl) pps.getAccountID(), + pps.getDefaultTransport()); + else + return new ManualProxyConnection((SipAccountIDImpl) pps.getAccountID()); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/net/RFC5922Matcher.java b/src/net/java/sip/communicator/impl/protocol/sip/net/RFC5922Matcher.java index 0027ec5..262b717 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/net/RFC5922Matcher.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/net/RFC5922Matcher.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,211 +15,211 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.protocol.sip.net;
-
-import java.security.cert.*;
-import java.text.*;
-import java.util.*;
-import java.util.regex.*;
-
-import javax.sip.address.*;
-
-import net.java.sip.communicator.impl.protocol.sip.*;
-import net.java.sip.communicator.service.certificate.*;
-import net.java.sip.communicator.util.*;
-
-/**
- * Matcher that extracts certificate identities according to <a
- * href="http://tools.ietf.org/html/rfc5922#section-7.1">RFC5922, Section
- * 7.1</a> and compares them with the rules from Section 7.2 and 7.3.
- * @see #PNAME_STRICT_RFC5922 for wildcard handling; the default is false
- *
- * @author Ingo Bauersachs
- */
-public class RFC5922Matcher
- implements CertificateMatcher
-{
- /**
- * When set to true, enables strict validation of the hostname according to
- * <a href="http://tools.ietf.org/html/rfc5922#section-7.2">RFC5922 Section
- * 7.2</a>
- */
- public final static String PNAME_STRICT_RFC5922 =
- "net.java.sip.communicator.sip.tls.STRICT_RFC5922";
-
- private ProtocolProviderServiceSipImpl provider;
-
- /**
- * Creates a new instance of this class.
- * @param provider The SIP Provider to which this matcher belongs.
- */
- public RFC5922Matcher(ProtocolProviderServiceSipImpl provider)
- {
- this.provider = provider;
- }
-
- /** Our class logger. */
- private static final Logger logger = Logger
- .getLogger(CertificateMatcher.class);
-
- /*
- * (non-Javadoc)
- *
- * @see
- * net.java.sip.communicator.service.certificate.CertificateMatcher#verify
- * (java.lang.Iterable, java.security.cert.X509Certificate)
- */
- public void verify(Iterable<String> identitiesToTest, X509Certificate cert)
- throws CertificateException
- {
- boolean strict = SipActivator.getConfigurationService()
- .getBoolean(PNAME_STRICT_RFC5922, false);
-
- // if any of the identities is contained in the certificate we're good
- boolean oneMatched = false;
- Iterable<String> certIdentities = extractCertIdentities(cert);
- for (String identity : identitiesToTest)
- {
- // check if the intended hostname is contained in one of the
- // hostnames of the certificate according to
- // http://tools.ietf.org/html/rfc5922#section-7.2
- for(String dnsName : certIdentities)
- {
- try
- {
- if(NetworkUtils.compareDnsNames(dnsName, identity) == 0)
- {
- // one of the hostnames matched, we're good to go
- return;
- }
-
- if(!strict
- // is a wildcard name
- && dnsName.startsWith("*.")
- // contains at least two dots (*.example.com)
- && identity.indexOf(".") < identity.lastIndexOf(".")
- // compare *.example.com stripped to example.com with
- // - foo.example.com stripped to example.com
- // - foo.bar.example.com to bar.example.com
- && NetworkUtils.compareDnsNames(
- dnsName.substring(2),
- identity.substring(identity.indexOf(".")+1)) == 0)
- {
- // the wildcard matched, we're good to go
- return;
- }
- }
- catch (ParseException e)
- {} // we don't care - this hostname did not match
- }
- }
- if (!oneMatched)
- throw new CertificateException("None of <" + identitiesToTest
- + "> matched by the rules of RFC5922 to the cert with CN="
- + cert.getSubjectDN());
- }
-
- private Iterable<String> extractCertIdentities(X509Certificate cert)
- {
- List<String> certIdentities = new ArrayList<String>();
- Collection<List<?>> subjAltNames = null;
- try
- {
- subjAltNames = cert.getSubjectAlternativeNames();
- }
- catch (CertificateParsingException ex)
- {
- logger.error("Error parsing TLS certificate", ex);
- }
- // subjAltName types are defined in rfc2459
- final Integer dnsNameType = 2;
- final Integer uriNameType = 6;
- if (subjAltNames != null)
- {
- if (logger.isDebugEnabled())
- logger.debug("found subjAltNames: " + subjAltNames);
-
- // First look for a URI in the subjectAltName field
- for (List<?> altName : subjAltNames)
- {
- // 0th position is the alt name type
- // 1st position is the alt name data
- if (altName.get(0).equals(uriNameType))
- {
- SipURI altNameUri;
- try
- {
- altNameUri =
- provider.getAddressFactory().createSipURI(
- (String) altName.get(1));
- // only sip URIs are allowed
- if (!"sip".equals(altNameUri.getScheme()))
- continue;
- // user certificates are not allowed
- if (altNameUri.getUser() != null)
- continue;
- String altHostName = altNameUri.getHost();
- if (logger.isDebugEnabled())
- {
- logger.debug("found uri " + altName.get(1)
- + ", hostName " + altHostName);
- }
- certIdentities.add(altHostName);
- }
- catch (ParseException e)
- {
- logger.error("certificate contains invalid uri: "
- + altName.get(1));
- }
- }
-
- }
- // DNS An implementation MUST accept a domain name system
- // identifier as a SIP domain identity if and only if no other
- // identity is found that matches the "sip" URI type described
- // above.
- if (certIdentities.isEmpty())
- {
- for (List<?> altName : subjAltNames)
- {
- if (altName.get(0).equals(dnsNameType))
- {
- if (logger.isDebugEnabled())
- logger.debug("found dns " + altName.get(1));
- certIdentities.add(altName.get(1).toString());
- }
- }
- }
- }
- else
- {
- // If and only if the subjectAltName does not appear in the
- // certificate, the implementation MAY examine the CN field of the
- // certificate. If a valid DNS name is found there, the
- // implementation MAY accept this value as a SIP domain identity.
- String dname = cert.getSubjectDN().getName();
- String cname = "";
- try
- {
- Pattern EXTRACT_CN =
- Pattern.compile(".*CN\\s*=\\s*([\\w*\\.]+).*");
- Matcher matcher = EXTRACT_CN.matcher(dname);
- if (matcher.matches())
- {
- cname = matcher.group(1);
- if (logger.isDebugEnabled())
- {
- logger.debug("found CN: " + cname + " from DN: "
- + dname);
- }
- certIdentities.add(cname);
- }
- }
- catch (Exception ex)
- {
- logger.error("exception while extracting CN", ex);
- }
- }
- return certIdentities;
- }
-}
+package net.java.sip.communicator.impl.protocol.sip.net; + +import java.security.cert.*; +import java.text.*; +import java.util.*; +import java.util.regex.*; + +import javax.sip.address.*; + +import net.java.sip.communicator.impl.protocol.sip.*; +import net.java.sip.communicator.service.certificate.*; +import net.java.sip.communicator.util.*; + +/** + * Matcher that extracts certificate identities according to <a + * href="http://tools.ietf.org/html/rfc5922#section-7.1">RFC5922, Section + * 7.1</a> and compares them with the rules from Section 7.2 and 7.3. + * @see #PNAME_STRICT_RFC5922 for wildcard handling; the default is false + * + * @author Ingo Bauersachs + */ +public class RFC5922Matcher + implements CertificateMatcher +{ + /** + * When set to true, enables strict validation of the hostname according to + * <a href="http://tools.ietf.org/html/rfc5922#section-7.2">RFC5922 Section + * 7.2</a> + */ + public final static String PNAME_STRICT_RFC5922 = + "net.java.sip.communicator.sip.tls.STRICT_RFC5922"; + + private ProtocolProviderServiceSipImpl provider; + + /** + * Creates a new instance of this class. + * @param provider The SIP Provider to which this matcher belongs. + */ + public RFC5922Matcher(ProtocolProviderServiceSipImpl provider) + { + this.provider = provider; + } + + /** Our class logger. */ + private static final Logger logger = Logger + .getLogger(CertificateMatcher.class); + + /* + * (non-Javadoc) + * + * @see + * net.java.sip.communicator.service.certificate.CertificateMatcher#verify + * (java.lang.Iterable, java.security.cert.X509Certificate) + */ + public void verify(Iterable<String> identitiesToTest, X509Certificate cert) + throws CertificateException + { + boolean strict = SipActivator.getConfigurationService() + .getBoolean(PNAME_STRICT_RFC5922, false); + + // if any of the identities is contained in the certificate we're good + boolean oneMatched = false; + Iterable<String> certIdentities = extractCertIdentities(cert); + for (String identity : identitiesToTest) + { + // check if the intended hostname is contained in one of the + // hostnames of the certificate according to + // http://tools.ietf.org/html/rfc5922#section-7.2 + for(String dnsName : certIdentities) + { + try + { + if(NetworkUtils.compareDnsNames(dnsName, identity) == 0) + { + // one of the hostnames matched, we're good to go + return; + } + + if(!strict + // is a wildcard name + && dnsName.startsWith("*.") + // contains at least two dots (*.example.com) + && identity.indexOf(".") < identity.lastIndexOf(".") + // compare *.example.com stripped to example.com with + // - foo.example.com stripped to example.com + // - foo.bar.example.com to bar.example.com + && NetworkUtils.compareDnsNames( + dnsName.substring(2), + identity.substring(identity.indexOf(".")+1)) == 0) + { + // the wildcard matched, we're good to go + return; + } + } + catch (ParseException e) + {} // we don't care - this hostname did not match + } + } + if (!oneMatched) + throw new CertificateException("None of <" + identitiesToTest + + "> matched by the rules of RFC5922 to the cert with CN=" + + cert.getSubjectDN()); + } + + private Iterable<String> extractCertIdentities(X509Certificate cert) + { + List<String> certIdentities = new ArrayList<String>(); + Collection<List<?>> subjAltNames = null; + try + { + subjAltNames = cert.getSubjectAlternativeNames(); + } + catch (CertificateParsingException ex) + { + logger.error("Error parsing TLS certificate", ex); + } + // subjAltName types are defined in rfc2459 + final Integer dnsNameType = 2; + final Integer uriNameType = 6; + if (subjAltNames != null) + { + if (logger.isDebugEnabled()) + logger.debug("found subjAltNames: " + subjAltNames); + + // First look for a URI in the subjectAltName field + for (List<?> altName : subjAltNames) + { + // 0th position is the alt name type + // 1st position is the alt name data + if (altName.get(0).equals(uriNameType)) + { + SipURI altNameUri; + try + { + altNameUri = + provider.getAddressFactory().createSipURI( + (String) altName.get(1)); + // only sip URIs are allowed + if (!"sip".equals(altNameUri.getScheme())) + continue; + // user certificates are not allowed + if (altNameUri.getUser() != null) + continue; + String altHostName = altNameUri.getHost(); + if (logger.isDebugEnabled()) + { + logger.debug("found uri " + altName.get(1) + + ", hostName " + altHostName); + } + certIdentities.add(altHostName); + } + catch (ParseException e) + { + logger.error("certificate contains invalid uri: " + + altName.get(1)); + } + } + + } + // DNS An implementation MUST accept a domain name system + // identifier as a SIP domain identity if and only if no other + // identity is found that matches the "sip" URI type described + // above. + if (certIdentities.isEmpty()) + { + for (List<?> altName : subjAltNames) + { + if (altName.get(0).equals(dnsNameType)) + { + if (logger.isDebugEnabled()) + logger.debug("found dns " + altName.get(1)); + certIdentities.add(altName.get(1).toString()); + } + } + } + } + else + { + // If and only if the subjectAltName does not appear in the + // certificate, the implementation MAY examine the CN field of the + // certificate. If a valid DNS name is found there, the + // implementation MAY accept this value as a SIP domain identity. + String dname = cert.getSubjectDN().getName(); + String cname = ""; + try + { + Pattern EXTRACT_CN = + Pattern.compile(".*CN\\s*=\\s*([\\w*\\.]+).*"); + Matcher matcher = EXTRACT_CN.matcher(dname); + if (matcher.matches()) + { + cname = matcher.group(1); + if (logger.isDebugEnabled()) + { + logger.debug("found CN: " + cname + " from DN: " + + dname); + } + certIdentities.add(cname); + } + } + catch (Exception ex) + { + logger.error("exception while extracting CN", ex); + } + } + return certIdentities; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/net/SslNetworkLayer.java b/src/net/java/sip/communicator/impl/protocol/sip/net/SslNetworkLayer.java index f1c5856..9d088a2 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/net/SslNetworkLayer.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/net/SslNetworkLayer.java @@ -18,6 +18,7 @@ package net.java.sip.communicator.impl.protocol.sip.net; import gov.nist.core.net.*; +import gov.nist.javax.sip.*; import java.io.*; import java.net.*; @@ -421,4 +422,9 @@ public class SslNetworkLayer return 0; } + + @Override + public void setSipStack(SipStackImpl sipStack) + { + } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/sdp/SdpUtils.java b/src/net/java/sip/communicator/impl/protocol/sip/sdp/SdpUtils.java index c51a96c..e30b12d 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/sdp/SdpUtils.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/sdp/SdpUtils.java @@ -36,6 +36,7 @@ import org.jitsi.service.neomedia.*; import org.jitsi.service.neomedia.MediaType; import org.jitsi.service.neomedia.format.*; import org.jitsi.util.*; +import org.opentelecoms.javax.sdp.*; /** * The class contains a number of utility methods that are meant to facilitate @@ -64,7 +65,7 @@ public class SdpUtils /** * A reference to the currently valid SDP factory instance. */ - private static final SdpFactory sdpFactory = SdpFactory.getInstance(); + private static final SdpFactory sdpFactory = new NistSdpFactory(); /** * The name of the SDP attribute that defines zrtp hello hash. @@ -455,8 +456,21 @@ public class SdpUtils while (iter.hasNext()) { Map.Entry<String, String> ntry = iter.next(); - Attribute adv = sdpFactory.createAttribute(ntry.getKey(), - payloadType + " " + ntry.getValue()); + Attribute adv; + switch (ntry.getKey()) + { + // RFC7587, Sect. 7 says there's no payload number for ptime + case "ptime": + case "maxptime": + adv = sdpFactory.createAttribute(ntry.getKey(), + ntry.getValue()); + break; + default: + adv = sdpFactory.createAttribute(ntry.getKey(), + payloadType + " " + ntry.getValue()); + break; + } + mediaAttributes.add(adv); } @@ -567,7 +581,7 @@ public class SdpUtils public static SessionDescription createSessionDescription( InetAddress localAddress, String userName, - Vector<MediaDescription> mediaDescriptions) + List<MediaDescription> mediaDescriptions) throws OperationFailedException { SessionDescription sessDescr = null; @@ -613,8 +627,9 @@ public class SdpUtils sessDescr.setConnection(c); if ( mediaDescriptions != null) - sessDescr.setMediaDescriptions(mediaDescriptions); - + { + sessDescr.setMediaDescriptions( new Vector<>(mediaDescriptions)); + } return sessDescr; } catch (SdpException exc) @@ -655,7 +670,7 @@ public class SdpUtils public static SessionDescription createSessionUpdateDescription( SessionDescription descToUpdate, InetAddress newConnectionAddress, - Vector<MediaDescription> newMediaDescriptions) + List<MediaDescription> newMediaDescriptions) throws OperationFailedException { SessionDescription update = createSessionDescription( @@ -1711,7 +1726,7 @@ public class SdpUtils * <tt>descs</tt> <tt>Vector</tt>. */ private static MediaDescription removeMediaDesc( - Vector<MediaDescription> descs, + List<MediaDescription> descs, MediaType type) { for (Iterator<MediaDescription> i = descs.iterator(); i.hasNext();) diff --git a/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf index c15c025..2015a17 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf @@ -5,9 +5,22 @@ Bundle-Vendor: jitsi.org Bundle-Version: 0.0.1 Bundle-SymbolicName: net.java.sip.communicator.protocol.sip Import-Package: ch.imvs.sdes4j.srtp, + gov.nist.core, + gov.nist.core.net, + gov.nist.javax.sip, + gov.nist.javax.sip.address, + gov.nist.javax.sip.header, + gov.nist.javax.sip.header.extensions, + gov.nist.javax.sip.message, + gov.nist.javax.sip.stack, javax.net, javax.net.ssl, javax.security.auth.x500, + javax.sdp, + javax.sip, + javax.sip.address, + javax.sip.header, + javax.sip.message, javax.xml.datatype, javax.xml.namespace, javax.xml.parsers, @@ -76,6 +89,7 @@ Import-Package: ch.imvs.sdes4j.srtp, org.jitsi.util.xml, org.json.simple, org.osgi.framework, + org.opentelecoms.javax.sdp, org.w3c.dom, org.xml.sax Export-Package: net.java.sip.communicator.impl.protocol.sip, @@ -88,6 +102,4 @@ Export-Package: net.java.sip.communicator.impl.protocol.sip, net.java.sip.communicator.impl.protocol.sip.xcap.model.resourcelists, net.java.sip.communicator.impl.protocol.sip.xcap.model.xcapcaps, net.java.sip.communicator.impl.protocol.sip.xcap.model.xcaperror, - net.java.sip.communicator.impl.protocol.sip.xcap.utils, - javax.sdp, - gov.nist.javax.sdp.fields + net.java.sip.communicator.impl.protocol.sip.xcap.utils diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ContactGroupSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ContactGroupSSHImpl.java deleted file mode 100644 index 7de2e16..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/ContactGroupSSHImpl.java +++ /dev/null @@ -1,580 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * A simple, straightforward implementation of a ssh ContactGroup. Since - * the SSH protocol is not a real one, we simply store all group details - * in class fields. You should know that when implementing a real protocol, - * the contact group implementation would rather encapsulate group objects from - * the protocol stack and group property values should be returned by - * consulting the encapsulated object. - * - * @author Shobhit Jindal - */ -public class ContactGroupSSHImpl - implements ContactGroup -{ - - /** - * The name of this SSH contact group. - */ - private String groupName = null; - - /** - * The list of this group's members. - */ - private Vector<Contact> contacts = new Vector<Contact>(); - - /** - * The list of sub groups belonging to this group. - */ - private Vector<ContactGroup> subGroups = new Vector<ContactGroup>(); - - /** - * The group that this group belongs to (or null if this is the root group). - */ - private ContactGroupSSHImpl parentGroup = null; - - /** - * Determines whether this group is really in the contact list or whether - * it is here only temporarily and will be gone next time we restart. - */ - private boolean isPersistent = true; - - /** - * The protocol provider that created us. - */ - private ProtocolProviderServiceSSHImpl parentProvider = null; - - /** - * Determines whether this group has been resolved on the server. - * Unresolved groups are groups that were available on previous runs and - * that the meta contact list has stored. During all next runs, when - * bootstrapping, the meta contact list would create these groups as - * unresolved. Once a protocol provider implementation confirms that the - * groups are still on the server, it would issue an event indicating that - * the groups are now resolved. - */ - private boolean isResolved = true; - - /** - * An id uniquely identifying the group. For many protocols this could be - * the group name itself. - */ - private String uid = null; - private static final String UID_SUFFIX = ".uid"; - - /** - * Creates a ContactGroupSSHImpl with the specified name. - * - * @param groupName the name of the group. - * @param parentProvider the protocol provider that created this group. - */ - public ContactGroupSSHImpl( - String groupName, - ProtocolProviderServiceSSHImpl parentProvider) - { - this.groupName = groupName; - this.uid = groupName + UID_SUFFIX; - this.parentProvider = parentProvider; - } - - /** - * Determines whether the group may contain subgroups or not. - * - * @return always true in this implementation. - */ - public boolean canContainSubgroups() - { - return true; - } - - /** - * Returns the protocol provider that this group belongs to. - * @return a regerence to the ProtocolProviderService instance that this - * ContactGroup belongs to. - */ - public ProtocolProviderService getProtocolProvider() - { - return parentProvider; - } - - /** - * Returns an Iterator over all contacts, member of this - * <tt>ContactGroup</tt>. - * - * @return a java.util.Iterator over all contacts inside this - * <tt>ContactGroup</tt> - */ - public Iterator<Contact> contacts() - { - return contacts.iterator(); - } - - /** - * Adds the specified contact to this group. - * @param contactToAdd the ContactSSHImpl to add to this group. - */ - public void addContact(ContactSSH contactToAdd) - { - this.contacts.add(contactToAdd); - contactToAdd.setParentGroup(this); - } - - /** - * Returns the number of <tt>Contact</tt> members of this - * <tt>ContactGroup</tt> - * - * @return an int indicating the number of <tt>Contact</tt>s, members of - * this <tt>ContactGroup</tt>. - */ - public int countContacts() - { - return contacts.size(); - } - - /** - * Returns the number of subgroups contained by this - * <tt>ContactGroup</tt>. - * - * @return the number of subGroups currently added to this group. - */ - public int countSubgroups() - { - return subGroups.size(); - } - - /** - * Adds the specified contact group to the contained by this group. - * @param subgroup the ContactGroupSSHImpl to add as a subgroup to this - * group. - */ - public void addSubgroup(ContactGroupSSHImpl subgroup) - { - this.subGroups.add(subgroup); - subgroup.setParentGroup(this); - } - - /** - * Sets the group that is the new parent of this group - * @param parent ContactGroupSSHImpl - */ - void setParentGroup(ContactGroupSSHImpl parent) - { - this.parentGroup = parent; - } - - /** - * Returns the contact group that currently contains this group or null if - * this is the root contact group. - * @return the contact group that currently contains this group or null if - * this is the root contact group. - */ - public ContactGroup getParentContactGroup() - { - return this.parentGroup; - } - - /** - * Removes the specified contact group from the this group's subgroups. - * @param subgroup the ContactGroupSSHImpl subgroup to remove. - */ - public void removeSubGroup(ContactGroupSSHImpl subgroup) - { - this.subGroups.remove(subgroup); - subgroup.setParentGroup(null); - } - - /** - * Returns the group that is parent of the specified sshGroup or null - * if no parent was found. - * @param sshGroup the group whose parent we're looking for. - * @return the ContactGroupSSHImpl instance that sshGroup - * belongs to or null if no parent was found. - */ - public ContactGroupSSHImpl findGroupParent( - ContactGroupSSHImpl sshGroup) - { - if ( subGroups.contains(sshGroup) ) - return this; - - Iterator<ContactGroup> subGroupsIter = subgroups(); - while (subGroupsIter.hasNext()) - { - ContactGroupSSHImpl subgroup - = (ContactGroupSSHImpl) subGroupsIter.next(); - - ContactGroupSSHImpl parent - = subgroup.findGroupParent(sshGroup); - - if(parent != null) - return parent; - } - return null; - } - - /** - * Returns the group that is parent of the specified sshContact or - * null if no parent was found. - * - * @param sshContact the contact whose parent we're looking for. - * @return the ContactGroupSSHImpl instance that sshContact - * belongs to or <tt>null</tt> if no parent was found. - */ - public ContactGroupSSHImpl findContactParent( - ContactSSHImpl sshContact) - { - if ( contacts.contains(sshContact) ) - return this; - - Iterator<ContactGroup> subGroupsIter = subgroups(); - while (subGroupsIter.hasNext()) - { - ContactGroupSSHImpl subgroup - = (ContactGroupSSHImpl) subGroupsIter.next(); - - ContactGroupSSHImpl parent - = subgroup.findContactParent(sshContact); - - if(parent != null) - return parent; - } - return null; - } - - - - /** - * Returns the <tt>Contact</tt> with the specified address or identifier. - * - * @param id the addres or identifier of the <tt>Contact</tt> we are - * looking for. - * @return the <tt>Contact</tt> with the specified id or address. - */ - public Contact getContact(String id) - { - Iterator<Contact> contactsIter = contacts(); - while (contactsIter.hasNext()) - { - ContactSSHImpl contact = (ContactSSHImpl) contactsIter.next(); - if (contact.getAddress().equals(id)) - return contact; - - } - return null; - } - - /** - * Returns the subgroup with the specified index. - * - * @param index the index of the <tt>ContactGroup</tt> to retrieve. - * @return the <tt>ContactGroup</tt> with the specified index. - */ - public ContactGroup getGroup(int index) - { - return subGroups.get(index); - } - - /** - * Returns the subgroup with the specified name. - * - * @param groupName the name of the <tt>ContactGroup</tt> to retrieve. - * @return the <tt>ContactGroup</tt> with the specified index. - */ - public ContactGroup getGroup(String groupName) - { - Iterator<ContactGroup> groupsIter = subgroups(); - while (groupsIter.hasNext()) - { - ContactGroupSSHImpl contactGroup - = (ContactGroupSSHImpl) groupsIter.next(); - if (contactGroup.getGroupName().equals(groupName)) - return contactGroup; - - } - return null; - - } - - /** - * Returns the name of this group. - * - * @return a String containing the name of this group. - */ - public String getGroupName() - { - return this.groupName; - } - - /** - * Sets this group a new name. - * @param newGrpName a String containing the new name of this group. - */ - public void setGroupName(String newGrpName) - { - this.groupName = newGrpName; - } - - /** - * Returns an iterator over the sub groups that this - * <tt>ContactGroup</tt> contains. - * - * @return a java.util.Iterator over the <tt>ContactGroup</tt> children - * of this group (i.e. subgroups). - */ - public Iterator<ContactGroup> subgroups() - { - return subGroups.iterator(); - } - - /** - * Removes the specified contact from this group. - * @param contact the ContactSSHImpl to remove from this group - */ - public void removeContact(ContactSSHImpl contact) - { - this.contacts.remove(contact); - } - - /** - * Returns the contact with the specified id or null if no such contact - * exists. - * @param id the id of the contact we're looking for. - * @return ContactSSHImpl - */ - public ContactSSHImpl findContactByID(String id) - { - //first go through the contacts that are direct children. - Iterator<Contact> contactsIter = contacts(); - - while(contactsIter.hasNext()) - { - ContactSSHImpl mContact = (ContactSSHImpl)contactsIter.next(); - - if( mContact.getAddress().equals(id) ) - return mContact; - } - - //if we didn't find it here, let's try in the subougroups - Iterator<ContactGroup> groupsIter = subgroups(); - - while( groupsIter.hasNext() ) - { - ContactGroupSSHImpl mGroup = (ContactGroupSSHImpl)groupsIter.next(); - - ContactSSHImpl mContact = mGroup.findContactByID(id); - - if (mContact != null) - return mContact; - } - - return null; - } - - /** - * Returns a String representation of this group and the contacts it - * contains (may turn out to be a relatively long string). - * @return a String representing this group and its child contacts. - */ - @Override - public String toString() - { - StringBuffer buff = new StringBuffer(getGroupName()); - buff.append(".subGroups=" + countSubgroups() + ":\n"); - - Iterator<ContactGroup> subGroups = subgroups(); - while (subGroups.hasNext()) - { - ContactGroup group = subGroups.next(); - buff.append(group.toString()); - if (subGroups.hasNext()) - buff.append("\n"); - } - - buff.append("\nChildContacts="+countContacts()+":["); - - Iterator<Contact> contacts = contacts(); - while (contacts.hasNext()) - { - Contact contact = contacts.next(); - buff.append(contact.toString()); - if(contacts.hasNext()) - buff.append(", "); - } - return buff.append("]").toString(); - } - - /** - * Specifies whether or not this contact group is being stored by the - * server. - * Non persistent contact groups are common in the case of simple, - * non-persistent presence operation sets. They could however also be seen - * in persistent presence operation sets when for example we have received - * an event from someone not on our contact list and the contact that we - * associated with that user is placed in a non persistent group. Non - * persistent contact groups are volatile even when coming from a - * persistent presence op. set. They would only exist until the - * application is closed and will not be there next time it is loaded. - * - * @param isPersistent true if the contact group is to be persistent and - * false otherwise. - */ - public void setPersistent(boolean isPersistent) - { - this.isPersistent = isPersistent; - } - - /** - * Determines whether or not this contact group is being stored by the - * server. Non persistent contact groups exist for the sole purpose of - * containing non persistent contacts. - * @return true if the contact group is persistent and false otherwise. - */ - public boolean isPersistent() - { - return isPersistent; - } - - /** - * Returns null as no persistent data is required and the contact address is - * sufficient for restoring the contact. - * <p> - * @return null as no such data is needed. - */ - public String getPersistentData() - { - return null; - } - - /** - * Determines whether or not this contact has been resolved against the - * server. Unresolved contacts are used when initially loading a contact - * list that has been stored in a local file until the presence operation - * set has managed to retrieve all the contact list from the server and has - * properly mapped contacts to their on-line buddies. - * @return true if the contact has been resolved (mapped against a buddy) - * and false otherwise. - */ - public boolean isResolved() - { - return isResolved; - } - - /** - * Makes the group resolved or unresolved. - * - * @param resolved true to make the group resolved; false to - * make it unresolved - */ - public void setResolved(boolean resolved) - { - this.isResolved = resolved; - } - - /** - * Returns a <tt>String</tt> that uniquely represnets the group inside - * the current protocol. The string MUST be persistent (it must not change - * across connections or runs of the application). In many cases (Jabber, - * ICQ) the string may match the name of the group as these protocols - * only allow a single level of contact groups and there is no danger of - * having the same name twice in the same contact list. Other protocols - * (no examples come to mind but that doesn't bother me ;) ) may be - * supporting mutilple levels of grooups so it might be possible for group - * A and group B to both contain groups named C. In such cases the - * implementation must find a way to return a unique identifier in this - * method and this UID should never change for a given group. - * - * @return a String representing this group in a unique and persistent - * way. - */ - public String getUID() - { - return uid; - } - - /** - * Ugly but tricky conversion method. - * @param uid the uid we'd like to get a name from - * @return the name of the group with the specified <tt>uid</tt>. - */ - static String createNameFromUID(String uid) - { - return uid.substring(0, uid.length() - (UID_SUFFIX.length())); - } - - /** - * Indicates whether some other object is "equal to" this one which in terms - * of contact groups translates to having the equal names and matching - * subgroups and child contacts. The resolved status of contactgroups and - * contacts is deliberately ignored so that groups and/or contacts would - * be assumed equal even if it differs. - * <p> - * @param obj the reference object with which to compare. - * @return <code>true</code> if this contact group has the equal child - * contacts and subgroups to those of the <code>obj</code> argument. - */ - @Override - public boolean equals(Object obj) - { - if(obj == null - || !(obj instanceof ContactGroupSSHImpl)) - return false; - - ContactGroupSSHImpl sshGroup - = (ContactGroupSSHImpl)obj; - - if( ! sshGroup.getGroupName().equals(getGroupName()) - || ! sshGroup.getUID().equals(getUID()) - || sshGroup.countContacts() != countContacts() - || sshGroup.countSubgroups() != countSubgroups()) - return false; - - //traverse child contacts - Iterator<Contact> theirContacts = sshGroup.contacts(); - - while(theirContacts.hasNext()) - { - Contact theirContact = theirContacts.next(); - Contact ourContact = getContact(theirContact.getAddress()); - - if(ourContact == null - || !ourContact.equals(theirContact)) - return false; - } - - //traverse subgroups - Iterator<ContactGroup> theirSubgroups = sshGroup.subgroups(); - - while(theirSubgroups.hasNext()) - { - ContactGroup theirSubgroup = theirSubgroups.next(); - ContactGroup ourSubgroup = getGroup(theirSubgroup.getGroupName()); - - if(ourSubgroup == null - || !ourSubgroup.equals(theirSubgroup)) - return false; - } - - return true; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSH.java b/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSH.java deleted file mode 100644 index a13414b..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSH.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; - -import net.java.sip.communicator.service.protocol.*; - -import com.jcraft.jsch.*; - -/** - * This interface represents a Contact of SSH Type - * As a SSH Session is specific to a contact, additional information needed - * to maintain its state with the remote server is present here - * - * @author Shobhit Jindal - */ -interface ContactSSH - extends Contact -{ - /** - * An event type indicating that the message being received is a standard - * conversation message sent by another contact. - */ - public static final int CONVERSATION_MESSAGE_RECEIVED = 1; - - /** - * An event type indicting that the message being received is a system - * message being sent by the server or a system administrator. - */ - public static final int SYSTEM_MESSAGE_RECEIVED = 2; - - //Following eight function declations to be moved to Contact - - /** - * This method is only called when the contact is added to a new - * <tt>ContactGroupSSHImpl</tt> by the - * <tt>ContactGroupSSHImpl</tt> itself. - * - * @param newParentGroup the <tt>ContactGroupSSHImpl</tt> that is now - * parent of this <tt>ContactSSHImpl</tt> - */ - void setParentGroup (ContactGroupSSHImpl newParentGroup); - - /** - * Sets <tt>sshPresenceStatus</tt> as the PresenceStatus that this - * contact is currently in. - * @param sshPresenceStatus the <tt>SSHPresenceStatus</tt> - * currently valid for this contact. - */ - public void setPresenceStatus (PresenceStatus sshPresenceStatus); - - /** - * Returns the persistent presence operation set that this contact belongs - * to. - * - * @return the <tt>OperationSetPersistentPresenceSSHImpl</tt> that - * this contact belongs to. - */ - public OperationSetPersistentPresence - getParentPresenceOperationSet (); - - /** - * Returns the BasicInstant Messaging operation set that this contact - * belongs to. - * - * @return the <tt>OperationSetBasicInstantMessagingSSHImpl</tt> that - * this contact belongs to. - */ - public OperationSetBasicInstantMessaging - getParentBasicInstantMessagingOperationSet (); - - /** - * Returns the File Transfer operation set that this contact belongs - * to. - * - * @return the <tt>OperationSetFileTransferSSHImpl</tt> that - * this contact belongs to. - */ - public OperationSetFileTransfer - getFileTransferOperationSet (); - - /** - * Return the type of message received from remote server - * - * @return messageType - */ - public int getMessageType (); - - /** - * Sets the type of message received from remote server - * - * @param messageType - */ - public void setMessageType (int messageType); - - /** - * Stores persistent data of the contact. - * - * @param persistentData of the contact - */ - public void setPersistentData (String persistentData); - - /** - * Makes the contact resolved or unresolved. - * - * @param resolved true to make the contact resolved; false to - * make it unresolved - */ - public void setResolved (boolean resolved); - - /** - * Specifies whether or not this contact is being stored by the server. - * Non persistent contacts are common in the case of simple, non-persistent - * presence operation sets. They could however also be seen in persistent - * presence operation sets when for example we have received an event - * from someone not on our contact list. Non persistent contacts are - * volatile even when coming from a persistent presence op. set. They would - * only exist until the application is closed and will not be there next - * time it is loaded. - * - * @param isPersistent true if the contact is persistent and false - * otherwise. - */ - public void setPersistent (boolean isPersistent); - - /** - * Returns true if a command has been sent whos reply was not received yet - * false otherwise - * - * @return commandSent - */ - public boolean isCommandSent (); - - /** - * Set the state of commandSent variable which determines whether a reply - * to a command sent is awaited - * - * @param commandSent - */ - public void setCommandSent (boolean commandSent); - - /** - * Initializes the reader and writers associated with shell of this contact - * - * @param shellInputStream The InputStream of stack - * @param shellOutputStream The OutputStream of stack - */ - void initializeShellIO (InputStream shellInputStream, - OutputStream shellOutputStream); - - /** - * Closes the readers and writer associated with shell of this contact - */ - void closeShellIO (); - - /** - * Determines whether a connection to a remote server is already underway - * - * @return connectionInProgress - */ - public boolean isConnectionInProgress (); - - /** - * Sets the status of connection attempt to remote server - * - * @param connectionInProgress - */ - public void setConnectionInProgress (boolean connectionInProgress); - -// /** -// * Sets the PS1 prompt of the current shell of Contact -// * This method is synchronized -// * -// * @param sshPrompt to be associated -// */ -// public void setShellPrompt(String sshPrompt); -// -// /** -// * Returns the PS1 prompt of the current shell of Contact -// * -// * @return sshPrompt -// */ -// public String getShellPrompt(); - - - /** - * Saves the details of contact in persistentData - */ - public void savePersistentDetails (); - - /* - * Returns the SSHContactInfo associated with this contact - * - * @return sshConfigurationForm - */ - public SSHContactInfo getSSHConfigurationForm (); - - /** - * Returns the JSch Stack identified associated with this contact - * - * @return jsch - */ - JSch getJSch (); - - /** - * Starts the timer and its task to periodically update the status of - * remote machine - */ - void startTimerTask (); - - /** - * Stops the timer and its task to stop updating the status of - * remote machine - */ - void stopTimerTask (); - - /** - * Sets the JSch Stack identified associated with this contact - * - * @param jsch to be associated - */ - void setJSch (JSch jsch); - - /** - * Returns the Username associated with this contact - * - * @return userName - */ - String getUserName (); - - /** - * Returns the Hostname associated with this contact - * - * @return hostName - */ - String getHostName (); - - /** - * Returns the Password associated with this contact - * - * @return password - */ - String getPassword (); - - /** - * Sets the Password associated with this contact - * - * @param password - */ - void setPassword (String password); - - /** - * Returns the SSH Session associated with this contact - * - * @return sshSession - */ - Session getSSHSession (); - - /** - * Sets the SSH Session associated with this contact - * - * @param sshSession the newly created SSH Session to be associated - */ - void setSSHSession (Session sshSession); - - /** - * Returns the SSH Shell Channel associated with this contact - * - * @return shellChannel - */ - Channel getShellChannel (); - - /** - * Sets the SSH Shell channel associated with this contact - * - * @param shellChannel to be associated with SSH Session of this contact - */ - void setShellChannel (Channel shellChannel); - - /** - * Sends a message a line to remote machine via the Shell Writer - * - * @param message to be sent - * @throws IOException if message failed to be sent - */ - public void sendLine (String message) - throws IOException; - -// /** -// * Reads a line from the remote machine via the Shell Reader -// * -// * @return message read -// */ -// public String getLine() -// throws IOException; - - /** - * Returns the Input Stream associated with SSH Channel of this contact - * - * @return shellInputStream associated with SSH Channel of this contact - */ - public InputStream getShellInputStream (); - -// /** -// * Sets the Input Stream associated with SSH Channel of this contact -// * -// * @param shellInputStream to be associated with SSH Channel of this -// * contact -// */ -// public void setShellInputStream(InputStream shellInputStream); - - /** - * Returns the Output Stream associated with SSH Channel of this contact - * - * @return shellOutputStream associated with SSH Channel of this contact - */ - public OutputStream getShellOutputStream (); - -// /** -// * Sets the Output Stream associated with SSH Channel of this contact -// * -// * @param shellOutputStream to be associated with SSH Channel of this -// * contact -// */ -// public void setShellOutputStream(OutputStream shellOutputStream); -// - /** - * Returns the BufferedReader associated with SSH Channel of this contact - * - * @return shellReader associated with SSH Channel of this contact - */ - public InputStreamReader getShellReader (); -// -// /** -// * Sets the BufferedReader associated with SSH Channel of this contact -// * -// * @param shellReader to be associated with SSH Channel of this contact -// */ -// public void setShellReader(BufferedReader shellReader); - - /** - * Returns the PrintWriter associated with SSH Channel of this contact - * - * @return shellWriter associated with SSH Channel of this contact - */ - public PrintWriter getShellWriter (); - -// /** -// * Sets the PrintWriter associated with SSH Channel of this contact -// * -// * @param shellWriter to be associated with SSH Channel of this contact -// */ -// public void setShellWriter(PrintWriter shellWriter); -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSHImpl.java deleted file mode 100644 index 15da209..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSHImpl.java +++ /dev/null @@ -1,918 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.Base64; // disambiguation -import net.java.sip.communicator.util.Logger; - -import com.jcraft.jsch.*; -// disambiguation - -/** - * A Contact of SSH Type - * - * @author Shobhit Jindal - */ -public class ContactSSHImpl - extends AbstractContact - implements ContactSSH -{ - private static final Logger logger - = Logger.getLogger(ContactSSHImpl.class); - - /** - * This acts as a separator between details stored in persistent data - */ - private final String separator = - Resources.getString("impl.protocol.ssh.DETAILS_SEPARATOR"); - - /** - * The identifier for SSH Stack - * Java Secure Channel JSch - */ - private JSch jsch; - - /** - * Interface for user to provide details about machine - */ - private SSHContactInfo sshConfigurationForm; - - /** - * A Timer Daemon to update the status of this contact - */ - private Timer timer = new Timer(true); - - /** - * A Daemon to retrieve and fire messages received from remote machine - */ - private SSHReaderDaemon contactSSHReaderDaemon; - - /** - * The id of the contact. - */ - private String contactID = null; - - /** - * The persistentData of the contact. - */ - private String persistentData = null; - -// /** -// * This stores the prompt string of shell -// */ -// private String sshPrompt; - - /** - * The provider that created us. - */ - private ProtocolProviderServiceSSHImpl parentProvider = null; - - /** - * The identifier of the type of message received from server - */ - private int messageType; - - - - /** - * The identifier for SSH Session with the remote server - */ - private Session sshSession = null; - - /** - * The identifier for a sshShellChannel with the remote server is of type - * shell - for an interactive SSH Session with the remote machine - * - * Other types - * sftp - to tranfer files from/to the remote machine - * exec - X forwarding - * direct-tcpip - stream forwarding - */ - private Channel sshShellChannel = null; - - /** - * The identifier for the Shell Input Stream associated with SSH Sesion - */ - private InputStream shellInputStream = null; - - /** - * The identifier for the Shell Output Stream associated with SSH Sesion - */ - private OutputStream shellOutputStream = null; - - /** - * Higher wrapper for shellInputStream - */ - private InputStreamReader shellReader = null; - - /** - * Higher wrapper for shellOutputStream - */ - private PrintWriter shellWriter = null; - - /** - * The group that belong to. - */ - private ContactGroupSSHImpl parentGroup = null; - - /** - * The presence status of the contact. - */ - private PresenceStatus presenceStatus = SSHStatusEnum.NOT_AVAILABLE; - - /** - * Determines whether this contact is persistent, i.e. member of the contact - * list or whether it is here only temporarily. - */ - private boolean isPersistent = false; - - /** - * Determines whether the contact has been resolved (i.e. we have a - * confirmation that it is still on the server contact list). - */ - private boolean isResolved = true; - - /** - * Determines whether an connection attempt to remote server is already - * underway - */ - private boolean isConnectionInProgress = false; - - /** - * Determines whether the message received from remote machine is as a - * result of command sent to it - */ - private boolean commandSent = false; - - /** - * A lock to synchronize the access of commandSent boolean object - * with the reader thread. - */ - private final Object lock = new Object(); - - /** - * Creates an instance of a meta contact with the specified string used - * as a name and identifier. - * - * @param id the identifier of this contact (also used as a name). - * @param parentProvider the provider that created us. - */ - public ContactSSHImpl( - String id, - ProtocolProviderServiceSSHImpl parentProvider) - { - this.contactID = id; - this.parentProvider = parentProvider; - - this.sshConfigurationForm = - new SSHContactInfo(this); - - this.savePersistentDetails(); - } - - /** - * Initializes the reader and writers associated with shell of this contact - * - * @param shellInputStream The InputStream of stack - * @param shellOutputStream The OutputStream of stack - */ - public void initializeShellIO( - InputStream shellInputStream, - OutputStream shellOutputStream) - { - this.shellInputStream = shellInputStream; - this.shellOutputStream = shellOutputStream; - shellReader = new InputStreamReader(shellInputStream); - shellWriter = new PrintWriter(shellOutputStream); - - contactSSHReaderDaemon = new SSHReaderDaemon(this); - contactSSHReaderDaemon.setDaemon(true); - contactSSHReaderDaemon.isActive(true); - contactSSHReaderDaemon.start(); - } - - /** - * Closes the readers and writer associated with shell of this contact - */ - public void closeShellIO() - { - try - { - shellReader.close(); - shellInputStream.close(); - } - catch(IOException ex) - {} - - try - { - shellWriter.close(); - shellOutputStream.close(); - } - catch(IOException ex) - {} - - shellInputStream = null; - - shellReader = null; - - shellOutputStream = null; - - shellWriter = null; - - try - { - sshShellChannel.disconnect(); - } - catch(Exception e) - {} - - // Removing the reference to current channel - // a new shell channel will be created for the next message - sshShellChannel = null; - - // remove the reference of session if it were also disconnected - // like in the case of exit command - if(!sshSession.isConnected()) - { - sshSession = null; - jsch = null; - } - - ((OperationSetPersistentPresenceSSHImpl) - getParentPresenceOperationSet()). - changeContactPresenceStatus(this, SSHStatusEnum.ONLINE); - } - - /** - * Sends a message a line to remote machine via the Shell Writer - * - * @param message to be sent - */ - public void sendLine(String message) - throws IOException - { -// logger.debug("SSH TO: " + this.contactID + ": " + message); - shellWriter.println(message); - shellWriter.flush(); - } - - /** - * Reads a line from the remote machine via the Shell Reader - * - * @return message read - */ -// public String getLine() -// throws IOException -// { -// String line = shellReader.readLine(); -//// logger.debug("SSH FROM: " + this.contactID + ": " + line); -// -// // null is never returned normally, the reading attempt returs a -// // string -// // or blocks until one line is available -// if(line == null) -// { -// sshShellChannel.disconnect(); -// sshShellChannel = null; -// sshSession = null; -// throw(new IOException("Unexpected Reply from remote Server")); -// } -// return line; -// } - - /** - * Starts the timer and its task to periodically update the status of - * remote machine - */ - public void startTimerTask() - { - timer.scheduleAtFixedRate(new ContactTimerSSHImpl(this), - 2000, sshConfigurationForm.getUpdateInterval()*1000); - } - - /** - * Stops the timer and its task to stop updating the status of - * remote machine - */ - public void stopTimerTask() - { - timer.cancel(); - } - - - /** - * Saves the details of contact in persistentData seperated by - * separator - * Passowrd is saved unsecurely using Base64 encoding - */ - public void savePersistentDetails() - { - persistentData = - this.sshConfigurationForm.getHostName() + - separator + - this.sshConfigurationForm.getUserName() + - separator + - new String(Base64.encode(this.sshConfigurationForm.getPassword() - .getBytes())) + - separator + sshConfigurationForm.getPort() + - separator + - sshConfigurationForm.getTerminalType() + - separator + - sshConfigurationForm.getUpdateInterval(); - } - - /** - * Stores persistent data in fields of the contact seperated by - * separator. - * - * @param persistentData of the contact - */ - public void setPersistentData(String persistentData) - { - try - { - this.persistentData = persistentData; - int firstCommaIndex = this.persistentData.indexOf(separator); - int secondCommaIndex = this.persistentData.indexOf(separator, - firstCommaIndex +1); - int thirdCommaIndex = this.persistentData.indexOf(separator, - secondCommaIndex +1); - int fourthCommaIndex = this.persistentData.indexOf(separator, - thirdCommaIndex +1); - int fifthCommaIndex = this.persistentData.indexOf(separator, - fourthCommaIndex +1); - - if (logger.isDebugEnabled()) - logger.debug("Commas: " + firstCommaIndex + " " + secondCommaIndex + " " - + thirdCommaIndex + " " +fourthCommaIndex + " " - +fifthCommaIndex); - - this.sshConfigurationForm.setHostNameField( - this.persistentData.substring(0,firstCommaIndex)); - - this.sshConfigurationForm.setUserNameField( - this.persistentData.substring(firstCommaIndex+1, - secondCommaIndex)); - - if( (thirdCommaIndex - secondCommaIndex) > 1) - { - if(this.persistentData.substring(secondCommaIndex+1).length()>0) - this.sshConfigurationForm.setPasswordField( - new String(Base64.decode(this.persistentData - .substring(secondCommaIndex+1, thirdCommaIndex)))); - } - - - this.sshConfigurationForm.setPort( - this.persistentData.substring(thirdCommaIndex + 1, - fourthCommaIndex)); - - this.sshConfigurationForm.setTerminalType( - this.persistentData.substring(fourthCommaIndex + 1, - fifthCommaIndex)); - - this.sshConfigurationForm.setUpdateInterval( - Integer.parseInt(this.persistentData.substring(fifthCommaIndex+1))); - } - catch(Exception ex) - { - logger.error("Error setting persistent data!", ex); - } - } - - /** - * Determines whether a connection to a remote server is already underway - * - * @return isConnectionInProgress - */ - public boolean isConnectionInProgress() - { - return this.isConnectionInProgress; - } - - /** - * Sets the status of connection attempt to remote server - * This method is synchronized - * - * @param isConnectionInProgress - */ - public synchronized void setConnectionInProgress( - boolean isConnectionInProgress) - { - this.isConnectionInProgress = isConnectionInProgress; - } - - /** - * Returns the SSHContactInfo associated with this contact - * - * @return sshConfigurationForm - */ - public SSHContactInfo getSSHConfigurationForm() - { - return this.sshConfigurationForm; - } - - /** - * Returns the JSch Stack identified associated with this contact - * - * @return jsch - */ - public JSch getJSch() - { - return this.jsch; - } - - /** - * Sets the JSch Stack identified associated with this contact - * - * @param jsch to be associated - */ - public void setJSch(JSch jsch) - { - this.jsch = jsch; - } - - /** - * This method is only called when the contact is added to a new - * <tt>ContactGroupSSHImpl</tt> by the - * <tt>ContactGroupSSHImpl</tt> itself. - * - * @param newParentGroup the <tt>ContactGroupSSHImpl</tt> that is now - * parent of this <tt>ContactSSHImpl</tt> - */ - public void setParentGroup(ContactGroupSSHImpl newParentGroup) - { - this.parentGroup = newParentGroup; - } - - /** - * Returns the Hostname associated with this contact - * - * @return hostName - */ - public String getHostName() - { - return sshConfigurationForm.getHostName(); - } - - /** - * Returns a String that can be used for identifying the contact. - * - * @return a String id representing and uniquely identifying the contact. - */ - public String getAddress() - { - return contactID; - } - - /** - * Returns a String that could be used by any user interacting modules - * for referring to this contact. - * - * @return a String that can be used for referring to this contact when - * interacting with the user. - */ - public String getDisplayName() - { - return contactID; - } - - /** - * Returns a byte array containing an image (most often a photo or an - * avatar) that the contact uses as a representation. - * - * @return byte[] an image representing the contact. - */ - public byte[] getImage() - { - return null; - } - - /** - * Returns true if a command has been sent whos reply was not received yet - * false otherwise - * - * @return commandSent - */ - public boolean isCommandSent() - { - return this.commandSent; - } - - /** - * Set the state of commandSent variable which determines whether a reply - * to a command sent is awaited - */ - public void setCommandSent(boolean commandSent) - { - synchronized(lock) - { - this.commandSent = commandSent; - } - } - - /** - * Return the type of message received from remote server - * - * @return messageType - */ - public int getMessageType() - { - return this.messageType; - } - - /** - * Sets the type of message received from remote server - * - * @param messageType - */ - public void setMessageType(int messageType) - { - this.messageType = messageType; - } - - /** - * Returns the status of the contact. - * - * @return presenceStatus - */ - public PresenceStatus getPresenceStatus() - { - return this.presenceStatus; - } - - /** - * Sets <tt>sshPresenceStatus</tt> as the PresenceStatus that this - * contact is currently in. - * @param sshPresenceStatus the <tt>SSHPresenceStatus</tt> - * currently valid for this contact. - */ - public void setPresenceStatus(PresenceStatus sshPresenceStatus) - { - this.presenceStatus = sshPresenceStatus; - } - - /** - * Returns a reference to the protocol provider that created the contact. - * - * @return a refererence to an instance of the ProtocolProviderService - */ - public ProtocolProviderService getProtocolProvider() - { - return parentProvider; - } - - /** - * Determines whether or not this contact represents our own identity. - * - * @return true - */ - public boolean isLocal() - { - return true; - } - - /** - * Returns the group that contains this contact. - * @return a reference to the <tt>ContactGroupSSHImpl</tt> that - * contains this contact. - */ - public ContactGroup getParentContactGroup() - { - return this.parentGroup; - } - - /** - * Returns a string representation of this contact, containing most of its - * representative details. - * - * @return a string representation of this contact. - */ - @Override - public String toString() - { - StringBuffer buff - = new StringBuffer("ContactSSHImpl[ DisplayName=") - .append(getDisplayName()).append("]"); - - return buff.toString(); - } - - /** - * Determines whether or not this contact is being stored by the server. - * Non persistent contacts are common in the case of simple, non-persistent - * presence operation sets. They could however also be seen in persistent - * presence operation sets when for example we have received an event - * from someone not on our contact list. Non persistent contacts are - * volatile even when coming from a persistent presence op. set. They would - * only exist until the application is closed and will not be there next - * time it is loaded. - * - * @return true if the contact is persistent and false otherwise. - */ - public boolean isPersistent() - { - return isPersistent; - } - - /** - * Specifies whether or not this contact is being stored by the server. - * Non persistent contacts are common in the case of simple, non-persistent - * presence operation sets. They could however also be seen in persistent - * presence operation sets when for example we have received an event - * from someone not on our contact list. Non persistent contacts are - * volatile even when coming from a persistent presence op. set. They would - * only exist until the application is closed and will not be there next - * time it is loaded. - * - * @param isPersistent true if the contact is persistent and false - * otherwise. - */ - public void setPersistent(boolean isPersistent) - { - this.isPersistent = isPersistent; - } - - - /** - * Returns persistent data of the contact. - * - * @return persistentData of the contact - */ - public String getPersistentData() - { - return persistentData; - } - - /** - * Determines whether or not this contact has been resolved against the - * server. Unresolved contacts are used when initially loading a contact - * list that has been stored in a local file until the presence operation - * set has managed to retrieve all the contact list from the server and has - * properly mapped contacts to their on-line buddies. - * - * @return true if the contact has been resolved (mapped against a buddy) - * and false otherwise. - */ - public boolean isResolved() - { - return isResolved; - } - - /** - * Makes the contact resolved or unresolved. - * - * @param resolved true to make the contact resolved; false to - * make it unresolved - */ - public void setResolved(boolean resolved) - { - this.isResolved = resolved; - } - - /** - * Returns the persistent presence operation set that this contact belongs - * to. - * - * @return the <tt>OperationSetPersistentPresenceSSHImpl</tt> that - * this contact belongs to. - */ - public OperationSetPersistentPresence - getParentPresenceOperationSet() - { - return - parentProvider - .getOperationSet(OperationSetPersistentPresence.class); - } - - /** - * Returns the BasicInstant Messaging operation set that this contact - * belongs to. - * - * @return the <tt>OperationSetBasicInstantMessagingSSHImpl</tt> that - * this contact belongs to. - */ - public OperationSetBasicInstantMessaging - getParentBasicInstantMessagingOperationSet() - { - return - parentProvider - .getOperationSet(OperationSetBasicInstantMessaging.class); - } - - /** - * Returns the File Transfer operation set that this contact belongs - * to. - * - * @return the <tt>OperationSetFileTransferSSHImpl</tt> that - * this contact belongs to. - */ - public OperationSetFileTransfer - getFileTransferOperationSet() - { - return parentProvider.getOperationSet(OperationSetFileTransfer.class); - } - - - /** - * Returns the SSH Session associated with this contact - * - * @return sshSession - */ - public Session getSSHSession() - { - return this.sshSession; - } - - /** - * Sets the SSH Session associated with this contact - * - * @param sshSession the newly created SSH Session to be associated - */ - public void setSSHSession(Session sshSession) - { - this.sshSession = sshSession; - } - - /** - * Returns the SSH Shell Channel associated with this contact - * - * @return sshShellChannel - */ - public Channel getShellChannel() - { - return this.sshShellChannel; - } - - /** - * Sets the SSH Shell channel associated with this contact - * - * @param sshShellChannel to be associated with SSH Session of this contact - */ - public void setShellChannel(Channel sshShellChannel) - { - this.sshShellChannel = sshShellChannel; - } - - /** - * Returns the Input Stream associated with SSH Channel of this contact - * - * @return shellInputStream associated with SSH Channel of this contact - */ - public InputStream getShellInputStream() - { - return this.shellInputStream; - } - -// /** -// * Sets the Input Stream associated with SSH Channel of this contact -// * -// * @param shellInputStream to be associated with SSH Channel of -// * this contact -// */ -// public void setShellInputStream(InputStream shellInputStream) -// { -// this.shellInputStream = shellInputStream; -// } - - /** - * Returns the Output Stream associated with SSH Channel of this contact - * - * @return shellOutputStream associated with SSH Channel of this contact - */ - public OutputStream getShellOutputStream() - { - return this.shellOutputStream; - } - -// /** -// * Sets the Output Stream associated with SSH Channel of this contact -// * -// * @param shellOutputStream to be associated with SSH Channel of this contact -// */ -// public void setShellOutputStream(OutputStream shellOutputStream) -// { -// this.shellOutputStream = shellOutputStream; -// } - - /** - * Returns the BufferedReader associated with SSH Channel of this contact - * - * @return shellReader associated with SSH Channel of this contact - */ - public InputStreamReader getShellReader() - { - return this.shellReader; - } - -// /** -// * Sets the BufferedReader associated with SSH Channel of this contact -// * -// * @param shellReader to be associated with SSH Channel of this contact -// */ -// public void setShellReader(BufferedReader shellReader) -// { -// this.shellReader = shellReader; -// } - - /** - * Returns the PrintWriter associated with SSH Channel of this contact - * - * @return shellWriter associated with SSH Channel of this contact - */ - public PrintWriter getShellWriter() - { - return this.shellWriter; - } - -// /** -// * Sets the PrintWriter associated with SSH Channel of this contact -// * -// * @param shellWriter to be associated with SSH Channel of this contact -// */ -// public void setShellWriter(PrintWriter shellWriter) -// { -// this.shellWriter = shellWriter; -// } - - /** - * Returns the userName associated with SSH Channel of this contact - * - * @return userName associated with SSH Channel of this contact - */ - public String getUserName() - { - return sshConfigurationForm.getUserName(); - } - - /** - * Returns the password associated with SSH Channel of this contact - * - * @return password associated with SSH Channel of this contact - */ - public String getPassword() - { - return sshConfigurationForm.getPassword(); - } - - /** - * Sets the Password associated with this contact - * - * @param password - */ - public void setPassword(String password) - { - this.sshConfigurationForm.setPasswordField(password); - savePersistentDetails(); - } - -// /** -// * Sets the PS1 prompt of the current shell of Contact -// * -// * @param sshPrompt to be associated -// */ -// public void setShellPrompt(String sshPrompt) -// { -// this.sshPrompt = sshPrompt; -// } -// -// /** -// * Returns the PS1 prompt of the current shell of Contact -// * -// * @return sshPrompt -// */ -// public String getShellPrompt() -// { -// return this.sshPrompt; -// } - - /** - * Return the current status message of this contact. - * - * @return the current status message - */ - public String getStatusMessage() - { - return presenceStatus.getStatusName(); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ContactTimerSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ContactTimerSSHImpl.java deleted file mode 100644 index 4e3664d..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/ContactTimerSSHImpl.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; -import java.net.*; -import java.util.*; - -import net.java.sip.communicator.util.*; - -/** - * Timer Task to update the reachability status of SSH Contact in contact list. - * (Reachability of remote machine from user's machine) - * The timer is started at either of the two places - * - A new contact - OperationSetPersistentPresenceSSHImpl - * .createUnresolvedContact - * - Existing Contact - OperationSetPersistentPresenceSSHImpl.subscribe - * - * @author Shobhit Jindal - */ -public class ContactTimerSSHImpl - extends TimerTask -{ - private static final Logger logger - = Logger.getLogger(OperationSetFileTransferSSHImpl.class); - - /** - * The contact ID of the remote machine - */ - private ContactSSH sshContact; - - /** - * PersistentPresence Identifer assoiciated with SSH Contact - */ - private OperationSetPersistentPresenceSSHImpl persistentPresence; - - /** - * The method which is called at regular intervals to update the status - * of remote machines - * - * Presently only ONLINE and OFFILINE status are checked - */ - @Override - public void run() - { - try - { - InetAddress remoteMachine = InetAddress.getByName( - sshContact.getSSHConfigurationForm().getHostName()); - - //check if machine is reachable - if(remoteMachine.isReachable( - sshContact.getSSHConfigurationForm().getUpdateInterval())) - { - if (sshContact.getPresenceStatus().equals(SSHStatusEnum.OFFLINE) - || sshContact.getPresenceStatus().equals(SSHStatusEnum - .NOT_AVAILABLE)) - { - // change status to online - persistentPresence.changeContactPresenceStatus( - sshContact, SSHStatusEnum.ONLINE); - - if (logger.isDebugEnabled()) - logger.debug("SSH Host " + sshContact - .getSSHConfigurationForm().getHostName() + ": Online"); - } - - } - else throw new IOException(); - - } - catch (IOException ex) - { - if (sshContact.getPresenceStatus().equals(SSHStatusEnum.ONLINE) - || sshContact.getPresenceStatus().equals( - SSHStatusEnum.NOT_AVAILABLE)) - { - persistentPresence.changeContactPresenceStatus( - sshContact, SSHStatusEnum.OFFLINE); - - if (logger.isDebugEnabled()) - logger.debug("SSH Host " + sshContact.getSSHConfigurationForm() - .getHostName() + ": Offline"); - } - } - } - /** - * Creates a new instance of ContactTimerSSHImpl - * - * @param sshContact the <tt>Contact</tt> - */ - public ContactTimerSSHImpl(ContactSSH sshContact) - { - super(); - this.sshContact = sshContact; - this.persistentPresence = (OperationSetPersistentPresenceSSHImpl) - sshContact.getParentPresenceOperationSet(); - } - -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/FileTransferSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/FileTransferSSHImpl.java deleted file mode 100644 index 1e95032..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/FileTransferSSHImpl.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * SSH implementation of the <tt>AbstractFileTransfer</tt>. - * - * @author Yana Stamcheva - */ -public class FileTransferSSHImpl - extends AbstractFileTransfer -{ - private final SSHFileTransferDaemon fileTransfer; - - private final Date initialDate; - - /** - * Creates an SSH implementation of the file transfer interface. - * - * @param fileTransfer the SSH file transfer - * @param date the initial date of the transfer - */ - public FileTransferSSHImpl( SSHFileTransferDaemon fileTransfer, - Date date) - { - this.fileTransfer = fileTransfer; - this.initialDate = date; - } - - /** - * Cancels this file transfer. When this method is called transfer should - * be interrupted. - */ - @Override - public void cancel() - { - // TODO: Implement cancel() for SSH file transfer. - } - - /** - * Returns the number of bytes already transfered through this file transfer. - * - * @return the number of bytes already transfered through this file transfer - */ - @Override - public long getTransferedBytes() - { - // TODO: Implement getTransferedBytes() for SSH file transfer. - return 0; - } - - public int getDirection() - { - return IN; - } - - public File getLocalFile() - { - return null; - } - - public Contact getContact() - { - return null; - } - - public String getID() - { - return null; - } - - public Date getInitialDate() - { - return initialDate; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/MessageSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/MessageSSHImpl.java deleted file mode 100644 index a8324f7..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/MessageSSHImpl.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import net.java.sip.communicator.service.protocol.*; - -/** - * Very simple message implementation for the SSH protocol. - * - * @author Shobhit Jindal - * @author Lubomir Marinov - */ -public class MessageSSHImpl - extends AbstractMessage -{ - - /** - * The content type of the message. - */ - public static String contentType = "text/plain"; - - /** - * Creates a message instance according to the specified parameters. - * - * @param content the message body - * @param contentType message content type or null for text/plain - * @param contentEncoding message encoding or null for UTF8 - * @param subject the subject of the message or null for no subject. - */ - public MessageSSHImpl(String content, String contentType, - String contentEncoding, String subject) - { - super(content, null, contentEncoding, subject); - - MessageSSHImpl.contentType = contentType; - } - - /** - * Returns the type of the content of this message. - * - * @return the type of the content of this message. - */ - @Override - public String getContentType() - { - return contentType; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java deleted file mode 100644 index 9073ad5..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; - -/** - * Instant messaging functionality for the SSH protocol. - * - * @author Shobhit Jindal - */ -public class OperationSetBasicInstantMessagingSSHImpl - extends AbstractOperationSetBasicInstantMessaging -{ - - /** - * The currently valid persistent presence operation set.. - */ - private OperationSetPersistentPresenceSSHImpl opSetPersPresence = null; - - /** - * The currently valid file transfer operation set - */ - private OperationSetFileTransferSSHImpl fileTransfer; - - /** - * The protocol provider that created us. - */ - private ProtocolProviderServiceSSHImpl parentProvider = null; - - /** - * Creates an instance of this operation set keeping a reference to the - * parent protocol provider and presence operation set. - * - * @param provider The provider instance that creates us. - */ - public OperationSetBasicInstantMessagingSSHImpl( - ProtocolProviderServiceSSHImpl provider) - { - this.parentProvider = provider; - - this.opSetPersPresence - = (OperationSetPersistentPresenceSSHImpl) - provider - .getOperationSet(OperationSetPersistentPresence.class); - } - - @Override - public Message createMessage(String content, String contentType, - String encoding, String subject) - { - return new MessageSSHImpl(content, contentType, encoding, subject); - } - - /** - * Sends the <tt>message</tt> to the destination indicated by the - * <tt>to</tt> contact. An attempt is made to re-establish the shell - * connection if the current one is invalid. - * The reply from server is sent by a seperate reader thread - * - * @param to the <tt>Contact</tt> to send <tt>message</tt> to - * @param message the <tt>Message</tt> to send. - * @throws IllegalStateException if the underlying ICQ stack is not - * registered and initialized. - * @throws IllegalArgumentException if <tt>to</tt> is not an instance - * belonging to the underlying implementation. - */ - public void sendInstantMessage( - Contact to, - Message message) - throws IllegalStateException, - IllegalArgumentException - { - if( !(to instanceof ContactSSHImpl) ) - throw new IllegalArgumentException( - "The specified contact is not a SSH contact." - + to); - - ContactSSH sshContact = (ContactSSH)to; - - // making sure no messages are sent and no new threads are triggered, - // until a thread trying to connect to remote server returns - if(sshContact.isConnectionInProgress()) - { - deliverMessage( - createMessage("A connection attempt is in progress"), - (ContactSSHImpl)to); - return; - } - - if( !parentProvider.isShellConnected(sshContact) ) - { - - try - { - /** - * creating a new SSH session / shell channel - * - first message - * - session is timed out - * - network problems - */ - parentProvider.connectShell(sshContact, message); - - //the first message is ignored - return; - } - catch (Exception ex) - { - throw new IllegalStateException(ex.getMessage()); - } - } - - if(wrappedMessage(message.getContent(), sshContact)) - { - fireMessageDelivered(message, to); - return; - } - - try - { - sshContact.sendLine(message.getContent()); - sshContact.setCommandSent(true); - } - catch (IOException ex) - { - // Closing IO Streams - sshContact.closeShellIO(); - - throw new IllegalStateException(ex.getMessage()); - } - - fireMessageDelivered(message, to); - } - - /** - * Check the message for wrapped Commands - * All commands begin with / - * - * @param message from user - * @param sshContact of the remote machine - * - * @return true if the message had commands, false otherwise - */ - private boolean wrappedMessage( - String message, - ContactSSH sshContact) - { - if(message.startsWith("/upload")) - { - int firstSpace = message.indexOf(' '); - - try - { - sshContact.getFileTransferOperationSet().sendFile( - sshContact, - null, - message.substring(message.indexOf(' ', firstSpace+1) + 1), - message.substring( - firstSpace+1, - message.indexOf(' ', firstSpace+1))); - } - catch (Exception e) - { - e.printStackTrace(); - } - - return true; - } - else if(message.startsWith("/download")) - { - int firstSpace = message.indexOf(' '); - - try - { - sshContact.getFileTransferOperationSet().sendFile( - null, - sshContact, - message.substring(firstSpace+1, message.indexOf(' ', - firstSpace+1)), - message.substring(message.indexOf(' ', firstSpace+1) + 1)); - } - catch(Exception e) - { - e.printStackTrace(); - } - return true; - } - return false; - } - - /** - * In case the <tt>to</tt> Contact corresponds to another ssh - * protocol provider registered with SIP Communicator, we deliver - * the message to them, in case the <tt>to</tt> Contact represents us, we - * fire a <tt>MessageReceivedEvent</tt>, and if <tt>to</tt> is simply - * a contact in our contact list, then we simply echo the message. - * - * @param message the <tt>Message</tt> the message to deliver. - * @param to the <tt>Contact</tt> that we should deliver the message to. - */ - void deliverMessage( - Message message, - ContactSSH to) - { - String userID = to.getAddress(); - - //if the user id is owr own id, then this message is being routed to us - //from another instance of the ssh provider. - if (userID.equals(this.parentProvider.getAccountID().getUserID())) - { - //check who is the provider sending the message - String sourceUserID - = to.getProtocolProvider().getAccountID().getUserID(); - - //check whether they are in our contact list - Contact from = opSetPersPresence.findContactByID(sourceUserID); - - //and if not - add them there as volatile. - if(from == null) - { - from = opSetPersPresence.createVolatileContact(sourceUserID); - } - - //and now fire the message received event. - fireMessageReceived(message, from); - } - else - { - //if userID is not our own, try an check whether another provider - //has that id and if yes - deliver the message to them. - ProtocolProviderServiceSSHImpl sshProvider - = this.opSetPersPresence.findProviderForSSHUserID(userID); - if(sshProvider != null) - { - OperationSetBasicInstantMessagingSSHImpl opSetIM - = (OperationSetBasicInstantMessagingSSHImpl) - sshProvider - .getOperationSet( - OperationSetBasicInstantMessaging.class); - opSetIM.deliverMessage(message, to); - } - else - { - //if we got here then "to" is simply someone in our contact - //list so let's just echo the message. - fireMessageReceived(message, to); - } - } - } - - /** - * Notifies all registered message listeners that a message has been - * received. - * - * @param message the <tt>Message</tt> that has been received. - * @param from the <tt>Contact</tt> that <tt>message</tt> was received from. - */ - @Override - protected void fireMessageReceived(Message message, Contact from) - { - fireMessageEvent( - new MessageReceivedEvent( - message, - from, - new Date(), - ((ContactSSH) from).getMessageType())); - } - - /** - * Determines whether the SSH protocol provider supports - * sending and receiving offline messages. - * - * @return <tt>false</tt> - */ - public boolean isOfflineMessagingSupported() - { - return false; - } - - /** - * Determines wheter the protocol supports the supplied content type - * - * @param contentType the type we want to check - * @return <tt>true</tt> if the protocol supports it and - * <tt>false</tt> otherwise. - */ - public boolean isContentTypeSupported(String contentType) - { - return MessageSSHImpl.contentType.equals(contentType); - } - -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java deleted file mode 100644 index 61d8160..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; - -/** - * This class provides operations to upload/download files to remote machines - * - * @author Shobhit Jindal - */ -public class OperationSetFileTransferSSHImpl - implements OperationSetFileTransfer -{ - private static final Logger logger - = Logger.getLogger(OperationSetFileTransferSSHImpl.class); - - /** - * Currently registered message listeners. - */ - private Vector<FileTransferListener> fileTransferListeners - = new Vector<FileTransferListener>(); - - /** - * The protocol provider that created us. - */ - private ProtocolProviderServiceSSHImpl parentProvider = null; - - - /** - * Creates a new instance of OperationSetFileTransferSSHImpl - * - * @param parentProvider the parent protocol provider service - */ - public OperationSetFileTransferSSHImpl( - ProtocolProviderServiceSSHImpl parentProvider) - { - this.parentProvider = parentProvider; - } - - /** - * Registers a FileTransferListener with this operation set so that it gets - * notifications of start, complete, failure of file transfers - * - * @param listener the <tt>FileListener</tt> to register. - */ - public void addFileTransferListener( - FileTransferListener listener) - { - synchronized (fileTransferListeners) - { - if(!fileTransferListeners.contains(listener)) - fileTransferListeners.add(listener); - } - } - - public void removeFileTransferListener( - FileTransferListener listener) - { - synchronized (fileTransferListeners) - { - fileTransferListeners.remove(listener); - } - } - - /** - * Sends a file transfer request to the given <tt>toContact</tt>. - * @param toContact the contact that should receive the file - * @param file the file to send - */ - public FileTransfer sendFile( Contact toContact, - File file) - { - return this.sendFile( toContact, - null, - file.getAbsolutePath(), - file.getAbsolutePath()); - } - - /** - * The file transfer method to/from the remote machine - * either toContact is null(we are downloading file from remote machine - * or fromContact is null(we are uploading file to remote machine - * - * @param toContact - the file recipient - * @param fromContact - the file sender - * @param remotePath - the identifier for the remote file - * @param localPath - the identifier for the local file - */ - public FileTransfer sendFile( - Contact toContact, - Contact fromContact, - String remotePath, - String localPath) - { - if(toContact == null) - { - SSHFileTransferDaemon fileTransferDaemon - = new SSHFileTransferDaemon( - (ContactSSH)fromContact, - parentProvider); - - if(localPath.endsWith(System.getProperty("file.separator"))) - localPath += remotePath.substring(remotePath.lastIndexOf( - System.getProperty("file.separator")) + 1); - - fileTransferDaemon.downloadFile( - remotePath, - localPath); - - return new FileTransferSSHImpl(fileTransferDaemon, new Date()); - } - else if(fromContact == null) - { - SSHFileTransferDaemon fileTransferDaemon - = new SSHFileTransferDaemon( - (ContactSSH) toContact, - parentProvider); - - fileTransferDaemon.uploadFile( - remotePath, - localPath); - - return new FileTransferSSHImpl(fileTransferDaemon, new Date()); - } - - // code should not reach here - // assert false; - logger.error("we should not be here !"); - return null; - } - - /** - * Returns the maximum file length supported by the protocol in bytes. - * @return the file length that is supported. - */ - public long getMaximumFileLength() - { - return 2048*1024*1024; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetPersistentPresenceSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetPersistentPresenceSSHImpl.java deleted file mode 100644 index d4c63bd..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetPersistentPresenceSSHImpl.java +++ /dev/null @@ -1,980 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; - -import org.osgi.framework.*; - -/** - * A SSH implementation of a persistent presence operation set. In order - * to simulate server persistence, this operation set would simply accept all - * unresolved contacts and resolve them immediately. A real world protocol - * implementation would save it on a server using methods provided by the - * protocol stack. - * - * @author Shobhit Jindal - */ -public class OperationSetPersistentPresenceSSHImpl - extends AbstractOperationSetPersistentPresence<ProtocolProviderServiceSSHImpl> -{ - private static final Logger logger = - Logger.getLogger(OperationSetPersistentPresenceSSHImpl.class); - - /** - * The root of the ssh contact list. - */ - private ContactGroupSSHImpl contactListRoot = null; - - /** - * The currently active status message. - */ - private String statusMessage = "Online"; - - /** - * Our default presence status. - */ - private PresenceStatus presenceStatus = SSHStatusEnum.ONLINE; - - /** - * Creates an instance of this operation set keeping a reference to the - * specified parent <tt>provider</tt>. - * @param provider the ProtocolProviderServiceSSHImpl instance that - * created us. - */ - public OperationSetPersistentPresenceSSHImpl( - ProtocolProviderServiceSSHImpl provider) - { - super(provider); - - contactListRoot = new ContactGroupSSHImpl("RootGroup", provider); - - //add our unregistration listener - parentProvider.addRegistrationStateChangeListener( - new UnregistrationListener()); - } - - /** - * This function changes the status of contact as well as that of the - * provider - * - * @param sshContact the contact of the remote machine - * @param newStatus new status of the contact - */ - public void changeContactPresenceStatus( - ContactSSH sshContact, - PresenceStatus newStatus) - { - PresenceStatus oldStatus = sshContact.getPresenceStatus(); - sshContact.setPresenceStatus(newStatus); - fireContactPresenceStatusChangeEvent( - sshContact - , sshContact.getParentContactGroup() - , oldStatus); - fireProviderStatusChangeEvent(oldStatus); - } - - /** - * Creates a group with the specified name and parent in the server - * stored contact list. - * - * @param parent the group where the new group should be created - * @param groupName the name of the new group to create. - */ - public void createServerStoredContactGroup( - ContactGroup parent, - String groupName) - { - ContactGroupSSHImpl newGroup - = new ContactGroupSSHImpl(groupName, parentProvider); - - ((ContactGroupSSHImpl)parent).addSubgroup(newGroup); - - this.fireServerStoredGroupEvent( - newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - - /** - * A SSH Provider method to use for fast filling of a contact list. - * - * @param contactGroup the group to add - */ - public void addSSHGroup(ContactGroupSSHImpl contactGroup) - { - contactListRoot.addSubgroup(contactGroup); - } - - /** - * A SSH Provider method to use for fast filling of a contact list. - * This method would add both the group and fire an event. - * - * @param parent the group where <tt>contactGroup</tt> should be added. - * @param contactGroup the group to add - */ - public void addSSHGroupAndFireEvent( - ContactGroupSSHImpl parent, - ContactGroupSSHImpl contactGroup) - { - parent.addSubgroup(contactGroup); - - this.fireServerStoredGroupEvent( - contactGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - - - /** - * Returns a reference to the contact with the specified ID in case we - * have a subscription for it and null otherwise/ - * - * @param contactID a String identifier of the contact which we're - * seeking a reference of. - * @return a reference to the Contact with the specified - * <tt>contactID</tt> or null if we don't have a subscription for the - * that identifier. - */ - public Contact findContactByID(String contactID) - { - return contactListRoot.findContactByID(contactID); - } - - /** - * Sets the specified status message. - * @param statusMessage a String containing the new status message. - */ - public void setStatusMessage(String statusMessage) - { - this.statusMessage = statusMessage; - } - - /** - * Returns the status message that was last set through - * setCurrentStatusMessage. - * - * @return the last status message that we have requested and the aim - * server has confirmed. - */ - public String getCurrentStatusMessage() - { - return statusMessage; - } - - /** - * Returns the protocol specific contact instance representing the local - * user. - * - * @return the Contact (address, phone number, or uin) that the Provider - * implementation is communicating on behalf of. - */ - public Contact getLocalContact() - { - return null; - } - - /** - * Returns a PresenceStatus instance representing the state this provider - * is currently in. - * - * @return the PresenceStatus last published by this provider. - */ - public PresenceStatus getPresenceStatus() - { - return presenceStatus; - } - - /** - * Returns the root group of the server stored contact list. - * - * @return the root ContactGroup for the ContactList stored by this - * service. - */ - public ContactGroup getServerStoredContactListRoot() - { - return contactListRoot; - } - - /** - * Returns the set of PresenceStatus objects that a user of this service - * may request the provider to enter. - * - * @return Iterator a PresenceStatus array containing "enterable" status - * instances. - */ - public Iterator<PresenceStatus> getSupportedStatusSet() - { - return SSHStatusEnum.supportedStatusSet(); - } - - /** - * Removes the specified contact from its current parent and places it - * under <tt>newParent</tt>. - * - * @param contactToMove the <tt>Contact</tt> to move - * @param newParent the <tt>ContactGroup</tt> where <tt>Contact</tt> - * would be placed. - */ - public void moveContactToGroup( - Contact contactToMove, - ContactGroup newParent) - { - ContactSSHImpl sshContact - = (ContactSSHImpl)contactToMove; - - ContactGroupSSHImpl parentSSHGroup - = findContactParent(sshContact); - - parentSSHGroup.removeContact(sshContact); - - //if this is a volatile contact then we haven't really subscribed to - //them so we'd need to do so here - if(!sshContact.isPersistent()) - { - //first tell everyone that the volatile contact was removed - fireSubscriptionEvent(sshContact - , parentSSHGroup - , SubscriptionEvent.SUBSCRIPTION_REMOVED); - - try - { - //now subscribe - this.subscribe(newParent, contactToMove.getAddress()); - - //now tell everyone that we've added the contact - fireSubscriptionEvent(sshContact - , newParent - , SubscriptionEvent.SUBSCRIPTION_CREATED); - } - catch (Exception ex) - { - logger.error("Failed to move contact " - + sshContact.getAddress() - , ex); - } - } - else - { - ( (ContactGroupSSHImpl) newParent) - .addContact(sshContact); - - fireSubscriptionMovedEvent(contactToMove - , parentSSHGroup - , newParent); - } - } - - /** - * Requests the provider to enter into a status corresponding to the - * specified paramters. - * - * @param status the PresenceStatus as returned by - * getRequestableStatusSet - * @param statusMessage the message that should be set as the reason to - * enter that status - * @throws IllegalArgumentException if the status requested is not a - * valid PresenceStatus supported by this provider. - * @throws IllegalStateException if the provider is not currently - * registered. - */ - public void publishPresenceStatus( - PresenceStatus status, - String statusMessage) - throws IllegalArgumentException, - IllegalStateException - { - PresenceStatus oldPresenceStatus = this.presenceStatus; - this.presenceStatus = status; - this.statusMessage = statusMessage; - - this.fireProviderStatusChangeEvent(oldPresenceStatus); - - -// //since we are not a real protocol, we set the contact presence status -// //ourselves and make them have the same status as ours. -// changePresenceStatusForAllContacts( getServerStoredContactListRoot() -// , getPresenceStatus()); -// -// //now check whether we are in someone else's contact list and modify -// //our status there -// List contacts = findContactsPointingToUs(); -// -// Iterator contactsIter = contacts.iterator(); -// while (contactsIter.hasNext()) -// { -// ContactSSHImpl contact -// = (ContactSSHImpl) contactsIter.next(); -// -// PresenceStatus oldStatus = contact.getPresenceStatus(); -// contact.setPresenceStatus(status); -// contact.getParentPresenceOperationSet() -// .fireContactPresenceStatusChangeEvent( -// contact -// , contact.getParentContactGroup() -// , oldStatus); -// -// } - } - - - - /** - * Get the PresenceStatus for a particular contact. - * - * @param contactIdentifier the identifier of the contact whose status - * we're interested in. - * @return PresenceStatus the <tt>PresenceStatus</tt> of the specified - * <tt>contact</tt> - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * retrieving the status fails due to errors experienced during - * network communication - */ - public PresenceStatus queryContactStatus(String contactIdentifier) - throws IllegalArgumentException, - IllegalStateException, - OperationFailedException - { - return findContactByID(contactIdentifier).getPresenceStatus(); - } - - /** - * Sets the presence status of <tt>contact</tt> to <tt>newStatus</tt>. - * - * @param contact the <tt>ContactSSHImpl</tt> whose status we'd like - * to set. - * @param newStatus the new status we'd like to set to <tt>contact</tt>. - */ - private void changePresenceStatusForContact( - ContactSSH contact, - PresenceStatus newStatus) - { - PresenceStatus oldStatus = contact.getPresenceStatus(); - contact.setPresenceStatus(newStatus); - - fireContactPresenceStatusChangeEvent( - contact, findContactParent(contact), oldStatus); - } - - /** - * Sets the presence status of all <tt>contact</tt>s in our contact list - * (except those that correspond to another provider registered with SC) - * to <tt>newStatus</tt>. - * - * @param newStatus the new status we'd like to set to <tt>contact</tt>. - * @param parent the group in which we'd have to update the status of all - * direct and indirect child contacts. - */ - private void changePresenceStatusForAllContacts( - ContactGroup parent, - PresenceStatus newStatus) - { - //first set the status for contacts in this group - Iterator<Contact> childContacts = parent.contacts(); - - while(childContacts.hasNext()) - { - ContactSSHImpl contact - = (ContactSSHImpl)childContacts.next(); - - if(findProviderForSSHUserID(contact.getAddress()) != null) - { - //this is a contact corresponding to another SIP Communicator - //provider so we won't change it's status here. - continue; - } - PresenceStatus oldStatus = contact.getPresenceStatus(); - contact.setPresenceStatus(newStatus); - - fireContactPresenceStatusChangeEvent( - contact, parent, oldStatus); - } - - //now call this method recursively for all subgroups - Iterator<ContactGroup> subgroups = parent.subgroups(); - - while(subgroups.hasNext()) - { - ContactGroup subgroup = subgroups.next(); - changePresenceStatusForAllContacts(subgroup, newStatus); - } - } - - /** - * Returns the group that is parent of the specified sshGroup or null - * if no parent was found. - * @param sshGroup the group whose parent we're looking for. - * @return the ContactGroupSSHImpl instance that sshGroup - * belongs to or null if no parent was found. - */ - public ContactGroupSSHImpl findGroupParent( - ContactGroupSSHImpl sshGroup) - { - return contactListRoot.findGroupParent(sshGroup); - } - - /** - * Returns the group that is parent of the specified sshContact or - * null if no parent was found. - * @param sshContact the contact whose parent we're looking for. - * @return the ContactGroupSSHImpl instance that sshContact - * belongs to or null if no parent was found. - */ - public ContactGroupSSHImpl findContactParent( - ContactSSH sshContact) - { - return (ContactGroupSSHImpl)sshContact - .getParentContactGroup(); - } - - - /** - * Removes the specified group from the server stored contact list. - * - * @param group the group to remove. - * - * @throws IllegalArgumentException if <tt>group</tt> was not found in this - * protocol's contact list. - */ - public void removeServerStoredContactGroup(ContactGroup group) - throws IllegalArgumentException - { - ContactGroupSSHImpl sshGroup - = (ContactGroupSSHImpl)group; - - ContactGroupSSHImpl parent = findGroupParent(sshGroup); - - if(parent == null) - { - throw new IllegalArgumentException( - "group " + group - + " does not seem to belong to this protocol's contact " - + "list."); - } - - parent.removeSubGroup(sshGroup); - - this.fireServerStoredGroupEvent( - sshGroup, ServerStoredGroupEvent.GROUP_REMOVED_EVENT); - } - - /** - * Renames the specified group from the server stored contact list. - * - * @param group the group to rename. - * @param newName the new name of the group. - */ - public void renameServerStoredContactGroup( - ContactGroup group, - String newName) - { - ((ContactGroupSSHImpl)group).setGroupName(newName); - - this.fireServerStoredGroupEvent( - group, ServerStoredGroupEvent - .GROUP_RENAMED_EVENT); - } - - - /** - * Persistently adds a subscription for the presence status of the - * contact corresponding to the specified contactIdentifier and indicates - * that it should be added to the specified group of the server stored - * contact list. - * - * @param parent the parent group of the server stored contact list - * where the contact should be added. <p> - * @param contactIdentifier the contact whose status updates we are - * subscribing for. - * @throws IllegalArgumentException if <tt>contact</tt> or - * <tt>parent</tt> are not a contact known to the underlying protocol - * provider. - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * subscribing fails due to errors experienced during network - * communication - */ - public void subscribe( - ContactGroup parent, - String contactIdentifier) - throws IllegalArgumentException, - IllegalStateException, - OperationFailedException - { - ContactSSH sshContact = new ContactSSHImpl(contactIdentifier, - parentProvider); - -/* ProtocolProviderServiceSSHImpl.getUIService().getConfigurationWindow() - .setVisible(true); -*/ - sshContact.setParentGroup((ContactGroupSSHImpl)parent); - sshContact.getSSHConfigurationForm().setVisible(true); - - - -/* Gets the domain name or IP address of the sshContact machine via the - * UI Service Interface - sshContact.setPersistentData(ProtocolProviderServiceSSHImpl - .getUIService().getPopupDialog() - .showInputPopupDialog("Enter Domain Name or IP Address of " - + sshContact.getDisplayName())); - - // contact is added to list later after the user has provided - // details in SSHConfigurationForm - - // addContactToList method is called -*/ - } - - /** - * Add a contact to the specified group - * - * @param parent the group - * @param sshContact the contact - */ - public void addContactToList( - ContactGroup parent, - ContactSSH sshContact) - { - // Adds the sshContact to the sshContact list - - ((ContactGroupSSHImpl)parent).addContact(sshContact); - - fireSubscriptionEvent(sshContact, - parent, - SubscriptionEvent.SUBSCRIPTION_CREATED); - - //notify presence listeners for the status change. - fireContactPresenceStatusChangeEvent(sshContact - , parent - , SSHStatusEnum.NOT_AVAILABLE); - - sshContact.startTimerTask(); - } - - /** - * Adds a subscription for the presence status of the contact - * corresponding to the specified contactIdentifier. - * - * @param contactIdentifier the identifier of the contact whose status - * updates we are subscribing for. <p> - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * subscribing fails due to errors experienced during network - * communication - */ - public void subscribe(String contactIdentifier) throws - IllegalArgumentException, - IllegalStateException, - OperationFailedException - { - subscribe(contactListRoot, contactIdentifier); - - } - - /** - * Removes a subscription for the presence status of the specified - * contact. - * - * @param contact the contact whose status updates we are unsubscribing - * from. - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * unsubscribing fails due to errors experienced during network - * communication - */ - public void unsubscribe(Contact contact) throws - IllegalArgumentException, - IllegalStateException, - OperationFailedException - { - ContactGroupSSHImpl parentGroup - = (ContactGroupSSHImpl)((ContactSSHImpl)contact) - .getParentContactGroup(); - - parentGroup.removeContact((ContactSSHImpl)contact); - - fireSubscriptionEvent(contact, - ((ContactSSHImpl)contact).getParentContactGroup() - , SubscriptionEvent.SUBSCRIPTION_REMOVED); - } - - /** - * Creates and returns a unresolved contact from the specified - * <tt>address</tt> and <tt>persistentData</tt>. The method will not try - * to establish a network connection and resolve the newly created Contact - * against the server. The protocol provider may will later try and resolve - * the contact. When this happens the corresponding event would notify - * interested subscription listeners. - * - * @param address an identifier of the contact that we'll be creating. - * @param persistentData a String returned Contact's getPersistentData() - * method during a previous run and that has been persistently stored - * locally. - * @return the unresolved <tt>Contact</tt> created from the specified - * <tt>address</tt> and <tt>persistentData</tt> - */ - public Contact createUnresolvedContact( - String address, - String persistentData) - { - return createUnresolvedContact(address - , persistentData - , getServerStoredContactListRoot()); - } - - /** - * Creates and returns a unresolved contact from the specified - * <tt>address</tt> and <tt>persistentData</tt>. The method will not try - * to establish a network connection and resolve the newly created Contact - * against the server. The protocol provider may will later try and resolve - * the contact. When this happens the corresponding event would notify - * interested subscription listeners. - * - * @param address an identifier of the contact that we'll be creating. - * @param persistentData a String returned Contact's getPersistentData() - * method during a previous run and that has been persistently stored - * locally. - * @param parent the group where the unresolved contact is - * supposed to belong to. - * - * @return the unresolved <tt>Contact</tt> created from the specified - * <tt>address</tt> and <tt>persistentData</tt> - */ - public Contact createUnresolvedContact( - String address, - String persistentData, - ContactGroup parent) - { - ContactSSH contact = new ContactSSHImpl( - address, - parentProvider); - - contact.setPersistentData(persistentData); - contact.startTimerTask(); - - // SSH Contacts are resolved by default - contact.setResolved(true); - - ( (ContactGroupSSHImpl) parent).addContact(contact); - - fireSubscriptionEvent(contact, - parent, - SubscriptionEvent.SUBSCRIPTION_CREATED); - - //since we don't have any server, we'll simply resolve the contact - //ourselves as if we've just received an event from the server telling - //us that it has been resolved. - fireSubscriptionEvent( - contact, parent, SubscriptionEvent.SUBSCRIPTION_RESOLVED); - - return contact; - } - - /** - * Looks for a ssh protocol provider registered for a user id matching - * <tt>sshUserID</tt>. - * - * @param sshUserID the ID of the SSH user whose corresponding - * protocol provider we'd like to find. - * @return ProtocolProviderServiceSSHImpl a ssh protocol - * provider registered for a user with id <tt>sshUserID</tt> or null - * if there is no such protocol provider. - */ - public ProtocolProviderServiceSSHImpl - findProviderForSSHUserID(String sshUserID) - { - BundleContext bc = SSHActivator.getBundleContext(); - - String osgiQuery = "(&" - + "(" + ProtocolProviderFactory.PROTOCOL - + "=" + ProtocolNames.SSH + ")" - + "(" + ProtocolProviderFactory.USER_ID - + "=" + sshUserID + ")" - + ")"; - - ServiceReference[] refs = null; - try - { - refs = bc.getServiceReferences( - ProtocolProviderService.class.getName() - ,osgiQuery); - } - catch (InvalidSyntaxException ex) - { - logger.error("Failed to execute the following osgi query: " - + osgiQuery - , ex); - } - - if(refs != null && refs.length > 0) - { - return (ProtocolProviderServiceSSHImpl)bc.getService(refs[0]); - } - - return null; - } - - /** - * Looks for ssh protocol providers that have added us to their - * contact list and returns list of all contacts representing us in these - * providers. - * - * @return a list of all contacts in other providers' contact lists that - * point to us. - */ - public List<Contact> findContactsPointingToUs() - { - List<Contact> contacts = new LinkedList<Contact>(); - BundleContext bc = SSHActivator.getBundleContext(); - - String osgiQuery = - "(" + ProtocolProviderFactory.PROTOCOL - + "=SSH)"; - - ServiceReference[] refs = null; - try - { - refs = bc.getServiceReferences( - ProtocolProviderService.class.getName() - ,osgiQuery); - } - catch (InvalidSyntaxException ex) - { - logger.error("Failed to execute the following osgi query: " - + osgiQuery - , ex); - } - - for (int i =0; refs != null && i < refs.length; i++) - { - ProtocolProviderServiceSSHImpl gibProvider - = (ProtocolProviderServiceSSHImpl)bc.getService(refs[i]); - - OperationSetPersistentPresenceSSHImpl opSetPersPresence - = (OperationSetPersistentPresenceSSHImpl)gibProvider - .getOperationSet(OperationSetPersistentPresence.class); - - Contact contact = opSetPersPresence.findContactByID( - parentProvider.getAccountID().getUserID()); - - if (contact != null) - contacts.add(contact); - } - - return contacts; - } - - - /** - * Creates and returns a unresolved contact group from the specified - * <tt>address</tt> and <tt>persistentData</tt>. The method will not try - * to establish a network connection and resolve the newly created - * <tt>ContactGroup</tt> against the server or the contact itself. The - * protocol provider will later resolve the contact group. When this happens - * the corresponding event would notify interested subscription listeners. - * - * @param groupUID an identifier, returned by ContactGroup's getGroupUID, - * that the protocol provider may use in order to create the group. - * @param persistentData a String returned ContactGroups's - * getPersistentData() method during a previous run and that has been - * persistently stored locally. - * @param parentGroup the group under which the new group is to be created - * or null if this is group directly underneath the root. - * @return the unresolved <tt>ContactGroup</tt> created from the specified - * <tt>uid</tt> and <tt>persistentData</tt> - */ - public ContactGroup createUnresolvedContactGroup( - String groupUID, - String persistentData, - ContactGroup parentGroup) - { - ContactGroupSSHImpl newGroup - = new ContactGroupSSHImpl( - ContactGroupSSHImpl.createNameFromUID(groupUID) - , parentProvider); - newGroup.setResolved(false); - - //if parent is null then we're adding under root. - if(parentGroup == null) - parentGroup = getServerStoredContactListRoot(); - - ((ContactGroupSSHImpl)parentGroup).addSubgroup(newGroup); - - this.fireServerStoredGroupEvent( - newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); - - return newGroup; - } - - private class UnregistrationListener - implements RegistrationStateChangeListener - { - /** - * The method is called by a ProtocolProvider implementation whenver - * a change in the registration state of the corresponding provider had - * occurred. The method is particularly interested in events stating - * that the ssh provider has unregistered so that it would fire - * status change events for all contacts in our buddy list. - * - * @param evt ProviderStatusChangeEvent the event describing the status - * change. - */ - public void registrationStateChanged(RegistrationStateChangeEvent evt) - { - if (! evt.getNewState().equals(RegistrationState.UNREGISTERED) - && !evt.getNewState().equals(RegistrationState - .AUTHENTICATION_FAILED) - && !evt.getNewState().equals(RegistrationState.CONNECTION_FAILED)) - { - return; - } - - //send event notifications saying that all our buddies are - //offline. The icq protocol does not implement top level buddies - //nor subgroups for top level groups so a simple nested loop - //would be enough. - Iterator<ContactGroup> groupsIter - = getServerStoredContactListRoot().subgroups(); - while (groupsIter.hasNext()) - { - ContactGroup group = groupsIter.next(); - Iterator<Contact> contactsIter = group.contacts(); - - while (contactsIter.hasNext()) - { - ContactSSHImpl contact - = (ContactSSHImpl) contactsIter.next(); - - PresenceStatus oldContactStatus - = contact.getPresenceStatus(); - - if (!oldContactStatus.isOnline()) - continue; - - contact.setPresenceStatus(SSHStatusEnum.OFFLINE); - - fireContactPresenceStatusChangeEvent( - contact - , contact.getParentContactGroup() - , oldContactStatus); - } - } - } - } - - /** - * Returns the volatile group or null if this group has not yet been - * created. - * - * @return a volatile group existing in our contact list or <tt>null</tt> - * if such a group has not yet been created. - */ - private ContactGroupSSHImpl getNonPersistentGroup() - { - for (int i = 0 - ; i < getServerStoredContactListRoot().countSubgroups() - ; i++) - { - ContactGroupSSHImpl gr = - (ContactGroupSSHImpl)getServerStoredContactListRoot() - .getGroup(i); - - if(!gr.isPersistent()) - return gr; - } - - return null; - } - - - /** - * Creates a non persistent contact for the specified address. This would - * also create (if necessary) a group for volatile contacts that would not - * be added to the server stored contact list. This method would have no - * effect on the server stored contact list. - * - * @param contactAddress the address of the volatile contact we'd like to - * create. - * @return the newly created volatile contact. - */ - public ContactSSHImpl createVolatileContact(String contactAddress) - { - //First create the new volatile contact; - ContactSSHImpl newVolatileContact = new ContactSSHImpl( - contactAddress, - this.parentProvider); - - newVolatileContact.setPersistent(false); - - - //Check whether a volatile group already exists and if not create - //one - ContactGroupSSHImpl theVolatileGroup = getNonPersistentGroup(); - - - //if the parent volatile group is null then we create it - if (theVolatileGroup == null) - { - theVolatileGroup = new ContactGroupSSHImpl( - SSHActivator.getResources().getI18NString( - "service.gui.NOT_IN_CONTACT_LIST_GROUP_NAME") - , parentProvider); - theVolatileGroup.setResolved(false); - theVolatileGroup.setPersistent(false); - theVolatileGroup.addContact(newVolatileContact); - - this.contactListRoot.addSubgroup(theVolatileGroup); - - fireServerStoredGroupEvent(theVolatileGroup - , ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - - //now add the volatile contact instide it - theVolatileGroup.addContact(newVolatileContact); - fireSubscriptionEvent(newVolatileContact - , theVolatileGroup - , SubscriptionEvent.SUBSCRIPTION_CREATED); - - return newVolatileContact; - } - - /** - * DUMMY METHOD - * Handler for incoming authorization requests. - * - * @param handler an instance of an AuthorizationHandler for - * authorization requests coming from other users requesting - * permission add us to their contact list. - */ - public void setAuthorizationHandler(AuthorizationHandler handler) - { - } - -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolIconSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolIconSSHImpl.java deleted file mode 100644 index 592b2b1..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolIconSSHImpl.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -/** - * Represents the SSH protocol icon. Implements the <tt>ProtocolIcon</tt> - * interface in order to provide a SSH logo image in two different sizes. - * - * @author Shobhit Jindal - */ -public class ProtocolIconSSHImpl - implements ProtocolIcon -{ - private static Logger logger - = Logger.getLogger(ProtocolIconSSHImpl.class); - - /** - * 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.ssh.SSH_16x16")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_32x32, - getImageInBytes("service.protocol.ssh.SSH_32x32")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_48x48, - getImageInBytes("service.protocol.ssh.SSH_48x48")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_64x64, - getImageInBytes("service.protocol.ssh.SSH_64x64")); - } - - /** - * A hash table containing the protocol icon in different sizes. - */ - private static Hashtable<String, String> iconPathsTable - = new Hashtable<String, String>(); - static { - iconPathsTable.put(ProtocolIcon.ICON_SIZE_16x16, - SSHActivator.getResources().getImagePath( - "service.protocol.ssh.SSH_16x16")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_32x32, - SSHActivator.getResources().getImagePath( - "service.protocol.ssh.SSH_32x32")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_48x48, - SSHActivator.getResources().getImagePath( - "service.protocol.ssh.SSH_48x48")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_64x64, - SSHActivator.getResources().getImagePath( - "service.protocol.ssh.SSH_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(); - } - - /** - * Returns TRUE if a icon with the given size is supported, FALSE-otherwise. - * - * @return 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 - * @return the icon - */ - 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("protocolIconSsh"); - } - - /** - * 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. - */ - public static byte[] getImageInBytes(String imageID) - { - InputStream in = SSHActivator.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; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSH.java b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSH.java deleted file mode 100644 index 8c38f60..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSH.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.java.sip.communicator.impl.protocol.ssh; - -import net.java.sip.communicator.service.protocol.*; - -import org.osgi.framework.*; - -/** - * - * @author Shobhit Jindal - */ -public abstract class ProtocolProviderFactorySSH - extends ProtocolProviderFactory -{ - /** - * The name of a property representing the IDENTITY_FILE of the protocol for - * a ProtocolProviderFactory. - */ - public static final String IDENTITY_FILE = "IDENTITY_FILE"; - - /** - * The name of a property representing the KNOWN_HOSTS_FILE of the protocol - * for a ProtocolProviderFactory. - */ - public static final String KNOWN_HOSTS_FILE = "KNOWN_HOSTS_FILE"; - - protected ProtocolProviderFactorySSH(BundleContext bundleContext, - String protocolName) - { - super(bundleContext, protocolName); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSHImpl.java deleted file mode 100644 index 68e33bc..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSHImpl.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -import org.osgi.framework.*; - -/** - * The SSH protocol provider factory creates instances of the SSH - * protocol provider service. One Service instance corresponds to one account. - * - * @author Shobhit Jindal - */ -public class ProtocolProviderFactorySSHImpl - extends ProtocolProviderFactorySSH -{ - - /** - * Creates an instance of the ProtocolProviderFactorySSHImpl. - */ - public ProtocolProviderFactorySSHImpl() - { - super(SSHActivator.getBundleContext(), ProtocolNames.SSH); - } - - /** - * Initializaed and creates an account corresponding to the specified - * accountProperties and registers the resulting ProtocolProvider in the - * <tt>context</tt> BundleContext parameter. - * - * @param userIDStr tha/a 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. - */ - @Override - public AccountID installAccount( - String userIDStr, - Map<String, String> accountProperties) - { - BundleContext context = SSHActivator.getBundleContext(); - if (context == null) - throw new NullPointerException("The specified BundleContext was " + - "null"); - - if (userIDStr == null) - throw new NullPointerException("The specified AccountID was null"); - - if (accountProperties == null) - throw new NullPointerException("The specified property map was" + - " null"); - - accountProperties.put(USER_ID, userIDStr); - - AccountID accountID = new SSHAccountID(userIDStr, accountProperties); - - //make sure we haven't seen this account id before. - if (registeredAccounts.containsKey(accountID)) - throw new IllegalStateException( - "An account for id " + userIDStr + " was already" + - " installed!"); - - //first store the account and only then load it as the load generates - //an osgi event, the osgi event triggers (through the UI) a call to the - //ProtocolProviderService.register() method and it needs to acces - //the configuration service and check for a stored password. - this.storeAccount(accountID, false); - - accountID = loadAccount(accountProperties); - -/* ServiceReference ppServiceRef = context - .getServiceReference(ProtocolProviderService.class.getName()); - - ProtocolProviderService ppService = (ProtocolProviderService) - context.getService(ppServiceRef); - - OperationSetPersistentPresence operationSetPersistentPresence = - (OperationSetPersistentPresence) ppService.getOperationSet( - OperationSetPersistentPresence.class); - - try - { - // The below should never fail for SSH accounts - operationSetPersistentPresence.subscribe(userIDStr); - - } - catch(OperationFailedException ex) - { - ex.printStackTrace(); - } -*/ - return accountID; - } - - @Override - protected AccountID createAccountID(String userID, Map<String, String> accountProperties) - { - return new SSHAccountID(userID, accountProperties); - } - - @Override - protected ProtocolProviderService createService(String userID, - AccountID accountID) - { - ProtocolProviderServiceSSHImpl service = - new ProtocolProviderServiceSSHImpl(); - - service.initialize(userID, accountID); - return service; - } - -// /** -// * 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). -// * -// * @param accountID the AccountID for the account whose password we're -// * storing. -// * @param passwd the password itself. -// * -// * @throws java.lang.IllegalArgumentException if no account corresponding -// * to <tt>accountID</tt> has been previously stored. -// */ -// public void storePassword(AccountID accountID, String passwd) -// throws IllegalArgumentException -// { -// super.storePassword(SSHActivator.getBundleContext(), -// accountID, -// String.valueOf(Base64.encode(passwd.getBytes()))); -// } -// -// /** -// * 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. -// * -// * @throws java.lang.IllegalArgumentException if no account corresponding -// * to <tt>accountID</tt> has been previously stored. -// */ -// public String loadPassword(AccountID accountID) -// throws IllegalArgumentException -// { -// String password = super.loadPassword(SSHActivator.getBundleContext() -// , accountID ); -// return(String.valueOf(Base64.decode(password))); -// } - - @Override - public void modifyAccount( ProtocolProviderService protocolProvider, - Map<String, String> accountProperties) - throws NullPointerException - { - // TODO Auto-generated method stub - - } - -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderServiceSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderServiceSSHImpl.java deleted file mode 100644 index a4b16e6..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderServiceSSHImpl.java +++ /dev/null @@ -1,662 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; - -import javax.swing.*; - -import net.java.sip.communicator.service.gui.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.Logger; - -import org.osgi.framework.*; - -import com.jcraft.jsch.*; - -/** - * A SSH implementation of the ProtocolProviderService. - * - * @author Shobhit Jindal - */ -public class ProtocolProviderServiceSSHImpl - extends AbstractProtocolProviderService -{ - 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 final Object initializationLock = new Object(); - - private OperationSetBasicInstantMessagingSSHImpl basicInstantMessaging; - - private OperationSetFileTransferSSHImpl fileTranfer; - - /** - * 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() - { - if (logger.isTraceEnabled()) - 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); - - addSupportedOperationSet( - OperationSetPersistentPresence.class, - persistentPresence); - //register it once again for those that simply need presence and - //won't be smart enough to check for a persistent presence - //alternative - addSupportedOperationSet( - OperationSetPresence.class, - persistentPresence); - - //initialize the IM operation set - basicInstantMessaging = new - OperationSetBasicInstantMessagingSSHImpl( - this); - addSupportedOperationSet( - OperationSetBasicInstantMessaging.class, - basicInstantMessaging); - - //initialze the file transfer operation set - fileTranfer = new OperationSetFileTransferSSHImpl(this); - addSupportedOperationSet( - OperationSetFileTransfer.class, - 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 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); - - //initializing the reader and writers of ssh contact - - persistentPresence.changeContactPresenceStatus( - sshContact, - SSHStatusEnum.CONNECTED); - - showWelcomeMessage(sshContact); - - sshContact.setMessageType(ContactSSH - .CONVERSATION_MESSAGE_RECEIVED); - - sshContact.setConnectionInProgress(false); - - Thread.sleep(1500); - - sshContact.setCommandSent(true); - - basicInstantMessaging.sendInstantMessage( - sshContact, - firstMessage); - } - // rigorous 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 - * @throws IOException if the shell channel cannot be created - */ - 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 - * @throws JSchException if something went wrong in JSch - * @throws IOException if I/O exception occurred - */ - 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 - { - if (logger.isInfoEnabled()) - logger.info("Creating a new SSH Session to " - + sshContact.getHostName()); - - // creating a new JSch Stack identifier for contact - JSch jsch = new JSch(); - - String knownHosts = - accountID.getAccountPropertyString("KNOWN_HOSTS_FILE"); - - if(!knownHosts.equals("Optional")) - jsch.setKnownHosts(knownHosts); - - String identitiyKey = - accountID.getAccountPropertyString("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; - if (logger.isTraceEnabled()) - 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); - - if (logger.isInfoEnabled()) - 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 - * @throws IOException if I/O exception occurred - */ - 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(); -*/ - if (logger.isDebugEnabled()) - 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; - } - - /** - * 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 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 state of the registration of this protocol provider with - * the corresponding registration service. - * - * @return ProviderRegistrationState - */ - public RegistrationState getRegistrationState() - { - return currentRegistrationState; - } - - /** - * 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; - } - if (logger.isTraceEnabled()) - 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); - } - - /* - * (non-Javadoc) - * - * @see net.java.sip.communicator.service.protocol.ProtocolProviderService# - * isSignallingTransportSecure() - */ - public boolean isSignalingTransportSecure() - { - return false; - } - - /** - * Returns the "transport" protocol of this instance used to carry the - * control channel for the current protocol service. - * - * @return The "transport" protocol of this instance: TCP. - */ - public TransportProtocol getTransportProtocol() - { - return TransportProtocol.TCP; - } - - /** - * Returns the ssh protocol icon. - * @return the ssh protocol icon - */ - public ProtocolIcon getProtocolIcon() - { - return sshIcon; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/Resources.java b/src/net/java/sip/communicator/impl/protocol/ssh/Resources.java deleted file mode 100644 index 48b9b49..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/Resources.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import net.java.sip.communicator.service.resources.*; - -/** - * @author Shobhit Jindal - */ -public class Resources -{ - /** - * The SSH logo imageID. - */ - public static ImageID SSH_LOGO = new ImageID("protocolIconSsh"); - - /** - * Returns an string corresponding to the given key. - * - * @param key The key of the string. - * - * @return a string corresponding to the given key. - */ - public static String getString(String key) - { - return SSHActivator.getResources().getI18NString(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 byte[] getImage(ImageID imageID) - { - return SSHActivator.getResources().getImageInBytes(imageID.getId()); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHAccountID.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHAccountID.java deleted file mode 100644 index 8f9dda7..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/SSHAccountID.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.java.sip.communicator.impl.protocol.ssh; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * The SSH implementation of a sip-communicator account id. - * @author Shobhit Jindal - */ -public class SSHAccountID - extends AccountID -{ - /** - * Creates an account id from the specified id and account properties. - * - * @param userID the user identifier correspnding to thi account - * @param accountProperties any other properties necessary for the account. - */ - SSHAccountID(String userID, Map<String, String> accountProperties) - { - super(userID - , accountProperties - , "SSH" - , "sip-communicator.org"); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHActivator.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHActivator.java deleted file mode 100644 index 7108020..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/SSHActivator.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.resources.*; -import net.java.sip.communicator.util.*; - -import org.jitsi.service.resources.*; -import org.osgi.framework.*; - -/** - * Loads the SSH provider factory and registers its services in the OSGI - * bundle context. - * - * @author Shobhit Jindal - */ -public class SSHActivator - implements BundleActivator -{ - private static final Logger logger - = Logger.getLogger(SSHActivator.class); - - /** - * A reference to the registration of our SSH protocol provider - * factory. - */ - private ServiceRegistration sshPpFactoryServReg = null; - - /** - * A reference to the SSH protocol provider factory. - */ - private static ProtocolProviderFactorySSHImpl - sshProviderFactory = null; - - /** - * The currently valid bundle context. - */ - private static BundleContext bundleContext = null; - - private static ResourceManagementService resourcesService; - - /** - * Called when this bundle is started. In here we'll export the - * ssh ProtocolProviderFactory implementation so that it could be - * possible to register accounts with it in SIP Communicator. - * - * @param context The execution context of the bundle being started. - * @throws Exception If this method throws an exception, this bundle is - * marked as stopped and the Framework will remove this bundle's - * listeners, unregister all services registered by this bundle, and - * release all services used by this bundle. - */ - public void start(BundleContext context) - throws Exception - { - bundleContext = context; - - Hashtable<String, String> hashtable = new Hashtable<String, String>(); - hashtable.put(ProtocolProviderFactory.PROTOCOL, "SSH"); - - sshProviderFactory = new ProtocolProviderFactorySSHImpl(); - - //reg the ssh provider factory. - sshPpFactoryServReg = context.registerService( - ProtocolProviderFactory.class.getName(), - sshProviderFactory, - hashtable); - - if (logger.isInfoEnabled()) - logger.info("SSH protocol implementation [STARTED]."); - } - - /** - * Returns a reference to the bundle context that we were started with. - * @return bundleContext a reference to the BundleContext instance - * that we were started with. - */ - public static BundleContext getBundleContext() - { - return bundleContext; - } - - /** - * Retrurns a reference to the protocol provider factory that we have - * registered. - * @return a reference to the <tt>ProtocolProviderFactoryJabberImpl</tt> - * instance that we have registered from this package. - */ - public static ProtocolProviderFactorySSHImpl - getProtocolProviderFactory() - { - return sshProviderFactory; - } - - - /** - * Called when this bundle is stopped so the Framework can perform the - * bundle-specific activities necessary to stop the bundle. - * - * @param context The execution context of the bundle being stopped. - * @throws Exception If this method throws an exception, the bundle is - * still marked as stopped, and the Framework will remove the bundle's - * listeners, unregister all services registered by the bundle, and - * release all services used by the bundle. - */ - public void stop(BundleContext context) - throws Exception - { - sshProviderFactory.stop(); - sshPpFactoryServReg.unregister(); - if (logger.isInfoEnabled()) - logger.info("SSH protocol implementation [STOPPED]."); - } - - /** - * Returns the <tt>ResourceManagementService</tt>. - * - * @return the <tt>ResourceManagementService</tt>. - */ - public static ResourceManagementService getResources() - { - if (resourcesService == null) - resourcesService = - ResourceManagementServiceUtils.getService(bundleContext); - return resourcesService; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHContactInfo.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHContactInfo.java deleted file mode 100644 index 47bb484..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/SSHContactInfo.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.awt.*; -import java.awt.event.*; -import java.text.*; - -import javax.swing.*; -import javax.swing.text.*; - -import net.java.sip.communicator.plugin.desktoputil.*; - -/** - * @author Shobhit Jindal - */ -class SSHContactInfo - extends SIPCommDialog -{ - /** - * Serial version UID. - */ - private static final long serialVersionUID = 0L; - - private ContactSSH sshContact; - - private JPanel mainPanel = new TransparentPanel(); - private JPanel machinePanel = new TransparentPanel(); - private JPanel detailNamesPanel = new TransparentPanel(); - private JPanel detailFieldsPanel = new TransparentPanel(); - private JPanel detailsPanel = new TransparentPanel(); - - private JCheckBox addDetailsCheckBox = new SIPCommCheckBox("Add Details"); - - private JButton doneButton = new JButton("Done"); - private JLabel machineID = new JLabel("Hostname / IP: "); - private JTextField machineIDField = new JTextField(); - private JLabel userName = new JLabel("User Name: "); - private JTextField userNameField = new JTextField(); - private JLabel password = new JLabel("Password: "); - private JTextField passwordField = new JPasswordField(); - private JLabel port = new JLabel("Port: "); - - private JFormattedTextField portField; - private JLabel secs = new JLabel("secs"); - private JLabel statusUpdate = new JLabel("Update Interval: "); - private JLabel terminalType = new JLabel("Terminal Type: "); - private JTextField terminalTypeField = new JTextField("SIP Communicator"); - private JSpinner updateTimer = new JSpinner(); - - private JPanel emptyPanel1 = new TransparentPanel(); - - private JPanel emptyPanel2 = new TransparentPanel(); - - private JPanel emptyPanel3 = new TransparentPanel(); - - private JPanel emptyPanel4 = new TransparentPanel(); - - private JPanel emptyPanel5 = new TransparentPanel(); - - private JPanel emptyPanel6 = new TransparentPanel(); - - private JPanel emptyPanel7 = new TransparentPanel(); - - private JPanel emptyPanel8 = new TransparentPanel(); - - private JPanel emptyPanel9 = new TransparentPanel(); - - private JPanel emptyPanel10 = new TransparentPanel(); - - private JPanel emptyPanel11 = new TransparentPanel(); - -// private ContactGroup contactGroup = null; - - /** - * Creates a new instance of SSHContactInfo - * - * @param sshContact the concerned contact - */ - public SSHContactInfo(ContactSSH sshContact) { - super(true); - - this.sshContact = sshContact; - initForm(); - - this.getContentPane().add(mainPanel); - - this.setSize(370, 325); - - this.setResizable(false); - - this.setTitle("SSH: Account Details of " + sshContact.getDisplayName()); - - Toolkit toolkit = Toolkit.getDefaultToolkit(); - Dimension screenSize = toolkit.getScreenSize(); - - int x = (screenSize.width - this.getWidth()) / 2; - int y = (screenSize.height - this.getHeight()) / 2; - - this.setLocation(x,y); - -// ProtocolProviderServiceSSHImpl.getUIService().getConfigurationWindow(). -// addConfigurationForm(this); - } - - /** - * initialize the form. - */ - public void initForm() { - updateTimer.setValue(30); - MaskFormatter maskFormatter = new MaskFormatter(); - try { - maskFormatter.setMask("#####"); - } catch (ParseException ex) { - ex.printStackTrace(); - } - maskFormatter.setAllowsInvalid(false); - portField = new JFormattedTextField(maskFormatter); - portField.setValue(22); - - userNameField.setEnabled(false); - passwordField.setEditable(false); - portField.setEnabled(false); - terminalTypeField.setEnabled(false); - updateTimer.setEnabled(false); - - mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); - machinePanel.setLayout(new BoxLayout(machinePanel, BoxLayout.X_AXIS)); - detailNamesPanel.setLayout(new BoxLayout(detailNamesPanel, - BoxLayout.Y_AXIS)); - detailFieldsPanel.setLayout(new BoxLayout(detailFieldsPanel, - BoxLayout.Y_AXIS)); - detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.X_AXIS)); - - machinePanel.add(machineID); - machinePanel.add(machineIDField); - - detailNamesPanel.add(userName); - detailNamesPanel.add(emptyPanel1); - detailNamesPanel.add(password); - detailNamesPanel.add(emptyPanel2); - detailNamesPanel.add(port); - detailNamesPanel.add(emptyPanel3); - detailNamesPanel.add(statusUpdate); - detailNamesPanel.add(emptyPanel4); - detailNamesPanel.add(terminalType); - - detailFieldsPanel.add(userNameField); - detailFieldsPanel.add(emptyPanel5); - detailFieldsPanel.add(passwordField); - detailFieldsPanel.add(emptyPanel6); - detailFieldsPanel.add(portField); - detailFieldsPanel.add(emptyPanel7); - detailFieldsPanel.add(updateTimer); - detailFieldsPanel.add(emptyPanel8); - detailFieldsPanel.add(terminalTypeField); - - detailsPanel.add(detailNamesPanel); - detailsPanel.add(detailFieldsPanel); - - detailsPanel.setBorder(BorderFactory.createTitledBorder("Details")); - - mainPanel.add(emptyPanel9); - mainPanel.add(machinePanel); - mainPanel.add(addDetailsCheckBox); - mainPanel.add(detailsPanel); - mainPanel.add(emptyPanel10); - mainPanel.add(doneButton); - mainPanel.add(emptyPanel11); - - addDetailsCheckBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent event) { - addDetailsCheckBox.setEnabled(false); - userNameField.setEnabled(true); - passwordField.setEditable(true); - portField.setEnabled(true); - terminalTypeField.setEnabled(true); - updateTimer.setEnabled(true); - - userNameField.grabFocus(); - } - }); - - doneButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent event) { - if(machineIDField.getText().equals("")) { - machineIDField.setText("Field needed"); - return; - } - - sshContact.savePersistentDetails(); - - //add contact to contact list - ((OperationSetPersistentPresenceSSHImpl)sshContact - .getParentPresenceOperationSet()) - .addContactToList( - sshContact.getParentContactGroup(), - sshContact); - - setVisible(false); - } - }); - } - - /** - * Return the ssh icon - * - * @return the ssh icon - */ - public byte[] getIcon() { - return Resources.getImage(Resources.SSH_LOGO); - } - -// -// public void setContactGroup(ContactGroup contactGroup) -// { -// this.contactGroup = contactGroup; -// } -// -// public ContactGroup getContactGroup() -// { -// return this.contactGroup; -// } - - /** - * Sets the UserName of the dialog - * - * @param userName to be associated - */ - public void setUserNameField(String userName) { - this.userNameField.setText(userName); - } - - /** - * Sets the Password of the dialog - * - * @param password to be associated - */ - public void setPasswordField(String password) { - this.passwordField.setText(password); - } - - /** - * Return the hostname - * - * @return the hostname - */ - public String getHostName() { - return this.machineIDField.getText(); - } - - /** - * Return the username - * - * @return the username - */ - public String getUserName() { - return this.userNameField.getText(); - } - - /** - * Return the password - * - * @return the password in a clear form - */ - public String getPassword() { - return this.passwordField.getText(); - } - - /** - * Return the terminal type - * - * @return the terminal type - */ - public String getTerminalType() { - return this.terminalTypeField.getText(); - } - - /** - * Return the port - * - * @return the port value - */ - public int getPort() { - return Integer.parseInt(this.portField.getText().trim()); - } - - /** - * Return the update interval - * - * @return the update interval - */ - public int getUpdateInterval() { - return Integer.parseInt(String.valueOf(this.updateTimer.getValue())); - } - - /** - * Sets the HostName of the dialog - * - * @param hostName to be associated - */ - public void setHostNameField(String hostName) { - this.machineIDField.setText(hostName); - } - - /** - * Sets the Terminal Type of the dialog - * - * @param termType to be associated - */ - public void setTerminalType(String termType) { - this.terminalTypeField.setText(termType); - } - - /** - * Sets the Update Interval of the dialog - * - * @param interval to be associated - */ - public void setUpdateInterval(int interval) { - this.updateTimer.setValue(interval); - } - - /** - * Sets the Port of the dialog - * - * @param port to be associated - */ - public void setPort(String port) { - this.portField.setText(port); - } - - @Override - protected void close(boolean isEscaped) - { - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHFileTransferDaemon.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHFileTransferDaemon.java deleted file mode 100644 index 3b522ed..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/SSHFileTransferDaemon.java +++ /dev/null @@ -1,468 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; - -import net.java.sip.communicator.service.gui.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.Logger; - -import com.jcraft.jsch.*; - -/** - * @author Shobhit Jindal - */ -public class SSHFileTransferDaemon - extends Thread -{ - private static final Logger logger = - Logger.getLogger(SSHFileTransferDaemon .class); - - /** - * The contact of the remote machine - */ - private ContactSSH sshContact; - - /** - * The currently valid ssh protocol provider - */ - private ProtocolProviderServiceSSHImpl ppService; - - /** - * JSch Channel to be used for file transfer - */ - private Channel fileTransferChannel; - - /** - * The identifier for the Input Stream associated with SCP Channel - */ - private InputStream scpInputStream = null; - - /** - * The identifier for the Output Stream associated with SCP Channel - */ - private OutputStream scpOutputStream = null; - - /** - * Identifier of local file - */ - private String localPath; - - /** - * Identifier of remote file - */ - private String remotePath; - - /** - * File to be uploaded or saved - */ - private File file; - - /** - * The file input stream associated with the file to be uploaded - */ - private FileInputStream fileInputStream; - - /** - * The file output stream associated with the file to be uploaded - */ - private FileOutputStream fileOutputStream; - - /** - * The boolean which determines whether we are uploading or downloading - * files - */ - private boolean uploadFile; - - /** - * The currently valid ssh persistent presence operation set - */ - private OperationSetPersistentPresenceSSHImpl opSetPersPresence = null; - - /** - * The currently valid ssh instant messaging operation set - */ - private OperationSetBasicInstantMessagingSSHImpl instantMessaging = null; - - /** - * Creates a new instance of SSHFileTransferDaemon - * - * - * @param sshContact The contact of the remote machine - * @param ppService The current ssh protocol provider - */ - public SSHFileTransferDaemon( - ContactSSH sshContact, - ProtocolProviderServiceSSHImpl ppService) - { - super(); - this.sshContact = sshContact; - this.opSetPersPresence = (OperationSetPersistentPresenceSSHImpl) - ppService.getOperationSet(OperationSetPersistentPresence.class); - this.instantMessaging = (OperationSetBasicInstantMessagingSSHImpl) - ppService.getOperationSet( - OperationSetBasicInstantMessaging.class); - this.ppService = ppService; - } - - /** - * This method is called when file is to be transfered from local machine - * to remote machine - * - * @param remotePath - the identifier for the remote file - * @param localPath - the identifier for the local file - */ - public void uploadFile( - String remotePath, - String localPath) - { - this.uploadFile = true; - this.remotePath = remotePath; - this.localPath = localPath; - - file = new File(localPath); - - start(); - } - - /** - * This method is called when a file is to be downloaded from remote machine - * to local machine - * - * @param remotePath - the identifier for the remote file - * @param localPath - the identifier for the local file - */ - public void downloadFile( - String remotePath, - String localPath) - { - this.uploadFile = false; - this.remotePath = remotePath; - this.localPath = localPath; - - file = new File(localPath); - - start(); - } - - /** - * Background thread for the file transfer - */ - @Override - public void run() - { - //oldStatus to be resumed earlier - PresenceStatus oldStatus = sshContact.getPresenceStatus(); - - opSetPersPresence.changeContactPresenceStatus( - sshContact, - SSHStatusEnum.CONNECTING); - - try - { - //create a new JSch session if current is invalid - if( !ppService.isSessionValid(sshContact)) - ppService.createSSHSessionAndLogin(sshContact); - - fileTransferChannel = sshContact.getSSHSession() - .openChannel("exec"); - String command; - - // -p = Preserves modification times, access times, and modes from - // the original file - if(uploadFile) - command = "scp -p -t " + remotePath; - else - command = "scp -f " + remotePath; - - //the command to be executed on the remote terminal - ((ChannelExec)fileTransferChannel).setCommand(command); - - scpInputStream = fileTransferChannel.getInputStream(); - scpOutputStream = fileTransferChannel.getOutputStream(); - - fileTransferChannel.connect(); - - //file transfer is setup - opSetPersPresence.changeContactPresenceStatus( - sshContact, - SSHStatusEnum.FILE_TRANSFER); - - if(uploadFile) - { - instantMessaging.deliverMessage( - instantMessaging.createMessage( - "Uploading " + file.getName() + " to server"), - sshContact); - - upload(); - } - else - { - instantMessaging.deliverMessage( - instantMessaging.createMessage( - "Downloading " + file.getName() + " from server"), - sshContact); - - download(); - } - - } - catch(Exception ex) - { - //presently errors(any type) are directly logged directly in chat - instantMessaging.deliverMessage( - instantMessaging.createMessage(ex.getMessage()), - sshContact); - - logger.error(ex.getMessage()); - - try - { - if(fileInputStream!=null) - { - fileInputStream.close(); - } - - if(fileOutputStream!=null) - { - fileOutputStream.close(); - } - } - catch(Exception e) - {} - } - - // restore old status - opSetPersPresence.changeContactPresenceStatus( - sshContact, - oldStatus); - } - - /** - * Check for error in reading stream of remote machine - * - * @return 0 for success, 1 for error, 2 for fatal error, -1 otherwise - * @throws IOException when the network goes down - */ - private int checkAck(InputStream inputStream) - throws IOException - { - int result = inputStream.read(); - - // read error message - if(result==1 || result==2) - { - StringBuffer buffer = new StringBuffer(); - - int ch; - - do - { - //read a line of message - ch = inputStream.read(); - buffer.append((char)ch); - - }while(ch != '\n'); - - ProtocolProviderServiceSSHImpl - .getUIService() - .getPopupDialog() - .showMessagePopupDialog( - buffer.toString(), - "File Transfer Error: " - + sshContact.getDisplayName(), - PopupDialog.ERROR_MESSAGE); - - logger.error(buffer.toString()); - } - - return result; - } - - /** - * Uploads the file to the remote server - * - * @throws IOException when the network goes down - * @throws OperationFailedException when server behaves unexpectedly - */ - private void upload() - throws IOException, - OperationFailedException - { - fileInputStream = new FileInputStream(file); - - byte[] buffer = new byte[1024]; - int result, bytesRead; - - if( (result = checkAck(scpInputStream)) !=0) - throw new OperationFailedException("Error in Ack", result); - - // send "C0644 filesize filename", where filename should not include '/' - long filesize= file.length(); - String command = "C0644 " + filesize + " "; - -// if(lfile.lastIndexOf('/')>0) -// { -// command+=lfile.substring(lfile.lastIndexOf('/')+1); -// } -// else -// { -// command+=lfile; -// } - - command += file.getName() + "\n"; - if (logger.isTraceEnabled()) - logger.trace(command); - scpOutputStream.write(command.getBytes()); - scpOutputStream.flush(); - - if( (result = checkAck(scpInputStream)) !=0) - throw new OperationFailedException("Error in Ack", result); - - while(true) - { - bytesRead = fileInputStream.read(buffer, 0, buffer.length); - if(bytesRead <= 0) - break; - - scpOutputStream.write(buffer, 0, bytesRead); //out.flush(); - } - fileInputStream.close(); - fileInputStream = null; - - // send '\0' - buffer[0]=0; scpOutputStream.write(buffer, 0, 1); - scpOutputStream.flush(); - - if( (result = checkAck(scpInputStream)) !=0) - throw new OperationFailedException("Error in Ack", result); - - scpInputStream.close(); - scpOutputStream.close(); - - fileTransferChannel.disconnect(); - - instantMessaging.deliverMessage( - instantMessaging.createMessage(file.getName() - + " uploaded to Server"), - sshContact); - } - - /** - * Downloads a file from the remote machine - * - * @throws IOException when the network goes down - * @throws OperationFailedException when server behaves unexpectedly - */ - private void download() - throws IOException, - OperationFailedException - { - fileOutputStream = new FileOutputStream(file); - - int result; - - byte[] buffer = new byte[1024]; - - // send '\0' - buffer[0]=0; - - scpOutputStream.write(buffer, 0, 1); - scpOutputStream.flush(); - - int ch = checkAck(scpInputStream); - - if(ch!='C') - { - throw new OperationFailedException("Invalid reply from server", 12); - } - - // read '0644 ' - scpInputStream.read(buffer, 0, 5); - - long filesize=0L; - while(true) - { - if(scpInputStream.read(buffer, 0, 1) < 0) - { - // error - break; - } - if(buffer[0]==' ')break; - filesize=filesize*10L+buffer[0]-'0'; - } - - String file=null; - for(int i=0;true;i++) - { - scpInputStream.read(buffer, i, 1); - if(buffer[i]==(byte)0x0a) - { - file=new String(buffer, 0, i); - break; - } - } - - //System.out.println("filesize="+filesize+", file="+file); - - // send '\0' - buffer[0]=0; - scpOutputStream.write(buffer, 0, 1); - scpOutputStream.flush(); - - // read a content of lfile - int foo; - while(true) - { - if(buffer.length<filesize) - foo=buffer.length; - else - foo=(int)filesize; - - foo = scpInputStream.read(buffer, 0, foo); - if(foo<0) - break; - - fileOutputStream.write(buffer, 0, foo); - filesize-=foo; - if(filesize==0L) break; - } - fileOutputStream.close(); - fileOutputStream=null; - - if( (result = checkAck(scpInputStream)) !=0) - throw new OperationFailedException("Error in Ack", result); - - // send '\0' - buffer[0]=0; - scpOutputStream.write(buffer, 0, 1); - scpOutputStream.flush(); - - scpInputStream.close(); - scpOutputStream.close(); - - fileTransferChannel.disconnect(); - - instantMessaging.deliverMessage( - instantMessaging.createMessage( - this.file.getName() + " downloaded from Server"), - sshContact); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHReaderDaemon.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHReaderDaemon.java deleted file mode 100644 index 2c33b5d..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/SSHReaderDaemon.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.io.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * - * @author Shobhit Jindal - */ -public class SSHReaderDaemon - extends Thread -{ - - /** - * A Buffer to aggregate replies to be sent as one message - */ - private StringBuffer replyBuffer; - - /** - * The identifier of Contact representing the remote machine - */ - private ContactSSHImpl sshContact; - - /** - * The identifier of the message received from server - */ - private String message; - - /** - * An identifier representing the state of Reader Daemon - */ - private boolean isActive = false; - - /** - * This OperationSet delivers incoming message - */ - private OperationSetBasicInstantMessagingSSHImpl instantMessaging; - - /** - * Input Stream of remote user to be read - */ - private InputStream shellInputStream; - - /** - * Buffered Reader associated with above input stream - */ - private InputStreamReader shellReader; - -// /** -// * This OperationSet delivers incoming message -// */ -// private OperationSetPersistentPresenceSSHImpl persistentPresence; - - /** - * Bytes available in Input Stream before reading - */ - private int bytesAvailable; - - private int bytesRead; - - int bufferCount; - - char buf; - - /** - * Creates a new instance of SSHReaderDaemon - */ - public SSHReaderDaemon(ContactSSH sshContact) - { - this.sshContact = (ContactSSHImpl)sshContact; - instantMessaging = - (OperationSetBasicInstantMessagingSSHImpl) - sshContact - .getProtocolProvider() - .getOperationSet( - OperationSetBasicInstantMessaging.class); - } - - /** - * Reads the remote machine, updating the chat window as necessary - * in a background thread - */ - @Override - public void run() - { - shellInputStream = sshContact.getShellInputStream(); - shellReader = sshContact.getShellReader(); - replyBuffer = new StringBuffer(); - - - try - { - do - { - bytesAvailable = shellInputStream.available(); - - if(bytesAvailable == 0 ) - { - // wait if more data is available - // for a slower connection this value need to be raised - // to avoid splitting of messages - Thread.sleep(250); - continue; - } - - bufferCount = 0; - -// if(replyBuffer > 0) - - do - { - // store the responses in a buffer - storeMessage(replyBuffer); - - Thread.sleep(250); - - bytesAvailable = shellInputStream.available(); - - }while(bytesAvailable > 0 && bufferCount<16384); - - message = replyBuffer.toString(); - - if(sshContact.isCommandSent()) - { - // if the response is as a result of a command sent - sshContact.setMessageType( - ContactSSH.CONVERSATION_MESSAGE_RECEIVED); - - message = message.substring(message.indexOf('\n') + 1); - - sshContact.setCommandSent(false); - } - else - { - // server sent an asynchronous message to the terminal - // display it as a system message - sshContact.setMessageType( - ContactSSH.SYSTEM_MESSAGE_RECEIVED); - - //popup disabled -// JOptionPane.showMessageDialog( -// null, -// message, -// "Message from " + sshContact.getDisplayName(), -// JOptionPane.INFORMATION_MESSAGE); - } - - instantMessaging.deliverMessage( - instantMessaging.createMessage(message), - sshContact); - - replyBuffer.delete(0, replyBuffer.length()); - - }while(isActive); - } - catch(Exception ex) - { - ex.printStackTrace(); - } - } - - /** - * Stores the response from server in a temporary buffer - * the bytes available are determined before the function is called - * - * @param replyBuffer to store the response from server - * - * @throws IOException if the network goes down - */ - private void storeMessage(StringBuffer replyBuffer) throws IOException - { - do - { - buf = (char)shellInputStream.read(); - -// System.out.println(String.valueOf(buf)+ " " + (int)buf); - - replyBuffer.append(String.valueOf(buf)); - -// logger.debug(shellReader.readLine()); - - bufferCount++; - - bytesAvailable--; - - }while(bytesAvailable>0 && bufferCount<32700); - } - - public void isActive(boolean isActive) - { - this.isActive = isActive; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHStatusEnum.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHStatusEnum.java deleted file mode 100644 index 0877399..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/SSHStatusEnum.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.ssh; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * An implementation of <tt>PresenceStatus</tt> that enumerates all states that - * a SSH contact can fall into. - * - * @author Shobhit Jindal - */ -public class SSHStatusEnum - extends PresenceStatus -{ - - /** - * Indicates an Offline status or status with 0 connectivity. - */ - public static final SSHStatusEnum OFFLINE - = new SSHStatusEnum( - 0, - "Offline", - ProtocolIconSSHImpl - .getImageInBytes("service.protocol.ssh.OFFLINE_STATUS_ICON")); - - /** - * The Not Available status. Indicates that the user has connectivity - * but might not be able to immediately act (i.e. even less immediately - * than when in an Away status ;-P ) upon initiation of communication. - * - */ - public static final SSHStatusEnum NOT_AVAILABLE - = new SSHStatusEnum( - 35, - "Not Available", - ProtocolIconSSHImpl - .getImageInBytes("service.protocol.ssh.NA_STATUS_ICON")); - - /** - * The Connecting status. Indicate that the user is connecting to remote - * server - */ - public static final SSHStatusEnum CONNECTING - = new SSHStatusEnum( - 55, - "Connecting", - ProtocolIconSSHImpl - .getImageInBytes("service.protocol.ssh.CONNECTING_ICON")); - - /** - * The Online status. Indicate that the user is able and willing to - * communicate. - */ - public static final SSHStatusEnum ONLINE - = new SSHStatusEnum( - 65, - "Online", - ProtocolIconSSHImpl - .getImageInBytes("service.protocol.ssh.SSH_16x16")); - - - /** - * The Connecting status. Indicate that the user is connecting to remote - * server - */ - public static final SSHStatusEnum CONNECTED - = new SSHStatusEnum( - 70, - "Connecting", - ProtocolIconSSHImpl - .getImageInBytes("service.protocol.ssh.CONNECTED_ICON")); - - /** - * The File Transfer status. Indicate that the user is transfering a file - * to/from a remote server - */ - public static final SSHStatusEnum FILE_TRANSFER - = new SSHStatusEnum( - 75, - "Transfering File", - ProtocolIconSSHImpl - .getImageInBytes("service.protocol.ssh.FILE_TRANSFER_ICON")); - - /** - * Initialize the list of supported status states. - */ - private static List<PresenceStatus> supportedStatusSet = new LinkedList<PresenceStatus>(); - static - { - supportedStatusSet.add(OFFLINE); -// supportedStatusSet.add(NOT_AVAILABLE); - supportedStatusSet.add(ONLINE); -// supportedStatusSet.add(CONNECTING); - } - - /** - * Creates an instance of <tt>SSHPresneceStatus</tt> with the - * specified parameters. - * @param status the connectivity level of the new presence status instance - * @param statusName the name of the presence status. - * @param statusIcon the icon associated with this status - */ - private SSHStatusEnum(int status, - String statusName, - byte[] statusIcon) - { - super(status, statusName, statusIcon); - } - - /** - * Returns an iterator over all status instances supproted by the ssh - * provider. - * @return an <tt>Iterator</tt> over all status instances supported by the - * ssh provider. - */ - static Iterator<PresenceStatus> supportedStatusSet() - { - return supportedStatusSet.iterator(); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHUserInfo.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHUserInfo.java deleted file mode 100644 index 082b05f..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/SSHUserInfo.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.java.sip.communicator.impl.protocol.ssh; - -import javax.swing.*; - -import com.jcraft.jsch.*; - -/** - * SSHUserInfo passes authentication details to JSch SSH Stack - * - * @author Shobhit Jindal - */ -class SSHUserInfo - implements UserInfo, - UIKeyboardInteractive -{ - /** - * The Contact of the remote machine - */ - private ContactSSH sshContact; - - /** - * Identifier for failure of authentication - * more explanation below in promptPassword function - */ - private boolean failedOnce = false; - - /** - * Password field for requesting auth details from user - */ - JTextField passwordField=new JPasswordField(20); - - /** - * Creates a UserInfo instance - * - * @param sshContact the contact concerned - */ - SSHUserInfo(ContactSSH sshContact) - { - this.sshContact = sshContact; - } - - /** - * Returns the password of account associated with this contact - * - * @return the password of account associated with this contact - */ - public String getPassword() - { - return sshContact.getPassword(); - } - - /** - * Prompt for accepting the cipher information of the remote server - * - * @param str the string to display - * - * @return the user's answer - */ - public boolean promptYesNo(String str) - { - Object[] options={ "yes", "no" }; - int foo=JOptionPane.showOptionDialog(null, - str, - "Warning", - JOptionPane.DEFAULT_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, options, options[0]); - return foo==0; - } - - /** - * Passphrase authentication presently not implemented - * - * @return null - */ - public String getPassphrase() - { return null; } - - /** - * Passphrase authentication presently not implemented - * - * @return true - */ - public boolean promptPassphrase(String message) - { return true; } - - /** - * Asks user to re-enter password information in case of an auth failure - * - * @param message the message to display - * - * @return the user's answer - */ - public boolean promptPassword(String message) - { - /** - * Auth always fails for the first time for Redhat based machines. - * Trying again with the same password - */ - if(!failedOnce) - { - failedOnce = true; - return true; - } - - Object[] ob={passwordField}; - int result=JOptionPane.showConfirmDialog(null, ob, "Auth Failed: " - + message, - JOptionPane.OK_CANCEL_OPTION); - - if(result==JOptionPane.OK_OPTION) - { - sshContact.setPassword(passwordField.getText()); - return true; - } - - return false; - } - - /** - * Shows a message from server - * - * @param message The message to display - */ - public void showMessage(String message) - { - JOptionPane.showMessageDialog(null, message); - } - - /** - * Keyboard Interactive Auth - not implemented - */ - public String[] promptKeyboardInteractive( - String destination, - String name, - String instruction, - String[] prompt, - boolean[] echo) - { - String response[] = new String[prompt.length]; - response[0] = sshContact.getPassword(); - return response; - } - - -} diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ssh.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/ssh/ssh.provider.manifest.mf deleted file mode 100644 index eb9472f..0000000 --- a/src/net/java/sip/communicator/impl/protocol/ssh/ssh.provider.manifest.mf +++ /dev/null @@ -1,20 +0,0 @@ -Bundle-Activator: net.java.sip.communicator.impl.protocol.ssh.SSHActivator -Bundle-Name: SSH Protocol Provider -Bundle-Description: A bundle providing support for the SSH protocol. -Bundle-Vendor: jitsi.org -Bundle-Version: 0.0.1 -Bundle-SymbolicName: net.java.sip.communicator.protocol.ssh -Import-Package: org.osgi.framework, - javax.crypto, - javax.crypto.interfaces, - javax.crypto.spec, - javax.swing, - javax.swing.border, - javax.swing.text, - org.jitsi.service.configuration, - net.java.sip.communicator.service.gui, - net.java.sip.communicator.service.protocol, - net.java.sip.communicator.service.protocol.event, - org.jitsi.service.resources, net.java.sip.communicator.service.resources, - net.java.sip.communicator.util, - net.java.sip.communicator.plugin.desktoputil diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/AbstractContactGroupYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/AbstractContactGroupYahooImpl.java deleted file mode 100644 index 0514c16..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/AbstractContactGroupYahooImpl.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import net.java.sip.communicator.service.protocol.*; - -/** - * The Yahoo implementation of the service.protocol.ContactGroup interface. There - * are two types of groups possible here. <tt>RootContactGroupYahooImpl</tt> - * which is the root node of the ContactList itself and - * <tt>ContactGroupYahooImpl</tt> which represents standard groups. The - * reason for having those 2 is that generally, Yahoo groups may not contain - * subgroups. A contact list on the other hand may not directly contain buddies. - * - * - * The reason for having an abstract class is only - being able to esily - * recognize our own (Yahoo) contacts. - * @author Damian Minkov - */ -public abstract class AbstractContactGroupYahooImpl - implements ContactGroup -{ - - -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/AdHocChatRoomInvitationYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/AdHocChatRoomInvitationYahooImpl.java deleted file mode 100644 index e849f6c..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/AdHocChatRoomInvitationYahooImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import net.java.sip.communicator.service.protocol.*; - -/** - * The Yahoo implementation of the <tt>AdHocChatRoomInvitation</tt> interface. - * - * @author Rupert Burchardi - * @author Valentin Martinet - */ -public class AdHocChatRoomInvitationYahooImpl - implements AdHocChatRoomInvitation -{ - /** - * Corresponding chat room instance. - */ - private AdHocChatRoom chatRoom; - /** - * The name of the inviter - */ - private String inviter; - - /** - * The invitation reason. - */ - private String reason; - - - /** - * Creates an instance of the <tt>ChatRoomInvitationMsnImpl</tt> by - * specifying the targetChatRoom, the inviter, the reason. - * - * @param targetChatRoom The <tt>AdHocChatRoom</tt> for which the invitation - * is - * @param inviter The <tt>Contact</tt>, which sent the invitation - * @param reason The Reason for the invitation - */ - public AdHocChatRoomInvitationYahooImpl( AdHocChatRoom targetChatRoom, - String inviter, - String reason) - { - this.chatRoom = targetChatRoom; - this.inviter = inviter; - this.reason = reason; - } - - /** - * Returns the corresponding chat room. - * @return The ad-hoc chat room - */ - public AdHocChatRoom getTargetAdHocChatRoom() - { - return chatRoom; - } - - /** - * Returns the corresponding inviter. - * @return The name of the inviter - */ - public String getInviter() - { - return inviter; - } - - /** - * Returns the invitation reason. - * @return the invitation reason - */ - public String getReason() - { - return reason; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/AdHocChatRoomYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/AdHocChatRoomYahooImpl.java deleted file mode 100644 index 2775f6f..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/AdHocChatRoomYahooImpl.java +++ /dev/null @@ -1,581 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; -import ymsg.network.*; - -/** - * Represents a Yahoo ad-hoc chat room, where multiple chat users could - * communicate in a many-to-many fashion. - * - * @author Rupert Burchardi - * @author Valentin Martinet - */ -public class AdHocChatRoomYahooImpl - implements AdHocChatRoom -{ - private static final Logger logger = Logger - .getLogger(AdHocChatRoomYahooImpl.class); - - /** - * Listeners that will be notified of changes in member status in the room - * such as member joined, left or being kicked or dropped. - */ - private Vector<AdHocChatRoomParticipantPresenceListener> memberListeners - = new Vector<AdHocChatRoomParticipantPresenceListener>(); - - /** - * Listeners that will be notified every time a new message is received on - * this ad-hoc chat room. - */ - private Vector<AdHocChatRoomMessageListener> messageListeners - = new Vector<AdHocChatRoomMessageListener>(); - - /** - * The protocol provider that created us - */ - private ProtocolProviderServiceYahooImpl provider = null; - - /** - * The operation set that created us. - */ - private OperationSetAdHocMultiUserChatYahooImpl opSetMuc = null; - - /** - * The list of participants of this chat room. - */ - private Hashtable<String, Contact> participants - = new Hashtable<String, Contact>(); - - /** - * The nickname of this chat room local user participant. - */ - private String nickname; - - /** - * The yahoo conference model of this ad-hoc chat room, its the - * representation of an ad-hoc chat room in the lib for this protocol. - */ - private YahooConference yahooConference = null; - - /** - * Creates an instance of a chat room that has been. - * - * @param multiUserChat - * MultiUserChat - * @param provider - * a reference to the currently valid jabber protocol provider. - */ - public AdHocChatRoomYahooImpl( YahooConference multiUserChat, - ProtocolProviderServiceYahooImpl provider) - { - this.yahooConference = multiUserChat; - this.provider = provider; - this.opSetMuc = (OperationSetAdHocMultiUserChatYahooImpl) provider - .getOperationSet(OperationSetAdHocMultiUserChat.class); - } - - /** - * Registers <tt>listener</tt> so that it would receive events every time a - * new message is received on this chat room. - * - * @param listener A <tt>MessageListener</tt> that would be notified every - * time a new message is received on this chat room. - */ - public void addMessageListener(AdHocChatRoomMessageListener listener) - { - synchronized (messageListeners) - { - if (!messageListeners.contains(listener)) - messageListeners.add(listener); - } - } - - /** - * Removes <tt>listener</tt> so that it won't receive any further message - * events from this room. - * - * @param listener The <tt>MessageListener</tt> to remove from this room - */ - public void removeMessageListener(AdHocChatRoomMessageListener listener) - { - synchronized (messageListeners) - { - messageListeners.remove(listener); - } - } - - /** - * Adds a listener that will be notified of changes in our status in the - * room. - * - * @param listener A participant status listener. - */ - public void addParticipantPresenceListener( - AdHocChatRoomParticipantPresenceListener listener) - { - synchronized (memberListeners) - { - if (!memberListeners.contains(listener)) - memberListeners.add(listener); - } - } - - /** - * Removes a listener that was being notified of changes in the status of - * other chat room participants. - * - * @param listener A participant status listener. - */ - public void removeParticipantPresenceListener( - AdHocChatRoomParticipantPresenceListener listener) - { - synchronized (memberListeners) - { - memberListeners.remove(listener); - } - } - - /** - * Create a Message instance for sending a simple text messages with default - * (text/plain) content type and encoding. - * - * @param messageText - * the string content of the message. - * @return Message the newly created message - */ - public Message createMessage(String messageText) - { - Message msg = new MessageYahooImpl(messageText, - OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE, - OperationSetBasicInstantMessaging.DEFAULT_MIME_ENCODING, null); - return msg; - } - - /** - * Returns a <tt>List</tt> of <tt>Contact</tt>s corresponding to all members - * currently participating in this room. - * - * @return a <tt>List</tt> of <tt>Contact</tt> corresponding to all room - * members. - */ - public List<Contact> getParticipants() - { - return new LinkedList<Contact>(participants.values()); - } - - /** - * Updates the member list of the chat room. - * - */ - public void updateParticipantsList() - { - Iterator<?> it = yahooConference.getMembers().iterator(); - - while (it.hasNext()) - { - YahooUser user = (YahooUser) it.next(); - Contact contact; - OperationSetPersistentPresenceYahooImpl presenceOpSet - = (OperationSetPersistentPresenceYahooImpl) this - .getParentProvider().getOperationSet( - OperationSetPersistentPresence.class); - - contact = presenceOpSet.findContactByID(user.getId()); - - if(!participants.containsKey(contact.getDisplayName())) - { - participants.put(contact.getDisplayName(), contact); - } - } - } - - /** - * Returns the identifier of this <tt>AdHocChatRoom</tt>. - * - * @return a <tt>String</tt> containing the identifier of this - * <tt>AdHocChatRoom</tt>. - */ - public String getIdentifier() - { - return yahooConference.getName(); - } - - /** - * Returns the number of participants that are currently in this ad-hoc chat - * room. - * - * @return the number of <tt>Contact</tt>s, currently participating in - * this ad-hoc room. - */ - public int getParticipantsCount() - { - return yahooConference.getMembers().size(); - } - - /** - * Returns the name of this <tt>AdHocChatRoom</tt>. - * - * @return a <tt>String</tt> containing the name of this - * <tt>AdHocChatRoom</tt>. - */ - public String getName() - { - return yahooConference.getName(); - } - - /** - * Returns the protocol provider service that created us. - * - * @return the protocol provider service that created us. - */ - public ProtocolProviderService getParentProvider() - { - return provider; - } - - /** - * Returns the local user's nickname in the context of this chat room or - * <tt>null</tt> if not currently joined. - * - * @return the nickname currently being used by the local user in the - * context of the local ad-hoc chat room. - */ - - public String getUserNickname() - { - if(nickname == null) - nickname = provider.getYahooSession().getLoginIdentity().getId(); - - return nickname; - } - - /** - * Invites another user to this room. If we're not joined nothing will - * happen. - * - * @param userAddress The identifier of the contact (email address or yahoo - * id) - * @param reason The invite reason, which is send to the invitee. - */ - public void invite(String userAddress, String reason) - { - try - { - provider.getYahooSession().extendConference(yahooConference, - userAddress, reason); - } - catch (IOException ioe) - { - if (logger.isDebugEnabled()) - logger.debug("Failed to invite the user: " + userAddress - + " Error: " + ioe); - } - } - - /** - * Indicates whether or not this chat room is corresponding to a server - * channel. Note: Returns always <code>false</code>. - * - * @return Always <code>false</code> since system chat room can't be joined - * with current yahoo library. - */ - public boolean isSystem() - { - return false; - } - - /** - * Joins this chat room with the nickname of the local user so that the user - * would start receiving events and messages for it. - * - * @throws OperationFailedException with the corresponding code if an error - * occurs while joining the room. - */ - public void join() throws OperationFailedException - { - this.nickname = provider.getAccountID().getUserID(); - try - { - provider.getYahooSession().acceptConferenceInvite(yahooConference); - - // We don't specify a reason. - opSetMuc.fireLocalUserPresenceEvent(this, - LocalUserAdHocChatRoomPresenceChangeEvent.LOCAL_USER_JOINED, - null); - } - catch (Exception e) - { - if (logger.isDebugEnabled()) - logger.debug("Couldn't join the chat room: " - + yahooConference.getName() + e); - } - } - - /** - * Leave this chat room. Once this method is called, the user won't be - * listed as a member of the chat room any more and no further chat events - * will be delivered. Depending on the underlying protocol and - * implementation leave() might cause the room to be destroyed if it has - * been created by the local user. - */ - public void leave() - { - try - { - provider.getYahooSession().leaveConference(yahooConference); - - Iterator< Map.Entry<String, Contact>> membersSet - = participants.entrySet().iterator(); - - while (membersSet.hasNext()) - { - Map.Entry<String, Contact> memberEntry = membersSet.next(); - Contact participant = memberEntry.getValue(); - - fireParticipantPresenceEvent(participant, - AdHocChatRoomParticipantPresenceChangeEvent.CONTACT_LEFT, - "Local user has left the chat room."); - } - } - catch (IOException ioe) - { - if (logger.isDebugEnabled()) - logger.debug("Failed to leave the chat room: " - + yahooConference.getName() + " Error: " + ioe); - } - - participants.clear(); - } - - /** - * Sends the <tt>message</tt> to the destination indicated by the - * <tt>to</tt> contact. - * - * @param message The <tt>Message</tt> to send. - * @throws OperationFailedException if the underlying stack is not - * registered or initialized or if the chat room is not joined. - */ - public void sendMessage(Message message) throws OperationFailedException - { - assertConnected(); - - try - { - provider.getYahooSession().sendConferenceMessage(yahooConference, - message.getContent()); - - AdHocChatRoomMessageDeliveredEvent msgDeliveredEvt - = new AdHocChatRoomMessageDeliveredEvent( - this, - new Date(), - message, - ChatRoomMessageDeliveredEvent.CONVERSATION_MESSAGE_DELIVERED); - - fireMessageEvent(msgDeliveredEvt); - } - catch (Exception e) - { - if (logger.isDebugEnabled()) - logger.debug("Failed to send a conference message."); - } - } - - /** - * Notifies all interested listeners that a - * <tt>AdHocChatRoomMessageDeliveredEvent</tt>, - * <tt>AdHocChatRoomMessageReceivedEvent</tt> or a - * <tt>AdHocChatRoomMessageDeliveryFailedEvent</tt> has been fired. - * @param evt The specific event - */ - public void fireMessageEvent(EventObject evt) - { - Iterator<AdHocChatRoomMessageListener> listeners = null; - synchronized (messageListeners) - { - listeners = new ArrayList<AdHocChatRoomMessageListener>( - messageListeners).iterator(); - } - - while (listeners.hasNext()) - { - AdHocChatRoomMessageListener listener = listeners.next(); - - if (evt instanceof AdHocChatRoomMessageDeliveredEvent) - { - listener.messageDelivered( - (AdHocChatRoomMessageDeliveredEvent) evt); - } - else if (evt instanceof AdHocChatRoomMessageReceivedEvent) - { - listener.messageReceived( - (AdHocChatRoomMessageReceivedEvent) evt); - } - else if (evt instanceof AdHocChatRoomMessageDeliveryFailedEvent) - { - listener.messageDeliveryFailed( - (AdHocChatRoomMessageDeliveryFailedEvent) evt); - } - } - } - - /** - * Creates the corresponding AdHocChatRoomParticipantPresenceChangeEvent and - * notifies all <tt>AdHocChatRoomParticipantPresenceListener</tt>s that a - * Contact has joined or left this <tt>AdHocChatRoom</tt>. - * - * @param participant the <tt>Contact</tt> that this - * @param eventID the identifier of the event - * @param eventReason the reason of the event - */ - public void fireParticipantPresenceEvent(Contact participant, String eventID, - String eventReason) - { - AdHocChatRoomParticipantPresenceChangeEvent evt - = new AdHocChatRoomParticipantPresenceChangeEvent(this, - participant, - eventID, - eventReason); - - if (logger.isTraceEnabled()) - logger.trace("Will dispatch the following ChatRoom event: " + evt); - - Iterator<AdHocChatRoomParticipantPresenceListener> listeners = null; - synchronized (memberListeners) - { - listeners = new ArrayList<AdHocChatRoomParticipantPresenceListener> - (memberListeners).iterator(); - } - - while (listeners.hasNext()) - { - AdHocChatRoomParticipantPresenceListener listener = listeners.next(); - - listener.participantPresenceChanged(evt); - } - } - - /** - * Finds the participant of this ad-hoc chat room corresponding to the - * given address. - * - * @param address the address to search for. - * @return the participant of this chat room corresponding to the given - * nick name. - */ - public Contact findParticipantForAddress(String address) - { - Iterator<Contact> participantsIter - = this.participants.values().iterator(); - - while (participantsIter.hasNext()) - { - Contact contact = participantsIter.next(); - - if (contact.getAddress().equals(address)) - { - return contact; - } - } - - return null; - } - - /** - * Removes the specified ad-hoc chat room participant from the participants - * list of this ad-hoc chat room. - * @param participant The member, who should be removed from the ad-hoc chat room - * participants list. - */ - public void removeChatRoomParticipant(Contact participant) - { - if(participant == null) - return; - - participants.remove(participant.getDisplayName()); - - fireParticipantPresenceEvent(participant, - AdHocChatRoomParticipantPresenceChangeEvent.CONTACT_LEFT, null); - } - - /** - * Adds a participant to the ad-hoc chat room participant list. - * @param participant The participant, who should be added to the ad-hoc - * chat room participant list. - */ - public void addChatRoomParticipant(Contact participant) - { - if (participant == null) - return; - - if (!participants.containsKey(participant.getDisplayName())) - { - participants.put(participant.getDisplayName(), participant); - - fireParticipantPresenceEvent(participant, - AdHocChatRoomParticipantPresenceChangeEvent.CONTACT_JOINED, - null); - } - } - - /** - * Returns the yahoo conference model of this chat room. - * @return The yahoo conference. - */ - public YahooConference getYahooConference() - { - return yahooConference; - } - - /** - * 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 (provider == null) - throw new IllegalStateException( - "The provider must be non-null and signed on the " - +"service before being able to communicate."); - if (!provider.isRegistered()) - throw new IllegalStateException( - "The provider must be signed on the service before " - +"being able to communicate."); - } - - /** - * Determines whether this chat room should be stored in the configuration - * file or not. If the chat room is persistent it still will be shown after a - * restart in the chat room list. A non-persistent chat room will be only in - * the chat room list until the the program is running. - * - * @return true if this chat room is persistent, false otherwise - */ - public boolean isPersistent() - { - return false; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ContactGroupYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ContactGroupYahooImpl.java deleted file mode 100644 index 1f73aac..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/ContactGroupYahooImpl.java +++ /dev/null @@ -1,445 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import ymsg.network.*; - -/** - * The Yahoo implementation of the ContactGroup interface. Intances of this class - * (contrary to <tt>RootContactGroupYahooImpl</tt>) may only contain buddies - * and cannot have sub groups. Note that instances of this class only use the - * corresponding smack source group for reading their names and only - * initially fill their <tt>buddies</tt> <tt>java.util.List</tt> with - * the ContactYahooImpl objects corresponding to those contained in the source - * group at the moment it is being created. They would, however, never try to - * sync or update their contents ulteriorly. This would have to be done through - * the addContact()/removeContact() methods. - * The content of buddies is created on creating of the group and when the smack - * source group is changed. - * - * @author Damian Minkov - * @author Emil Ivov - */ -public class ContactGroupYahooImpl - extends AbstractContactGroupYahooImpl -{ - private final Map<String, Contact> buddies - = new Hashtable<String, Contact>(); - - private boolean isResolved = false; - - /** - * The Yahoo Group corresponding to this contact group. - */ - private YahooGroup yahooGroup = null; - - /** - * a list that would always remain empty. We only use it so that we're able - * to extract empty iterators - */ - private final List<ContactGroup> dummyGroupsList - = new LinkedList<ContactGroup>(); - - private String tempId = null; - - private final ServerStoredContactListYahooImpl ssclCallback; - - /** - * Creates an Yahoo group using the specified <tt>YahooGroup</tt> as - * a source. The newly created group will always return the name of the - * underlying RosterGroup and would thus automatically adapt to changes. - * It would, however, not receive or try to poll for modifications of the - * buddies it contains and would therefore have to be updated manually by - * ServerStoredContactListImpl update will only be done if source group - * is changed. - - * @param yahooGroup the Yahoo Group correspoinding to the group - * @param groupMembers the group members that we should add to the group. - * @param ssclCallback a callback to the server stored contact list - * we're creating. - * @param isResolved a boolean indicating whether or not the group has been - * resolved against the server. - */ - ContactGroupYahooImpl( - YahooGroup yahooGroup, - Vector<YahooUser> groupMembers, - ServerStoredContactListYahooImpl ssclCallback, - boolean isResolved) - { - this.yahooGroup = yahooGroup; - this.isResolved = isResolved; - this.ssclCallback = ssclCallback; - - for (YahooUser yahooUser : groupMembers) - { - //only add the contact if it doesn't already exist in some other - //group. this would be necessary if Yahoo! one day start allowing - //the same contact in more than one group, which is not quite - //unlikely since most of the other protocols do it. - if(ssclCallback.findContactByYahooUser(yahooUser) != null) - { - continue; - } - - - addContact( - new ContactYahooImpl(yahooUser,ssclCallback, true, true)); - } - } - - ContactGroupYahooImpl( String id, - ServerStoredContactListYahooImpl ssclCallback) - { - this.tempId = id; - this.isResolved = false; - this.ssclCallback = ssclCallback; - } - - - /** - * Returns the number of <tt>Contact</tt> members of this - * <tt>ContactGroup</tt> - * - * @return an int indicating the number of <tt>Contact</tt>s, - * members of this <tt>ContactGroup</tt>. - */ - public int countContacts() - { - return buddies.size(); - } - - /** - * Returns a reference to the root group which in Yahoo is the parent of - * any other group since the protocol does not support subgroups. - * @return a reference to the root group. - */ - public ContactGroup getParentContactGroup() - { - return ssclCallback.getRootGroup(); - } - - /** - * Adds the specified contact to the end of this group. - * @param contact the new contact to add to this group - */ - void addContact(ContactYahooImpl contact) - { - buddies.put(contact.getAddress().toLowerCase(), contact); - } - - - /** - * Removes the specified contact from this contact group - * @param contact the contact to remove. - */ - void removeContact(ContactYahooImpl contact) - { - buddies.remove(contact.getAddress().toLowerCase()); - } - - /** - * Returns an Iterator over all contacts, member of this - * <tt>ContactGroup</tt>. - * - * @return a java.util.Iterator over all contacts inside this - * <tt>ContactGroup</tt>. In case the group doesn't contain any - * memebers it will return an empty iterator. - */ - public Iterator<Contact> contacts() - { - return buddies.values().iterator(); - } - - /** - * Returns the <tt>Contact</tt> with the specified address or - * identifier. - * @param id the addres or identifier of the <tt>Contact</tt> we are - * looking for. - * @return the <tt>Contact</tt> with the specified id or address. - */ - public Contact getContact(String id) - { - return this.findContact(id); - } - - /** - * Returns the name of this group. - * @return a String containing the name of this group. - */ - public String getGroupName() - { - if(isResolved) - return ServerStoredContactListYahooImpl - .replaceIllegalChars(yahooGroup.getName()); - else - return tempId; - } - - /** - * Determines whether the group may contain subgroups or not. - * - * @return always false since only the root group may contain subgroups. - */ - public boolean canContainSubgroups() - { - return false; - } - - /** - * Returns the subgroup with the specified index (i.e. always null since - * this group may not contain subgroups). - * - * @param index the index of the <tt>ContactGroup</tt> to retrieve. - * @return always null - */ - public ContactGroup getGroup(int index) - { - return null; - } - - /** - * Returns the subgroup with the specified name. - * @param groupName the name of the <tt>ContactGroup</tt> to retrieve. - * @return the <tt>ContactGroup</tt> with the specified index. - */ - public ContactGroup getGroup(String groupName) - { - return null; - } - - /** - * Returns an empty iterator. Subgroups may only be present in the root - * group. - * - * @return an empty iterator - */ - public Iterator<ContactGroup> subgroups() - { - return dummyGroupsList.iterator(); - } - - /** - * Returns the number of subgroups contained by this group, which is - * always 0 since sub groups in the protocol may only be contained - * by the root group - <tt>RootContactGroupImpl</tt>. - * @return a 0 int. - */ - public int countSubgroups() - { - return 0; - } - - /** - * Returns a hash code value for the object, which is actually the hashcode - * value of the groupname. - * - * @return a hash code value for this ContactGroup. - */ - @Override - public int hashCode() - { - return getGroupName().hashCode(); - } - - /** - * Indicates whether some other object is "equal to" this group. - * - * @param obj the reference object with which to compare. - * @return <tt>true</tt> if this object is the same as the obj - * argument; <tt>false</tt> otherwise. - */ - @Override - public boolean equals(Object obj) - { - if( obj == this ) - return true; - - if (obj == null - || !(obj instanceof ContactGroupYahooImpl) ) - return false; - - if(!((ContactGroup)obj).getGroupName().equals(getGroupName())) - return false; - - if(getProtocolProvider() != ((ContactGroup)obj).getProtocolProvider()) - return false; - - //since Yahoo does not support having two groups with the same name - // at this point we could bravely state that the groups are the same - // and not bother to compare buddies. (gotta check that though) - return true; - } - - /** - * Returns the protocol provider that this group belongs to. - * @return a reference to the ProtocolProviderService instance that this - * ContactGroup belongs to. - */ - public ProtocolProviderService getProtocolProvider() - { - return this.ssclCallback.getParentProvider(); - } - - /** - * Returns a string representation of this group, in the form - * YahooGroup.GroupName[size]{ buddy1.toString(), buddy2.toString(), ...}. - * @return a String representation of the object. - */ - @Override - public String toString() - { - StringBuffer buff = new StringBuffer("YahooGroup."); - buff.append(getGroupName()); - buff.append(", childContacts="+countContacts()+":["); - - Iterator<Contact> contacts = contacts(); - while (contacts.hasNext()) - { - Contact contact = contacts.next(); - buff.append(contact.toString()); - if(contacts.hasNext()) - buff.append(", "); - } - return buff.append("]").toString(); - } - - /** - * Returns the contact encapsulating with the spcieified name or - * null if no such contact was found. - * - * @param id the id for the contact we're looking for. - * @return the <tt>ContactYahooImpl</tt> corresponding to the specified - * screnname or null if no such contact existed. - */ - ContactYahooImpl findContact(String id) - { - if(id == null) - return null; - return (ContactYahooImpl)buddies.get(id.toLowerCase()); - } - - /** - * Determines whether or not this contact group is being stored by the - * server. Non persistent contact groups exist for the sole purpose of - * containing non persistent contacts. - * @return true if the contact group is persistent and false otherwise. - */ - public boolean isPersistent() - { - return true; - } - - /** - * Returns null as no persistent data is required and the contact address is - * sufficient for restoring the contact. - * <p> - * @return null as no such data is needed. - */ - public String getPersistentData() - { - return null; - } - - /** - * Determines whether or not this contact group has been resolved against - * the server. Unresolved group are used when initially loading a contact - * list that has been stored in a local file until the presence operation - * set has managed to retrieve all the contact list from the server and has - * properly mapped contacts and groups to their corresponding on-line - * buddies. - * @return true if the contact has been resolved (mapped against a buddy) - * and false otherwise. - */ - public boolean isResolved() - { - return isResolved; - } - - /** - * Resolve this contact group against the specified group - * @param yahooGroup the server stored group - */ - @SuppressWarnings("unchecked") //jymsg legacy code - void setResolved(YahooGroup yahooGroup) - { - if(isResolved) - return; - - this.isResolved = true; - - this.yahooGroup = yahooGroup; - - Vector<YahooUser> contacts = yahooGroup.getMembers(); - for (YahooUser item : contacts) - { - ContactYahooImpl contact = - ssclCallback.findContactById(item.getId()); - if(contact != null) - { - contact.setResolved(item); - - ssclCallback.fireContactResolved(this, contact); - } - else - { - ContactYahooImpl newContact = - new ContactYahooImpl(item, ssclCallback, true, true); - addContact(newContact); - - ssclCallback.fireContactAdded(this, newContact); - } - } - } - - /** - * Returns a <tt>String</tt> that uniquely represnets the group. In this we - * use the name of the group as an identifier. This may cause problems - * though, in clase the name is changed by some other application between - * consecutive runs of the sip-communicator. - * - * @return a String representing this group in a unique and persistent - * way. - */ - public String getUID() - { - return getGroupName(); - } - - /** - * The source group we are encapsulating - * @return YahooGroup - */ - YahooGroup getSourceGroup() - { - return yahooGroup; - } - - /** - * Change the source group - * change the buddies - * - * @param newGroup YahooGroup - */ - void setSourceGroup(YahooGroup newGroup) - { - this.yahooGroup = newGroup; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ContactYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ContactYahooImpl.java deleted file mode 100644 index b23ee3d..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/ContactYahooImpl.java +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.yahooconstants.*; -import net.java.sip.communicator.util.*; -import ymsg.network.*; - -/** - * The Yahoo implementation of the service.protocol.Contact interface. - * @author Damian Minkov - * @author Emil Ivov - */ -public class ContactYahooImpl - extends AbstractContact -{ - private static final Logger logger = - Logger.getLogger(ContactYahooImpl.class); - - private YahooUser contact = null; - private byte[] image = null; - private PresenceStatus status = YahooStatusEnum.OFFLINE; - private ServerStoredContactListYahooImpl ssclCallback = null; - private boolean isPersistent = false; - private boolean isResolved = false; - private boolean isVolatile = false; - - private String yahooID = null; - private String id = null; - - private String statusMessage = null; - - /** - * Creates an YahooContactImpl with custom yahooID - * @param yahooID sets the contact Id if its different from the YahooUser id - * @param contact the contact object that we will be encapsulating. - * @param ssclCallback a reference to the ServerStoredContactListImpl - * instance that created us. - * @param isPersistent determines whether this contact is persistent or not. - * @param isResolved specifies whether the contact has been resolved against - * the server contact list - */ - ContactYahooImpl( - String yahooID, - YahooUser contact, - ServerStoredContactListYahooImpl ssclCallback, - boolean isPersistent, - boolean isResolved) - { - this.yahooID = yahooID; - - this.contact = contact; - this.ssclCallback = ssclCallback; - this.isPersistent = isPersistent; - this.isResolved = isResolved; - - if(contact != null) - id = contact.getId(); - else if(yahooID != null) - id = YahooSession.getYahooUserID(yahooID); - } - - /** - * Creates an YahooContactImpl - * @param contact the contact object that we will be encapsulating. - * @param ssclCallback a reference to the ServerStoredContactListImpl - * instance that created us. - * @param isPersistent determines whether this contact is persistent or not. - * @param isResolved specifies whether the contact has been resolved against - * the server contact list - */ - ContactYahooImpl( - YahooUser contact, - ServerStoredContactListYahooImpl ssclCallback, - boolean isPersistent, - boolean isResolved) - { - this(null, contact, ssclCallback, isPersistent, isResolved); - } - - /** - * Creates volatile or unresolved contact - */ - ContactYahooImpl( - String id, - ServerStoredContactListYahooImpl ssclCallback, - boolean isResolved, - boolean isPersistent, - boolean isVolatile) - { - this.yahooID = id; - this.ssclCallback = ssclCallback; - this.isPersistent = isPersistent; - this.isResolved = isResolved; - this.isVolatile = isVolatile; - - if(id != null) - this.id = YahooSession.getYahooUserID(yahooID); - } - - /** - * Returns the Yahoo Userid of this contact - * @return the Yahoo Userid of this contact - */ - public String getAddress() - { - // if the contact is volatile or with custom id return it - if(yahooID != null) - return yahooID; - // otherwise return the supplied contact id - else - return contact.getId(); - } - - /** - * Returns the custom yahooID if set - */ - String getYahooID() - { - return yahooID; - } - - /** - * Returns the contact Id. - * If contact missing the yahooID without @yahoo.com part is returned - */ - String getID() - { - return id; - } - - /** - * Returns whether the contact is volatile. - */ - boolean isVolatile() - { - return isVolatile; - } - - /** - * Returns an avatar if one is already present or <tt>null</tt> in case it - * is not in which case it the method also queues the contact for image - * updates. - * - * @return the avatar of this contact or <tt>null</tt> if no avatar is - * currently available. - */ - public byte[] getImage() - { - return getImage(true); - } - - /** - * Returns a reference to the image assigned to this contact. If no image - * is present and the retrieveIfNecessary flag is true, we schedule the - * image for retrieval from the server. - * - * @param retrieveIfNecessary specifies whether the method should queue - * this contact for avatar update from the server. - * - * @return a reference to the image currently stored by this contact. - */ - public byte[] getImage(boolean retrieveIfNecessary) - { - try - { - if(retrieveIfNecessary) - { - if(ssclCallback.getParentProvider() == null - || !ssclCallback.getParentProvider().isRegistered()) - { - throw new IllegalStateException( - "The provider must be signed on the service before " - +"being able to communicate."); - } - - YahooSession ses = ssclCallback.getParentProvider(). - getYahooSession(); - if(image == null && ses != null) - ses.requestPicture(id); - } - } - catch (Exception e) - { - if (logger.isInfoEnabled()) - logger.info("Error requesting image!", e); - } - - if(logger.isDebugEnabled()) - logger.debug("returning picture " + image); - - return image; - } - - /** - * Used to set the image of the contact if it is updated - * - * @param image a photo/avatar associated with this contact. - */ - protected void setImage(byte[] image) - { - if (logger.isInfoEnabled()) - logger.info("setting image " + image); - - this.image = image; - } - - /** - * Returns a string representation of this contact, containing most of its - * representative details. - * - * @return a string representation of this contact. - */ - @Override - public String toString() - { - StringBuffer buff = new StringBuffer("YahooContact[ id="); - buff.append(getAddress()).append("]"); - - return buff.toString(); - } - - /** - * Sets the status that this contact is currently in. The method is to - * only be called as a result of a status update received from the server. - * - * @param status the YahooStatusEnum that this contact is currently in. - */ - void updatePresenceStatus(PresenceStatus status) - { - this.status = status; - } - - /** - * Returns the status of the contact as per the last status update we've - * received for it. Note that this method is not to perform any network - * operations and will simply return the status received in the last - * status update message. If you want a reliable way of retrieving someone's - * status, you should use the <tt>queryContactStatus()</tt> method in - * <tt>OperationSetPresence</tt>. - * @return the PresenceStatus that we've received in the last status update - * pertaining to this contact. - */ - public PresenceStatus getPresenceStatus() - { - return status; - } - - /** - * Returns a String that could be used by any user interacting modules for - * referring to this contact. An alias is not necessarily unique but is - * often more human readable than an address (or id). - * @return a String that can be used for referring to this contact when - * interacting with the user. - */ - public String getDisplayName() - { - return getAddress(); - } - - /** - * Returns a reference to the contact group that this contact is currently - * a child of or null if the underlying protocol does not suppord persistent - * presence. - * @return a reference to the contact group that this contact is currently - * a child of or null if the underlying protocol does not suppord persistent - * presence. - */ - public ContactGroup getParentContactGroup() - { - return ssclCallback.findContactGroup(this); - } - - - /** - * Returns a reference to the protocol provider that created the contact. - * @return a refererence to an instance of the ProtocolProviderService - */ - public ProtocolProviderService getProtocolProvider() - { - return ssclCallback.getParentProvider(); - } - - /** - * Determines whether or not this contact is being stored by the server. - * Non persistent contacts are common in the case of simple, non-persistent - * presence operation sets. They could however also be seen in persistent - * presence operation sets when for example we have received an event - * from someone not on our contact list. Non persistent contacts are - * volatile even when coming from a persistent presence op. set. They would - * only exist until the application is closed and will not be there next - * time it is loaded. - * @return true if the contact is persistent and false otherwise. - */ - public boolean isPersistent() - { - return isPersistent; - } - - /** - * Specifies whether this contact is to be considered persistent or not. The - * method is to be used _only_ when a non-persistent contact has been added - * to the contact list and its encapsulated VolatileBuddy has been repalced - * with a standard buddy. - * @param persistent true if the buddy is to be considered persistent and - * false for volatile. - */ - void setPersistent(boolean persistent) - { - this.isPersistent = persistent; - } - - /** - * Resolve this contact against the given entry - * @param entry the server stored entry - */ - void setResolved(YahooUser entry) - { - if(isResolved) - return; - - this.isResolved = true; - contact = entry; - isVolatile = false; - } - - /** - * Returns the persistent data - * @return the persistent data - */ - public String getPersistentData() - { - return null; - } - - /** - * Determines whether or not this contact has been resolved against the - * server. Unresolved contacts are used when initially loading a contact - * list that has been stored in a local file until the presence operation - * set has managed to retrieve all the contact list from the server and has - * properly mapped contacts to their on-line buddies. - * @return true if the contact has been resolved (mapped against a buddy) - * and false otherwise. - */ - public boolean isResolved() - { - return isResolved; - } - - public void setPersistentData(String persistentData) - { - } - - /** - * Get source contact - * @return YahooContact - */ - YahooUser getSourceContact() - { - return contact; - } - - /** - * Return the current status message of this contact. - * - * @return the current status message - */ - public String getStatusMessage() - { - return statusMessage; - } - - /** - * Sets the current status message for this contact - * @param statusMessage the message - */ - protected void setStatusMessage(String statusMessage) - { - this.statusMessage = statusMessage; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/FileTransferImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/FileTransferImpl.java deleted file mode 100644 index 3f42079..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/FileTransferImpl.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * The Filetransfer imeplementation for yahoo. - * @author Damian Minkov - */ -public class FileTransferImpl - extends AbstractFileTransfer - -{ - private ProtocolProviderServiceYahooImpl yahooProvider; - private String id = null; - private Contact contact = null; - private File file = null; - private int direction = -1; - private long transferedBytes; - - public FileTransferImpl(ProtocolProviderServiceYahooImpl yahooProvider, - String id, Contact contact, File file, int direction) - { - this.yahooProvider = yahooProvider; - this.id = id; - this.contact = contact; - this.file = file; - this.direction = direction; - } - - /** - * Cancels this file transfer. When this method is called transfer should - * be interrupted. - */ - @Override - public void cancel() - { - yahooProvider.getYahooSession().cancelRunningFileTransfer(id); - } - - /** - * Returns the number of bytes already transfered through this file transfer. - * - * @return the number of bytes already transfered through this file transfer - */ - @Override - public long getTransferedBytes() - { - return transferedBytes; - } - - /** - * Uniquie ID that is identifying the FileTransfer - * if the request has been accepted. - * - * @return the id. - */ - public String getID() - { - return id; - } - - /** - * The file transfer direction. - * @return returns the direction of the file transfer : IN or OUT. - */ - public int getDirection() - { - return direction; - } - - /** - * Returns the file that is transfered. - * - * @return the file - */ - public File getLocalFile() - { - return file; - } - - /** - * Returns the contact that we are transfering files with. - * @return the contact. - */ - public Contact getContact() - { - return contact; - } - - /** - * @param transferedBytes the transferedBytes to set - */ - public void setTransferedBytes(long transferedBytes) - { - this.transferedBytes = transferedBytes; - } - -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/IncomingFileTransferRequestYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/IncomingFileTransferRequestYahooImpl.java deleted file mode 100644 index 25561d0..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/IncomingFileTransferRequestYahooImpl.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; - -/** - * Implementation of the incoming file transfer request. - * - * @author Damian Minkov - */ -public class IncomingFileTransferRequestYahooImpl - implements IncomingFileTransferRequest -{ - private String id; - - /** - * The yahoo provider. - */ - private ProtocolProviderServiceYahooImpl yahooProvider; - - private final OperationSetFileTransferYahooImpl fileTransferOpSet; - - private Contact sender; - - private Date date; - - private String fileName; - - private long fileSize; - - public IncomingFileTransferRequestYahooImpl( - ProtocolProviderServiceYahooImpl yahooProvider, - OperationSetFileTransferYahooImpl fileTransferOpSet, - Contact sender, - Date date, - String fileName, - String fileSize, - String id) - { - this.yahooProvider = yahooProvider; - this.fileTransferOpSet = fileTransferOpSet; - this.sender = sender; - this.date = date; - this.fileName = fileName; - - try - { - this.fileSize = Long.valueOf(fileSize); - } - catch (NumberFormatException e) - {} - - this.id = id; - } - - /** - * Unique ID that is identifying the request and then the FileTransfer - * if the request has been accepted. - * - * @return the id. - */ - public String getID() - { - return id; - } - - /** - * Returns a String that represents the name of the file that is being - * received. - * If there is no name, returns null. - * @return a String that represents the name of the file - */ - public String getFileName() - { - return fileName; - } - - /** - * Returns a String that represents the description of the file that is - * being received. - * If there is no description available, returns null. - * - * @return a String that represents the description of the file - */ - public String getFileDescription() - { - return ""; - } - - /** - * Returns a long that represents the size of the file that is being - * received. - * If there is no file size available, returns null. - * - * @return a long that represents the size of the file - */ - public long getFileSize() - { - return fileSize; - } - - /** - * Returns a String that represents the name of the sender of the file - * being received. - * If there is no sender name available, returns null. - * - * @return a String that represents the name of the sender - */ - public Contact getSender() - { - return sender; - } - - /** - * Function called to accept and receive the file. - * - * @param file the file to accept - * @return the <tt>FileTransfer</tt> object managing the transfer - */ - public FileTransfer acceptFile(File file) - { - AbstractFileTransfer incomingTransfer = null; - - incomingTransfer = - new FileTransferImpl(yahooProvider, - id, sender, file, FileTransfer.IN); - - yahooProvider.getYahooSession().fileTransferAccept(id, file); - - FileTransferCreatedEvent event - = new FileTransferCreatedEvent(incomingTransfer, new Date()); - - fileTransferOpSet.fireFileTransferCreated(event); - - incomingTransfer.fireStatusChangeEvent( - FileTransferStatusChangeEvent.PREPARING); - - return incomingTransfer; - } - - /** - * Function called to refuse the file. - */ - public void rejectFile() - { - yahooProvider.getYahooSession().fileTransferReject(id); - - fileTransferOpSet.fireFileTransferRequestRejected( - new FileTransferRequestEvent( - fileTransferOpSet, this, this.getDate())); - } - - /** - * @return the date - */ - public Date getDate() - { - return date; - } - - /** - * Returns the thumbnail contained in this request. - * - * @return the thumbnail contained in this request - */ - public byte[] getThumbnail() - { - return null; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/MessageYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/MessageYahooImpl.java deleted file mode 100644 index e35a3c1..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/MessageYahooImpl.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import net.java.sip.communicator.service.protocol.*; - -/** - * A simple implementation of the <tt>Message</tt> interface. Right now the - * message only supports test contents and no binary data. - * - * @author Damian Minkov - * @author Lubomir Marinov - */ -public class MessageYahooImpl - extends AbstractMessage -{ - - /** - * Creates an instance of this Message with the specified parameters. - * - * @param content the text content of the message. - * @param contentType a MIME string indicating the content type of the - * <tt>content</tt> String. - * @param contentEncoding a MIME String indicating the content encoding of - * the <tt>content</tt> String. - * @param subject the subject of the message or null for empty. - */ - public MessageYahooImpl(String content, String contentType, - String contentEncoding, String subject) - { - super(content, contentType, contentEncoding, subject); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetAdHocMultiUserChatYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetAdHocMultiUserChatYahooImpl.java deleted file mode 100644 index dd5f73f..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetAdHocMultiUserChatYahooImpl.java +++ /dev/null @@ -1,714 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; -import ymsg.network.*; -import ymsg.network.event.*; - -/** - * A Yahoo implementation of the ad-hoc multi user chat operation set. - * - * @author Rupert Burchardi - * @author Valentin Martinet - * @author Yana Stamcheva - */ -public class OperationSetAdHocMultiUserChatYahooImpl -implements OperationSetAdHocMultiUserChat -{ - private static final Logger logger = - Logger.getLogger(OperationSetAdHocMultiUserChatYahooImpl.class); - - /** - * A list of listeners subscribed for invitations multi user chat events. - */ - private final List<AdHocChatRoomInvitationListener> invitationListeners - = new Vector<AdHocChatRoomInvitationListener>(); - - /** - * A list of listeners subscribed for events indicating rejection of a multi - * user chat invitation sent by us. - */ - private final List<AdHocChatRoomInvitationRejectionListener> - invitationRejectionListeners - = new Vector<AdHocChatRoomInvitationRejectionListener>(); - - /** - * Listeners that will be notified of changes in our status in the room such - * as us being kicked, banned, or granted admin permissions. - */ - private final List<LocalUserAdHocChatRoomPresenceListener> presenceListeners - = new Vector<LocalUserAdHocChatRoomPresenceListener>(); - - /** - * A list of the rooms that are currently open by this account. - */ - private final Hashtable<String, AdHocChatRoomYahooImpl> chatRoomCache - = new Hashtable<String, AdHocChatRoomYahooImpl>(); - - /** - * The currently valid Yahoo protocol provider service implementation. - */ - private final ProtocolProviderServiceYahooImpl yahooProvider; - - /** - * The operation set for the basic instant messaging, provides some message - * format functions. - */ - private final OperationSetBasicInstantMessagingYahooImpl opSetBasic; - - /** - * Instantiates the user operation set with a currently valid instance of - * the Yahoo protocol provider. - * - * @param yahooProvider a currently valid instance of - * ProtocolProviderServiceYahooImpl. - */ - OperationSetAdHocMultiUserChatYahooImpl( - ProtocolProviderServiceYahooImpl yahooProvider) - { - this.yahooProvider = yahooProvider; - - yahooProvider - .addRegistrationStateChangeListener(new RegistrationStateListener()); - - opSetBasic = - (OperationSetBasicInstantMessagingYahooImpl) yahooProvider - .getOperationSet(OperationSetBasicInstantMessaging.class); - } - - /** - * Adds a listener to invitation notifications. - * - * @param listener An invitation listener. - */ - public void addInvitationListener(AdHocChatRoomInvitationListener listener) - { - synchronized (invitationListeners) - { - if (!invitationListeners.contains(listener)) - invitationListeners.add(listener); - } - } - - /** - * Removes a listener that was being notified of changes in our status in a - * room such as us being kicked, banned or dropped. - * - * @param listener the <tt>LocalUserAdHocChatRoomPresenceListener</tt>. - */ - public void removeInvitationListener( - AdHocChatRoomInvitationListener listener) - { - synchronized (invitationListeners) - { - invitationListeners.remove(listener); - } - } - - /** - * Subscribes <tt>listener</tt> so that it would receive events indicating - * rejection of a multi user chat invitation that we've sent earlier. - * - * @param listener the listener that we'll subscribe for invitation - * rejection events. - */ - - public void addInvitationRejectionListener( - AdHocChatRoomInvitationRejectionListener listener) - { - synchronized (invitationRejectionListeners) - { - if (!invitationRejectionListeners.contains(listener)) - invitationRejectionListeners.add(listener); - } - } - - /** - * Removes <tt>listener</tt> from the list of invitation listeners - * registered to receive invitation rejection events. - * - * @param listener the invitation listener to remove. - */ - public void removeInvitationRejectionListener( - AdHocChatRoomInvitationRejectionListener listener) - { - synchronized (invitationRejectionListeners) - { - invitationRejectionListeners.remove(listener); - } - } - - /** - * Adds a listener that will be notified of changes in our status in a chat - * room such as us being kicked, banned or dropped. - * - * @param listener the <tt>LocalUserAdHocChatRoomPresenceListener</tt>. - */ - public void addPresenceListener( - LocalUserAdHocChatRoomPresenceListener listener) - { - synchronized (presenceListeners) - { - if (!presenceListeners.contains(listener)) - presenceListeners.add(listener); - } - } - - /** - * Removes a listener that was being notified of changes in our status in a - * room such as us being kicked, banned or dropped. - * - * @param listener the <tt>LocalUserChatRoomPresenceListener</tt>. - */ - public void removePresenceListener( - LocalUserAdHocChatRoomPresenceListener listener) - { - synchronized (presenceListeners) - { - presenceListeners.remove(listener); - } - } - - /** - * Creates a room with the named <tt>roomName</tt> and according to the - * specified <tt>roomProperties</tt> on the server that this protocol - * provider is currently connected to. Note the roomProperties also contain - * users that we like to invite to the chatRoom, this is required in the - * yahoo protocol. - * - * @param roomName the name of the <tt>AdHocChatRoom</tt> to create. - * @param roomProperties properties specifying how the room should be - * created. - * - * @throws OperationFailedException if the room couldn't be created for some - * reason (e.g. room already exists; user already joined to an - * existent room or user has no permissions to create a chat - * room). - * - * @return ChatRoom the chat room that we've just created. - */ - public AdHocChatRoom createAdHocChatRoom(String roomName, - Map<String, Object> roomProperties) - throws OperationFailedException - { - return createAdHocChatRoom(roomName, (String[]) null, ""); - } - - /** - * Creates an ad-hoc room with the named <tt>adHocRoomName</tt> and in - * including to the specified <tt>contacts</tt>. When the method returns the - * ad-hoc room the local user will have joined it. - * - * @return the ad-hoc room that has been just created - * @param adHocRoomName the name of the room to be created - * @param contacts the list of contacts ID - * @param reason the reason for contacts' invitation - * @throws OperationFailedException if the room couldn't be created for - * some reason - */ - public AdHocChatRoom createAdHocChatRoom( String adHocRoomName, - List<String> contacts, - String reason) - throws OperationFailedException - { - String[] contactsToInvite = new String[contacts.size()]; - for(int i=0; i<contacts.size(); i++) - { - contactsToInvite[i] = contacts.get(i); - } - return createAdHocChatRoom( - adHocRoomName, contactsToInvite, reason); - } - - /** - * Creates an ad-hoc room with the named <tt>adHocRoomName</tt> and in - * including to the specified <tt>contacts</tt>. When the method returns the - * ad-hoc room the local user will have joined it. - * - * @param roomName name of the chatroom - * @param invitedContacts contacts to be invited to this room - * @param reason reason of this invitation - * @return AdHocChatRoom the ad-hoc room that has been just created - * @throws OperationFailedException - */ - private AdHocChatRoom createAdHocChatRoom( - String roomName, - String[] invitedContacts, - String reason) - throws OperationFailedException - { - if (invitedContacts == null) - invitedContacts = new String[0]; - - AdHocChatRoom chatRoom = null; - - try - { - YahooConference conference = - yahooProvider.getYahooSession().createConference( - invitedContacts, // users invited to this conference - reason, // invite message / topic - yahooProvider.getYahooSession().getLoginIdentity()); - - chatRoom = createLocalChatRoomInstance(conference); - } - catch (Exception e) - { - String errorMessage - = "Failed to create chat room with name: " + roomName; - - if (logger.isDebugEnabled()) - logger.debug(errorMessage, e); - throw new OperationFailedException(errorMessage, - OperationFailedException.CHAT_ROOM_NOT_JOINED, e); - } - chatRoom.join(); - return chatRoom; - } - - /** - * Creates a <tt>AdHocChatRoom</tt> instance from the specified Yahoo - * conference. - * - * @param yahooConference The chat room model from the yahoo lib. - * - * @return AdHocChatRoom the chat room that we've just created. - */ - private AdHocChatRoomYahooImpl createLocalChatRoomInstance( - YahooConference yahooConference) - { - synchronized (chatRoomCache) - { - AdHocChatRoomYahooImpl newChatRoom - = new AdHocChatRoomYahooImpl(yahooConference, yahooProvider); - - chatRoomCache.put(yahooConference.getName(), newChatRoom); - - return newChatRoom; - } - } - - /** - * Creates a <tt>AdHocChatRoom</tt> instance (where the inviter is - * represented by inviterID parameter) from the specified Yahoo conference. - * - * @param yahooConference The chat room model from the yahoo lib. - * @param inviterID inviter's Yahoo ID which has to be added as room member - * - * @return AdHocChatRoom the chat room that we've just created. - */ - private AdHocChatRoomYahooImpl createLocalChatRoomInstance( - YahooConference yahooConference, String inviterID) - { - synchronized (chatRoomCache) - { - AdHocChatRoomYahooImpl newChatRoom - = new AdHocChatRoomYahooImpl(yahooConference, yahooProvider); - - OperationSetPersistentPresenceYahooImpl opSetPresence = - (OperationSetPersistentPresenceYahooImpl) yahooProvider - .getOperationSet(OperationSetPersistentPresence.class); - - newChatRoom.addChatRoomParticipant( - opSetPresence.findContactByID(inviterID)); - chatRoomCache.put(yahooConference.getName(), newChatRoom); - - return newChatRoom; - } - } - - /** - * Returns the <tt>AdHocChatRoomYahooImpl</tt> corresponding to the given - * <tt>conference</tt> if such exists, otherwise returns null. - * - * @param conference the <tt>YahooConference</tt>, for which we're searching - * correspondence - * @return the <tt>AdHocChatRoomYahooImpl</tt> corresponding to the given - * <tt>conference</tt> if such exists, otherwise returns null - */ - private AdHocChatRoomYahooImpl getLocalChatRoomInstance( - YahooConference conference) - { - synchronized (chatRoomCache) - { - for (AdHocChatRoomYahooImpl chatRoom : chatRoomCache.values()) - { - if (chatRoom.getYahooConference().equals(conference)) - return chatRoom; - } - } - - return null; - } - - /** - * Informs the sender of an invitation that we decline their invitation. - * - * @param invitation the connection to use for sending the rejection. - * @param rejectReason the reason to reject the given invitation - */ - public void rejectInvitation(AdHocChatRoomInvitation invitation, - String rejectReason) - { - AdHocChatRoomYahooImpl chatRoom = - (AdHocChatRoomYahooImpl) invitation.getTargetAdHocChatRoom(); - - try - { - yahooProvider.getYahooSession().declineConferenceInvite( - chatRoom.getYahooConference(), rejectReason); - } - catch (IOException e) - { - if (logger.isDebugEnabled()) - logger.debug("Failed to reject Invitation: " + e); - } - } - - /** - * Delivers a <tt>AdHocChatRoomInvitationReceivedEvent</tt> to all - * registered <tt>AdHocChatRoomInvitationListener</tt>s. - * - * @param targetChatRoom the room that invitation refers to - * @param inviter the inviter that sent the invitation - * @param reason the reason why the inviter sent the invitation - */ - public void fireInvitationEvent(AdHocChatRoom targetChatRoom, - String inviter, String reason) - { - AdHocChatRoomInvitationYahooImpl invitation = - new AdHocChatRoomInvitationYahooImpl(targetChatRoom, inviter, - reason); - - AdHocChatRoomInvitationReceivedEvent evt = - new AdHocChatRoomInvitationReceivedEvent(this, invitation, - new Date(System.currentTimeMillis())); - - Iterable<AdHocChatRoomInvitationListener> listeners; - synchronized (invitationListeners) - { - listeners - = new ArrayList<AdHocChatRoomInvitationListener>( - invitationListeners); - } - - for (AdHocChatRoomInvitationListener listener : listeners) - listener.invitationReceived(evt); - } - - /** - * Delivers a <tt>AdHocChatRoomInvitationRejectedEvent</tt> to all - * registered <tt>AdHocChatRoomInvitationRejectionListener</tt>s. - * - * @param sourceChatRoom the room that invitation refers to - * @param invitee the name of the invitee that rejected the invitation - * @param reason the reason of the rejection - */ - public void fireInvitationRejectedEvent(AdHocChatRoom sourceChatRoom, - String invitee, String reason) - { - AdHocChatRoomInvitationRejectedEvent evt = - new AdHocChatRoomInvitationRejectedEvent( - this, sourceChatRoom, invitee, - reason, new Date(System.currentTimeMillis())); - - Iterable<AdHocChatRoomInvitationRejectionListener> listeners; - synchronized (invitationRejectionListeners) - { - listeners - = new ArrayList<AdHocChatRoomInvitationRejectionListener>( - invitationRejectionListeners); - } - - for (AdHocChatRoomInvitationRejectionListener listener : listeners) - listener.invitationRejected(evt); - } - - /** - * Delivers a <tt>LocalUserAdHocChatRoomPresenceChangeEvent</tt> to all - * registered <tt>LocalUserAdHocChatRoomPresenceListener</tt>s. - * - * @param chatRoom the <tt>ChatRoom</tt> which has been joined, left, etc. - * @param eventType the type of this event; one of LOCAL_USER_JOINED, - * LOCAL_USER_LEFT, etc. - * @param reason the reason - */ - public void fireLocalUserPresenceEvent(AdHocChatRoom chatRoom, - String eventType, String reason) - { - LocalUserAdHocChatRoomPresenceChangeEvent evt = - new LocalUserAdHocChatRoomPresenceChangeEvent( - this, chatRoom, eventType, - reason); - - Iterable<LocalUserAdHocChatRoomPresenceListener> listeners; - synchronized (presenceListeners) - { - listeners = - new ArrayList<LocalUserAdHocChatRoomPresenceListener>( - presenceListeners); - } - - for (LocalUserAdHocChatRoomPresenceListener listener : listeners) - listener.localUserAdHocPresenceChanged(evt); - } - - /** - * Create a Message instance for sending arbitrary MIME-encoding content. - * - * @param content content value - * @param contentType the MIME-type for <tt>content</tt> - * @param contentEncoding encoding used for <tt>content</tt> - * @param subject a <tt>String</tt> subject or <tt>null</tt> for now - * subject. - * @return the newly created message. - * @throws UnsupportedEncodingException missing utf-8 in platform we use. - */ - private Message createMessage(byte[] content, String contentType, - String contentEncoding, String subject) - throws UnsupportedEncodingException - { - return new MessageYahooImpl(new String(content, "UTF-8"), contentType, - contentEncoding, subject); - } - - /** - * Creates a message by a given message text. - * - * @param messageText The message text. - * @return the newly created message. - */ - public Message createMessage(String messageText) - { - return new MessageYahooImpl(messageText, - OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE, - OperationSetBasicInstantMessaging.DEFAULT_MIME_ENCODING, null); - } - - /** - * Our listener that will tell us when we're registered to yahoo network. - * - */ - 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 (evt.getNewState() == RegistrationState.REGISTERED) - { - yahooProvider.getYahooSession().addSessionListener( - new YahooMessageListener()); - } - } - } - - /** - * Our group chat message listener, it extends the SessionAdapter from the - * the Yahoo library. - * - */ - private class YahooMessageListener - extends SessionAdapter - { - - @Override - public void conferenceInviteDeclinedReceived(SessionConferenceEvent ev) - { - if (logger.isDebugEnabled()) - logger.debug("Group Chat invite declined received. " - + ev.toString()); - try - { - AdHocChatRoom chatRoom = getLocalChatRoomInstance(ev.getRoom()); - - fireInvitationRejectedEvent(chatRoom, ev.getFrom(), ev - .getMessage()); - } - catch (Exception e) - { - if (logger.isDebugEnabled()) - logger.debug("Error: " + e); - } - } - - @Override - public void conferenceInviteReceived(SessionConferenceEvent ev) - { - if (logger.isDebugEnabled()) - logger.debug("Conference Invite Received: " + ev.toString()); - - try - { - AdHocChatRoom chatRoom = getLocalChatRoomInstance(ev.getRoom()); - - if (chatRoom == null) - { - chatRoom = - createLocalChatRoomInstance(ev.getRoom(), ev.getFrom()); - - fireInvitationEvent( - chatRoom, ev.getFrom(), ev.getMessage()); - } - - } - catch (Exception e) - { - if (logger.isDebugEnabled()) - logger.debug("Error: " + e); - } - } - - @Override - public void conferenceLogoffReceived(SessionConferenceEvent ev) - { - if (logger.isDebugEnabled()) - logger.debug("Conference Logoff Received: " + ev.toString()); - - try - { - AdHocChatRoomYahooImpl chatRoom - = getLocalChatRoomInstance(ev.getRoom()); - - if (chatRoom != null) - { - Contact participant = - chatRoom.findParticipantForAddress(ev.getFrom()); - - chatRoom.removeChatRoomParticipant(participant); - } - } - catch (Exception e) - { - logger - .debug("Failed to remove a user from the chat room. " + e); - } - } - - @Override - public void conferenceLogonReceived(SessionConferenceEvent ev) - { - if (logger.isDebugEnabled()) - logger.debug("Conference Logon Received: " + ev.toString()); - - try - { - AdHocChatRoomYahooImpl chatRoom - = getLocalChatRoomInstance(ev.getRoom()); - - if (chatRoom != null) - { - OperationSetPersistentPresenceYahooImpl presenceOpSet = - (OperationSetPersistentPresenceYahooImpl) chatRoom - .getParentProvider().getOperationSet( - OperationSetPersistentPresence.class); - - Contact participant = - presenceOpSet.findContactByID(ev.getFrom()); - - chatRoom.addChatRoomParticipant(participant); - } - } - catch (Exception e) - { - if (logger.isDebugEnabled()) - logger.debug("Failed to add a user to the chat room. " + e); - } - } - - @Override - public void conferenceMessageReceived(SessionConferenceEvent ev) - { - if (logger.isDebugEnabled()) - logger.debug("Conference Message Received: " + ev.toString()); - - try - { - String formattedMessage = ev.getMessage(); - if (logger.isDebugEnabled()) - logger.debug("original message received : " + formattedMessage); - - formattedMessage = opSetBasic.decodeMessage(formattedMessage); - if (logger.isDebugEnabled()) - logger.debug("formatted Message : " + formattedMessage); - // As no indications in the protocol is it html or not. No harm - // to set all messages html - doesn't affect the appearance of - // the gui - - Message newMessage = - createMessage( - formattedMessage.getBytes("UTF-8"), - OperationSetBasicInstantMessaging.HTML_MIME_TYPE, - OperationSetBasicInstantMessaging.DEFAULT_MIME_ENCODING, - null); - - AdHocChatRoomYahooImpl chatRoom = - getLocalChatRoomInstance(ev.getRoom()); - - if (chatRoom != null) - { - Contact member = - chatRoom.findParticipantForAddress(ev.getFrom()); - - AdHocChatRoomMessageReceivedEvent msgReceivedEvent = - new AdHocChatRoomMessageReceivedEvent( - chatRoom, - member, - new Date(), - newMessage, - AdHocChatRoomMessageReceivedEvent - .CONVERSATION_MESSAGE_RECEIVED); - - chatRoom.fireMessageEvent(msgReceivedEvent); - } - } - catch (Exception e) - { - logger - .debug("Error while receiving a multi user chat message: " - + e); - } - - } - - @Override - public void connectionClosed(SessionEvent ev) - { - if (logger.isDebugEnabled()) - logger.debug("Connection Closed: " + ev.toString()); - } - } - - public List<AdHocChatRoom> getAdHocChatRooms() - { - return new ArrayList<AdHocChatRoom>(chatRoomCache.values()); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetBasicInstantMessagingYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetBasicInstantMessagingYahooImpl.java deleted file mode 100644 index 84059de..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetBasicInstantMessagingYahooImpl.java +++ /dev/null @@ -1,649 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; -import java.util.*; -import java.util.regex.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; -import ymsg.network.event.*; -import ymsg.support.*; - -/** - * A straightforward implementation of the basic instant messaging operation - * set. - * - * @author Damian Minkov - * @author Symphorien Wanko - * @author Keio Kraaner - */ -public class OperationSetBasicInstantMessagingYahooImpl - extends AbstractOperationSetBasicInstantMessaging - implements OperationSetInstantMessageFiltering -{ - /** - * Logger for this class - */ - private static final Logger logger = - Logger.getLogger(OperationSetBasicInstantMessagingYahooImpl.class); - - /** - * Yahoo has limit of message length. If exceeded - * message is not delivered and no notification is received for that. - */ - private static final int MAX_MESSAGE_LENGTH = 800; // 949 - - /** - * A regexp that is used to escape some chars in messages. - */ - private static final Pattern MESSAGE_CHARS_ESCAPE = Pattern.compile("([.()^&$*|])"); - - /** - * A list of filters registered for message events. - */ - private final List<EventFilter> eventFilters = new ArrayList<EventFilter>(); - - /** - * The provider that created us. - */ - private ProtocolProviderServiceYahooImpl yahooProvider = null; - - /** - * Message decoder allows to convert Yahoo formated messages, which can - * contains some specials characters, to HTML or to plain text. - */ - private final MessageDecoder messageDecoder = new MessageDecoder(); - - /** - * A reference to the persistent presence operation set that we use - * to match incoming messages to <tt>Contact</tt>s and vice versa. - */ - private OperationSetPersistentPresenceYahooImpl opSetPersPresence = null; - private static final Pattern FONT_SIZE_0_PATTERN = Pattern.compile("(<font) (.*) size=\"0\">"); - private static final Pattern FONT_SIZE_INT_PATTERN = Pattern.compile("(<font) (.*) size=\"(\\d+)\">"); - - /** - * Creates an instance of this operation set. - * @param provider a ref to the <tt>ProtocolProviderServiceImpl</tt> - * that created us and that we'll use for retrieving the underlying aim - * connection. - */ - OperationSetBasicInstantMessagingYahooImpl( - ProtocolProviderServiceYahooImpl provider) - { - this.yahooProvider = provider; - provider.addRegistrationStateChangeListener( - new RegistrationStateListener()); - } - - /** - * Determines wheter the protocol provider (or the protocol itself) support - * sending and receiving offline messages. Most often this method would - * return true for protocols that support offline messages and false for - * those that don't. It is however possible for a protocol to support these - * messages and yet have a particular account that does not (i.e. feature - * not enabled on the protocol server). In cases like this it is possible - * for this method to return true even when offline messaging is not - * supported, and then have the sendMessage method throw an - * OperationFailedException with code - OFFLINE_MESSAGES_NOT_SUPPORTED. - * - * @return <tt>true</tt> if the protocol supports offline messages and - * <tt>false</tt> otherwise. - */ - public boolean isOfflineMessagingSupported() - { - return true; - } - - /** - * Determines wheter the protocol supports the supplied content type - * - * @param contentType the type we want to check - * @return <tt>true</tt> if the protocol supports it and - * <tt>false</tt> otherwise. - */ - public boolean isContentTypeSupported(String contentType) - { - if(contentType.equals(DEFAULT_MIME_TYPE) || - contentType.equals(HTML_MIME_TYPE)) - return true; - else - return false; - } - - @Override - public Message createMessage(String content, String contentType, - String encoding, String subject) - { - return new MessageYahooImpl(content, contentType, encoding, subject); - } - - /** - * Sends the <tt>message</tt> to the destination indicated by the - * <tt>to</tt> contact. - * - * @param to the <tt>Contact</tt> to send <tt>message</tt> to - * @param message the <tt>Message</tt> to send. - * @throws IllegalStateException if the underlying stack is - * not registered and initialized. - * @throws IllegalArgumentException if <tt>to</tt> is not an - * instance of ContactImpl. - */ - public void sendInstantMessage(Contact to, Message message) - throws IllegalStateException, IllegalArgumentException - { - assertConnected(); - - if( !(to instanceof ContactYahooImpl) ) - throw new IllegalArgumentException( - "The specified contact is not a Yahoo contact." - + to); - - try - { - String toUserID = ((ContactYahooImpl) to).getID(); - - MessageDeliveredEvent msgDeliveryPendingEvt = - new MessageDeliveredEvent(message, to, new Date()); - - MessageDeliveredEvent[] msgDeliveryPendingEvts = messageDeliveryPendingTransform(msgDeliveryPendingEvt); - - if (msgDeliveryPendingEvts == null || msgDeliveryPendingEvts.length == 0) - return; - - for (MessageDeliveredEvent event : msgDeliveryPendingEvts) - { - byte[] msgBytesToBeSent = - event.getSourceMessage().getContent().trim() - .getBytes("UTF-8"); - - // split the message in parts with max allowed length - // and send them all - do - { - if (msgBytesToBeSent.length > MAX_MESSAGE_LENGTH) - { - byte[] tmp1 = new byte[MAX_MESSAGE_LENGTH]; - System.arraycopy(msgBytesToBeSent, 0, tmp1, 0, - MAX_MESSAGE_LENGTH); - - byte[] tmp2 = - new byte[msgBytesToBeSent.length - - MAX_MESSAGE_LENGTH]; - System.arraycopy(msgBytesToBeSent, MAX_MESSAGE_LENGTH, - tmp2, 0, tmp2.length); - - msgBytesToBeSent = tmp2; - - yahooProvider.getYahooSession().sendMessage(toUserID, - new String(tmp1, "UTF-8")); - } - else - { - yahooProvider.getYahooSession().sendMessage(toUserID, - new String(msgBytesToBeSent, "UTF-8")); - } - - MessageDeliveredEvent msgDeliveredEvt = - new MessageDeliveredEvent(message, to, new Date()); - - if (msgDeliveredEvt != null) - fireMessageEvent(msgDeliveredEvt); - } - while (msgBytesToBeSent.length > MAX_MESSAGE_LENGTH); - } - } - catch (IOException ex) - { - logger.fatal("Cannot Send Message! " + ex.getMessage()); - MessageDeliveryFailedEvent evt = - new MessageDeliveryFailedEvent( - message, - to, - MessageDeliveryFailedEvent.NETWORK_FAILURE); - - if (evt != null) - fireMessageEvent(evt); - } - } - - /** - * Utility method throwing an exception if the stack is not properly - * initialized. - * @throws IllegalStateException if the underlying stack is - * not registered and initialized. - */ - private void assertConnected() throws IllegalStateException - { - if (yahooProvider == null) - throw new IllegalStateException( - "The provider must be non-null and signed on the " - +"service before being able to communicate."); - if (!yahooProvider.isRegistered()) - throw new IllegalStateException( - "The provider must be signed on the service before " - +"being able to communicate."); - } - - /** - * Our listener that will tell us when we're registered to - */ - private class RegistrationStateListener - implements RegistrationStateChangeListener - { - /** - * The method is called by a ProtocolProvider implementation whenver - * 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) - { - opSetPersPresence = - (OperationSetPersistentPresenceYahooImpl) yahooProvider - .getOperationSet(OperationSetPersistentPresence.class); - - yahooProvider.getYahooSession(). - addSessionListener(new YahooMessageListener()); - } - } - } - - /** - * Delivers the specified event to all registered message listeners. - * @param evt the <tt>EventObject</tt> that we'd like delivered to all - * registered message listerners. - */ - @Override - protected void fireMessageEvent(EventObject evt) - { - // check if this event should be filtered out - Iterator<EventFilter> filters; - synchronized (eventFilters) - { - filters = new ArrayList<EventFilter>(eventFilters).iterator(); - } - // return if a filter has filtered this event out - boolean filtered = false; - while (filters.hasNext()) - { - try - { - if (filters.next().filterEvent(evt)) - { - filtered = true; - } - } - catch(Exception exc) - { - logger.error("An exception occurred while filtering an event.", - exc); - } - } - - if (filtered) - { - if (logger.isTraceEnabled()) - logger.trace("Message event filtered."); - return; - } - - super.fireMessageEvent(evt); - } - - /** - * This class provides methods to listen for yahoo events which interest us. - */ - private class YahooMessageListener - extends SessionAdapter - { - /** - * Overrides <tt>messageReceived</tt> from <tt>SessionAdapter</tt>, - * called when we receive a new intant message. - * - * @param ev Event with information on the received message - */ - @Override - public void messageReceived(SessionEvent ev) - { - handleNewMessage(ev); - } - - /** - * Overrides <tt>offlineMessageReceived</tt> from <tt>SessionAdapter</tt>, - * called when we receive a message which has been sent to us - * when we were offline. - * - * @param ev Event with information on the received message - */ - @Override - public void offlineMessageReceived(SessionEvent ev) - { - handleNewMessage(ev); - } - - /** - * Overrides <tt>newMailReceived</tt> from <tt>SessionAdapter</tt>, - * called when yahoo alert us that there is a new message in our mailbox. - * There is two types of notification, the first one provides only - * the number of unread mails and the second gives informations about - * a precise new mail. Here, we care about only the second case in which - * we should always have the email of the sender of the mail. - * - * @param ev Event with information on the received email - */ - @Override - public void newMailReceived(SessionNewMailEvent ev) - { - // why, if I provide mail@yahoo.FR when registering my account, - // SC later tells me that my email address is mail@yahoo.COM ?? - // because of this users will always be sent on yahoo.com mail - // login page rather than their usual (yahoo.XXX) login page. - String myEmail = yahooProvider.getAccountID().getAccountAddress(); - - // we don't process incoming email event without source address. - // it allows us to avoid some spams - if ((ev.getEmailAddress() == null) - || (ev.getEmailAddress().indexOf('@') < 0)) - { - return; - } - - String yahooMailLogon = "http://mail." - + myEmail.substring(myEmail.indexOf('@') + 1); - - yahooMailLogon = " <a href=\"" - + yahooMailLogon + "\">" - + yahooMailLogon + "</a>"; - - // FIXME Escape HTML! - String newMail = YahooActivator.getResources().getI18NString( - "service.gui.NEW_MAIL", - new String[]{ev.getFrom(), - "<" + ev.getEmailAddress() + ">", - ev.getSubject(), - " "+yahooMailLogon}) ; - - Message newMailMessage = new MessageYahooImpl( - newMail, - HTML_MIME_TYPE, - DEFAULT_MIME_ENCODING, - null); - - Contact sourceContact = opSetPersPresence. - findContactByID(ev.getEmailAddress()); - - if (sourceContact == null) - { - if (logger.isDebugEnabled()) - logger.debug("received a new mail from an unknown contact: " - + ev.getFrom() - + " <" + ev.getEmailAddress() + ">"); - //create the volatile contact - sourceContact = opSetPersPresence - .createVolatileContact(ev.getEmailAddress()); - } - MessageReceivedEvent msgReceivedEvt - = new MessageReceivedEvent( - newMailMessage, sourceContact, new Date(), - MessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED); - - fireMessageEvent(msgReceivedEvt); - } - - /** - * Handle incoming message by creating an appropriate Sip Communicator - * <tt>Message</tt> and firing a <tt>MessageReceivedEvent</tt> - * to interested listeners. - * - * @param ev The original <tt>SessionEvent</tt> which noticed us - * of an incoming message. - */ - private void handleNewMessage(SessionEvent ev) - { - if (logger.isDebugEnabled()) - logger.debug("Message received : " + ev); - - // to keep things simple, we can decodeToText() - //String formattedMessage = processLinks( - // messageDecoder.decodeToText(ev.getMessage())); - - String formattedMessage = ev.getMessage(); - if (logger.isDebugEnabled()) - logger.debug("original message received : " + formattedMessage); - - formattedMessage = decodeMessage(formattedMessage); - - if (logger.isDebugEnabled()) - logger.debug("formatted Message : " + formattedMessage); - // As no indications in the protocol is it html or not. No harm - // to set all messages html - doesn't affect the appearance of the - // gui - Message newMessage = - createMessage(formattedMessage, HTML_MIME_TYPE, - DEFAULT_MIME_ENCODING, null); - - Contact sourceContact = opSetPersPresence. - findContactByID(ev.getFrom()); - - if(sourceContact == null) - { - if (logger.isDebugEnabled()) - logger.debug("received a message from an unknown contact: " - + ev.getFrom()); - //create the volatile contact - sourceContact = opSetPersPresence - .createVolatileContact(ev.getFrom()); - } - - MessageReceivedEvent msgReceivedEvt - = new MessageReceivedEvent( - newMessage, sourceContact , new Date()); - - // msgReceivedEvt = messageReceivedTransform(msgReceivedEvt); - - if (msgReceivedEvt != null) - fireMessageEvent(msgReceivedEvt); - } - } - - /** - * Decode the received chat message. - * If the message contains \u001b the following text is decoded by - * the MessageDecoder of yahoo api - * Then make http links clickable and fix the font size of html code - * - * @param message the chat message - * @return a decoded message. - */ - String decodeMessage(String message) - { - message = messageDecoder.decodeToHTML(message); - message = processLinks(message); - message = - FONT_SIZE_0_PATTERN.matcher(message) - .replaceAll("$1 $2 size=\"10\">"); - message = - FONT_SIZE_INT_PATTERN.matcher(message) - .replaceAll("$1 $2 style=\"font-size: $3px;\">"); - return message; - } - - /** - * Format links in the given message. Skips all links, which are already in - * HTML format and converts all other links. - * - * @param message The source message string. - * @return The message string with properly formatted links. - */ - public String processLinks(String message) - { - StringBuffer msgBuffer = new StringBuffer(); - - // We match two groups of Strings. The first group is the group of any - // String. The second group is a well formatted HTML link. - Pattern p = Pattern.compile("(.*?)(<a[\\s][^<]*(/>|</a>))", - Pattern.CASE_INSENSITIVE); - - Matcher m = p.matcher(message); - - int lastMatchIndex = 0; - while (m.find()) - { - lastMatchIndex = m.end(); - - String matchGroup1 = m.group(1); - String matchGroup2 = m.group(2); - - String formattedString = formatLinksToHTML(matchGroup1); - - m.appendReplacement(msgBuffer, - replaceSpecialRegExpChars(formattedString) + matchGroup2); - } - - String tailString = message.substring(lastMatchIndex); - - String formattedTailString = formatLinksToHTML(tailString); - - msgBuffer.append(formattedTailString); - - return msgBuffer.toString(); - } - - /** - * Replaces some chars that are special in a regular expression. - * - * @param text The initial text. - * @return the formatted text - */ - private static String replaceSpecialRegExpChars(String text) - { - return MESSAGE_CHARS_ESCAPE.matcher(text).replaceAll("\\\\$1"); - } - - /** - * Goes through the given text and converts all links to HTML links. - * <p> - * For example all occurrences of http://jitsi.org/ will be - * replaced by <a href="http://jitsi.org/"> - * http://jitsi.org/</a>. The same is true for all strings - * starting with "www". - * - * @param text the text on which the regular expression would be performed - * @return the initial text containing only HTML links - */ - private static String formatLinksToHTML(String text) - { - String wwwURL = "(www\\." + // Matches the "www" string. - "[^/?#<\"'\\s]+" + // Matches at least one char of - // any type except / ? # < " ' - // and space. - "[\\.]" + // Matches the second point of the link. - "[^?#<\"'\\s]+" + // Matches at least one char of - // any type except ? # < " ' - // and space. - "(\\?[^#<\"'\\s]*)?" + - "(#.*)?)"; - - String protocolURL - = "([^\"'<>:/?#\\s]+" + // Matches at least one char of - // any type except " ' < > : / ? # - // and space. - "://" + // Matches the :// delimiter in links - "[^/?#<\"'\\s]*" + // Matches any number of times any char - // except / ? # < " ' and space. - "[^?#<\"'\\s]*" + // Matches any number of times any char - // except ? # < " ' and space. - "(\\?[^#<\"'\\s]*)?" + - "(#.*)?)"; - - String url = '(' + wwwURL + '|' + protocolURL + ')'; - - Pattern p = Pattern.compile(url, Pattern.CASE_INSENSITIVE); - - Matcher m = p.matcher(text); - - StringBuffer linkBuffer = new StringBuffer(); - - while (m.find()) - { - String linkGroup = m.group(); - - String replacement; - if (linkGroup.startsWith("www")) - { - replacement = "<A href=\"" + "http://" - + linkGroup + "\">" + linkGroup + "</A>"; - } - else - { - replacement = "<A href=\"" + linkGroup - + "\">" + linkGroup + "</A>"; - } - - m.appendReplacement(linkBuffer, - replaceSpecialRegExpChars(replacement)); - } - - m.appendTail(linkBuffer); - - return linkBuffer.toString(); - } - - /** - * Registers an <tt>EventFilter</tt> with this operation set so that - * events, that do not need processing, are filtered out. - * - * @param filter the <tt>EventFilter</tt> to register. - */ - public void addEventFilter(EventFilter filter) - { - synchronized(eventFilters) - { - if(!eventFilters.contains(filter)) - { - eventFilters.add(filter); - } - } - } - - /** - * Unregisteres an <tt>EventFilter</tt> so that it won't check any more - * if an event should be filtered out. - * - * @param filter the <tt>EventFilter</tt> to unregister. - */ - public void removeEventFilter(EventFilter filter) - { - synchronized(eventFilters) - { - eventFilters.remove(filter); - } - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetFileTransferYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetFileTransferYahooImpl.java deleted file mode 100644 index 85f25b1..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetFileTransferYahooImpl.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; -import ymsg.network.event.*; - -/** - * The Yahoo protocol filetransfer OperationSet. - * - * @author Damian Minkov - */ -public class OperationSetFileTransferYahooImpl - implements OperationSetFileTransfer, - SessionFileTransferListener -{ - /** - * The logger for this class. - */ - private static final Logger logger = - Logger.getLogger(OperationSetFileTransferYahooImpl.class); - - /** - * The provider that created us. - */ - private final ProtocolProviderServiceYahooImpl yahooProvider; - - /** - * A list of listeners registered for file transfer events. - */ - private ArrayList<FileTransferListener> fileTransferListeners - = new ArrayList<FileTransferListener>(); - - /** - * A list of active fileTransfers. - */ - private Hashtable<String, Object> activeFileTransfers - = new Hashtable<String, Object>(); - - /** - * Constructor - * @param provider is the provider that created us - */ - public OperationSetFileTransferYahooImpl( - ProtocolProviderServiceYahooImpl provider) - { - this.yahooProvider = provider; - - provider.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 - { - try - { - assertConnected(); - - if(file.length() > getMaximumFileLength()) - throw new IllegalArgumentException( - "File length exceeds the allowed one for this protocol"); - - ArrayList<String> filesToSend = new ArrayList<String>(); - filesToSend.add(file.getCanonicalPath()); - Date sentDate = new Date(); - String id = yahooProvider.getYahooSession().sendFiles( - filesToSend, toContact.getAddress()); - - FileTransferImpl ft = - new FileTransferImpl(yahooProvider, - id, toContact, file, FileTransfer.OUT); - - // Notify all interested listeners that a file transfer has been - // created. - FileTransferCreatedEvent event - = new FileTransferCreatedEvent(ft, sentDate); - - fireFileTransferCreated(event); - - ft.fireStatusChangeEvent(FileTransferStatusChangeEvent.PREPARING); - - return ft; - } - catch(IOException e) - { - logger.error("Cannot send fileTransfer", e); - return null; - } - } - - /** - * 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 (yahooProvider == null) - throw new IllegalStateException( - "The provider must be non-null and signed on the " - +"service before being able to send a file."); - else if (!yahooProvider.isRegistered()) - throw new IllegalStateException( - "The provider must be signed on the service before " - +"being able to send a file."); - } - - /** - * 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) - { - activeFileTransfers.put( - event.getFileTransfer().getID(), event.getFileTransfer()); - - Iterator<FileTransferListener> listeners = null; - synchronized (fileTransferListeners) - { - listeners = new ArrayList<FileTransferListener> - (fileTransferListeners).iterator(); - } - - while (listeners.hasNext()) - { - FileTransferListener listener = listeners.next(); - listener.fileTransferCreated(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. - */ - 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 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); - } - } - - private int getStateMapping(int s) - { - switch(s) - { - case SessionFileTransferEvent.REFUSED : - return FileTransferStatusChangeEvent.REFUSED; - case SessionFileTransferEvent.CANCEL : - return FileTransferStatusChangeEvent.CANCELED; - case SessionFileTransferEvent.FAILED : - return FileTransferStatusChangeEvent.FAILED; - case SessionFileTransferEvent.IN_PROGRESS : - return FileTransferStatusChangeEvent.IN_PROGRESS; - case SessionFileTransferEvent.RECEIVED : - return FileTransferStatusChangeEvent.COMPLETED; - case SessionFileTransferEvent.SENT : - return FileTransferStatusChangeEvent.COMPLETED; - default: return FileTransferStatusChangeEvent.WAITING; - } - } - - /** - * Starting point for incoming filetransfer. - * @param ev - */ - public void fileTransferRequestReceived(SessionFileTransferEvent ev) - { - OperationSetPersistentPresenceYahooImpl opSetPersPresence - = (OperationSetPersistentPresenceYahooImpl) - yahooProvider.getOperationSet( - OperationSetPersistentPresence.class); - - Contact sender = opSetPersPresence.findContactByID(ev.getFrom()); - - if(sender == null) - return; - - Date recvDate = new Date(); - - for(int i = 0; i < ev.getFileNames().size(); i++) - { - String fileName = ev.getFileNames().get(i); - String fileSize = ev.getFileSizes().get(i); - - IncomingFileTransferRequest req = - new IncomingFileTransferRequestYahooImpl( - yahooProvider, this, sender, recvDate, - fileName, fileSize, - ev.getId()); - - activeFileTransfers.put(ev.getId(), req); - fireFileTransferRequest( - new FileTransferRequestEvent(this, req, recvDate)); - } - } - - /** - * Status changed for filetransfer. - * @param ev - */ - public void statusChanged(SessionFileTransferEvent ev) - { - if(ev.getId() == null) - return; - - Object ftObj = activeFileTransfers.get(ev.getId()); - - if(ftObj == null) - { - logger.warn("File Transfer or request not found. " + ev.getId() + "/ " + ev.getState()); - return; - } - - int newState = ev.getState(); - - if(newState == SessionFileTransferEvent.CANCEL - || newState == SessionFileTransferEvent.FAILED - || newState == SessionFileTransferEvent.RECEIVED - || newState == SessionFileTransferEvent.REFUSED - || newState == SessionFileTransferEvent.SENT) - { - // this is an final state so remove it from active filetransfers - activeFileTransfers.remove(ev.getId()); - } - - if(ftObj instanceof IncomingFileTransferRequest) - { - if(newState == SessionFileTransferEvent.REFUSED) - { - IncomingFileTransferRequestYahooImpl req = - (IncomingFileTransferRequestYahooImpl)ftObj; - fireFileTransferRequestCanceled( - new FileTransferRequestEvent(this, req, req.getDate())); - return; - } - } - - if(!(ftObj instanceof FileTransferImpl)) - { - logger.warn("File Transfer not found." + ftObj); - return; - } - - FileTransferImpl ft = (FileTransferImpl)ftObj; - - if( newState == SessionFileTransferEvent.IN_PROGRESS) - { - // if we start sending progress fire that we are in progress - if(ev.getProgress() == 0) - ft.fireStatusChangeEvent( - FileTransferStatusChangeEvent.IN_PROGRESS); - - ft.setTransferedBytes(ev.getProgress()); - ft.fireProgressChangeEvent( - System.currentTimeMillis(), ev.getProgress()); - } - else - ft.fireStatusChangeEvent(getStateMapping(newState)); - } - - /** - * Returns the maximum file length supported by the protocol in bytes. - * Supports up to 256MB. - * - * @return the file length that is supported. - */ - public long getMaximumFileLength() - { - return 268435456l;// = 256*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) - { - yahooProvider.getYahooSession().addSessionFileListener( - OperationSetFileTransferYahooImpl.this); - } - else if (evt.getNewState() == RegistrationState.UNREGISTERED) - { - YahooSession ys = yahooProvider.getYahooSession(); - if(ys != null) - ys.removeSessionFileListener( - OperationSetFileTransferYahooImpl.this); - } - } - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetPersistentPresenceYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetPersistentPresenceYahooImpl.java deleted file mode 100644 index a99bdb7..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetPersistentPresenceYahooImpl.java +++ /dev/null @@ -1,954 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.service.protocol.yahooconstants.*; -import net.java.sip.communicator.util.*; -import ymsg.network.*; -import ymsg.network.event.*; - -/** - * The Yahoo implementation of a Persistent Presence Operation set. This class - * manages our own presence status as well as subscriptions for the presence - * status of our buddies. It also offers methods for retrieving and modifying - * the buddy contact list and adding listeners for changes in its layout. - * - * @author Damian Minkov - */ -public class OperationSetPersistentPresenceYahooImpl - extends AbstractOperationSetPersistentPresence<ProtocolProviderServiceYahooImpl> -{ - private static final Logger logger = - Logger.getLogger(OperationSetPersistentPresenceYahooImpl.class); - - /** - * Contains our current status message. Note that this field would only - * be changed once the server has confirmed the new status message and - * not immediately upon setting a new one.. - */ - private String currentStatusMessage = ""; - - /** - * The presence status that we were last notified of entering. - * The initial one is OFFLINE - */ - private PresenceStatus currentStatus = YahooStatusEnum.OFFLINE; - - /** - * Sometimes status changes are received before the contact list is inited - * here we store such events so we can show them correctly - */ -// private Hashtable earlyStatusChange = new Hashtable(); - - /** - * The array list we use when returning from the getSupportedStatusSet() - * method. - */ - private static final List<PresenceStatus> supportedPresenceStatusSet = new ArrayList<PresenceStatus>(); - static{ - supportedPresenceStatusSet.add(YahooStatusEnum.AVAILABLE); - supportedPresenceStatusSet.add(YahooStatusEnum.BE_RIGHT_BACK); - supportedPresenceStatusSet.add(YahooStatusEnum.BUSY); - supportedPresenceStatusSet.add(YahooStatusEnum.IDLE); - supportedPresenceStatusSet.add(YahooStatusEnum.INVISIBLE); - supportedPresenceStatusSet.add(YahooStatusEnum.NOT_AT_DESK); - supportedPresenceStatusSet.add(YahooStatusEnum.NOT_AT_HOME); - supportedPresenceStatusSet.add(YahooStatusEnum.NOT_IN_OFFICE); - supportedPresenceStatusSet.add(YahooStatusEnum.OFFLINE); - supportedPresenceStatusSet.add(YahooStatusEnum.ON_THE_PHONE); - supportedPresenceStatusSet.add(YahooStatusEnum.ON_VACATION); - supportedPresenceStatusSet.add(YahooStatusEnum.OUT_TO_LUNCH); - supportedPresenceStatusSet.add(YahooStatusEnum.STEPPED_OUT); - } - - /** - * A map containing bindings between SIP Communicator's yahoo presence status - * instances and Yahoo status codes - */ - private static final Map<PresenceStatus, Long> scToYahooModesMappings - = new Hashtable<PresenceStatus, Long>(); - static{ - scToYahooModesMappings.put(YahooStatusEnum.AVAILABLE, - StatusConstants.STATUS_AVAILABLE); - scToYahooModesMappings.put(YahooStatusEnum.BE_RIGHT_BACK, - StatusConstants.STATUS_BRB); - scToYahooModesMappings.put(YahooStatusEnum.BUSY, - StatusConstants.STATUS_BUSY); - scToYahooModesMappings.put(YahooStatusEnum.IDLE, - StatusConstants.STATUS_IDLE); - scToYahooModesMappings.put(YahooStatusEnum.INVISIBLE, - StatusConstants.STATUS_INVISIBLE); - scToYahooModesMappings.put(YahooStatusEnum.NOT_AT_DESK, - StatusConstants.STATUS_NOTATDESK); - scToYahooModesMappings.put(YahooStatusEnum.NOT_AT_HOME, - StatusConstants.STATUS_NOTATHOME); - scToYahooModesMappings.put(YahooStatusEnum.NOT_IN_OFFICE, - StatusConstants.STATUS_NOTINOFFICE); - scToYahooModesMappings.put(YahooStatusEnum.OFFLINE, - StatusConstants.STATUS_OFFLINE); - scToYahooModesMappings.put(YahooStatusEnum.ON_THE_PHONE, - StatusConstants.STATUS_ONPHONE); - scToYahooModesMappings.put(YahooStatusEnum.ON_VACATION, - StatusConstants.STATUS_ONVACATION); - scToYahooModesMappings.put(YahooStatusEnum.OUT_TO_LUNCH, - StatusConstants.STATUS_OUTTOLUNCH); - scToYahooModesMappings.put(YahooStatusEnum.STEPPED_OUT, - StatusConstants.STATUS_STEPPEDOUT); - } - - /** - * The server stored contact list that will be encapsulating smack's - * buddy list. - */ - private ServerStoredContactListYahooImpl ssContactList = null; - - /** - * Listens for events that are fired while registering to server. - * After we are registered instance is cleared and never used. - */ - private EarlyEventListener earlyEventListener = null; - - /** - * Status events are received before subscription one. - * And when subscription is received we deliver - * and the status events. - */ - private StatusUpdater statusUpdater = new StatusUpdater(); - - public OperationSetPersistentPresenceYahooImpl( - ProtocolProviderServiceYahooImpl provider) - { - super(provider); - - ssContactList = new ServerStoredContactListYahooImpl( this , provider); - - parentProvider.addRegistrationStateChangeListener( - new RegistrationStateListener()); - } - - /** - * Registers a listener that would receive events upong changes in server - * stored groups. - * - * @param listener a ServerStoredGroupChangeListener impl that would - * receive events upong group changes. - */ - @Override - public void addServerStoredGroupChangeListener(ServerStoredGroupListener - listener) - { - ssContactList.addGroupListener(listener); - } - - /** - * Creates a group with the specified name and parent in the server - * stored contact list. - * - * @param parent the group where the new group should be created - * @param groupName the name of the new group to create. - * @throws OperationFailedException if such group already exists - */ - public void createServerStoredContactGroup(ContactGroup parent, - String groupName) - throws OperationFailedException - { - assertConnected(); - - if (!parent.canContainSubgroups()) - throw new IllegalArgumentException( - "The specified contact group cannot contain child groups. Group:" - + parent ); - - ssContactList.createGroup(groupName); - } - - /** - * Creates a non persistent contact for the specified address. This would - * also create (if necessary) a group for volatile contacts that would not - * be added to the server stored contact list. The volatile contact would - * remain in the list until it is really added to the contact list or - * until the application is terminated. - * @param id the address of the contact to create. - * @return the newly created volatile <tt>ContactImpl</tt> - */ - public ContactYahooImpl createVolatileContact(String id) - { - return ssContactList.createVolatileContact(id); - } - - /** - * Creates and returns a unresolved contact from the specified - * <tt>address</tt> and <tt>persistentData</tt>. - * - * @param address an identifier of the contact that we'll be creating. - * @param persistentData a String returned Contact's getPersistentData() - * method during a previous run and that has been persistently stored - * locally. - * @param parentGroup the group where the unresolved contact is supposed - * to belong to. - * @return the unresolved <tt>Contact</tt> created from the specified - * <tt>address</tt> and <tt>persistentData</tt> - */ - public Contact createUnresolvedContact(String address, - String persistentData, - ContactGroup parentGroup) - { - if(! (parentGroup instanceof ContactGroupYahooImpl || - parentGroup instanceof RootContactGroupYahooImpl) ) - throw new IllegalArgumentException( - "Argument is not an yahoo contact group (group=" - + parentGroup + ")"); - - ContactYahooImpl contact = - ssContactList.createUnresolvedContact(parentGroup, address); - - contact.setPersistentData(persistentData); - - return contact; - } - - /** - * Creates and returns a unresolved contact from the specified - * <tt>address</tt> and <tt>persistentData</tt>. - * - * @param address an identifier of the contact that we'll be creating. - * @param persistentData a String returned Contact's getPersistentData() - * method during a previous run and that has been persistently stored - * locally. - * @return the unresolved <tt>Contact</tt> created from the specified - * <tt>address</tt> and <tt>persistentData</tt> - */ - public Contact createUnresolvedContact(String address, - String persistentData) - { - return createUnresolvedContact( address - , persistentData - , getServerStoredContactListRoot()); - } - - /** - * Creates and returns a unresolved contact group from the specified - * <tt>address</tt> and <tt>persistentData</tt>. - * - * @param groupUID an identifier, returned by ContactGroup's - * getGroupUID, that the protocol provider may use in order to create - * the group. - * @param persistentData a String returned ContactGroups's - * getPersistentData() method during a previous run and that has been - * persistently stored locally. - * @param parentGroup the group under which the new group is to be - * created or null if this is group directly underneath the root. - * @return the unresolved <tt>ContactGroup</tt> created from the - * specified <tt>uid</tt> and <tt>persistentData</tt> - */ - public ContactGroup createUnresolvedContactGroup(String groupUID, - String persistentData, ContactGroup parentGroup) - { - return ssContactList.createUnresolvedContactGroup(groupUID); - } - - /** - * Returns a reference to the contact with the specified ID in case we - * have a subscription for it and null otherwise/ - * - * @param contactID a String identifier of the contact which we're - * seeking a reference of. - * @return a reference to the Contact with the specified - * <tt>contactID</tt> or null if we don't have a subscription for the - * that identifier. - */ - public Contact findContactByID(String contactID) - { - return ssContactList.findContactById(contactID); - } - - /** - * Returns the status message that was confirmed by the serfver - * - * @return the last status message that we have requested and the aim - * server has confirmed. - */ - public String getCurrentStatusMessage() - { - return currentStatusMessage; - } - - /** - * Returns the protocol specific contact instance representing the local - * user. - * - * @return the Contact (address, phone number, or uin) that the Provider - * implementation is communicating on behalf of. - */ - public Contact getLocalContact() - { - return null; - } - - /** - * Returns a PresenceStatus instance representing the state this provider - * is currently in. - * - * @return the PresenceStatus last published by this provider. - */ - public PresenceStatus getPresenceStatus() - { - return currentStatus; - } - - /** - * Returns the root group of the server stored contact list. - * - * @return the root ContactGroup for the ContactList stored by this - * service. - */ - public ContactGroup getServerStoredContactListRoot() - { - return ssContactList.getRootGroup(); - } - - /** - * Returns the set of PresenceStatus objects that a user of this service - * may request the provider to enter. - * - * @return Iterator a PresenceStatus array containing "enterable" status - * instances. - */ - public Iterator<PresenceStatus> getSupportedStatusSet() - { - return supportedPresenceStatusSet.iterator(); - } - - /** - * Removes the specified contact from its current parent and places it - * under <tt>newParent</tt>. - * - * @param contactToMove the <tt>Contact</tt> to move - * @param newParent the <tt>ContactGroup</tt> where <tt>Contact</tt> - * would be placed. - */ - public void moveContactToGroup(Contact contactToMove, - ContactGroup newParent) - { - assertConnected(); - - if( !(contactToMove instanceof ContactYahooImpl) ) - throw new IllegalArgumentException( - "The specified contact is not an yahoo contact." + contactToMove); - if( !(newParent instanceof ContactGroupYahooImpl) ) - throw new IllegalArgumentException( - "The specified group is not an yahoo contact group." - + newParent); - - ssContactList.moveContact((ContactYahooImpl)contactToMove, - (ContactGroupYahooImpl)newParent); - } - - /** - * Requests the provider to enter into a status corresponding to the - * specified paramters. - * - * @param status the PresenceStatus as returned by - * getRequestableStatusSet - * @param statusMessage the message that should be set as the reason to - * enter that status - * @throws IllegalArgumentException if the status requested is not a - * valid PresenceStatus supported by this provider. - * @throws IllegalStateException if the provider is not currently - * registered. - * @throws OperationFailedException with code NETWORK_FAILURE if - * publishing the status fails due to a network error. - */ - public void publishPresenceStatus(PresenceStatus status, - String statusMessage) throws - IllegalArgumentException, IllegalStateException, - OperationFailedException - { - assertConnected(); - - if (!(status instanceof YahooStatusEnum)) - throw new IllegalArgumentException( - status + " is not a valid Yahoo status"); - - if(status.equals(YahooStatusEnum.OFFLINE)) - { - parentProvider.unregister(); - return; - } - - try - { - if(statusMessage != null && statusMessage.length() != 0) - { - boolean isAvailable = false; - - if(status.equals(YahooStatusEnum.AVAILABLE)) - isAvailable = true; - - // false - away - // true - available - parentProvider.getYahooSession(). - setStatus(statusMessage, isAvailable); - } - - parentProvider.getYahooSession().setStatus( - scToYahooModesMappings.get(status).longValue()); - - fireProviderStatusChangeEvent(currentStatus, status); - } - catch(IOException ex) - { - throw new OperationFailedException("Failed to set Status", - OperationFailedException.NETWORK_FAILURE); - } - } - - /** - * Get the PresenceStatus for a particular contact. - * - * @param contactIdentifier the identifier of the contact whose status - * we're interested in. - * @return PresenceStatus the <tt>PresenceStatus</tt> of the specified - * <tt>contact</tt> - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * retrieving the status fails due to errors experienced during - * network communication - */ - public PresenceStatus queryContactStatus(String contactIdentifier) throws - IllegalArgumentException, IllegalStateException, - OperationFailedException - { - - ContactYahooImpl contact = ssContactList.findContactById(contactIdentifier); - if(contact == null) - { - if (logger.isInfoEnabled()) - logger.info("Contact not found id :" + contactIdentifier); - return null; - } - else - return yahooStatusToPresenceStatus(contact.getSourceContact().getStatus()); - } - - /** - * Removes the specified group from the server stored contact list. - * - * @param group the group to remove. - */ - public void removeServerStoredContactGroup(ContactGroup group) - { - assertConnected(); - - if( !(group instanceof ContactGroupYahooImpl) ) - throw new IllegalArgumentException( - "The specified group is not an yahoo contact group: " + group); - - ssContactList.removeGroup(((ContactGroupYahooImpl)group)); - } - - /** - * Removes the specified group change listener so that it won't receive - * any further events. - * - * @param listener the ServerStoredGroupChangeListener to remove - */ - @Override - public void removeServerStoredGroupChangeListener(ServerStoredGroupListener - listener) - { - ssContactList.removeGroupListener(listener); - } - - /** - * Renames the specified group from the server stored contact list. - * - * @param group the group to rename. - * @param newName the new name of the group. - */ - public void renameServerStoredContactGroup(ContactGroup group, - String newName) - { - assertConnected(); - - if( !(group instanceof ContactGroupYahooImpl) ) - throw new IllegalArgumentException( - "The specified group is not an yahoo contact group: " + group); - - throw new UnsupportedOperationException("Renaming group not supported!"); - //ssContactList.renameGroup((ContactGroupYahooImpl)group, newName); - } - - /** - * Handler for incoming authorization requests. - * - * @param handler an instance of an AuthorizationHandler for - * authorization requests coming from other users requesting - * permission add us to their contact list. - */ - public void setAuthorizationHandler(AuthorizationHandler handler) - { - ssContactList.setAuthorizationHandler(handler); - - // we got a handler. Lets process if something has came - // during login process - if(earlyEventListener != null) - { - earlyEventListener.processEarlyAuthorizations(); - earlyEventListener = null; - } - } - - /** - * Persistently adds a subscription for the presence status of the - * contact corresponding to the specified contactIdentifier and indicates - * that it should be added to the specified group of the server stored - * contact list. - * - * @param parent the parent group of the server stored contact list - * where the contact should be added. <p> - * @param contactIdentifier the contact whose status updates we are - * subscribing for. - * @throws IllegalArgumentException if <tt>contact</tt> or - * <tt>parent</tt> are not a contact known to the underlying protocol - * provider. - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * subscribing fails due to errors experienced during network - * communication - */ - public void subscribe(ContactGroup parent, String contactIdentifier) throws - IllegalArgumentException, IllegalStateException, - OperationFailedException - { - assertConnected(); - - if(! (parent instanceof ContactGroupYahooImpl) ) - throw new IllegalArgumentException( - "Argument is not an yahoo contact group (group=" + parent + ")"); - - ssContactList.addContact((ContactGroupYahooImpl)parent, contactIdentifier); - } - - /** - * Adds a subscription for the presence status of the contact - * corresponding to the specified contactIdentifier. - * - * @param contactIdentifier the identifier of the contact whose status - * updates we are subscribing for. <p> - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * subscribing fails due to errors experienced during network - * communication - */ - public void subscribe(String contactIdentifier) throws - IllegalArgumentException, IllegalStateException, - OperationFailedException - { - assertConnected(); - - ssContactList.addContact(contactIdentifier); - } - - /** - * Removes a subscription for the presence status of the specified - * contact. - * - * @param contact the contact whose status updates we are unsubscribing - * from. - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * unsubscribing fails due to errors experienced during network - * communication - */ - public void unsubscribe(Contact contact) throws IllegalArgumentException, - IllegalStateException, OperationFailedException - { - assertConnected(); - - if(! (contact instanceof ContactYahooImpl) ) - throw new IllegalArgumentException( - "Argument is not an yahoo contact (contact=" + contact + ")"); - - ssContactList.removeContact((ContactYahooImpl)contact); - } - - /** - * Converts the specified yahoo status to one of the status fields of the - * YahooStatusEnum class. - * - * @param status the yahoo Status - * @return a PresenceStatus instance representation of the yahoo Status - * parameter. The returned result is one of the YahooStatusEnum fields. - */ - YahooStatusEnum yahooStatusToPresenceStatus(long status) - { - if(status == StatusConstants.STATUS_AVAILABLE) - return YahooStatusEnum.AVAILABLE; - else if(status == StatusConstants.STATUS_BRB) - return YahooStatusEnum.BE_RIGHT_BACK; - else if(status == StatusConstants.STATUS_BUSY) - return YahooStatusEnum.BUSY; - else if(status == StatusConstants.STATUS_NOTATHOME) - return YahooStatusEnum.NOT_AT_HOME; - else if(status == StatusConstants.STATUS_NOTATDESK) - return YahooStatusEnum.NOT_AT_DESK; - else if(status == StatusConstants.STATUS_NOTINOFFICE) - return YahooStatusEnum.NOT_IN_OFFICE; - else if(status == StatusConstants.STATUS_ONPHONE) - return YahooStatusEnum.ON_THE_PHONE; - else if(status == StatusConstants.STATUS_ONVACATION) - return YahooStatusEnum.ON_VACATION; - else if(status == StatusConstants.STATUS_OUTTOLUNCH) - return YahooStatusEnum.OUT_TO_LUNCH; - else if(status == StatusConstants.STATUS_STEPPEDOUT) - return YahooStatusEnum.STEPPED_OUT; - else if(status == StatusConstants.STATUS_INVISIBLE) - return YahooStatusEnum.INVISIBLE; - else if(status == StatusConstants.STATUS_IDLE) - return YahooStatusEnum.IDLE; - else if(status == StatusConstants.STATUS_OFFLINE) - return YahooStatusEnum.OFFLINE; - // Yahoo supports custom statuses so if such is set just return available - else - return YahooStatusEnum.AVAILABLE; - } - - /** - * 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 (parentProvider == null) - throw new IllegalStateException( - "The provider must be non-null and signed on the yahoo " - +"service before being able to communicate."); - if (!parentProvider.isRegistered()) - throw new IllegalStateException( - "The provider must be signed on the yahoo service before " - +"being able to communicate."); - } - - /** - * Notify all provider presence listeners of the corresponding event change - * - * @param oldStatus - * the status our stack had so far - * @param newStatus - * the status we have from now on - */ - @Override - protected void fireProviderStatusChangeEvent( - PresenceStatus oldStatus, - PresenceStatus newStatus) - { - if (!oldStatus.equals(newStatus)) - { - currentStatus = newStatus; - - super.fireProviderStatusChangeEvent(oldStatus, newStatus); - } - } - - /** - * Statuses have been received durring login process - * so we will init them once we are logged in - */ - private void initContactStatuses() - { - YahooGroup[] groups = parentProvider.getYahooSession().getGroups(); - - for (YahooGroup item : groups) - { - @SuppressWarnings("unchecked") - Iterable<YahooUser> members = item.getMembers(); - - for (YahooUser user : members) - { - ContactYahooImpl sourceContact = - ssContactList.findContactById(user.getId()); - - if(sourceContact != null) - handleContactStatusChange(sourceContact, user); - } - } - } - - /** - * Our listener that will tell us when we're registered to server - * and is ready to accept us as a listener. - */ - 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 yahoo provider changed state from: " - + evt.getOldState() - + " to: " + evt.getNewState()); - - if(evt.getNewState() == RegistrationState.REGISTERING) - { - // add new listener waiting for events during login process - earlyEventListener - = new EarlyEventListener(parentProvider.getYahooSession()); - } - else if(evt.getNewState() == RegistrationState.REGISTERED) - { - parentProvider.getYahooSession(). - addSessionListener(new StatusChangedListener()); - - ssContactList.setYahooSession(parentProvider.getYahooSession()); - - initContactStatuses(); - - addSubscriptionListener(statusUpdater); - - if(earlyEventListener != null) - { - earlyEventListener.dispose(); - earlyEventListener = null; - } - } - else if(evt.getNewState() == RegistrationState.UNREGISTERED - || evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED - || evt.getNewState() == RegistrationState.CONNECTION_FAILED) - { - //since we are disconnected, we won't receive any further status - //updates so we need to change by ourselves our own status as - //well as set to offline all contacts in our contact list that - //were online - PresenceStatus oldStatus = currentStatus; - currentStatus = YahooStatusEnum.OFFLINE; - - fireProviderStatusChangeEvent(oldStatus, currentStatus); - - removeSubscriptionListener(statusUpdater); - - //send event notifications saying that all our buddies are - //offline. The protocol does not implement top level buddies - //nor subgroups for top level groups so a simple nested loop - //would be enough. - Iterator<ContactGroup> groupsIter = - getServerStoredContactListRoot().subgroups(); - while(groupsIter.hasNext()) - { - ContactGroup group = groupsIter.next(); - Iterator<Contact> contactsIter = group.contacts(); - - while(contactsIter.hasNext()) - { - ContactYahooImpl contact - = (ContactYahooImpl)contactsIter.next(); - - PresenceStatus oldContactStatus - = contact.getPresenceStatus(); - - if(!oldContactStatus.isOnline()) - continue; - - contact.updatePresenceStatus(YahooStatusEnum.OFFLINE); - - fireContactPresenceStatusChangeEvent( - contact - , contact.getParentContactGroup() - , oldContactStatus, YahooStatusEnum.OFFLINE); - } - } - - // clear listener - if(earlyEventListener != null) - { - earlyEventListener.dispose(); - earlyEventListener = null; - } - } - } - } - - private void handleContactStatusChange(YahooUser yFriend) - { - ContactYahooImpl sourceContact = - ssContactList.findContactById(yFriend.getId()); - - if(sourceContact == null) - { - if(parentProvider.getAccountID().getUserID(). - equals(yFriend.getId())) - { - // thats my own status - if (logger.isTraceEnabled()) - logger.trace("Own status changed to " + yFriend.getStatus()); - PresenceStatus oldStatus = currentStatus; - currentStatus = - yahooStatusToPresenceStatus(yFriend.getStatus()); - fireProviderStatusChangeEvent(oldStatus, currentStatus); - - return; - } - // strange - else - return; - } - - handleContactStatusChange(sourceContact, yFriend); - } - - void handleContactStatusChange(ContactYahooImpl sourceContact, YahooUser yFriend) - { - PresenceStatus oldStatus - = sourceContact.getPresenceStatus(); - - PresenceStatus newStatus = yahooStatusToPresenceStatus(yFriend.getStatus()); - - // statuses maybe the same and only change in status message - sourceContact.setStatusMessage(yFriend.getCustomStatusMessage()); - - // when old and new status are the same do nothing - no change - if(oldStatus.equals(newStatus)) - { - if (logger.isDebugEnabled()) - logger.debug("old(" + oldStatus + ") and new("+ newStatus + ") statuses are the same!"); - return; - } - - sourceContact.updatePresenceStatus(newStatus); - - ContactGroup parent - = ssContactList.findContactGroup(sourceContact); - - if (logger.isDebugEnabled()) - logger.debug("Will Dispatch the contact status event."); - fireContactPresenceStatusChangeEvent(sourceContact, parent, - oldStatus, newStatus); - } - - private class StatusChangedListener - extends SessionAdapter - { - @Override - public void friendsUpdateReceived(SessionFriendEvent evt) - { - if (logger.isDebugEnabled()) - logger.debug("Received a status update for contact " + evt); - - if(evt.getFriend() != null) - { - handleContactStatusChange(evt.getFriend()); - } - else if(evt.getFriends() != null) - { - YahooUser[] yfs = evt.getFriends(); - for (int i = 0; i < yfs.length; i++) - handleContactStatusChange(yfs[i]); - } - } - } - - /** - * Updates the statuses of newly created persistent contacts - */ - private class StatusUpdater - extends SubscriptionAdapter - { - @Override - public void subscriptionCreated(SubscriptionEvent evt) - { - ContactYahooImpl contact = - (ContactYahooImpl)evt.getSourceContact(); - - if(!contact.isPersistent() || !contact.isResolved()) - return; - - handleContactStatusChange(contact, contact.getSourceContact()); - } - } - - private class EarlyEventListener - extends SessionAdapter - { - private final List<SessionAuthorizationEvent> receivedAuthorizations - = new Vector<SessionAuthorizationEvent>(); - - /** - * The <code>YahooSession</code> this instance is listening to because - * the <code>YahooSession</code> isn't available in - * <code>parentProvider</code> after - * {@link RegistrationState#UNREGISTERED} and then this listener cannot - * be removed. - */ - private final YahooSession yahooSession; - - public EarlyEventListener(YahooSession yahooSession) - { - this.yahooSession = yahooSession; - this.yahooSession.addSessionListener(this); - } - - @Override - public void authorizationReceived(SessionAuthorizationEvent ev) - { - if(ev.isAuthorizationRequest()) - { - if (logger.isTraceEnabled()) - logger.trace("authorizationRequestReceived from " + - ev.getFrom()); - receivedAuthorizations.add(ev); - } - } - - public void dispose() - { - yahooSession.removeSessionListener(this); - } - - public void processEarlyAuthorizations() - { - for (SessionAuthorizationEvent e : receivedAuthorizations) - { - ssContactList.processAuthorizationRequest(e); - } - } - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetTypingNotificationsYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetTypingNotificationsYahooImpl.java deleted file mode 100644 index 81e266a..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/OperationSetTypingNotificationsYahooImpl.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; -import ymsg.network.event.*; - -/** - * Maps SIP Communicator typing notifications to those going and coming from - * smack lib. - * - * @author Damian Minkov - */ -public class OperationSetTypingNotificationsYahooImpl - extends AbstractOperationSetTypingNotifications<ProtocolProviderServiceYahooImpl> -{ - private static final Logger logger = - Logger.getLogger(OperationSetTypingNotificationsYahooImpl.class); - - /** - * An active instance of the opSetPersPresence operation set. We're using - * it to map incoming events to contacts in our contact list. - */ - private OperationSetPersistentPresenceYahooImpl opSetPersPresence = null; - - /** - * @param provider a ref to the <tt>ProtocolProviderServiceImpl</tt> - * that created us and that we'll use for retrieving the underlying aim - * connection. - */ - OperationSetTypingNotificationsYahooImpl( - ProtocolProviderServiceYahooImpl provider) - { - super(provider); - - provider.addRegistrationStateChangeListener(new ProviderRegListener()); - } - - /** - * Sends a notification to <tt>notifiedContatct</tt> that we have entered - * <tt>typingState</tt>. - * - * @param notifiedContact the <tt>Contact</tt> to notify - * @param typingState the typing state that we have entered. - * - * @throws java.lang.IllegalStateException if the underlying stack is - * not registered and initialized. - * @throws java.lang.IllegalArgumentException if <tt>notifiedContact</tt> is - * not an instance belonging to the underlying implementation. - */ - public void sendTypingNotification(Contact notifiedContact, int typingState) - throws IllegalStateException, IllegalArgumentException - { - assertConnected(); - - if( !(notifiedContact instanceof ContactYahooImpl) ) - throw new IllegalArgumentException( - "The specified contact is not an yahoo contact." - + notifiedContact); - - if(typingState == OperationSetTypingNotifications.STATE_TYPING) - { - - parentProvider.getYahooSession(). - keyTyped(notifiedContact.getAddress(), - parentProvider.getAccountID().getUserID()); - } - else - if(typingState == OperationSetTypingNotifications.STATE_STOPPED || - typingState == OperationSetTypingNotifications.STATE_PAUSED) - { - parentProvider.getYahooSession(). - stopTyping(notifiedContact.getAddress(), - parentProvider.getAccountID().getUserID()); - } - } - - private class TypingListener - extends SessionAdapter - { - @Override - public void notifyReceived(SessionNotifyEvent evt) - { - if(evt.isTyping()) - { - String typingUserID = evt.getFrom(); - - if(typingUserID != null) - { - Contact sourceContact = - opSetPersPresence.findContactByID(typingUserID); - - if(sourceContact == null) - return; - - // typing on - fireTypingNotificationsEvent( - sourceContact, - (evt.getMode() == 1) ? STATE_TYPING : STATE_STOPPED); - } - } - } - } - - /** - * Our listener that will tell us when we're registered and - * ready to accept us as a listener. - */ - private class ProviderRegListener - 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) - { - opSetPersPresence = - (OperationSetPersistentPresenceYahooImpl) parentProvider - .getOperationSet(OperationSetPersistentPresence.class); - - parentProvider - .getYahooSession().addSessionListener(new TypingListener()); - } - } - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolIconYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolIconYahooImpl.java deleted file mode 100644 index 34befc9..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolIconYahooImpl.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -import org.jitsi.service.resources.*; -import org.osgi.framework.*; - -/** - * Represents the Yahoo protocol icon. Implements the <tt>ProtocolIcon</tt> - * interface in order to provide an Yahoo icon image in two different sizes. - * - * @author Yana Stamcheva - */ -public class ProtocolIconYahooImpl - implements ProtocolIcon -{ - private static Logger logger = Logger.getLogger(ProtocolIconYahooImpl.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.yahoo.YAHOO_16x16")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_32x32, - getImageInBytes("service.protocol.yahoo.YAHOO_32x32")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_48x48, - getImageInBytes("service.protocol.yahoo.YAHOO_48x48")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_64x64, - getImageInBytes("service.protocol.yahoo.YAHOO_64x64")); - } - - /** - * A hash table containing 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.yahoo.YAHOO_16x16")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_32x32, - getResources().getImagePath("service.protocol.yahoo.YAHOO_32x32")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_48x48, - getResources().getImagePath("service.protocol.yahoo.YAHOO_48x48")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_64x64, - getResources().getImagePath("service.protocol.yahoo.YAHOO_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(); - } - - /** - * Returns 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("yahooConnectingIcon"); - } - - /** - * 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. - */ - public 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 = YahooActivator.getBundleContext() - .getServiceReference(ResourceManagementService.class.getName()); - - if(serviceReference == null) - return null; - - resourcesService = (ResourceManagementService) - YahooActivator.getBundleContext().getService(serviceReference); - } - - return resourcesService; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderFactoryYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderFactoryYahooImpl.java deleted file mode 100644 index 358286c..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderFactoryYahooImpl.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -import org.osgi.framework.*; - -/** - * The Yahoo implementation of the ProtocolProviderFactory. - * @author Damian Minkov - */ -public class ProtocolProviderFactoryYahooImpl - extends ProtocolProviderFactory -{ - - /** - * Creates an instance of the ProtocolProviderFactoryYahooImpl. - */ - protected ProtocolProviderFactoryYahooImpl() - { - super(YahooActivator.getBundleContext(), ProtocolNames.YAHOO); - } - - /** - * 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 uninstall account method. - * - * @param userIDStr the user identifier for the new account - * @param accountProperties a set of protocol (or implementation) - * specific properties defining the new account. - * @return the AccountID of the newly created account - */ - @Override - public AccountID installAccount( String userIDStr, - Map<String, String> accountProperties) - { - BundleContext context - = YahooActivator.getBundleContext(); - if (context == null) - throw new NullPointerException("The specified BundleContext was null"); - - if (userIDStr == null) - throw new NullPointerException("The specified AccountID was null"); - - if (accountProperties == null) - throw new NullPointerException("The specified property map was null"); - - accountProperties.put(USER_ID, userIDStr); - - AccountID accountID = new YahooAccountID(userIDStr, accountProperties); - - //make sure we haven't seen this account id before. - if( registeredAccounts.containsKey(accountID) ) - throw new IllegalStateException( - "An account for id " + userIDStr + " was already installed!"); - - //first store the account and only then load it as the load generates - //an osgi event, the osgi event triggers (through the UI) a call to - //the register() method and it needs to access the configuration service - //and check for a password. - this.storeAccount(accountID, false); - - accountID = loadAccount(accountProperties); - - return accountID; - } - - @Override - protected AccountID createAccountID(String userID, Map<String, String> accountProperties) - { - return new YahooAccountID(userID, accountProperties); - } - - @Override - protected ProtocolProviderService createService(String userID, - AccountID accountID) - { - ProtocolProviderServiceYahooImpl service = - new ProtocolProviderServiceYahooImpl(); - - service.initialize(userID, accountID); - return service; - } - - @Override - public void modifyAccount( ProtocolProviderService protocolProvider, - Map<String, String> accountProperties) - throws NullPointerException - { - BundleContext context - = YahooActivator.getBundleContext(); - - if (context == null) - throw new NullPointerException( - "The specified BundleContext was null"); - - if (protocolProvider == null) - throw new NullPointerException( - "The specified Protocol Provider was null"); - - YahooAccountID accountID - = (YahooAccountID) protocolProvider.getAccountID(); - - // If the given accountID doesn't correspond to an existing account - // we return. - if(!registeredAccounts.containsKey(accountID)) - return; - - ServiceRegistration registration = registeredAccounts.get(accountID); - - // kill the service - if (registration != null) - registration.unregister(); - - if (accountProperties == null) - throw new NullPointerException( - "The specified property map was null"); - - accountProperties.put(USER_ID, accountID.getUserID()); - - if (!accountProperties.containsKey(PROTOCOL)) - accountProperties.put(PROTOCOL, ProtocolNames.YAHOO); - - accountID.setAccountProperties(accountProperties); - - // First store the account and only then load it as the load generates - // an osgi event, the osgi event triggers (trhgough the UI) a call to - // the register() method and it needs to acces the configuration service - // and check for a password. - this.storeAccount(accountID); - - Hashtable<String, String> properties = new Hashtable<String, String>(); - properties.put(PROTOCOL, ProtocolNames.YAHOO); - properties.put(USER_ID, accountID.getUserID()); - - ((ProtocolProviderServiceYahooImpl)protocolProvider) - .initialize(accountID.getUserID(), accountID); - - // We store again the account in order to store all properties added - // during the protocol provider initialization. - this.storeAccount(accountID); - - registration - = context.registerService( - ProtocolProviderService.class.getName(), - protocolProvider, - properties); - - registeredAccounts.put(accountID, registration); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java deleted file mode 100644 index 264aaab..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java +++ /dev/null @@ -1,574 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; - -import net.java.sip.communicator.service.dns.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; -import ymsg.network.*; -import ymsg.network.event.*; - -/** - * An implementation of the protocol provider service over the Yahoo protocol - * - * @author Damian Minkov - */ -public class ProtocolProviderServiceYahooImpl - extends AbstractProtocolProviderService -{ - /** - * This class logger. - */ - private static final Logger logger = - Logger.getLogger(ProtocolProviderServiceYahooImpl.class); - - /** - * The current yahoo session. - */ - private YahooSession yahooSession = null; - - /** - * indicates whether or not the provider is initialized and ready for use. - */ - private boolean isInitialized = false; - - /** - * We use this to lock access to initialization. - */ - private final Object initializationLock = new Object(); - - /** - * The identifier of the account that this provider represents. - */ - private AccountID accountID = null; - - /** - * Used when we need to re-register - */ - private SecurityAuthority authority = null; - - /** - * The persistent presence operations set. - */ - private OperationSetPersistentPresenceYahooImpl persistentPresence = null; - - /** - * Typing notifications operations set. - */ - private OperationSetTypingNotificationsYahooImpl typingNotifications = null; - - /** - * The logo corresponding to the msn protocol. - */ - private ProtocolIconYahooImpl yahooIcon - = new ProtocolIconYahooImpl(); - - /** - * The connection listener. - */ - private YahooConnectionListener connectionListener = null; - - /** - * Returns the state of the registration of this protocol provider - * @return the <tt>RegistrationState</tt> that this provider is - * currently in or null in case it is in a unknown state. - */ - public RegistrationState getRegistrationState() - { - if(yahooSession != null && - yahooSession.getSessionStatus() == StatusConstants.MESSAGING) - return RegistrationState.REGISTERED; - else - return RegistrationState.UNREGISTERED; - } - - /** - * Starts the registration process. Connection details such as - * registration server, user name/number are provided through the - * configuration service through implementation specific properties. - * - * @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(final SecurityAuthority authority) - throws OperationFailedException - { - if(authority == null) - throw new IllegalArgumentException( - "The register method needs a valid non-null authority impl " - + " in order to be able and retrieve passwords."); - - this.authority = authority; - - connectAndLogin(authority, SecurityAuthority.AUTHENTICATION_REQUIRED); - } - - /** - * Connects and logins to the server - * @param authority SecurityAuthority - * @param authReasonCode the authentication reason code, which should - * indicate why are making an authentication request - * @throws OperationFailedException if login parameters - * as server port are not correct - */ - private void connectAndLogin( SecurityAuthority authority, - int authReasonCode) - throws OperationFailedException - { - synchronized(initializationLock) - { - //verify whether a password has already been stored for this account - String password = YahooActivator. - getProtocolProviderFactory().loadPassword(getAccountID()); - - // If the password hasn't been saved or the reason is one of those - // listed below we need to ask the user for credentials again. - if (password == null - || authReasonCode == SecurityAuthority.WRONG_PASSWORD - || authReasonCode == SecurityAuthority.WRONG_USERNAME) - { - //create a default credentials object - UserCredentials credentials = new UserCredentials(); - credentials.setUserName(getAccountID().getUserID()); - - //request a password from the user - credentials = authority.obtainCredentials( - getAccountID().getDisplayName(), - credentials, - authReasonCode); - - // in case user has canceled the login window - if(credentials == null) - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.UNREGISTERED, - RegistrationStateChangeEvent.REASON_USER_REQUEST, ""); - return; - } - - //extract the password the user passed us. - char[] pass = credentials.getPassword(); - - // the user didn't provide us a password (canceled the operation) - if(pass == null) - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.UNREGISTERED, - RegistrationStateChangeEvent.REASON_USER_REQUEST, ""); - return; - } - password = new String(pass); - - if (credentials.isPasswordPersistent()) - { - YahooActivator.getProtocolProviderFactory() - .storePassword(getAccountID(), password); - } - } - - yahooSession = new YahooSession(); - connectionListener = new YahooConnectionListener(); - yahooSession.addSessionListener(connectionListener); - - try - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.REGISTERING, - RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); - - yahooSession.login(getAccountID().getUserID(), password); - - if(yahooSession.getSessionStatus()==StatusConstants.MESSAGING) - { - persistentPresence.fireProviderStatusChangeEvent( - persistentPresence.getPresenceStatus(), - persistentPresence.yahooStatusToPresenceStatus( - yahooSession.getStatus())); - - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.REGISTERED, - RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); - } - else - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.UNREGISTERED, - RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); - } - } - catch (LoginRefusedException ex) - { - if(ex.getStatus() == StatusConstants.STATUS_BADUSERNAME) - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.AUTHENTICATION_FAILED, - RegistrationStateChangeEvent.REASON_NON_EXISTING_USER_ID, - null); - - reregister(SecurityAuthority.WRONG_USERNAME); - } - else if(ex.getStatus() == StatusConstants.STATUS_BAD) - { - YahooActivator.getProtocolProviderFactory() - .storePassword(getAccountID(), null); - - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.AUTHENTICATION_FAILED, - RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED, - null); - - // Try to re-register and ask the user to retype the password. - reregister(SecurityAuthority.WRONG_PASSWORD); - } - else if(ex.getStatus() == StatusConstants.STATUS_LOCKED) - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.AUTHENTICATION_FAILED, - RegistrationStateChangeEvent.REASON_RECONNECTION_RATE_LIMIT_EXCEEDED, - null); - } - } - catch (IOException ex) - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.CONNECTION_FAILED, - RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); - } - catch (DnssecRuntimeException ex) - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.UNREGISTERED, - RegistrationStateChangeEvent.REASON_USER_REQUEST, null); - } - } - } - - /** - * Reconnects if fails fire connection failed. - * @param reasonCode the appropriate <tt>SecurityAuthority</tt> reasonCode, - * which would specify the reason for which we're re-calling the login. - */ - void reregister(int reasonCode) - { - try - { - connectAndLogin(authority, reasonCode); - } - catch (OperationFailedException ex) - { - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.CONNECTION_FAILED, - RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); - } - } - - /** - * Ends the registration of this protocol provider with the service. - */ - public void unregister() - { - unregisterInternal(true); - } - - /** - * Unregister and fire the event if requested - * @param fireEvent boolean - */ - void unregisterInternal(boolean fireEvent) - { - RegistrationState currRegState = getRegistrationState(); - - if(fireEvent) - fireRegistrationStateChanged( - currRegState, - RegistrationState.UNREGISTERING, - RegistrationStateChangeEvent.REASON_USER_REQUEST, - null); - - try - { - if(connectionListener != null && yahooSession != null) - { - yahooSession.removeSessionListener(connectionListener); - connectionListener = null; - } - - if((yahooSession != null) - && (yahooSession.getSessionStatus() == StatusConstants.MESSAGING)) - yahooSession.logout(); - } - catch(Exception ex) - { - logger.error("Cannot logout! ", ex); - } - - yahooSession = null; - - if(fireEvent) - fireRegistrationStateChanged( - currRegState, - RegistrationState.UNREGISTERED, - RegistrationStateChangeEvent.REASON_USER_REQUEST, - null); - } - - /* - * (non-Javadoc) - * - * @see net.java.sip.communicator.service.protocol.ProtocolProviderService# - * isSignallingTransportSecure() - */ - public boolean isSignalingTransportSecure() - { - return false; - } - - /** - * Returns the "transport" protocol of this instance used to carry the - * control channel for the current protocol service. - * - * @return The "transport" protocol of this instance: TCP. - */ - public TransportProtocol getTransportProtocol() - { - return TransportProtocol.TCP; - } - - /** - * Returns the short name of the protocol that the implementation of this - * provider is based upon (like SIP, Msn, ICQ/AIM, or others for - * example). - * - * @return a String containing the short name of the protocol this - * service is taking care of. - */ - public String getProtocolName() - { - return ProtocolNames.YAHOO; - } - - /** - * Initialized 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 screenname the account id/uin/screenname of the account that - * we're about to create - * @param accountID the identifier of the account that this protocol - * provider represents. - * - * @see net.java.sip.communicator.service.protocol.AccountID - */ - protected void initialize(String screenname, - AccountID accountID) - { - synchronized(initializationLock) - { - this.accountID = accountID; - - addSupportedOperationSet( - OperationSetInstantMessageTransform.class, - new OperationSetInstantMessageTransformImpl()); - - //initialize the presence operationset - persistentPresence - = new OperationSetPersistentPresenceYahooImpl(this); - addSupportedOperationSet( - OperationSetPersistentPresence.class, - persistentPresence); - //register it once again for those that simply need presence - addSupportedOperationSet( - OperationSetPresence.class, - persistentPresence); - - //initialize the IM operation set - addSupportedOperationSet( - OperationSetBasicInstantMessaging.class, - new OperationSetBasicInstantMessagingYahooImpl(this)); - - //initialize the multi user chat operation set - addSupportedOperationSet( - OperationSetAdHocMultiUserChat.class, - new OperationSetAdHocMultiUserChatYahooImpl(this)); - - //initialize the typing notifications operation set - typingNotifications - = new OperationSetTypingNotificationsYahooImpl(this); - addSupportedOperationSet( - OperationSetTypingNotifications.class, - typingNotifications); - - addSupportedOperationSet( - OperationSetFileTransfer.class, - new OperationSetFileTransferYahooImpl(this)); - - isInitialized = true; - } - } - - /** - * 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() - { - synchronized(initializationLock){ - unregisterInternal(false); - yahooSession = null; - isInitialized = false; - } - } - - /** - * Returns true if the provider service implementation is initialized and - * ready for use by other services, and false otherwise. - * - * @return true if the provider is initialized and ready for use and false - * otherwise - */ - public boolean isInitialized() - { - return isInitialized; - } - - /** - * 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 Yahoo<tt>Session</tt>opened by this provider - * @return a reference to the <tt>Session</tt> last opened by this - * provider. - */ - YahooSession getYahooSession() - { - return yahooSession; - } - - /** - * Creates a RegistrationStateChange event 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. - */ - @Override - public void fireRegistrationStateChanged( RegistrationState oldState, - RegistrationState newState, - int reasonCode, - String reason) - { - if(newState.equals(RegistrationState.UNREGISTERED)) - { - unregisterInternal(false); - yahooSession = null; - } - - super.fireRegistrationStateChanged(oldState, newState, reasonCode, reason); - } - - /** - * Listens when we are logged in the server - * or incoming exception in the lib impl. - */ - private class YahooConnectionListener - extends SessionAdapter - { - /** - * Yahoo has logged us off the system, or the connection was lost - * - * @param ev the event - */ - @Override - public void connectionClosed(SessionEvent ev) - { - if(isRegistered()) - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.CONNECTION_FAILED, - RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); - } - - /** - * Some exception has occurred in stack. - * @param ev - */ - @Override - public void inputExceptionThrown(SessionExceptionEvent ev) - { - if(ev.getException() instanceof YMSG9BadFormatException) - { - logger.error("Yahoo protocol exception occured exception", - ev.getException()); - logger.error("Yahoo protocol exception occured exception cause", - ev.getException().getCause()); - } - else - logger.error( - "Yahoo protocol exception occured", ev.getException()); - - unregisterInternal(false); - if(isRegistered()) - fireRegistrationStateChanged( - getRegistrationState(), - RegistrationState.UNREGISTERED, - RegistrationStateChangeEvent.REASON_INTERNAL_ERROR, null); - } - } - - /** - * Returns the yahoo protocol icon. - * @return the yahoo protocol icon - */ - public ProtocolIcon getProtocolIcon() - { - return yahooIcon; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/RootContactGroupYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/RootContactGroupYahooImpl.java deleted file mode 100644 index 856bb30..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/RootContactGroupYahooImpl.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * A dummy ContactGroup implementation representing the ContactList root for - * Yahoo contact lists. - * @author Damian Minkov - */ -public class RootContactGroupYahooImpl - extends AbstractContactGroupYahooImpl -{ - private String ROOT_CONTACT_GROUP_NAME = "ContactListRoot"; - private List<ContactGroup> subGroups = new LinkedList<ContactGroup>(); - private boolean isResolved = false; - - /** - * An empty list that we use when returning an iterator. - */ - private List<Contact> dummyContacts = new LinkedList<Contact>(); - - private final ProtocolProviderServiceYahooImpl protocolProvider; - - /** - * Creates a ContactGroup instance. - */ - RootContactGroupYahooImpl(ProtocolProviderServiceYahooImpl protocolProvider) - { - this.protocolProvider = protocolProvider; - } - - /** - * The ContactListRoot is the only group that can contain subgroups. - * - * @return true (always) - */ - public boolean canContainSubgroups() - { - return true; - } - - /** - * Returns the name of this group which is always - * <tt>ROOT_CONTACT_GROUP_NAME</tt>. - * - * @return a String containing the name of this group. - */ - public String getGroupName() - { - return ROOT_CONTACT_GROUP_NAME; - } - - /** - * Adds the specified group to the end of the list of sub groups. - * @param group the group to add. - */ - void addSubGroup(ContactGroupYahooImpl group) - { - subGroups.add(group); - } - - /** - * Removes the specified from the list of sub groups - * @param group the group to remove. - */ - void removeSubGroup(ContactGroupYahooImpl group) - { - removeSubGroup(subGroups.indexOf(group)); - } - - /** - * Removes the sub group with the specified index. - * @param index the index of the group to remove - */ - void removeSubGroup(int index) - { - subGroups.remove(index); - } - - /** - * Returns the number of subgroups contained by this - * <tt>RootContactGroupImpl</tt>. - * - * @return an int indicating the number of subgroups that this - * ContactGroup contains. - */ - public int countSubgroups() - { - return subGroups.size(); - } - - /** - * Returns null as this is the root contact group. - * @return null as this is the root contact group. - */ - public ContactGroup getParentContactGroup() - { - return null; - } - - /** - * Returns the subgroup with the specified index. - * - * @param index the index of the <tt>ContactGroup</tt> to retrieve. - * @return the <tt>ContactGroup</tt> with the specified index. - */ - public ContactGroup getGroup(int index) - { - return subGroups.get(index); - } - - /** - * Returns the subgroup with the specified name. - * @param groupName the name of the <tt>ContactGroup</tt> to retrieve. - * @return the <tt>ContactGroup</tt> with the specified index. - */ - public ContactGroup getGroup(String groupName) - { - Iterator<ContactGroup> subgroups = subgroups(); - while (subgroups.hasNext()) - { - ContactGroup grp = subgroups.next(); - - if (grp.getGroupName().equals(groupName)) - return grp; - } - - return null; - } - - /** - * Returns an iterator over the sub groups that this - * <tt>ContactGroup</tt> contains. - * - * @return a java.util.Iterator over the <tt>ContactGroup</tt> - * children of this group (i.e. subgroups). - */ - public Iterator<ContactGroup> subgroups() - { - return subGroups.iterator(); - } - - /** - * Returns the number, which is always 0, of <tt>Contact</tt> members - * of this <tt>ContactGroup</tt> - * @return an int indicating the number of <tt>Contact</tt>s, members - * of this <tt>ContactGroup</tt>. - */ - public int countContacts() - { - return dummyContacts.size(); - } - - /** - * Returns an Iterator over all contacts, member of this - * <tt>ContactGroup</tt>. - * @return a java.util.Iterator over all contacts inside this - * <tt>ContactGroup</tt> - */ - public Iterator<Contact> contacts() - { - return dummyContacts.iterator(); - } - - /** - * Returns the <tt>Contact</tt> with the specified address or - * identifier. - * @param id the addres or identifier of the <tt>Contact</tt> we are - * looking for. - * @return the <tt>Contact</tt> with the specified id or address. - */ - public Contact getContact(String id) - { - //no contacts in the root group for this yahoo impl. - return null; - } - - - /** - * Returns a string representation of the root contact group that contains - * all subgroups and subcontacts of this group. - * - * @return a string representation of this root contact group. - */ - @Override - public String toString() - { - StringBuffer buff = new StringBuffer(getGroupName()); - buff.append(".subGroups=" + countSubgroups() + ":\n"); - - Iterator<ContactGroup> subGroups = subgroups(); - while (subGroups.hasNext()) - { - ContactGroup group = subGroups.next(); - buff.append(group.toString()); - if (subGroups.hasNext()) - buff.append("\n"); - } - return buff.toString(); - } - - /** - * Returns the protocol provider that this group belongs to. - * @return a regerence to the ProtocolProviderService instance that this - * ContactGroup belongs to. - */ - public ProtocolProviderService getProtocolProvider() - { - return protocolProvider; - } - - /** - * Determines whether or not this contact group is being stored by the - * server. Non persistent contact groups exist for the sole purpose of - * containing non persistent contacts. - * @return true if the contact group is persistent and false otherwise. - */ - public boolean isPersistent() - { - return true; - } - - /** - * Returns null as no persistent data is required and the group name is - * sufficient for restoring the contact. - * <p> - * @return null as no such data is needed. - */ - public String getPersistentData() - { - return null; - } - - /** - * Determines whether or not this group has been resolved against the - * server. Unresolved groups are used when initially loading a contact - * list that has been stored in a local file until the presence operation - * set has managed to retrieve all the contact list from the server and has - * properly mapped groups to their on-line buddies. - * @return true if the group has been resolved (mapped against a buddy) - * and false otherwise. - */ - public boolean isResolved() - { - return isResolved; - } - - /** - * Returns a <tt>String</tt> that uniquely represnets the group. In this we - * use the name of the group as an identifier. This may cause problems - * though, in clase the name is changed by some other application between - * consecutive runs of the sip-communicator. - * - * @return a String representing this group in a unique and persistent - * way. - */ - public String getUID() - { - return getGroupName(); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ServerStoredContactListYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ServerStoredContactListYahooImpl.java deleted file mode 100644 index e732b84..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/ServerStoredContactListYahooImpl.java +++ /dev/null @@ -1,1274 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; -import ymsg.network.*; -import ymsg.network.event.*; - -/** - * This class encapsulates the Roster class. Once created, it will - * register itself as a listener to the encapsulated Roster and modify it's - * local copy of Contacts and ContactGroups every time an event is generated - * by the underlying framework. The class would also generate - * corresponding sip-communicator events to all events coming from smack. - * - * @author Damian Minkov - * @author Emil Ivov - */ -public class ServerStoredContactListYahooImpl -{ - private static final Logger logger = - Logger.getLogger(ServerStoredContactListYahooImpl.class); - - /** - * If there is no group and we add contact with no parent - * a default group is created with name : DEFAULT_GROUP_NAME - */ - private static final String DEFAULT_GROUP_NAME = "General"; - - /** - * The root contagroup. The container for all yahoo buddies and groups. - */ - private final RootContactGroupYahooImpl rootGroup; - - /** - * The operation set that created us and that we could use when dispatching - * subscription events. - */ - private final OperationSetPersistentPresenceYahooImpl parentOperationSet; - - /** - * The provider that is on top of us. - */ - private final ProtocolProviderServiceYahooImpl yahooProvider; - - private YahooSession yahooSession = null; - - /** - * Listeners that would receive event notifications for changes in group - * names or other properties, removal or creation of groups. - */ - private Vector<ServerStoredGroupListener> serverStoredGroupListeners - = new Vector<ServerStoredGroupListener>(); - - private ContactListModListenerImpl contactListModListenerImpl - = new ContactListModListenerImpl(); - - /** - * Handler for incoming authorization requests. - */ - private AuthorizationHandler handler = null; - - private Hashtable<String, String> addedCustomYahooIds - = new Hashtable<String, String>(); - - /** - * Creates a ServerStoredContactList wrapper for the specified BuddyList. - * - * @param parentOperationSet the operation set that created us and that - * we could use for dispatching subscription events - * @param provider the provider that has instantiated us. - */ - ServerStoredContactListYahooImpl( - OperationSetPersistentPresenceYahooImpl parentOperationSet, - ProtocolProviderServiceYahooImpl provider) - { - //We need to init these as early as possible to ensure that the provider - //and the operationsset would not be null in the incoming events. - this.parentOperationSet = parentOperationSet; - - this.yahooProvider = provider; - this.rootGroup = new RootContactGroupYahooImpl(this.yahooProvider); - } - - /** - * Handler for incoming authorization requests. - * - * @param handler an instance of an AuthorizationHandler for - * authorization requests coming from other users requesting - * permission add us to their contact list. - */ - public void setAuthorizationHandler(AuthorizationHandler handler) - { - this.handler = handler; - } - - /** - * Returns the root group of the contact list. - * - * @return the root ContactGroup for the ContactList - */ - public ContactGroup getRootGroup() - { - return rootGroup; - } - - /** - * Registers the specified group listener so that it would receive events - * on group modification/creation/destruction. - * @param listener the ServerStoredGroupListener to register for group - * events - */ - void addGroupListener(ServerStoredGroupListener listener) - { - synchronized(serverStoredGroupListeners) - { - if(!serverStoredGroupListeners.contains(listener)) - serverStoredGroupListeners.add(listener); - } - } - - /** - * Removes the specified group listener so that it won't receive further - * events on group modification/creation/destruction. - * @param listener the ServerStoredGroupListener to unregister - */ - void removeGroupListener(ServerStoredGroupListener listener) - { - synchronized(serverStoredGroupListeners) - { - this.serverStoredGroupListeners.remove(listener); - } - } - - /** - * Creates the corresponding event and notifies all - * <tt>ServerStoredGroupListener</tt>s that the source group has been - * removed, changed, renamed or whatever happened to it. - * @param group the ContactGroup that has been created/modified/removed - * @param eventID the id of the event to generate. - */ - private void fireGroupEvent(ContactGroupYahooImpl group, int eventID) - { - //bail out if no one's listening - if(parentOperationSet == null){ - if (logger.isDebugEnabled()) - logger.debug("No presence op. set available. Bailing out."); - return; - } - - ServerStoredGroupEvent evt = new ServerStoredGroupEvent( - group - , eventID - , parentOperationSet.getServerStoredContactListRoot() - , yahooProvider - , parentOperationSet); - - if (logger.isTraceEnabled()) - logger.trace("Will dispatch the following grp event: " + evt); - - Iterable<ServerStoredGroupListener> listeners; - synchronized (serverStoredGroupListeners) - { - listeners - = new ArrayList<ServerStoredGroupListener>( - serverStoredGroupListeners); - } - - for (ServerStoredGroupListener listener : listeners) - { - try{ - if (eventID == ServerStoredGroupEvent.GROUP_REMOVED_EVENT) - listener.groupRemoved(evt); - else if (eventID == ServerStoredGroupEvent.GROUP_RENAMED_EVENT) - listener.groupNameChanged(evt); - else if (eventID == ServerStoredGroupEvent.GROUP_CREATED_EVENT) - listener.groupCreated(evt); - else if (eventID == ServerStoredGroupEvent.GROUP_RESOLVED_EVENT) - listener.groupResolved(evt); - }catch(Exception ex){ - logger.warn("Unhandled Exception! ", ex); - } - } - } - - /** - * Make the parent persistent presence operation set dispatch a contact - * removed event. - * @param parentGroup the group where that the removed contact belonged to. - * @param contact the contact that was removed. - */ - private void fireContactRemoved( ContactGroup parentGroup, - ContactYahooImpl contact) - { - //bail out if no one's listening - if(parentOperationSet == null){ - if (logger.isDebugEnabled()) - logger.debug("No presence op. set available. Bailing out."); - return; - } - - //dispatch - parentOperationSet.fireSubscriptionEvent( - contact, parentGroup, SubscriptionEvent.SUBSCRIPTION_REMOVED); - } - - /** - * Make the parent persistent presence operation set dispatch a subscription - * moved event. - * @param oldParentGroup the group where the source contact was located - * before being moved - * @param newParentGroup the group that the source contact is currently in. - * @param contact the contact that was added - */ - private void fireContactMoved( ContactGroup oldParentGroup, - ContactGroupYahooImpl newParentGroup, - ContactYahooImpl contact) - { - //bail out if no one's listening - if(parentOperationSet == null){ - if (logger.isDebugEnabled()) - logger.debug("No presence op. set available. Bailing out."); - return; - } - - //dispatch - parentOperationSet.fireSubscriptionMovedEvent( - contact, oldParentGroup, newParentGroup); - } - - /** - * Returns a reference to the provider that created us. - * @return a reference to a ProtocolProviderServiceImpl instance. - */ - ProtocolProviderServiceYahooImpl getParentProvider() - { - return yahooProvider; - } - - /** - * Returns the ConntactGroup with the specified name or null if no such - * group was found. - * <p> - * @param name the name of the group we're looking for. - * @return a reference to the ContactGroupYahooImpl instance we're looking - * for or null if no such group was found. - */ - public ContactGroupYahooImpl findContactGroup(String name) - { - String nameToLookFor = replaceIllegalChars(name); - Iterator<ContactGroup> contactGroups = rootGroup.subgroups(); - - while(contactGroups.hasNext()) - { - ContactGroupYahooImpl contactGroup - = (ContactGroupYahooImpl) contactGroups.next(); - - if (contactGroup.getGroupName().equals(nameToLookFor)) - return contactGroup; - } - - return null; - } - - /** - * Returns the Contact with the specified id or null if - * no such id was found. - * - * @param id the id of the contact to find. - * @return the <tt>Contact</tt> carrying the specified - * <tt>screenName</tt> or <tt>null</tt> if no such contact exits. - */ - public ContactYahooImpl findContactById(String id) - { - Iterator<ContactGroup> contactGroups = rootGroup.subgroups(); - ContactYahooImpl result = null; - - while(contactGroups.hasNext()) - { - ContactGroupYahooImpl contactGroup - = (ContactGroupYahooImpl) contactGroups.next(); - - result = contactGroup.findContact(id); - - if (result != null) - return result; - } - - return null; - } - - /** - * Returns the Contact corresponding to the specified <tt>YahooUser</tt> - * or null if no such id was found. - * - * @param yahooUser the YahooUser of the contact to find. - * @return the <tt>Contact</tt> carrying the specified - * <tt>screenName</tt> or <tt>null</tt> if no such contact exits. - */ - public ContactYahooImpl findContactByYahooUser(YahooUser yahooUser) - { - return findContactById(yahooUser.getId().toLowerCase()); - } - - /** - * Returns the ContactGroup containing the specified contact or null - * if no such group or contact exist. - * - * @param child the contact whose parent group we're looking for. - * @return the <tt>ContactGroup</tt> containing the specified - * <tt>contact</tt> or <tt>null</tt> if no such groupo or contact - * exist. - */ - public ContactGroup findContactGroup(ContactYahooImpl child) - { - Iterator<ContactGroup> contactGroups = rootGroup.subgroups(); - String contactAddress = child.getAddress(); - - while(contactGroups.hasNext()) - { - ContactGroupYahooImpl contactGroup - = (ContactGroupYahooImpl) contactGroups.next(); - - if( contactGroup.findContact(contactAddress)!= null) - return contactGroup; - } - - return null; - } - - /** - * Adds a new contact with the specified screenname to the list under a - * default location. - * @param id the id of the contact to add. - * @throws OperationFailedException - */ - public void addContact(String id) - throws OperationFailedException - { - ContactGroupYahooImpl parent = getFirstPersistentGroup(); - - if(parent == null) - { - // if there is no group create it - parent = createUnresolvedContactGroup(DEFAULT_GROUP_NAME); - } - - addContact(parent, id); - } - - /** - * Adds a new contact with the specified screenname to the list under the - * specified group. - * @param id the id of the contact to add. - * @param parent the group under which we want the new contact placed. - * @throws OperationFailedException if the contact already exist - */ - public void addContact(final ContactGroupYahooImpl parent, String id) - throws OperationFailedException - { - if (logger.isTraceEnabled()) - logger.trace("Adding contact " + id + " to parent=" + parent); - - //if the contact is already in the contact list and is not volatile, - //then only broadcast an event - ContactYahooImpl existingContact = findContactById(id); - - if( existingContact != null - && existingContact.isPersistent() ) - { - if (logger.isDebugEnabled()) - logger.debug("Contact " + id + " already exists."); - throw new OperationFailedException( - "Contact " + id + " already exists.", - OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS); - } - - if(id.indexOf("@") > -1 ) - addedCustomYahooIds.put(YahooSession.getYahooUserID(id), id); - - try - { - yahooSession.addFriend(YahooSession.getYahooUserID(id), - parent.getGroupName()); - } - catch(IOException ex) - { - throw new OperationFailedException( - "Contact cannot be added " + id, - OperationFailedException.NETWORK_FAILURE); - } - } - - /** - * Creates a non persistent contact for the specified address. This would - * also create (if necessary) a group for volatile contacts that would not - * be added to the server stored contact list. This method would have no - * effect on the server stored contact list. - * @param id the address of the contact to create. - * @return the newly created volatile <tt>ContactImpl</tt> - */ - ContactYahooImpl createVolatileContact(String id) - { - if (logger.isTraceEnabled()) - logger.trace("Creating volatile contact " + id); - ContactYahooImpl newVolatileContact = - new ContactYahooImpl(id, this, false, false, true); - - //Check whether a volatile group already exists and if not create one - ContactGroupYahooImpl theVolatileGroup = getNonPersistentGroup(); - - //if the parent group is null then create it - if (theVolatileGroup == null) - { - theVolatileGroup = new VolatileContactGroupYahooImpl( - YahooActivator.getResources().getI18NString( - "service.gui.NOT_IN_CONTACT_LIST_GROUP_NAME"), - this); - - theVolatileGroup.addContact(newVolatileContact); - - this.rootGroup.addSubGroup(theVolatileGroup); - - fireGroupEvent(theVolatileGroup - , ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - else - { - theVolatileGroup.addContact(newVolatileContact); - - fireContactAdded(theVolatileGroup, newVolatileContact); - } - - return newVolatileContact; - } - - - /** - * Creates a non resolved contact for the specified address and inside the - * specified group. The newly created contact would be added to the local - * contact list as a standard contact but when an event is received from the - * server concerning this contact, then it will be reused and only its - * isResolved field would be updated instead of creating the whole contact - * again. - * - * @param parentGroup the group where the unersolved contact is to be - * created - * @param id the Address of the contact to create. - * @return the newly created unresolved <tt>ContactImpl</tt> - */ - ContactYahooImpl createUnresolvedContact(ContactGroup parentGroup, - String id) - { - if (logger.isTraceEnabled()) - logger.trace("Creating unresolved contact " + id - + " to parent=" + parentGroup); - - ContactYahooImpl existingContact = findContactById(id); - - if( existingContact != null) - { - return existingContact; - } - - ContactYahooImpl newUnresolvedContact - = new ContactYahooImpl(id, this, false, true, false); - - if(parentGroup instanceof ContactGroupYahooImpl) - ((ContactGroupYahooImpl)parentGroup). - addContact(newUnresolvedContact); - - fireContactAdded(parentGroup, newUnresolvedContact); - - return newUnresolvedContact; - } - - /** - * Creates a non resolved contact group for the specified name. The newly - * created group would be added to the local contact list as any other group - * but when an event is received from the server concerning this group, then - * it will be reused and only its isResolved field would be updated instead - * of creating the whole group again. - * <p> - * @param groupName the name of the group to create. - * @return the newly created unresolved <tt>ContactGroupImpl</tt> - */ - ContactGroupYahooImpl createUnresolvedContactGroup(String groupName) - { - ContactGroupYahooImpl existingGroup = findContactGroup(groupName); - - if( existingGroup != null ) - { - if (logger.isDebugEnabled()) - logger.debug("ContactGroup " + groupName + " already exists."); - return existingGroup; - } - - ContactGroupYahooImpl newUnresolvedGroup = - new ContactGroupYahooImpl(groupName, this); - - this.rootGroup.addSubGroup(newUnresolvedGroup); - - fireGroupEvent(newUnresolvedGroup - , ServerStoredGroupEvent.GROUP_CREATED_EVENT); - - return newUnresolvedGroup; - } - - /** - * Creates the specified group on the server stored contact list. - * @param groupName a String containing the name of the new group. - * @throws OperationFailedException with code CONTACT_GROUP_ALREADY_EXISTS - * if the group we're trying to create is already in our contact list. - */ - public void createGroup(String groupName) - throws OperationFailedException - { - if (logger.isTraceEnabled()) - logger.trace("Creating group: " + groupName); - - ContactGroupYahooImpl existingGroup = findContactGroup(groupName); - - if( existingGroup != null && existingGroup.isPersistent() ) - { - if (logger.isDebugEnabled()) - logger.debug("ContactGroup " + groupName + " already exists."); - throw new OperationFailedException( - "ContactGroup " + groupName + " already exists.", - OperationFailedException.CONTACT_GROUP_ALREADY_EXISTS); - } - - // create unresolved group if friend is added - group will be resolved - createUnresolvedContactGroup(groupName); - } - - /** - * Removes the specified group from the buddy list. - * @param groupToRemove the group that we'd like removed. - */ - @SuppressWarnings("unchecked") //jymsg legacy code - public void removeGroup(ContactGroupYahooImpl groupToRemove) - { - // to remove group just remove all the contacts in it - - if (logger.isTraceEnabled()) - logger.trace("removing group " + groupToRemove); - - // if its not persistent group just remove it - if(!groupToRemove.isPersistent() || !groupToRemove.isResolved()) - { - rootGroup.removeSubGroup(groupToRemove); - fireGroupEvent(groupToRemove, - ServerStoredGroupEvent.GROUP_REMOVED_EVENT); - return; - } - - Vector<YahooUser> contacts - = groupToRemove.getSourceGroup().getMembers(); - - if(contacts.size() == 0) - { - // the group is empty just remove it - rootGroup.removeSubGroup(groupToRemove); - fireGroupEvent(groupToRemove, - ServerStoredGroupEvent.GROUP_REMOVED_EVENT); - return; - } - - /* - * ContactGroupYahooImpl#getGroupName() isn't a plain getter so - * performance-wise we're better off not calling it multiple times in - * the following loop. - */ - String groupToRemoveName = groupToRemove.getGroupName(); - - for (YahooUser item : contacts) - { - try - { - yahooSession.removeFriend(item.getId(), groupToRemoveName); - } - catch(IOException ex) - { - if (logger.isInfoEnabled()) - logger.info("Cannot Remove contact " + item.getId()); - } - } - } - - /** - * Removes a contact from the serverside list - * Event will come for successful operation - * @param contactToRemove ContactYahooImpl - */ - void removeContact(ContactYahooImpl contactToRemove) - { - if (logger.isTraceEnabled()) - logger.trace("Removing yahoo contact " - + contactToRemove.getSourceContact()); - - if(contactToRemove.isVolatile()) - { - ContactGroupYahooImpl parent = - (ContactGroupYahooImpl)contactToRemove.getParentContactGroup(); - - parent.removeContact(contactToRemove); - fireContactRemoved(parent, contactToRemove); - return; - } - - try - { - yahooSession.removeFriend( - contactToRemove.getSourceContact().getId(), - contactToRemove.getParentContactGroup().getGroupName()); - } - catch(IOException ex) - { - if (logger.isInfoEnabled()) - logger.info("Cannot Remove contact " + contactToRemove); - } - } - - /** - * Renames the specified group according to the specified new name.. - * @param groupToRename the group that we'd like removed. - * @param newName the new name of the group - */ - public void renameGroup(ContactGroupYahooImpl groupToRename, String newName) - { - // not working - /* - try - { - yahooSession.renameGroup(groupToRename.getGroupName(), newName); - } - catch(IOException ex) - { - if (logger.isInfoEnabled()) - logger.info("Cannot rename group " + groupToRename); - } - - fireGroupEvent(groupToRename, - ServerStoredGroupEvent.GROUP_RENAMED_EVENT); - */ - } - - /** - * Moves the specified <tt>contact</tt> to the group indicated by - * <tt>newParent</tt>. - * @param contact the contact that we'd like moved under the new group. - * @param newParent the group where we'd like the parent placed. - */ - public void moveContact(ContactYahooImpl contact, - ContactGroupYahooImpl newParent) - { - String userID = contact.getID(); - try - { - contactListModListenerImpl. - waitForMove(userID, - contact.getParentContactGroup().getGroupName()); - - yahooSession.addFriend( - userID, - newParent.getGroupName()); - } - catch(IOException ex) - { - contactListModListenerImpl.removeWaitForMove(userID); - logger.error("Contact cannot be added " + ex.getMessage()); - } - } - - /** - * Returns the volatile group - * - * @return ContactGroupYahooImpl - */ - private ContactGroupYahooImpl getNonPersistentGroup() - { - for (int i = 0; i < getRootGroup().countSubgroups(); i++) - { - ContactGroupYahooImpl gr = - (ContactGroupYahooImpl)getRootGroup().getGroup(i); - - if(!gr.isPersistent()) - return gr; - } - - return null; - } - - /** - * Returns the first persistent group - * - * @return ContactGroupIcqImpl - */ - private ContactGroupYahooImpl getFirstPersistentGroup() - { - for (int i = 0; i < getRootGroup().countSubgroups(); i++) - { - ContactGroupYahooImpl gr = - (ContactGroupYahooImpl)getRootGroup().getGroup(i); - - if(gr.isPersistent()) - return gr; - } - - return null; - } - - /** - * Make the parent persistent presence operation set dispatch a contact - * added event. - * @param parentGroup the group where the new contact was added - * @param contact the contact that was added - */ - void fireContactAdded( ContactGroup parentGroup, - ContactYahooImpl contact) - { - //bail out if no one's listening - if(parentOperationSet == null){ - if (logger.isDebugEnabled()) - logger.debug("No presence op. set available. Bailing out."); - return; - } - - //dispatch - parentOperationSet.fireSubscriptionEvent( - contact, parentGroup, SubscriptionEvent.SUBSCRIPTION_CREATED); - } - - /** - * Make the parent persistent presence operation set dispatch a contact - * resolved event. - * @param parentGroup the group that the resolved contact belongs to. - * @param contact the contact that was resolved - */ - void fireContactResolved( ContactGroup parentGroup, - ContactYahooImpl contact) - { - //bail out if no one's listening - if(parentOperationSet == null){ - if (logger.isDebugEnabled()) - logger.debug("No presence op. set available. Bailing out."); - return; - } - - //dispatch - parentOperationSet.fireSubscriptionEvent( - contact, parentGroup, SubscriptionEvent.SUBSCRIPTION_RESOLVED); - } - - /** - * When the protocol is online this method is used to fill or resolve - * the current contact list - */ - @SuppressWarnings("unchecked") //jymsg legacy code - private void initList() - { - if (logger.isTraceEnabled()) - logger.trace("Start init list of " - + yahooProvider.getAccountID().getUserID()); - - for (YahooGroup item : yahooSession.getGroups()) - { - ContactGroupYahooImpl group = findContactGroup(item.getName()); - - if(group == null) - { - // create the group as it doesn't exist - group = new ContactGroupYahooImpl( - item, item.getMembers(), this, true); - - rootGroup.addSubGroup(group); - - //tell listeners about the added group - fireGroupEvent(group, - ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - else - { - // the group exist so just resolved. The group will check and - // create or resolve its entries - group.setResolved(item); - - //fire an event saying that the group has been resolved - fireGroupEvent(group - , ServerStoredGroupEvent.GROUP_RESOLVED_EVENT); - - /** @todo if something to delete . delete it */ - } - - if (logger.isTraceEnabled()) - logger.trace("Init of group done! : " + group); - } - } - - /** - * @param name Name of the group to search - * @return The yahoo group with given name - */ - private YahooGroup findGroup(String name) - { - for (YahooGroup elem : yahooSession.getGroups()) - { - if(elem.getName().equals(name)) - return elem; - } - return null; - } - - /** - * Process incoming authorization requests. - * @param ev the event to process. - */ - void processAuthorizationRequest(SessionAuthorizationEvent ev) - { - if(handler == null) - return; - - Contact srcContact = findContactById(ev.getFrom()); - - // if there is no such contact we create it as - // volatile so we can fire notification - // and then if accepted add it in the protocol - // so we can receive its states - boolean isCurrentlyCreated = false; - if(srcContact == null) - { - srcContact = createVolatileContact(ev.getFrom()); - isCurrentlyCreated = true; - } - - AuthorizationRequest authRequest = new AuthorizationRequest(); - authRequest.setReason(ev.getMessage()); - - AuthorizationResponse authResponse = - handler.processAuthorisationRequest( - authRequest, srcContact); - - if (authResponse.getResponseCode() == AuthorizationResponse.IGNORE) - { - return; - } - else if (authResponse.getResponseCode() == AuthorizationResponse.REJECT) - { - removeContact((ContactYahooImpl)srcContact); - try - { - yahooSession.rejectFriendAuthorization( - ev, ev.getFrom(), authResponse.getReason()); - } - catch(IOException ex) - { - logger.error("cannot send auth deny", ex); - } - - return; - } - - // else we accepted it - try - { - yahooSession.acceptFriendAuthorization(ev, ev.getFrom()); - } - catch(IOException ex) - { - logger.error("cannot send auth deny", ex); - } - - if(isCurrentlyCreated) - try - { - addContact(ev.getFrom()); - } - catch (OperationFailedException ex) - { - logger.error("Cannot add friend", ex); - } - } - - /** - * Imulates firing adding contact in group and moving contact to group. - * When moving contact it is first adding to the new group then - * it is removed from the old one. - */ - private class ContactListModListenerImpl - extends SessionAdapter - { - private final Hashtable<String, Object> waitMove - = new Hashtable<String, Object>(); - - public void waitForMove(String id, String oldParent) - { - waitMove.put(id, oldParent); - } - - public void removeWaitForMove(String id) - { - waitMove.remove(id); - } - - /** - * Successfully added a friend - * friend - YahooUser of friend - * group - name of group added to - * @param ev fired event - */ - @Override - public void friendAddedReceived(SessionFriendEvent ev) - { - if (logger.isTraceEnabled()) - logger.trace("Receive event for adding a friend : " + ev); - - ContactGroupYahooImpl group = - findContactGroup(ev.getGroup()); - - if(group == null){ - if (logger.isTraceEnabled()) - logger.trace("Group not found!" + ev.getGroup()); - return; - } - - String contactID = ev.getFriend().getId(); - ContactYahooImpl contactToAdd = findContactById(contactID); - - // if group is note resolved resolve it - // this means newly created group - if(!group.isResolved()) - { - // if the contact is volatile me must remove it - // as new one will be created - if(contactToAdd != null && contactToAdd.isVolatile()) - { - ContactGroupYahooImpl parent - = (ContactGroupYahooImpl)contactToAdd - .getParentContactGroup(); - - parent.removeContact(contactToAdd); - fireContactRemoved(parent, contactToAdd); - } - - YahooGroup gr = findGroup(ev.getGroup()); - - if(gr != null) - group.setResolved(gr); - - // contact will be added when resolving the group - - return; - } - - - boolean isVolatile = false; - - if(contactToAdd == null) - { - if(addedCustomYahooIds.containsKey(contactID)) - { - String expectedContactID = - addedCustomYahooIds.remove(contactID); - - contactToAdd = - new ContactYahooImpl(expectedContactID, ev.getFriend(), - ServerStoredContactListYahooImpl.this, true, true); - } - else - { - contactToAdd = - new ContactYahooImpl(ev.getFriend(), - ServerStoredContactListYahooImpl.this, true, true); - } - } - else - { - isVolatile = contactToAdd.isVolatile(); - } - - //first check is contact is moving from a group - Object isWaitingForMove = waitMove.get(contactID); - - if(isWaitingForMove != null && isWaitingForMove instanceof String) - { - // waits for move into group - // will remove it from old group and will wait for event remove - // from group, then will fire moved to group event - String oldParent = (String)isWaitingForMove; - - group.addContact(contactToAdd); - waitMove.put(contactID, group.getSourceGroup()); - try - { - yahooSession.removeFriend(contactID, oldParent); - } - catch(IOException ex) - { - if (logger.isInfoEnabled()) - logger.info("Cannot Remove(till moving) contact :" + - contactToAdd + " from group " + oldParent); - } - return; - } - - if(isVolatile) - { - // we must remove the volatile buddy as we will add - // the persistent one. - // Volatile buddy is moving from the volatile group - // to the new one - ContactGroupYahooImpl parent = - (ContactGroupYahooImpl)contactToAdd.getParentContactGroup(); - - parent.removeContact(contactToAdd); - fireContactRemoved(parent, contactToAdd); - - contactToAdd.setPersistent(true); - contactToAdd.setResolved(ev.getFriend()); - - group.addContact(contactToAdd); - - fireContactAdded(group, contactToAdd); - waitMove.remove(contactID); - - return; - } - - group.addContact(contactToAdd); - fireContactAdded(group, contactToAdd); - } - - /** - * Successfully removed a friend - * friend - YahooUser of friend - * group - name of group removed from - * @param ev fired event - */ - @Override - public void friendRemovedReceived(SessionFriendEvent ev) - { - if (logger.isTraceEnabled()) - logger.trace("Receive event for removing a friend : " + ev); - - String contactID = ev.getFriend().getId(); - - // first check is this part of move action - Object waitForMoveObj = waitMove.get(contactID); - if(waitForMoveObj != null && waitForMoveObj instanceof YahooGroup) - { - // first get the group - oldParent - ContactGroupYahooImpl oldParent - = findContactGroup(ev.getGroup()); - ContactYahooImpl contactToRemove - = oldParent.findContact(contactID); - - oldParent.removeContact(contactToRemove); - waitMove.remove(contactID); - - ContactGroupYahooImpl newParent = - findContactGroup(((YahooGroup)waitForMoveObj).getName()); - - fireContactMoved(oldParent, newParent, contactToRemove); - return; - } - - ContactYahooImpl contactToRemove = findContactById(contactID); - - // strange we cannot find the contact to be removed - if(contactToRemove == null) - return; - - ContactGroupYahooImpl parentGroup = - (ContactGroupYahooImpl)contactToRemove. - getParentContactGroup(); - parentGroup.removeContact(contactToRemove); - fireContactRemoved(parentGroup, contactToRemove); - - // check if the group is deleted. If the contact is the last one in - // the group. The group is also deleted - if(findGroup(ev.getGroup()) == null) - { - rootGroup.removeSubGroup(parentGroup); - fireGroupEvent(parentGroup, - ServerStoredGroupEvent.GROUP_REMOVED_EVENT); - } - } - - /** - * Someone wants to add us to their friends list - * to - the target (us!) - * from - the user who wants to add us - * message - the request message text - * @param ev fired event - */ - @Override - public void contactRequestReceived(SessionEvent ev) - { - if (logger.isInfoEnabled()) - logger.info("contactRequestReceived : " + ev); - - if(handler == null || ev.getFrom() == null) - return; - - ContactYahooImpl contact = findContactById(ev.getFrom()); - - if(contact == null) - contact = createVolatileContact(ev.getFrom()); - - AuthorizationRequest request = new AuthorizationRequest(); - request.setReason(ev.getMessage()); - - AuthorizationResponse resp = - handler.processAuthorisationRequest(request, contact); - - if (resp.getResponseCode() == AuthorizationResponse.REJECT) - { - try{ - yahooSession.rejectContact(ev, resp.getReason()); - }catch(IOException ex){ - logger.error("Cannot send reject : " + ex.getMessage()); - } - } - } - - /** - * Someone has rejected our attempts to add them to our friends list - * from - the user who rejected us - * message - rejection message text - * @param ev fired event - */ - @Override - public void contactRejectionReceived(SessionEvent ev) - { - if (logger.isInfoEnabled()) - logger.info("contactRejectionReceived : " + ev); - - if(handler == null) - return; - - ContactYahooImpl contact = findContactById(ev.getFrom()); - - AuthorizationResponse resp = - new AuthorizationResponse(AuthorizationResponse.REJECT, - ev.getMessage()); - handler.processAuthorizationResponse(resp, contact); - } - - /** - * Invoked on picture received. - * @param ev fired event - */ - @Override - public void pictureReceived(SessionPictureEvent ev) - { - ContactYahooImpl contact = findContactById(ev.getFrom()); - - if(contact == null) - return; - - contact.setImage(ev.getPictureData()); - - parentOperationSet.fireContactPropertyChangeEvent( - ContactPropertyChangeEvent.PROPERTY_IMAGE, - contact, null, ev.getPictureData()); - } - - /** - * Process Authorization responses - * @param ev the event to process - */ - @Override - public void authorizationReceived(SessionAuthorizationEvent ev) - { - if(ev.isAuthorizationAccepted()) - { - if (logger.isTraceEnabled()) - logger.trace("authorizationAccepted from " + ev.getFrom()); - Contact srcContact = findContactById(ev.getFrom()); - - if(srcContact == null) - if (logger.isTraceEnabled()) - logger.trace("No contact found"); - else - handler.processAuthorizationResponse( - new AuthorizationResponse( - AuthorizationResponse.ACCEPT, - ev.getMessage()), - srcContact); - } - else if(ev.isAuthorizationDenied()) - { - if (logger.isTraceEnabled()) - logger.trace("authorizationDenied from " + ev.getFrom()); - Contact srcContact = findContactById(ev.getFrom()); - - if(srcContact == null) - if (logger.isTraceEnabled()) - logger.trace("No contact found"); - else - { - handler.processAuthorizationResponse( - new AuthorizationResponse( - AuthorizationResponse.REJECT, - ev.getMessage()), - srcContact); - try - { - removeContact((ContactYahooImpl)srcContact); - } catch (Exception ex) - { - logger.error("cannot remove denied contact : " + - srcContact, ex); - } - } - } - else if(ev.isAuthorizationRequest()) - { - if (logger.isTraceEnabled()) - logger.trace("authorizationRequestReceived from " - + ev.getFrom()); - processAuthorizationRequest(ev); - } - } - } - - /** - * Sets the yahoo session instance of the lib - * which comunicates with the server - * @param session YahooSession - */ - void setYahooSession(YahooSession session) - { - this.yahooSession = session; - session.addSessionListener(contactListModListenerImpl); - initList(); - } - - /** - * It seems that ymsg (or the Yahoo! service itself as the problem also - * appears with libpurple) would return illegal chars for names that were - * entered in cyrillic. We use this method to translate their names into - * something that we could actually display and store here. - * - * @param ymsgString the <tt>String</tt> containing illegal chars. - * - * @return a String where all illegal chars are converted into human - * readable ones - */ - static String replaceIllegalChars(String ymsgString) - { - return ymsgString.replace((char)26, '?'); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/VolatileContactGroupYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/VolatileContactGroupYahooImpl.java deleted file mode 100644 index e2c589a..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/VolatileContactGroupYahooImpl.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * The Yahoo implementation of the Volatile ContactGroup interface. - * - * @author Damian Minkov - */ -public class VolatileContactGroupYahooImpl - extends ContactGroupYahooImpl -{ - /** - * This contact group name - */ - private String contactGroupName = null; - - /** - * Creates an Yahoo group using the specified group name - * @param groupName String groupname - * @param ssclCallback a callback to the server stored contact list - * we're creating. - */ - VolatileContactGroupYahooImpl( - String groupName, - ServerStoredContactListYahooImpl ssclCallback) - { - super(groupName, ssclCallback); - this.contactGroupName = groupName; - } - - /** - * Returns the name of this group. - * @return a String containing the name of this group. - */ - @Override - public String getGroupName() - { - return contactGroupName; - } - - /** - * Returns a string representation of this group, in the form - * YahooGroup.GroupName[size]{ buddy1.toString(), buddy2.toString(), ...}. - * @return a String representation of the object. - */ - @Override - public String toString() - { - StringBuffer buff = new StringBuffer("VolatileYahooGroup."); - buff.append(getGroupName()); - buff.append(", childContacts="+countContacts()+":["); - - Iterator<Contact> contacts = contacts(); - - while (contacts.hasNext()) - { - Contact contact = contacts.next(); - - buff.append(contact.toString()); - if(contacts.hasNext()) - buff.append(", "); - } - return buff.append("]").toString(); - } - - /** - * Determines whether or not this contact group is being stored by the - * server. Non persistent contact groups exist for the sole purpose of - * containing non persistent contacts. - * @return true if the contact group is persistent and false otherwise. - */ - @Override - public boolean isPersistent() - { - return false; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/YahooAccountID.java b/src/net/java/sip/communicator/impl/protocol/yahoo/YahooAccountID.java deleted file mode 100644 index 0eb0e86..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/YahooAccountID.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * The Yahoo implementation of a sip-communicator AccountID - * - * @author Damian Minkov - */ -public class YahooAccountID - extends AccountID -{ - /** - * Creates an account id from the specified id and account properties. - * @param id the id identifying this account - * @param accountProperties any other properties necessary for the account. - */ - YahooAccountID(String id, Map<String, String> accountProperties ) - { - super(YahooSession.getYahooUserID(id), - accountProperties, ProtocolNames.YAHOO, "yahoo.com"); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/YahooActivator.java b/src/net/java/sip/communicator/impl/protocol/yahoo/YahooActivator.java deleted file mode 100644 index 1e7ca01..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/YahooActivator.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -import org.jitsi.service.configuration.*; -import org.jitsi.service.resources.*; -import org.osgi.framework.*; - -/** - * Loads the Yahoo provider factory and registers it with service in the OSGI - * bundle context. - * - * @author Damian Minkov - */ -public class YahooActivator - implements BundleActivator -{ - private ServiceRegistration yahooPpFactoryServReg = null; - private static BundleContext bundleContext = null; - private static ConfigurationService configurationService = null; - - private static ProtocolProviderFactoryYahooImpl yahooProviderFactory = null; - - private static ResourceManagementService resourcesService; - - /** - * Called when this bundle is started so the Framework can perform the - * bundle-specific activities necessary to start this bundle. - * - * @param context The execution context of the bundle being started. - * @throws Exception If this method throws an exception, this bundle is - * marked as stopped and the Framework will remove this bundle's - * listeners, unregister all services registered by this bundle, and - * release all services used by this bundle. - */ - public void start(BundleContext context) throws Exception - { - bundleContext = context; - - Hashtable<String, String> hashtable = new Hashtable<String, String>(); - hashtable.put(ProtocolProviderFactory.PROTOCOL, ProtocolNames.YAHOO); - - yahooProviderFactory = new ProtocolProviderFactoryYahooImpl(); - - //reg the yahoo account man. - yahooPpFactoryServReg = context.registerService( - ProtocolProviderFactory.class.getName(), - yahooProviderFactory, - hashtable); - } - - /** - * Returns a reference to a ConfigurationService implementation currently - * registered in the bundle context or null if no such implementation was - * found. - * - * @return ConfigurationService a currently valid implementation of the - * configuration service. - */ - public static ConfigurationService getConfigurationService() - { - if(configurationService == null) - { - ServiceReference confReference - = bundleContext.getServiceReference( - ConfigurationService.class.getName()); - configurationService - = (ConfigurationService) bundleContext.getService(confReference); - } - return configurationService; - } - - /** - * Returns a reference to the bundle context that we were started with. - * @return a reference to the BundleContext instance that we were started - * witn. - */ - public static BundleContext getBundleContext() - { - return bundleContext; - } - - /** - * Retrurns a reference to the protocol provider factory that we have - * registered. - * @return a reference to the <tt>ProtocolProviderFactoryYahooImpl</tt> - * instance that we have registered from this package. - */ - static ProtocolProviderFactoryYahooImpl getProtocolProviderFactory() - { - return yahooProviderFactory; - } - - /** - * Called when this bundle is stopped so the Framework can perform the - * bundle-specific activities necessary to stop the bundle. - * - * @param context The execution context of the bundle being stopped. - * @throws Exception If this method throws an exception, the bundle is - * still marked as stopped, and the Framework will remove the bundle's - * listeners, unregister all services registered by the bundle, and - * release all services used by the bundle. - */ - public void stop(BundleContext context) throws Exception - { - yahooProviderFactory.stop(); - yahooPpFactoryServReg.unregister(); - } - - public static ResourceManagementService getResources() - { - if (resourcesService == null) - { - ServiceReference serviceReference = bundleContext - .getServiceReference(ResourceManagementService.class.getName()); - - if(serviceReference == null) - return null; - - resourcesService = (ResourceManagementService) bundleContext - .getService(serviceReference); - } - - return resourcesService; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/YahooSession.java b/src/net/java/sip/communicator/impl/protocol/yahoo/YahooSession.java deleted file mode 100644 index f24e50c..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/YahooSession.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.yahoo; - -import java.io.*; - -import ymsg.network.*; - -/** - * Extends The Yahoo session to have access to some - * protected functionality - * Not working for now. - * - * @author Damian Minkov - */ -public class YahooSession - extends Session -{ - /** - * Renames a group. Not working for now - */ - public void renameGroup(String oldName, String newName) - throws IOException - { - transmitGroupRename(oldName, newName); - } - - /** - * Removes the server part from the given id - */ - public static String getYahooUserID(String id) - { - return (id.indexOf("@") > -1 ) - ? id.substring(0, id.indexOf("@")) - : id; - } - - /** - * Sending typing notifications - * @param to user we are notifing - * @param from our user id - */ - void keyTyped(String to, String from) - { - try { - transmitNotify(to, from, true, " ", NOTIFY_TYPING); - }catch(IOException e){} - } - - /** - * Sending stop typing notifications - * @param to user we are notifing - * @param from our user id - */ - void stopTyping(String to, String from) - { - try { - transmitNotify(to, from, false, " ", NOTIFY_TYPING); - }catch(IOException e){} - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/yahoo.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/yahoo/yahoo.provider.manifest.mf deleted file mode 100644 index 39e23bf..0000000 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/yahoo.provider.manifest.mf +++ /dev/null @@ -1,22 +0,0 @@ -Bundle-Activator: net.java.sip.communicator.impl.protocol.yahoo.YahooActivator -Bundle-Name: Yahoo Protocol Provider Implementation -Bundle-Description: An Yahoo implementation of the Protocol Provider Service. -Bundle-Vendor: jitsi.org -Bundle-Version: 0.0.1 -Bundle-SymbolicName: net.java.sip.communicator.protocol.yahoo -Import-Package: org.osgi.framework, - javax.net.ssl, - javax.swing, - javax.swing.text, - javax.xml.parsers, - javax.naming, - javax.naming.directory, - org.xml.sax, - sun.security.action, - org.jitsi.service.configuration, - org.jitsi.service.resources, net.java.sip.communicator.service.resources, - net.java.sip.communicator.util, - net.java.sip.communicator.service.dns, - net.java.sip.communicator.service.protocol, - net.java.sip.communicator.service.protocol.yahooconstants, - net.java.sip.communicator.service.protocol.event diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/BonjourService.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/BonjourService.java deleted file mode 100644 index 8258da7..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/BonjourService.java +++ /dev/null @@ -1,706 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.io.*; -import java.net.*; -import java.util.*; - -import net.java.sip.communicator.impl.protocol.zeroconf.jmdns.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -/** - * Class dealing with JmDNS and treating all the - * incoming connections on the bonjour port - * @author Christian Vincenot - */ -public class BonjourService extends Thread - implements ServiceListener, - DNSListener -{ - private static final Logger logger = - Logger.getLogger(BonjourService.class); - - private int port = 5298; - private ServerSocket sock = null; - private String id = null; - private JmDNS jmdns=null; - private final Map<String, Object> props = new Hashtable<String, Object>(); - private ServiceInfo service = null; - private boolean dead = false; - - private final List<ContactZeroconfImpl> contacts - = new Vector<ContactZeroconfImpl>(); - - private ProtocolProviderServiceZeroconfImpl pps; - OperationSetPersistentPresenceZeroconfImpl opSetPersPresence; - - private ZeroconfAccountID acc; - - /* Should maybe better get the status directly from OperationSetPresence */ - private PresenceStatus status = ZeroconfStatusEnum.OFFLINE; - - /** - * Returns the corresponding ProtocolProviderService - * @return corresponding ProtocolProviderService - */ - public ProtocolProviderServiceZeroconfImpl getPPS() - { - return pps; - } - - /** - * Returns the id of this service. - * @return returns the id of this service. - */ - String getID() - { - return id; - } - - /** - * Creates a new instance of the Bonjour service thread - * @param port TCP Port number on which to try to start the Bonjour service - * @param pps ProtocolProviderService instance - * which is creating this BonjourService - */ - public BonjourService(int port, - ProtocolProviderServiceZeroconfImpl pps) - { - this.acc = (ZeroconfAccountID) pps.getAccountID(); - this.port = port; - this.id = acc.getUserID(); - this.pps = pps; - - opSetPersPresence = - (OperationSetPersistentPresenceZeroconfImpl) pps - .getOperationSet(OperationSetPersistentPresence.class); - - // Gaim - props.put("1st", (acc.getFirst() == null)? "":acc.getFirst()); - props.put("email", (acc.getMail() == null)? "":acc.getMail()); - props.put("jid", this.id); - props.put("last", (acc.getLast() == null)?"":acc.getLast()); - props.put("msg", opSetPersPresence.getCurrentStatusMessage()); - props.put("status", "avail"); - - //iChat - props.put("phsh","000"); - //props.put("status","avail"); - //props.put("port.p2pj", "5298"); - props.put("vc", "C!"); - //props.put("1st", "John"); - props.put("txtvers","1"); - - //XEP-0174 (Final paper) - props.put("ext",""); - props.put("nick", (acc.getFirst() == null)? this.id:acc.getFirst()); - props.put("ver", "1"); - props.put("node", "SIP Communicator"); - - //Ours - props.put("client", "SIP Communicator"); - - changeStatus(opSetPersPresence.getPresenceStatus()); - - sock = createSocket(port); - if (sock == null) - return; - - port = sock.getLocalPort(); - - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: ServerSocket bound to port "+port); - - props.put("port.p2pj", Integer.toString(port)); - this.setDaemon(true); - this.start(); - } - - /* TODO: Better exception checking to avoid sudden exit and bonjour - * service shutdown */ - - /** - * Walk? - */ - @Override - public void run() - { - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Bonjour Service Thread up and running!"); - - /* Put jmDNS in DEBUD Mode : - * Following verbosity levels can be chosen : - * "INFO" , "WARNING", "SEVERE", "ALL", "FINE", "FINER", "FINEST", etc - */ - //System.setProperty("jmdns.debug", "0"); - - while (dead == false) - { - if (sock == null || sock.isClosed()) - { - sock = createSocket(port); - /* What should we do now? TEMPORARY: shutdown()*/ - if (sock == null) shutdown(); - port = sock.getLocalPort(); - props.put("port.p2pj", Integer.toString(port)); - //TODO: update JmDNS in case the port had to be changed! - } - try - { - Socket connection = sock.accept(); - ContactZeroconfImpl contact = getContact(null, - connection.getInetAddress()); - /*if (status.equals(ZeroconfStatusEnum.OFFLINE) - || status.equals(ZeroconfStatusEnum.INVISIBLE) */ - if (dead == true) break; - - if ((contact == null) - || (contact.getClientThread() != null)) - { - if (contact == null) - logger.error("ZEROCONF: Connexion from " - + "unknown contact [" - + connection.getInetAddress() - +"]. REJECTING!"); - else if (contact.getClientThread() == null) - logger.error("ZEROCONF: Redundant chat " - + "channel [" - + contact - +"]. REJECTING!"); - connection.close(); - } - else new ClientThread(connection, this); - } - catch(Exception e) - { - logger.error(e); - } - } - - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Going Offline - " - +"BonjourService Thread exiting!"); - } - - /** - * Might be used for shutdown... - */ - public void shutdown() - { - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Shutdown!"); - - dead = true; - try - { sock.close(); } - catch (Exception ex) - { logger.error(ex); } - - changeStatus(ZeroconfStatusEnum.OFFLINE); - if(jmdns != null) - jmdns.close(); - } - - private ServerSocket createSocket(int port) - { - ServerSocket sock=null; - try - { - sock = new ServerSocket(port); - } - catch(Exception e) - { - logger.error("ZEROCONF: Couldn't bind socket to port " - +port+"! Switching to an other port..."); - try - { - sock = new ServerSocket(0); - } - catch (IOException ex) - { - logger.error("ZEROCONF: FATAL ERROR => " - +"Couldn't bind to a port!!", ex); - } - } - - return sock; - } - - /** - * Changes the status of the local user. - * @param stat New presence status - */ - public void changeStatus(PresenceStatus stat) - { - /* [old_status == new_status ?] => NOP */ - if (stat.equals(status)) - return; - - /* [new_status == OFFLINE ?] => clean up everything */ - if (stat.equals(ZeroconfStatusEnum.OFFLINE)) - { - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Going OFFLINE"); - //jmdns.unregisterAllServices(); - jmdns.removeServiceListener("_presence._tcp.local.", this); - jmdns.close(); - jmdns=null; - //dead = true; - - // Erase all contacts by putting them OFFLINE - opSetPersPresence.changePresenceStatusForAllContacts( - opSetPersPresence.getServerStoredContactListRoot(), stat); - - try - { - sleep(1000); - } catch (InterruptedException ex) - { - logger.error(ex); - } - } - - /* [old_status == OFFLINE ?] => register service */ - else if (status.equals(ZeroconfStatusEnum.OFFLINE)) - { - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Getting out of OFFLINE state"); - props.put("status", stat.getStatusName()); - service = new ServiceInfo("_presence._tcp.local.", id, - port, 0, 0, props); - - try - { - jmdns = new JmDNS(); - jmdns.registerServiceType("_presence._tcp.local."); - jmdns.addServiceListener("_presence._tcp.local.", this); - jmdns.registerService(service); - - /* In case the ID had to be changed */ - id = service.getName(); - } - catch (Exception ex) - { logger.error(ex); } - - //dead = false; - - /* Normal status change */ - } - else - { - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF : Changing status"); - - props.put("status", stat.getStatusName()); - - /* FIXME: Not totally race condition free since the 3 calls aren't - * atomic, but that's not really critical since there's little - * change chance of concurrent local contact change, and this - * wouldn't have big consequences. - */ - ServiceInfo info = - jmdns.getLocalService(id.toLowerCase()+"._presence._tcp.local."); - if (info == null) - logger.error("ZEROCONF/JMDNS: PROBLEM GETTING " - +"LOCAL SERVICEINFO !!"); - - byte[] old = info.getTextBytes(); - info.setProps(props); - jmdns.updateInfos(info, old); - } - - status = stat; - } - - private class AddThread extends Thread - { - private String type, name; - public AddThread(String type, String name) - { - this.setDaemon(true); - this.type = type; - this.name = name; - this.start(); - } - - @Override - public void run() - { - ServiceInfo service = null; - while ((service == null) && (dead == false) - && !status.equals(ZeroconfStatusEnum.OFFLINE)) - { - service = jmdns.getServiceInfo(type, name, 10000); - if (service == null) - logger.error("BONJOUR: ERROR - Service Info of " - + name +" not found in cache!!"); - try - { - sleep(2); - } - catch (InterruptedException ex) - { - logger.error(ex); - } - } - if ((dead == false) && !status.equals(ZeroconfStatusEnum.OFFLINE)) - jmdns.requestServiceInfo(type, name); - //} else handleResolvedService(name, type, service); - } - } - - /* Service Listener Implementation */ - - /** - * A service has been added. - * - * @param event The ServiceEvent providing the name and fully qualified type - * of the service. - */ - public void serviceAdded(ServiceEvent event) - { - /* WARNING: DONT PUT ANY BLOCKING CALLS OR FLAWED LOOPS IN THIS METHOD. - * JmDNS calls this method without creating a new thread, so if this - * method doesn't return, jmDNS will hang !! - */ - - String name = event.getName(); - String type = event.getType(); - - if (name.equals(id)) - return; - - if (logger.isDebugEnabled()) - logger.debug("BONJOUR: "+name - +"["+type+"] detected! Trying to get information..."); - try - { - sleep(2); - } - catch (InterruptedException ex) - { - logger.error(ex); - } - - jmdns.printServices(); - - new AddThread(type, name); - } - - - - /** - * A service has been removed. - * - * @param event The ServiceEvent providing the name and fully qualified type - * of the service. - */ - public void serviceRemoved(ServiceEvent event) - { - String name = event.getName(); - if (name.equals(id)) - return; - - ContactZeroconfImpl contact = getContact(name, null); - - if(contact == null) - return; - - opSetPersPresence.changePresenceStatusForContact(contact, - ZeroconfStatusEnum.OFFLINE); - if (logger.isDebugEnabled()) - logger.debug("BONJOUR: Received announcement that " - +name+" went offline!"); - - } - - /** - * A service has been resolved. Its details are now available in the - * ServiceInfo record. - * - * @param event The ServiceEvent providing the name, the fully qualified - * type of the service, and the service info record, - * or null if the service could not be resolved. - */ - public void serviceResolved(ServiceEvent event) - { - String contactID = event.getName(); - String type = event.getType(); - ServiceInfo info = event.getInfo(); - - if (logger.isDebugEnabled()) - logger.debug("BONJOUR: Information about " - +contactID+" discovered"); - - handleResolvedService(contactID, type, info); - } - - private void handleResolvedService(String contactID, - String type, - ServiceInfo info) - { - if (contactID.equals(id)) - return; - - if (info.getAddress().toString().length() > 15) - { - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Temporarily ignoring IPv6 addresses!"); - return; - } - - ContactZeroconfImpl newFriend; - - synchronized(this) - { - if (getContact(contactID, info.getAddress()) != null) - { - if (logger.isDebugEnabled()) - logger.debug("Contact " - +contactID+" already in contact list! Skipping."); - return; - }; - if (logger.isDebugEnabled()) - logger.debug("ZEROCNF: ContactID " + contactID + - " Address " + info.getAddress()); - - if (logger.isDebugEnabled()) - logger.debug(" Address=>"+info.getAddress() - +":"+info.getPort()); - - for (Iterator<String> names = info.getPropertyNames(); - names.hasNext();) - { - String prop = names.next(); - if (logger.isDebugEnabled()) - logger.debug(" "+prop+"=>" - +info.getPropertyString(prop)); - } - - /* Creating the contact */ - String name = info.getPropertyString("1st"); - if (info.getPropertyString("last") != null) - name += " "+ info.getPropertyString("last"); - - int port = Integer.valueOf( - info.getPropertyString("port.p2pj")).intValue(); - - if (port < 1) - { - logger.error("ZEROCONF: Flawed contact announced himself" - +"without necessary parameters : "+contactID); - return; - } - - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Detected client "+name); - - newFriend = - opSetPersPresence.createVolatileContact( - contactID, this, name, - info.getAddress(), port); - } - /* Try to detect which client type it is */ - int clientType = ContactZeroconfImpl.XMPP; - if (info.getPropertyString("client") != null - && info.getPropertyString("client"). - compareToIgnoreCase("SIP Communicator") == 0) - clientType = ContactZeroconfImpl.SIPCOM; - - else if ((info.getPropertyString("jid") != null) - && (info.getPropertyString("node") == null)) - clientType = ContactZeroconfImpl.GAIM; - - else if (info.getPropertyString("jid") == null) - clientType = ContactZeroconfImpl.ICHAT; - - newFriend.setClientType(clientType); - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: CLIENT TYPE "+clientType); - - ZeroconfStatusEnum status = - ZeroconfStatusEnum.statusOf(info.getPropertyString("status")); - opSetPersPresence. - changePresenceStatusForContact(newFriend, - status == null?ZeroconfStatusEnum.ONLINE:status); - - // Listening for changes - jmdns.addListener(this, new DNSQuestion(info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_UNIQUE)); - } - - /** - * Callback called by JmDNS to inform the - * BonjourService of a potential status change of some contacts. - * @param jmdns JmDNS instance responsible for this - * @param now Timestamp - * @param record DNSRecord which changed - */ - public synchronized void updateRecord( JmDNS jmdns, - long now, - DNSRecord record) - { - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF/JMDNS: Received record update for "+record); - - int clazz = record.getClazz(); - int type = record.getType(); - - /* Check the info returned by JmDNS since we can't really trust its - * filtering. */ - if (!(((type & DNSConstants.TYPE_TXT) != 0) && - ((clazz & DNSConstants.CLASS_IN) != 0) && - record.isUnique() && - record.getName().endsWith("_presence._tcp.local."))) - return; - - String name = record.getName().replaceAll("._presence._tcp.local.",""); - ContactZeroconfImpl contact; - - synchronized(this) - { - contact = getContact(name, null); - - if (contact == null) { //return; - logger.error("ZEROCONF: BUG in jmDNS => Received update without " - +"previous contact annoucement. Trying to add contact"); - new AddThread("_presence._tcp.local.", name); - return; - } - } - - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: "+ name - + " changed status. Requesting fresh data!"); - - /* Since a record was updated, we can be sure that we can do a blocking - * getServiceInfo without risk. (Still, we use the method with timeout - * to avoid bad surprises). If some problems of status change refresh - * appear, we'll have to fall back on the method with callback as we've - * done for "ServiceAdded". - */ - - ServiceInfo info = jmdns.getServiceInfo("_presence._tcp.local.", name, - 1000); - if (info == null) - { - logger.error("ZEROCONF/JMDNS: Problem!! The service " - +"information was not in cache. See comment in " - +"BonjourService.java:updateRecord !!"); - return; - } - - /* Let's change what we can : status, message, etc */ - ZeroconfStatusEnum status = - ZeroconfStatusEnum.statusOf(info.getPropertyString("status")); - - opSetPersPresence. - changePresenceStatusForContact(contact, - status == null ? ZeroconfStatusEnum.ONLINE:status); - - } - - /** - * Returns an Iterator over all contacts. - * - * @return a java.util.Iterator over all contacts - */ - public Iterator<ContactZeroconfImpl> contacts() - { - return contacts.iterator(); - } - - /** - * Adds a contact to the locally stored list of contacts - * @param contact Zeroconf Contact to add to the local list - */ - public void addContact(ContactZeroconfImpl contact) - { - if (contact == null) - throw new IllegalArgumentException("contact"); - - synchronized(contacts) - { - contacts.add(contact); - } - } - /** - * Returns the <tt>Contact</tt> with the specified identifier or IP address. - * - * @param id the identifier of the <tt>Contact</tt> we are - * looking for. - * @param ip the IP address of the <tt>Contact</tt> we are looking for. - * @return the <tt>Contact</tt> with the specified id or address. - */ - public ContactZeroconfImpl getContact(String id, InetAddress ip) - { - if (id == null && ip == null) return null; - - synchronized(contacts) - { - Iterator<ContactZeroconfImpl> contactsIter = contacts(); - - while (contactsIter.hasNext()) - { - ContactZeroconfImpl contact = contactsIter.next(); - //System.out.println("ZEROCNF: Comparing "+id+ " "+ip+ - //" with "+ contact.getAddress()+ " " + contact.getIpAddress()); - if (((contact.getAddress().equals(id)) || (id == null)) - && ((contact.getIpAddress().equals(ip)) || (ip == null))) - return contact; - - } - } - //System.out.println("ZEROCNF: ERROR - " + - //"Couldn't find contact to get ["+id+" / "+ip+"]"); - return null; - } - - /** - * Removes the <tt>Contact</tt> with the specified identifier or IP address. - * - * - * @param id the identifier of the <tt>Contact</tt> we are - * looking for. - * @param ip the IP address of the <tt>Contact</tt> we are looking for. - */ - public void removeContact(String id, InetAddress ip) - { - synchronized(contacts) - { - Iterator<ContactZeroconfImpl> contactsIter = contacts(); - while (contactsIter.hasNext()) - { - ContactZeroconfImpl contact = contactsIter.next(); - if (((contact.getAddress().equals(id)) || (id == null)) - &&((contact.getIpAddress().equals(ip)) || (ip == null))) - { - if (contact.getClientThread() != null) - contact.getClientThread().cleanThread(); - contacts.remove(contact); - return; - } - }; - } - logger.error( - "ZEROCONF: ERROR - Couldn't find contact to delete ["+id+" / "+ip+"]"); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/ClientThread.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/ClientThread.java deleted file mode 100644 index 4367eed..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/ClientThread.java +++ /dev/null @@ -1,499 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.io.*; -import java.net.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -/** - * Class creating a thread responsible for handling the chat - * with the remote user on the other end of the socket - * - * @author Christian Vincenot - */ -public class ClientThread - extends Thread -{ - private static final Logger logger = Logger.getLogger(ClientThread.class); - - private OperationSetBasicInstantMessagingZeroconfImpl opSetBasicIM; - private OperationSetTypingNotificationsZeroconfImpl opSetTyping; - private Socket sock; - private InetAddress remoteIPAddress; - private OutputStream out; - private DataInputStream in; - private BonjourService bonjourService; - private ContactZeroconfImpl contact=null; - private boolean streamState = false; - - private String messagesQueue=null; - - /** - * Sets the contact with which we're chatting in this ClientThread - * @param contact Zeroconf contact with which we're chatting - */ - protected void setContact(ContactZeroconfImpl contact) - { - this.contact = contact; - } - - /** - * Set the stream as opened. This means that the - * conversation with the client is really opened - * from now on (the XML greetings are over) - */ - protected void setStreamOpen() - { - synchronized(this) - { - this.streamState = true; - } - } - - /** - * Says if the stream between the local user and the remote user - * is in an opened state (greetings are over and we can chat) - * @return Returns true if the stream is "opened" (ie, ready for chat) - */ - protected boolean isStreamOpened() - { - synchronized(this) - { - return this.streamState; - } - } - - /** - * Creates a new instance of ClientThread reponsible - * for handling the conversation with the remote user. - * @param sock Socket created for chatting - * @param bonjourService BonjourService which spawned this ClientThread - */ - public ClientThread(Socket sock, BonjourService bonjourService) - { - this.sock = sock; - this.remoteIPAddress = sock.getInetAddress(); - this.bonjourService = bonjourService; - this.opSetBasicIM = - (OperationSetBasicInstantMessagingZeroconfImpl) bonjourService - .getPPS().getOperationSet( - OperationSetBasicInstantMessaging.class); - - this.opSetTyping = - (OperationSetTypingNotificationsZeroconfImpl) bonjourService - .getPPS() - .getOperationSet(OperationSetTypingNotifications.class); - this.setDaemon(true); - - try - { - out = sock.getOutputStream(); - in = new DataInputStream(sock.getInputStream()); - } - catch (IOException e) - { - logger.error("Creating ClientThread: Couldn't get I/O for " - +"the connection", e); - //System.exit(1); - return; - } - - this.start(); - } - - /* - * Read a message from the socket. - * TODO: clean the code a bit and optimize it. - */ - private String readMessage() - { - String line; - byte[] bytes = new byte[10]; - - try - { - int i=0; - - while (i < 9) - { - i += in.read(bytes,0,9-i); - } - - line = new String(bytes); - bytes = new byte[1]; - if ((line.getBytes())[0] == '\n') - line = line.substring(1); - - if (line.startsWith("<message")) - { - while (true) - { - bytes[0] = in.readByte(); - line += new String(bytes); - - if ((line.endsWith("</message>")) - || (line.endsWith("stream>"))) - return line; - } - } - else - { - while (true) - { - bytes[0] = in.readByte(); - line += new String(bytes); - if ( ">".compareTo(new String(bytes)) == 0 ) - return line; - } - } - } - catch (IOException e) - { - logger.error("Couldn't get I/O for the connection", e); - //System.exit(1); - } - - return null; - } - - /* - * Parse the payload and extract the information. - * TODO: If needed, fill in the remaining fields of MessageZeroconfImpl - * like the baloon icon color, color/size/font of the text. - */ - private MessageZeroconfImpl parseMessage(String str) - { - if (str.startsWith("<?xml") || str.startsWith("<stream")) - return new MessageZeroconfImpl - (null, null, MessageZeroconfImpl.STREAM_OPEN); - - if (str.endsWith("stream>")) - return new MessageZeroconfImpl - (null, null, MessageZeroconfImpl.STREAM_CLOSE); - - if ((str.indexOf("<delivered/>") > 0) && (str.indexOf("<body>") < 0)) - return new MessageZeroconfImpl - (null, null, MessageZeroconfImpl.DELIVERED); - - if (!str.startsWith("<message")) - return new MessageZeroconfImpl - (null, null, MessageZeroconfImpl.UNDEF); - - /* TODO: Parse Enconding (& contact id to be able to double-check - * the source of a message) - * - * TODO: Check that the fields are outside of <body>..</body> - */ - - if ((str.indexOf("<body>") < 0) || (str.indexOf("</body>") < 0)) - return new MessageZeroconfImpl - (null, null, MessageZeroconfImpl.UNDEF); - - String temp = - str.substring(str.indexOf("<body>")+6, str.indexOf("</body>")); - - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: received message ["+temp+"]"); - - int messageType = MessageZeroconfImpl.MESSAGE; - - if ((str.indexOf("<id>") >= 0) && (str.indexOf("</id>") >= 0)) - messageType = MessageZeroconfImpl.TYPING; - - MessageZeroconfImpl msg = - new MessageZeroconfImpl( - temp, - null, - OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE, - messageType); - - return msg; - } - - private int handleMessage(MessageZeroconfImpl msg) - { - - switch(msg.getType()) - { - /* STREAM INIT */ - case MessageZeroconfImpl.STREAM_OPEN: - if (contact == null) - contact = bonjourService.getContact(null, remoteIPAddress); - if (!isStreamOpened()) - { - sendHello(); - setStreamOpen(); - } - if (messagesQueue != null) - { - write(messagesQueue); - messagesQueue = null; - } - break; - - /* ACK */ - case MessageZeroconfImpl.DELIVERED : break; - - /* NORMAL MESSAGE */ - case MessageZeroconfImpl.MESSAGE: - if (!isStreamOpened()) - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: client on the other side " - +"isn't polite (sending messages without " - +"saying hello :P"); - if (contact == null) - //TODO: Parse contact id to double-check - contact = bonjourService.getContact(null, remoteIPAddress); - - /* TODO: If we want to implement invisible status, we'll have to - * make this test less restrictive to handle messages from - * unannounced clients. - */ - if (contact == null) - { - logger.error("ZEROCONF: ERROR - Couldn't identify " - +"contact. Closing socket."); - return -1; - } - else if (contact.getClientThread() == null) - contact.setClientThread(this); - - opSetBasicIM.fireMessageReceived(msg, contact); - - opSetTyping.fireTypingNotificationsEvent(contact, - OperationSetTypingNotificationsZeroconfImpl.STATE_STOPPED); - break; - - case MessageZeroconfImpl.TYPING: - if (!isStreamOpened()) - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: client on the other side " - +"isn't polite (sending messages without " - +"saying hello :P"); - if (contact == null) - //TODO: Parse contact id to double-check - contact = bonjourService.getContact(null, remoteIPAddress); - opSetTyping.fireTypingNotificationsEvent(contact, - OperationSetTypingNotificationsZeroconfImpl.STATE_TYPING); - - /* TODO: code a private runnable class to be used as timeout - * to set the typing state to STATE_PAUSED when a few seconds - * without news have passed. - */ - - break; - - case MessageZeroconfImpl.STREAM_CLOSE: - sendBye(); - contact.setClientThread(null); - return 1; - - case MessageZeroconfImpl.UNDEF: - logger.error("ZEROCONF: received strange message. SKIPPING!"); - break; - } - - //System.out.println("RECEIVED MESSAGE "+ msg.getContent()+ - //" from "+contact.getAddress() + "!!!!!!!!!!!!!!"); - return 0; - } - - - private void write(String string) - { - //System.out.println("Writing " + string + "!!!!!!!!!"); - byte[] bytes = string.getBytes(); - try - { - out.write(bytes); - out.flush(); - } - catch (IOException e) - { - logger.error("Couldn't get I/O for the connection"); - if (contact != null) - { - contact.setClientThread(null); - } - - try - { - sock.close(); - } - catch (IOException ex) - { - logger.error(ex); - } - - } - } - - /** - * Say hello :) - */ - protected void sendHello() - { - switch(contact.getClientType()) - { - case ContactZeroconfImpl.GAIM: - case ContactZeroconfImpl.ICHAT: - case ContactZeroconfImpl.SIPCOM: - write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"); - write("<stream:stream xmlns=\"jabber:client\" " - +"xmlns:stream=\"http://etherx.jabber.org/streams\">"); - break; - case ContactZeroconfImpl.XMPP: - write("<stream:stream" - +"xmlns='jabber:client'" - +"xmlns:stream='http://etherx.jabber.org/streams'" - +"from='"+bonjourService.getID()+"'" - +"to='"+contact.getAddress()+"'" - +"version='1.0'>\n"); - break; - } - - /* Legacy: OLD XMPP (XEP-0174 Draft) */ - //write("<stream:stream to='"+sock.getInetAddress().getHostAddress() - //+"' xmlns='jabber:client' stream='http://etherx.jabber.org/streams'>"); - } - - private void sendBye() - { - write("</stream:stream>\n"); - } - - private String toXHTML(MessageZeroconfImpl msg) - { - switch(contact.getClientType()) - { - case ContactZeroconfImpl.XMPP: - return new String("<message to='" - +contact.getAddress()+"' from='" - +bonjourService.getID()+"'>" - + "<body>"+msg.getContent()+"</body>" - + "</message>\n"); - - case ContactZeroconfImpl.SIPCOM: - - case ContactZeroconfImpl.ICHAT: - return new String( - "<message to='"+sock.getInetAddress().getHostAddress() - +"' type='chat' id='"+bonjourService.getID()+"'>" - + "<body>"+msg.getContent()+"</body>" - + "<html xmlns='http://www.w3.org/1999/xhtml'>" - + "<body ichatballooncolor='#7BB5EE' " - + "ichattextcolor='#000000'>" - + "<font face='Helvetica' ABSZ='12' color='#000000'>" - + msg.getContent() - + "</font>" - + "</body>" - + "</html>" - + "<x xmlns='jabber:x:event'>" - + "<offline/>" - + "<delivered/>" - + "<composing/>" - + (msg.getType()==MessageZeroconfImpl.TYPING?"<id></id>":"") - + "</x>" - + "</message>"); - - case ContactZeroconfImpl.GAIM: - default: - return new String( - "<message to='"+contact.getAddress() - +"' from='"+bonjourService.getID() - + "' type='chat'><body>"+msg.getContent()+"</body>" - + "<html xmlns='http://www.w3.org/1999/xhtml'><body><font>" - + msg.getContent() - + "</font></body></html><x xmlns='jabber:x:event'><composing/>" - + (msg.getType()==MessageZeroconfImpl.TYPING?"<id></id>":"") - + "</x></message>\n"); - } - } - - - /** - * Send a message to the remote user - * @param msg Message to send - */ - public void sendMessage(MessageZeroconfImpl msg) - { - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Sending messag [" - +msg.getContent()+"] to " - + contact.getDisplayName()); - if (!isStreamOpened()) - { - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Stream not opened... " - +"will send the message later"); - messagesQueue += toXHTML(msg); - } - else write(toXHTML(msg)); - } - - /** - * Walk? - */ - @Override - public void run() - { - if (logger.isDebugEnabled()) - logger.debug("Bonjour: NEW CONNEXION from " - + sock.getInetAddress().getCanonicalHostName() - +" / "+sock.getInetAddress().getHostAddress()); - String input; - MessageZeroconfImpl msg=null; - - - input = readMessage(); - msg = parseMessage(input); - - while (handleMessage(msg) == 0 && !sock.isClosed()) - { - input = readMessage(); - msg = parseMessage(input); - } - - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF : OUT OF LOOP !! Closed chat."); - cleanThread(); - } - - /** - * Clean-up the thread to exit - */ - public void cleanThread() - { - /* I wonder if that's ok... */ - if (sock != null && sock.isClosed() == false) - { - sendBye(); - try - { - sock.close(); - } - catch (IOException ex) - { - logger.error(ex); - } - } - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/ContactGroupZeroconfImpl.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/ContactGroupZeroconfImpl.java deleted file mode 100644 index 568e087..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/ContactGroupZeroconfImpl.java +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * A simple, straightforward implementation of a zeroconf ContactGroup. Since - * the Zeroconf protocol, we simply store all group details - * in class fields. You should know that when implementing a real protocol, - * the contact group implementation would rather encapsulate group objects from - * the protocol stack and group property values should be returned by consulting - * the encapsulated object. - * - * @author Christian Vincenot - * @author Maxime Catelin - * @author Jonathan Martin - */ -public class ContactGroupZeroconfImpl - implements ContactGroup -{ - - /** - * The name of this Zeroconf contact group. - */ - private String groupName = null; - - /** - * The list of this group's members. - */ - private Vector<Contact> contacts = new Vector<Contact>(); - - /** - * The list of sub groups belonging to this group. - */ - private Vector<ContactGroup> subGroups = new Vector<ContactGroup>(); - - /** - * The group that this group belongs to (or null if this is the root group). - */ - private ContactGroupZeroconfImpl parentGroup = null; - - /** - * Determines whether this group is really in the contact list or whether - * it is here only temporarily and will be gone next time we restart. - */ - private boolean isPersistent = false; - - /** - * The protocol provider that created us. - */ - private ProtocolProviderServiceZeroconfImpl parentProvider = null; - - /** - * Determines whether this group has been resolved on the server. - * Unresolved groups are groups that were available on previous runs and - * that the meta contact list has stored. During all next runs, when - * bootstrapping, the meta contact list would create these groups as - * unresolved. Once a protocol provider implementation confirms that the - * groups are still on the server, it would issue an event indicating that - * the groups are now resolved. - */ - private boolean isResolved = true; - - /** - * An id uniquely identifying the group. For many protocols this could be - * the group name itself. - */ - private String uid = null; - private static final String UID_SUFFIX = ".uid"; - - /** - * Creates a ContactGroupZeroconfImpl with the specified name. - * - * @param groupName the name of the group. - * @param parentProvider the protocol provider that created this group. - */ - public ContactGroupZeroconfImpl( - String groupName, - ProtocolProviderServiceZeroconfImpl parentProvider) - { - this.groupName = groupName; - this.uid = groupName + UID_SUFFIX; - this.parentProvider = parentProvider; - } - - /** - * Determines whether the group may contain subgroups or not. - * - * @return always true in this implementation. - */ - public boolean canContainSubgroups() - { - return true; - } - - /** - * Returns the protocol provider that this group belongs to. - * @return a regerence to the ProtocolProviderService instance that this - * ContactGroup belongs to. - */ - public ProtocolProviderService getProtocolProvider() - { - return parentProvider; - } - - /** - * Returns an Iterator over all contacts, member of this - * <tt>ContactGroup</tt>. - * - * @return a java.util.Iterator over all contacts inside this - * <tt>ContactGroup</tt> - */ - public Iterator<Contact> contacts() - { - return contacts.iterator(); - } - - /** - * Adds the specified contact to this group. - * @param contactToAdd the ContactZeroconfImpl to add to this group. - */ - public void addContact(ContactZeroconfImpl contactToAdd) - { - this.contacts.add(contactToAdd); - contactToAdd.setParentGroup(this); - } - - /** - * Returns the number of <tt>Contact</tt> members of this - * <tt>ContactGroup</tt> - * - * @return an int indicating the number of <tt>Contact</tt>s, members of - * this <tt>ContactGroup</tt>. - */ - public int countContacts() - { - return contacts.size(); - } - - /** - * Returns the number of subgroups contained by this - * <tt>ContactGroup</tt>. - * - * @return the number of subGroups currently added to this group. - */ - public int countSubgroups() - { - return subGroups.size(); - } - - /** - * Adds the specified contact group to the contained by this group. - * @param subgroup the ContactGroupZeroconfImpl to add as a - * subgroup to this group. - */ - public void addSubgroup(ContactGroupZeroconfImpl subgroup) - { - this.subGroups.add(subgroup); - subgroup.setParentGroup(this); - } - - /** - * Sets the group that is the new parent of this group - * @param parent ContactGroupZeroconfImpl - */ - void setParentGroup(ContactGroupZeroconfImpl parent) - { - this.parentGroup = parent; - } - - /** - * Returns the contact group that currently contains this group or null if - * this is the root contact group. - * @return the contact group that currently contains this group or null if - * this is the root contact group. - */ - public ContactGroup getParentContactGroup() - { - return this.parentGroup; - } - - /** - * Removes the specified contact group from the this group's subgroups. - * @param subgroup the ContactGroupZeroconfImpl subgroup to remove. - */ - public void removeSubGroup(ContactGroupZeroconfImpl subgroup) - { - this.subGroups.remove(subgroup); - subgroup.setParentGroup(null); - } - - /** - * Returns the group that is parent of the specified zeroconfGroup or null - * if no parent was found. - * @param zeroconfGroup the group whose parent we're looking for. - * @return the ContactGroupZeroconfImpl instance that zeroconfGroup - * belongs to or null if no parent was found. - */ - public ContactGroupZeroconfImpl findGroupParent( - ContactGroupZeroconfImpl zeroconfGroup) - { - if ( subGroups.contains(zeroconfGroup) ) - return this; - - Iterator<ContactGroup> subGroupsIter = subgroups(); - while (subGroupsIter.hasNext()) - { - ContactGroupZeroconfImpl subgroup - = (ContactGroupZeroconfImpl) subGroupsIter.next(); - - ContactGroupZeroconfImpl parent - = subgroup.findGroupParent(zeroconfGroup); - - if(parent != null) - return parent; - } - return null; - } - - /** - * Returns the group that is parent of the specified zeroconfContact or - * null if no parent was found. - * - * @param zeroconfContact the contact whose parent we're looking for. - * @return the ContactGroupZeroconfImpl instance that zeroconfContact - * belongs to or <tt>null</tt> if no parent was found. - */ - public ContactGroupZeroconfImpl findContactParent( - ContactZeroconfImpl zeroconfContact) - { - if ( contacts.contains(zeroconfContact) ) - return this; - - Iterator<ContactGroup> subGroupsIter = subgroups(); - while (subGroupsIter.hasNext()) - { - ContactGroupZeroconfImpl subgroup - = (ContactGroupZeroconfImpl) subGroupsIter.next(); - - ContactGroupZeroconfImpl parent - = subgroup.findContactParent(zeroconfContact); - - if(parent != null) - return parent; - } - return null; - } - - - - /** - * Returns the <tt>Contact</tt> with the specified address or identifier. - * - * @param id the addres or identifier of the <tt>Contact</tt> we are - * looking for. - * @return the <tt>Contact</tt> with the specified id or address. - */ - public Contact getContact(String id) - { - Iterator<Contact> contactsIter = contacts(); - while (contactsIter.hasNext()) - { - ContactZeroconfImpl contact = - (ContactZeroconfImpl)contactsIter.next(); - - if (contact.getAddress().equals(id)) - return contact; - - } - return null; - } - - /** - * Returns the subgroup with the specified index. - * - * @param index the index of the <tt>ContactGroup</tt> to retrieve. - * @return the <tt>ContactGroup</tt> with the specified index. - */ - public ContactGroup getGroup(int index) - { - return subGroups.get(index); - } - - /** - * Returns the subgroup with the specified name. - * - * @param groupName the name of the <tt>ContactGroup</tt> to retrieve. - * @return the <tt>ContactGroup</tt> with the specified index. - */ - public ContactGroup getGroup(String groupName) - { - Iterator<ContactGroup> groupsIter = subgroups(); - while (groupsIter.hasNext()) - { - ContactGroupZeroconfImpl contactGroup - = (ContactGroupZeroconfImpl) groupsIter.next(); - if (contactGroup.getGroupName().equals(groupName)) - return contactGroup; - - } - return null; - - } - - /** - * Returns the name of this group. - * - * @return a String containing the name of this group. - */ - public String getGroupName() - { - return this.groupName; - } - - /** - * Sets this group a new name. - * @param newGrpName a String containing the new name of this group. - */ - public void setGroupName(String newGrpName) - { - this.groupName = newGrpName; - } - - /** - * Returns an iterator over the sub groups that this - * <tt>ContactGroup</tt> contains. - * - * @return a java.util.Iterator over the <tt>ContactGroup</tt> children - * of this group (i.e. subgroups). - */ - public Iterator<ContactGroup> subgroups() - { - return subGroups.iterator(); - } - - /** - * Removes the specified contact from this group. - * @param contact the ContactZeroconfImpl to remove from this group - */ - public void removeContact(ContactZeroconfImpl contact) - { - this.contacts.remove(contact); - } - - /** - * Returns the contact with the specified id or null if no such contact - * exists. - * @param id the id of the contact we're looking for. - * @return ContactZeroconfImpl - */ - public ContactZeroconfImpl findContactByID(String id) - { - //first go through the contacts that are direct children. - Iterator<Contact> contactsIter = contacts(); - - while(contactsIter.hasNext()) - { - ContactZeroconfImpl mContact = - (ContactZeroconfImpl)contactsIter.next(); - - if( mContact.getAddress().equals(id) ) - return mContact; - } - - //if we didn't find it here, let's try in the subougroups - Iterator<ContactGroup> groupsIter = subgroups(); - - while( groupsIter.hasNext() ) - { - ContactGroupZeroconfImpl mGroup = - (ContactGroupZeroconfImpl)groupsIter.next(); - - ContactZeroconfImpl mContact = mGroup.findContactByID(id); - - if (mContact != null) - return mContact; - } - - return null; - } - - - /** - * Returns a String representation of this group and the contacts it - * contains (may turn out to be a relatively long string). - * @return a String representing this group and its child contacts. - */ - @Override - public String toString() - { - - StringBuffer buff = new StringBuffer(getGroupName()); - buff.append(".subGroups=" + countSubgroups() + ":\n"); - - Iterator<ContactGroup> subGroups = subgroups(); - while (subGroups.hasNext()) - { - ContactGroupZeroconfImpl group = - (ContactGroupZeroconfImpl)subGroups.next(); - buff.append(group.toString()); - if (subGroups.hasNext()) - buff.append("\n"); - } - - buff.append("\nChildContacts="+countContacts()+":["); - - Iterator<Contact> contacts = contacts(); - while (contacts.hasNext()) - { - ContactZeroconfImpl contact = (ContactZeroconfImpl) contacts.next(); - buff.append(contact.toString()); - if(contacts.hasNext()) - buff.append(", "); - } - return buff.append("]").toString(); - } - - /** - * Specifies whether or not this contact group is being stored by the server. - * Non persistent contact groups are common in the case of simple, - * non-persistent presence operation sets. They could however also be seen - * in persistent presence operation sets when for example we have received - * an event from someone not on our contact list and the contact that we - * associated with that user is placed in a non persistent group. Non - * persistent contact groups are volatile even when coming from a persistent - * presence op. set. They would only exist until the application is closed - * and will not be there next time it is loaded. - * - * @param isPersistent true if the contact group is to be persistent and - * false otherwise. - */ - public void setPersistent(boolean isPersistent) - { - this.isPersistent = isPersistent; - } - - /** - * Determines whether or not this contact group is being stored by the - * server. Non persistent contact groups exist for the sole purpose of - * containing non persistent contacts. - * @return true if the contact group is persistent and false otherwise. - */ - public boolean isPersistent() - { - return isPersistent; - } - - /** - * Returns null as no persistent data is required and the contact address is - * sufficient for restoring the contact. - * <p> - * @return null as no such data is needed. - */ - public String getPersistentData() - { - return null; - } - - /** - * Determines whether or not this contact has been resolved against the - * server. Unresolved contacts are used when initially loading a contact - * list that has been stored in a local file until the presence operation - * set has managed to retrieve all the contact list from the server and has - * properly mapped contacts to their on-line buddies. - * @return true if the contact has been resolved (mapped against a buddy) - * and false otherwise. - */ - public boolean isResolved() - { - return isResolved; - } - - /** - * Makes the group resolved or unresolved. - * - * @param resolved true to make the group resolved; false to - * make it unresolved - */ - public void setResolved(boolean resolved) - { - this.isResolved = resolved; - } - - /** - * Returns a <tt>String</tt> that uniquely represnets the group inside - * the current protocol. The string MUST be persistent (it must not change - * across connections or runs of the application). In many cases (Jabber, - * ICQ) the string may match the name of the group as these protocols - * only allow a single level of contact groups and there is no danger of - * having the same name twice in the same contact list. Other protocols - * (no examples come to mind but that doesn't bother me ;) ) may be - * supporting mutilple levels of grooups so it might be possible for group - * A and group B to both contain groups named C. In such cases the - * implementation must find a way to return a unique identifier in this - * method and this UID should never change for a given group. - * - * @return a String representing this group in a unique and persistent - * way. - */ - public String getUID() - { - return uid; - } - - /** - * Ugly but tricky conversion method. - * @param uid the uid we'd like to get a name from - * @return the name of the group with the specified <tt>uid</tt>. - */ - static String createNameFromUID(String uid) - { - return uid.substring(0, uid.length() - (UID_SUFFIX.length())); - } - - /** - * Indicates whether some other object is "equal to" this one which in terms - * of contact groups translates to having the equal names and matching - * subgroups and child contacts. The resolved status of contactgroups and - * contacts is deliberately ignored so that groups and/or contacts would - * be assumed equal even if it differs. - * <p> - * @param obj the reference object with which to compare. - * @return <code>true</code> if this contact group has the equal child - * contacts and subgroups to those of the <code>obj</code> argument. - */ - @Override - public boolean equals(Object obj) - { - if(obj == null - || !(obj instanceof ContactGroupZeroconfImpl)) - return false; - - ContactGroupZeroconfImpl zeroconfGroup - = (ContactGroupZeroconfImpl)obj; - - if(!zeroconfGroup.getGroupName().equals(getGroupName()) || - !zeroconfGroup.getUID().equals(getUID()) || - zeroconfGroup.countContacts() != countContacts() || - zeroconfGroup.countSubgroups() != countSubgroups()) - return false; - - //traverse child contacts - Iterator<Contact> theirContacts = zeroconfGroup.contacts(); - - while(theirContacts.hasNext()) - { - ContactZeroconfImpl theirContact - = (ContactZeroconfImpl)theirContacts.next(); - - ContactZeroconfImpl ourContact - = (ContactZeroconfImpl)getContact(theirContact.getAddress()); - - if(ourContact == null - || !ourContact.equals(theirContact)) - return false; - } - - //traverse subgroups - Iterator<ContactGroup> theirSubgroups = zeroconfGroup.subgroups(); - - while(theirSubgroups.hasNext()) - { - ContactGroupZeroconfImpl theirSubgroup - = (ContactGroupZeroconfImpl)theirSubgroups.next(); - - ContactGroupZeroconfImpl ourSubgroup - = (ContactGroupZeroconfImpl)getGroup( - theirSubgroup.getGroupName()); - - if(ourSubgroup == null - || !ourSubgroup.equals(theirSubgroup)) - return false; - } - - return true; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/ContactZeroconfImpl.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/ContactZeroconfImpl.java deleted file mode 100644 index c22cf29..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/ContactZeroconfImpl.java +++ /dev/null @@ -1,468 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.net.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -/** - * A simple, straightforward implementation of a zeroconf Contact. Since - * the Zeroconf protocol is not a real one, we simply store all contact details - * in class fields. You should know that when implementing a real protocol, - * the contact implementation would rather encapsulate contact objects from - * the protocol stack and group property values should be returned after - * consulting the encapsulated object. - * - * @author Christian Vincenot - * @author Maxime Catelin - * @author Jonathan Martin - */ -public class ContactZeroconfImpl - extends AbstractContact -{ - private static final Logger logger - = Logger.getLogger(ContactZeroconfImpl.class); - - - /** - * The id of the contact. - */ - private String contactID = null; - - /** - * The ClientThread attached to this contact if we're already chatting - * with him. - */ - private ClientThread thread = null; - - /* - * Type of Client. - */ - /** - * Gaim/Pidgin client type - */ - public static final int GAIM = 1; - /** - * iChat client type - */ - public static final int ICHAT = 2; - /** - * XMPP - XEP-0174 client type - */ - public static final int XMPP = 3; - /** - * Another SIP Communicator client - */ - public static final int SIPCOM = 4; - private int clientType = XMPP; - - - /** - * The provider that created us. - */ - private ProtocolProviderServiceZeroconfImpl parentProvider = null; - - - /* - * The Bonjour Service who discovered this contact. - * TODO: This could probably be avoided using only the - * Protocol Provider. - */ - private BonjourService bonjourService; - - /** - * The group that belong to. - */ - private ContactGroupZeroconfImpl parentGroup = null; - - /** - * The presence status of the contact. - */ - private PresenceStatus presenceStatus = ZeroconfStatusEnum.OFFLINE; - - /** - * Determines whether this contact is persistent, - * i.e. member of the contact list or whether it is here only temporarily. - * Chris: should be set to false here - */ - private boolean isPersistent = false; - - /** - * Determines whether the contact has been resolved (i.e. we have a - * confirmation that it is still on the server contact list). - */ - private boolean isResolved = true; - - /** - * IP Address - */ - private InetAddress ipAddress; - - /** - * Port on which Bonjour is listening. - */ - private int port; - - /** - * Name announced by Bonjour. - */ - private String name; - - /** - * Contact personal message - */ - private String message; - - - /** - * Creates an instance of a meta contact with the specified string used - * as a name and identifier. - * @param bonjourId ID of the contact - * @param bonjourService BonjourService responsible for handling chat with - * this contact - * @param name Display name of this contact - * @param ipAddress IP address of this contact - * @param port Port declared by this contact for direct point-to-point chat - * @param parentProvider the provider that created us. - */ - public ContactZeroconfImpl( - String bonjourId, - ProtocolProviderServiceZeroconfImpl parentProvider, - BonjourService bonjourService, - String name, - InetAddress ipAddress, - int port) - { - this.contactID = bonjourId; - this.parentProvider = parentProvider; - this.bonjourService = bonjourService; - this.name = name; - this.ipAddress = ipAddress; - this.port = port; - bonjourService.addContact(this); - } - - /** - * This method is only called when the contact is added to a new - * <tt>ContactGroupZeroconfImpl</tt> by the - * <tt>ContactGroupZeroconfImpl</tt> itself. - * - * @param newParentGroup the <tt>ContactGroupZeroconfImpl</tt> that is now - * parent of this <tt>ContactZeroconfImpl</tt> - */ - void setParentGroup(ContactGroupZeroconfImpl newParentGroup) - { - this.parentGroup = newParentGroup; - } - - /** - * Return the BonjourService - * @return BonjourService - */ - public BonjourService getBonjourService() - { - return bonjourService; - } - - /** - * Return the ClientThread responsible for handling with this contact - * @return ClientThread corresponding to the chat with this contact or null - * if no chat was started - */ - protected ClientThread getClientThread() - { - return thread; - } - - /** - * Set the ClientThread responsible for handling with this contact - * @param thread ClientThread corresponding to the chat with this contact - * or null if the chat is over - */ - protected void setClientThread(ClientThread thread) - { - this.thread = thread; - } - - /** - * Return the type of client - * @return Type of client used by this contact - */ - public int getClientType() - { - return clientType; - } - - /** - * Sets the type of client - * @param clientType Type of client used by this contact - */ - public void setClientType(int clientType) - { - this.clientType = clientType; - } - - /** - * Returns a String that can be used for identifying the contact. - * - * @return a String id representing and uniquely identifying the contact. - */ - public String getAddress() - { - return contactID; - } - - /** - * Returns a String that could be used by any user interacting modules - * for referring to this contact. - * - * @return a String that can be used for referring to this contact when - * interacting with the user. - */ - public String getDisplayName() - { - return name; - } - - /** - * Returns the IP address declared by this Contact - * @return IP address declared by this Contact - */ - public InetAddress getIpAddress() - { - return ipAddress; - } - - /** - * Returns the TCP port declared by this Contact for direct chat - * @return the TCP port declared by this Contact for direct chat - */ - public int getPort() - { - return port; - } - - - /** - * Returns the status/private message displayed by this contact - * @return the status/private message displayed by this contact - */ - public String getMessage() - { - return message; - } - - /** - * Sets the status/private message displayed by this contact - * @param message the status/private message displayed by this contact - */ - public void setMessage(String message) - { - this.message = message; - } - - - /** - * Returns a byte array containing an image (most often a photo or an - * avatar) that the contact uses as a representation. - * - * @return byte[] an image representing the contact. - */ - public byte[] getImage() - { - return null; - } - - /** - * Returns the status of the contact. - * - * @return always ZeroconfStatusEnum. - */ - public PresenceStatus getPresenceStatus() - { - return this.presenceStatus; - } - - /** - * Sets <tt>zeroconfPresenceStatus</tt> as the PresenceStatus that this - * contact is currently in. - * @param zeroconfPresenceStatus the <tt>ZeroconfPresenceStatus</tt> - * currently valid for this contact. - */ - public void setPresenceStatus(PresenceStatus zeroconfPresenceStatus) - { - this.presenceStatus = zeroconfPresenceStatus; - - if (zeroconfPresenceStatus == ZeroconfStatusEnum.OFFLINE) { - try - { - bonjourService.opSetPersPresence.unsubscribe(this); - } - catch (Exception ex) - { - logger.error(ex); - } - } - } - - /** - * Returns a reference to the protocol provider that created the contact. - * - * @return a refererence to an instance of the ProtocolProviderService - */ - public ProtocolProviderService getProtocolProvider() - { - return parentProvider; - } - - /** - * Determines whether or not this contact represents our own identity. - * - * @return true in case this is a contact that represents ourselves and - * false otherwise. - */ - public boolean isLocal() - { - return false; - } - - /** - * Returns the group that contains this contact. - * @return a reference to the <tt>ContactGroupZeroconfImpl</tt> that - * contains this contact. - */ - public ContactGroup getParentContactGroup() - { - return this.parentGroup; - } - - /** - * Returns a string representation of this contact, containing most of its - * representative details. - * - * @return a string representation of this contact. - */ - @Override - public String toString() - { - StringBuffer buff - = new StringBuffer("ContactZeroconfImpl[ DisplayName=") - .append(getDisplayName()).append("]"); - - return buff.toString(); - } - - /** - * Determines whether or not this contact is being stored by the server. - * Non persistent contacts are common in the case of simple, non-persistent - * presence operation sets. They could however also be seen in persistent - * presence operation sets when for example we have received an event - * from someone not on our contact list. Non persistent contacts are - * volatile even when coming from a persistent presence op. set. They would - * only exist until the application is closed and will not be there next - * time it is loaded. - * - * @return true if the contact is persistent and false otherwise. - */ - public boolean isPersistent() - { - return isPersistent; - } - - /** - * Specifies whether or not this contact is being stored by the server. - * Non persistent contacts are common in the case of simple, non-persistent - * presence operation sets. They could however also be seen in persistent - * presence operation sets when for example we have received an event - * from someone not on our contact list. Non persistent contacts are - * volatile even when coming from a persistent presence op. set. They would - * only exist until the application is closed and will not be there next - * time it is loaded. - * - * @param isPersistent true if the contact is persistent and false - * otherwise. - */ - public void setPersistent(boolean isPersistent) - { - this.isPersistent = isPersistent; - } - - - /** - * Returns null as no persistent data is required and the contact address is - * sufficient for restoring the contact. - * <p> - * @return null as no such data is needed. - */ - public String getPersistentData() - { - return null; - } - - /** - * Determines whether or not this contact has been resolved against the - * server. Unresolved contacts are used when initially loading a contact - * list that has been stored in a local file until the presence operation - * set has managed to retrieve all the contact list from the server and has - * properly mapped contacts to their on-line buddies. - * - * @return true if the contact has been resolved (mapped against a buddy) - * and false otherwise. - */ - public boolean isResolved() - { - return isResolved; - } - - /** - * Makes the contact resolved or unresolved. - * - * @param resolved true to make the contact resolved; false to - * make it unresolved - */ - public void setResolved(boolean resolved) - { - this.isResolved = resolved; - } - - /** - * Returns the persistent presence operation set that this contact belongs - * to. - * - * @return the <tt>OperationSetPersistentPresenceZeroconfImpl</tt> that - * this contact belongs to. - */ - public OperationSetPersistentPresenceZeroconfImpl - getParentPresenceOperationSet() - { - return (OperationSetPersistentPresenceZeroconfImpl)parentProvider - .getOperationSet(OperationSetPersistentPresence.class); - } - - /** - * Return the current status message of this contact. - * - * @return null as the protocol has currently no support of status messages - */ - public String getStatusMessage() - { - return null; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/MessageZeroconfImpl.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/MessageZeroconfImpl.java deleted file mode 100644 index 3621edc..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/MessageZeroconfImpl.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import net.java.sip.communicator.service.protocol.*; - -/** - * Very simple message implementation for the Zeroconf protocol. - * - * @author Christian Vincenot - * @author Maxime Catelin - * @author Jonathan Martin - * @author Lubomir Marinov - */ -public class MessageZeroconfImpl - extends AbstractMessage -{ - - /** - * Message Type. - */ - private int type; - - /** - * Message type indicating that a stream is being created - */ - public static final int STREAM_OPEN = 0x1; - - /** - * Normal chat message - */ - public static final int MESSAGE = 0x2; - - /** - * Typing notification - */ - public static final int TYPING = 0x3; - - /** - * Message indicating that the stream is being closed - */ - public static final int STREAM_CLOSE = 0x4; - - /** - * Message indicating that the previsous message was delivered successfully - */ - public static final int DELIVERED = 0x5; - - /** - * Undefined message - */ - public static final int UNDEF = 0x6; - - /* - * The Baloon Icon color. (we probably won't ever use it) - */ - private int baloonColor = 0x7BB5EE; - - /* - * The Text Color. - */ - private int textColor = 0x000000; - - /* - * The font of the message. - */ - private String textFont = "Helvetica"; - - /* - * The size of the caracters composing the message. - */ - private int textSize = 12; - - /* - * The source contact id announced in the message. TODO: Could be set & - * checked to identify more precisely the contact in case several users - * would be sharing the same IP. - */ - private String contactID; - - /** - * Creates a message instance according to the specified parameters. - * - * @param content the message body - * @param contentEncoding message encoding or null for UTF8 - * @param contentType of the message - * @param type Type of message - */ - public MessageZeroconfImpl(String content, String contentEncoding, - String contentType, int type) - { - super(content, contentType, contentEncoding, null); - - this.type = type; - } - - /** - * Creates a message instance according to the specified parameters. - * - * @param type Type of message - * @param content the message body - * @param contentEncoding message encoding or null for UTF8 - */ - public MessageZeroconfImpl(String content, String contentEncoding, int type) - { - this(content, contentEncoding, - OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE, type); - } - - /** - * Returns the type of message. Always text/plain for Zeroconf, so null. - * - * @return null - */ - public int getType() - { - return type; - } - - /** - * Gets the baloon color declared in messages sent by iChat-like clients - * - * @return baloon color - */ - public int getBaloonColor() - { - return baloonColor; - } - - /** - * Sets the baloon color declared in messages sent by iChat-like clients - * - * @param baloonColor baloon color - */ - public void setBaloonColor(int baloonColor) - { - this.baloonColor = baloonColor; - } - - /** - * Returns the text color - * - * @return Text color - */ - public int getTextColor() - { - return textColor; - } - - /** - * Sets the text color - * - * @param textColor Text color - */ - public void setTextColor(int textColor) - { - this.textColor = textColor; - } - - /** - * Returns the text font - * - * @return Text font - */ - public String getTextFont() - { - return textFont; - } - - /** - * Sets the text color - * - * @param textFont Text font - */ - public void setTextFont(String textFont) - { - this.textFont = textFont; - } - - /** - * Returns the text size - * - * @return Text size - */ - public int getTextSize() - { - return textSize; - } - - /** - * Sets the text size - * - * @param textSize Text size - */ - public void setTextSize(int textSize) - { - this.textSize = textSize; - } - - /** - * Returns the contact's ID - * - * @return String representing the contact's ID - */ - public String getContactID() - { - return contactID; - } - - /** - * Sets the contact's ID - * - * @param contactID String representing the contact's ID - */ - public void setContactID(String contactID) - { - this.contactID = contactID; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/OperationSetBasicInstantMessagingZeroconfImpl.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/OperationSetBasicInstantMessagingZeroconfImpl.java deleted file mode 100644 index d49118b..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/OperationSetBasicInstantMessagingZeroconfImpl.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.io.*; -import java.net.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -/** - * Instant messaging functionalities for the Zeroconf protocol. - * - * @author Christian Vincenot - * - */ -public class OperationSetBasicInstantMessagingZeroconfImpl - extends AbstractOperationSetBasicInstantMessaging -{ - private static final Logger logger - = Logger.getLogger(OperationSetBasicInstantMessagingZeroconfImpl.class); - - /** - * The currently valid persistent presence operation set.. - */ - private final OperationSetPersistentPresenceZeroconfImpl opSetPersPresence; - - /** - * The protocol provider that created us. - */ - private final ProtocolProviderServiceZeroconfImpl parentProvider; - - /** - * Creates an instance of this operation set keeping a reference to the - * parent protocol provider and presence operation set. - * - * @param provider The provider instance that creates us. - * @param opSetPersPresence the currently valid - * <tt>OperationSetPersistentPresenceZeroconfImpl</tt> instance. - */ - public OperationSetBasicInstantMessagingZeroconfImpl( - ProtocolProviderServiceZeroconfImpl provider, - OperationSetPersistentPresenceZeroconfImpl opSetPersPresence) - { - this.opSetPersPresence = opSetPersPresence; - this.parentProvider = provider; - } - - @Override - public Message createMessage(String content, String contentType, - String encoding, String subject) - { - return new MessageZeroconfImpl(content, encoding, contentType, - MessageZeroconfImpl.MESSAGE); - } - - /** - * Sends the <tt>message</tt> to the destination indicated by the - * <tt>to</tt> contact. - * - * @param to the <tt>Contact</tt> to send <tt>message</tt> to - * @param message the <tt>Message</tt> to send. - * @throws IllegalStateException if the underlying Zeroconf stack is not - * registered and initialized. - * @throws IllegalArgumentException if <tt>to</tt> is not an instance - * belonging to the underlying implementation. - */ - public void sendInstantMessage(Contact to, Message message) throws - IllegalStateException, IllegalArgumentException - { - if( !(to instanceof ContactZeroconfImpl) ) - throw new IllegalArgumentException( - "The specified contact is not a Zeroconf contact." - + to); - - MessageZeroconfImpl msg = - (MessageZeroconfImpl)createMessage(message.getContent()); - - deliverMessage(msg, (ContactZeroconfImpl)to); - } - - /** - * In case the to the <tt>to</tt> Contact corresponds to another zeroconf - * protocol provider registered with SIP Communicator, we deliver - * the message to them, in case the <tt>to</tt> Contact represents us, we - * fire a <tt>MessageReceivedEvent</tt>, and if <tt>to</tt> is simply - * a contact in our contact list, then we simply echo the message. - * - * @param message the <tt>Message</tt> the message to deliver. - * @param to the <tt>Contact</tt> that we should deliver the message to. - */ - private void deliverMessage(Message message, ContactZeroconfImpl to) - { - ClientThread thread = to.getClientThread(); - try - { - if (thread == null) - { - Socket sock; - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF: Creating a chat connexion to " - +to.getIpAddress()+":"+to.getPort()); - sock = new Socket(to.getIpAddress(), to.getPort()); - thread = new ClientThread(sock, to.getBonjourService()); - thread.setStreamOpen(); - thread.setContact(to); - to.setClientThread(thread); - thread.sendHello(); - if (to.getClientType() == ContactZeroconfImpl.GAIM) - { - try - { - Thread.sleep(300); - } - catch (InterruptedException ex) - { - logger.error(ex); - } - } - } - - //System.out.println("ZEROCONF: Message content => "+ - //message.getContent()); - thread.sendMessage((MessageZeroconfImpl) message); - - fireMessageDelivered(message, to); - } - catch (IOException ex) - { - logger.error(ex); - } - } - - /** - * Notifies all registered message listeners that a message has been - * received. - * - * @param message the <tt>Message</tt> that has been received. - * @param from the <tt>Contact</tt> that <tt>message</tt> was received from. - */ - @Override - public void fireMessageReceived(Message message, Contact from) - { - super.fireMessageReceived(message, from); - } - - /** - * Determines whether the protocol provider (or the protocol itself) support - * sending and receiving offline messages. Most often this method would - * return true for protocols that support offline messages and false for - * those that don't. It is however possible for a protocol to support these - * messages and yet have a particular account that does not (i.e. feature - * not enabled on the protocol server). In cases like this it is possible - * for this method to return true even when offline messaging is not - * supported, and then have the sendMessage method throw an - * OperationFailedException with code - OFFLINE_MESSAGES_NOT_SUPPORTED. - * - * @return <tt>true</tt> if the protocol supports offline messages and - * <tt>false</tt> otherwise. - */ - public boolean isOfflineMessagingSupported() - { - return true; - } - - /** - * Determines whether the protocol supports the supplied content type - * - * @param contentType the type we want to check - * @return <tt>true</tt> if the protocol supports it and - * <tt>false</tt> otherwise. - */ - public boolean isContentTypeSupported(String contentType) - { - return contentType.equals(DEFAULT_MIME_TYPE); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/OperationSetPersistentPresenceZeroconfImpl.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/OperationSetPersistentPresenceZeroconfImpl.java deleted file mode 100644 index 412512e..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/OperationSetPersistentPresenceZeroconfImpl.java +++ /dev/null @@ -1,852 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.net.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; - -import org.osgi.framework.*; - -/** - * A Zeroconf implementation of a persistent presence operation set. In order - * to simulate server persistence, this operation set would simply accept all - * unresolved contacts and resolve them immediately. A real world protocol - * implementation would save it on a server using methods provided by the - * protocol stack. - * - * @author Christian Vincenot - * @author Maxime Catelin - * @author Jonathan Martin - */ -public class OperationSetPersistentPresenceZeroconfImpl - extends AbstractOperationSetPersistentPresence<ProtocolProviderServiceZeroconfImpl> -{ - private static final Logger logger = - Logger.getLogger(OperationSetPersistentPresenceZeroconfImpl.class); - - /** - * The root of the zeroconf contact list. - */ - private ContactGroupZeroconfImpl contactListRoot = null; - - /** - * The currently active status message. - */ - private String statusMessage = "The truth is out there..."; - - /** - * Our default presence status. - */ - private PresenceStatus presenceStatus = ZeroconfStatusEnum.OFFLINE; - - /** - * The <tt>AuthorizationHandler</tt> instance that we'd have to transmit - * authorization requests to for approval. - */ - private AuthorizationHandler authorizationHandler = null; - - /** - * Creates an instance of this operation set keeping a reference to the - * specified parent <tt>provider</tt>. - * @param provider the ProtocolProviderServiceZeroconfImpl instance that - * created us. - */ - public OperationSetPersistentPresenceZeroconfImpl( - ProtocolProviderServiceZeroconfImpl provider) - { - super(provider); - - contactListRoot = new ContactGroupZeroconfImpl("RootGroup", provider); - - //add our unregistration listener - parentProvider.addRegistrationStateChangeListener( - new UnregistrationListener()); - } - - /** - * Creates a group with the specified name and parent in the server - * stored contact list. - * - * @param parent the group where the new group should be created - * @param groupName the name of the new group to create. - */ - public void createServerStoredContactGroup(ContactGroup parent, - String groupName) - { - ContactGroupZeroconfImpl newGroup - = new ContactGroupZeroconfImpl(groupName, parentProvider); - - ((ContactGroupZeroconfImpl)parent).addSubgroup(newGroup); - - this.fireServerStoredGroupEvent( - newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - - /** - * A Zeroconf Provider method to use for fast filling of a contact list. - * - * @param contactGroup the group to add - */ - public void addZeroconfGroup(ContactGroupZeroconfImpl contactGroup) - { - contactListRoot.addSubgroup(contactGroup); - } - - /** - * A Zeroconf Provider method to use for fast filling of a contact list. - * This method would add both the group and fire an event. - * - * @param parent the group where <tt>contactGroup</tt> should be added. - * @param contactGroup the group to add - */ - public void addZeroconfGroupAndFireEvent( - ContactGroupZeroconfImpl parent - , ContactGroupZeroconfImpl contactGroup) - { - parent.addSubgroup(contactGroup); - - this.fireServerStoredGroupEvent( - contactGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - - - /** - * Returns a reference to the contact with the specified ID in case we - * have a subscription for it and null otherwise/ - * - * @param contactID a String identifier of the contact which we're - * seeking a reference of. - * @return a reference to the Contact with the specified - * <tt>contactID</tt> or null if we don't have a subscription for the - * that identifier. - */ - public Contact findContactByID(String contactID) - { - return contactListRoot.findContactByID(contactID); - } - - /** - * Sets the specified status message. - * @param statusMessage a String containing the new status message. - */ - public void setStatusMessage(String statusMessage) - { - this.statusMessage = statusMessage; - } - - /** - * Returns the status message that was last set through - * setCurrentStatusMessage. - * - * @return the last status message that we have requested and the aim - * server has confirmed. - */ - public String getCurrentStatusMessage() - { - return statusMessage; - } - - /** - * Returns a PresenceStatus instance representing the state this provider - * is currently in. - * - * @return the PresenceStatus last published by this provider. - */ - public PresenceStatus getPresenceStatus() - { - return presenceStatus; - } - - /** - * Returns the root group of the server stored contact list. - * - * @return the root ContactGroup for the ContactList stored by this - * service. - */ - public ContactGroup getServerStoredContactListRoot() - { - return contactListRoot; - } - - /** - * Returns the set of PresenceStatus objects that a user of this service - * may request the provider to enter. - * - * @return Iterator a PresenceStatus array containing "enterable" status - * instances. - */ - public Iterator<PresenceStatus> getSupportedStatusSet() - { - return ZeroconfStatusEnum.supportedStatusSet(); - } - - /** - * Removes the specified contact from its current parent and places it - * under <tt>newParent</tt>. - * - * @param contactToMove the <tt>Contact</tt> to move - * @param newParent the <tt>ContactGroup</tt> where <tt>Contact</tt> - * would be placed. - */ - public void moveContactToGroup(Contact contactToMove, - ContactGroup newParent) - { - ContactZeroconfImpl zeroconfContact - = (ContactZeroconfImpl)contactToMove; - - ContactGroupZeroconfImpl parentZeroconfGroup - = findContactParent(zeroconfContact); - - parentZeroconfGroup.removeContact(zeroconfContact); - - //if this is a volatile contact then we haven't really subscribed to - //them so we'd need to do so here - if(!zeroconfContact.isPersistent()) - { - //first tell everyone that the volatile contact was removed - fireSubscriptionEvent(zeroconfContact - , parentZeroconfGroup - , SubscriptionEvent.SUBSCRIPTION_REMOVED); - - try - { - //now subscribe - this.subscribe(newParent, contactToMove.getAddress()); - - //now tell everyone that we've added the contact - fireSubscriptionEvent(zeroconfContact - , newParent - , SubscriptionEvent.SUBSCRIPTION_CREATED); - } - catch (Exception ex) - { - logger.error("Failed to move contact " - + zeroconfContact.getAddress() - , ex); - } - } - else - { - ( (ContactGroupZeroconfImpl) newParent) - .addContact(zeroconfContact); - - fireSubscriptionMovedEvent(contactToMove - , parentZeroconfGroup - , newParent); - } - } - - /** - * Requests the provider to enter into a status corresponding to the - * specified paramters. - * - * @param status the PresenceStatus as returned by - * getRequestableStatusSet - * @param statusMessage the message that should be set as the reason to - * enter that status - * @throws IllegalArgumentException if the status requested is not a - * valid PresenceStatus supported by this provider. - * @throws IllegalStateException if the provider is not currently - * registered. - * @throws OperationFailedException with code NETWORK_FAILURE if - * publishing the status fails due to a network error. - */ - public void publishPresenceStatus(PresenceStatus status, - String statusMessage) - throws IllegalArgumentException, - IllegalStateException, - OperationFailedException - { - PresenceStatus oldPresenceStatus = this.presenceStatus; - this.presenceStatus = status; - this.statusMessage = statusMessage; - - //ICI: changer le statut du plugin Zeroconf!! - parentProvider.getBonjourService().changeStatus(status); - - this.fireProviderStatusChangeEvent(oldPresenceStatus); - - } - - /** - * Get the PresenceStatus for a particular contact. - * - * @param contactIdentifier the identifier of the contact whose status - * we're interested in. - * @return PresenceStatus the <tt>PresenceStatus</tt> of the specified - * <tt>contact</tt> - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * retrieving the status fails due to errors experienced during - * network communication - */ - public PresenceStatus queryContactStatus(String contactIdentifier) - throws IllegalArgumentException, - IllegalStateException, - OperationFailedException - { - return findContactByID(contactIdentifier).getPresenceStatus(); - } - - /** - * Sets the presence status of <tt>contact</tt> to <tt>newStatus</tt>. - * - * @param contact the <tt>ContactZeroconfImpl</tt> whose status we'd like - * to set. - * @param newStatus the new status we'd like to set to <tt>contact</tt>. - */ - public void changePresenceStatusForContact(ContactZeroconfImpl contact, - PresenceStatus newStatus) - { - PresenceStatus oldStatus = contact.getPresenceStatus(); - contact.setPresenceStatus(newStatus); - - fireContactPresenceStatusChangeEvent( - contact, findContactParent(contact), oldStatus); - } - - /** - * Sets the presence status of all <tt>contact</tt>s in our contact list - * (except those that correspond to another provider registered with SC) - * to <tt>newStatus</tt>. - * - * @param newStatus the new status we'd like to set to <tt>contact</tt>. - * @param parent the group in which we'd have to update the status of all - * direct and indirect child contacts. - */ - protected void changePresenceStatusForAllContacts(ContactGroup parent, - PresenceStatus newStatus) - { - //first set the status for contacts in this group - Iterator<Contact> childContacts = parent.contacts(); - - while(childContacts.hasNext()) - { - ContactZeroconfImpl contact - = (ContactZeroconfImpl)childContacts.next(); - - if(findProviderForZeroconfUserID(contact.getAddress()) != null) - { - //this is a contact corresponding to another SIP Communicator - //provider so we won't change it's status here. - continue; - } - PresenceStatus oldStatus = contact.getPresenceStatus(); - contact.setPresenceStatus(newStatus); - - fireContactPresenceStatusChangeEvent( - contact, parent, oldStatus); - } - - //now call this method recursively for all subgroups - Iterator<ContactGroup> subgroups = parent.subgroups(); - - while(subgroups.hasNext()) - { - ContactGroup subgroup = subgroups.next(); - changePresenceStatusForAllContacts(subgroup, newStatus); - } - } - - /** - * Returns the group that is parent of the specified zeroconfGroup or null - * if no parent was found. - * @param zeroconfGroup the group whose parent we're looking for. - * @return the ContactGroupZeroconfImpl instance that zeroconfGroup - * belongs to or null if no parent was found. - */ - public ContactGroupZeroconfImpl findGroupParent( - ContactGroupZeroconfImpl zeroconfGroup) - { - return contactListRoot.findGroupParent(zeroconfGroup); - } - - /** - * Returns the group that is parent of the specified zeroconfContact or - * null if no parent was found. - * @param zeroconfContact the contact whose parent we're looking for. - * @return the ContactGroupZeroconfImpl instance that zeroconfContact - * belongs to or null if no parent was found. - */ - public ContactGroupZeroconfImpl findContactParent( - ContactZeroconfImpl zeroconfContact) - { - return (ContactGroupZeroconfImpl)zeroconfContact - .getParentContactGroup(); - } - - - /** - * Removes the specified group from the server stored contact list. - * - * @param group the group to remove. - * - * @throws IllegalArgumentException if <tt>group</tt> was not found in this - * protocol's contact list. - */ - public void removeServerStoredContactGroup(ContactGroup group) - throws IllegalArgumentException - { - ContactGroupZeroconfImpl zeroconfGroup - = (ContactGroupZeroconfImpl)group; - - ContactGroupZeroconfImpl parent = findGroupParent(zeroconfGroup); - - if(parent == null){ - throw new IllegalArgumentException( - "group " + group - + " does not seem to belong to this protocol's contact list."); - } - - parent.removeSubGroup(zeroconfGroup); - - this.fireServerStoredGroupEvent( - zeroconfGroup, ServerStoredGroupEvent.GROUP_REMOVED_EVENT); - } - - /** - * Renames the specified group from the server stored contact list. - * - * @param group the group to rename. - * @param newName the new name of the group. - */ - public void renameServerStoredContactGroup(ContactGroup group, - String newName) - { - ((ContactGroupZeroconfImpl)group).setGroupName(newName); - - this.fireServerStoredGroupEvent( - group, - ServerStoredGroupEvent.GROUP_RENAMED_EVENT); - } - - /** - * Handler for incoming authorization requests. - * - * @param handler an instance of an AuthorizationHandler for - * authorization requests coming from other users requesting - * permission add us to their contact list. - */ - public void setAuthorizationHandler(AuthorizationHandler handler) - { - this.authorizationHandler = handler; - } - - /** - * Persistently adds a subscription for the presence status of the - * contact corresponding to the specified contactIdentifier and indicates - * that it should be added to the specified group of the server stored - * contact list. - * - * @param parent the parent group of the server stored contact list - * where the contact should be added. <p> - * @param contactIdentifier the contact whose status updates we are - * subscribing for. - * @throws IllegalArgumentException if <tt>contact</tt> or - * <tt>parent</tt> are not a contact known to the underlying protocol - * provider. - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * subscribing fails due to errors experienced during network - * communication - */ - public void subscribe(ContactGroup parent, String contactIdentifier) - throws IllegalArgumentException, - IllegalStateException, - OperationFailedException - { - /* ContactZeroconfImpl contact = new ContactZeroconfImpl( - contactIdentifier, - parentProvider, - null, null, null, 0); - - ((ContactGroupZeroconfImpl)parent).addContact(contact); - - fireSubscriptionEvent(contact, - parent, - SubscriptionEvent.SUBSCRIPTION_CREATED); - //if the newly added contact corresponds to another provider - set their - //status accordingly - ProtocolProviderServiceZeroconfImpl gibProvider - = findProviderForZeroconfUserID(contactIdentifier); - if(gibProvider != null) - { - OperationSetPersistentPresence opSetPresence - = (OperationSetPersistentPresence)gibProvider.getOperationSet( - OperationSetPersistentPresence.class); - - changePresenceStatusForContact( - contact - , (ZeroconfStatusEnum)opSetPresence.getPresenceStatus()); - } - else - { - //otherwise - since we are not a real protocol, we set the contact - //presence status ourselves - changePresenceStatusForContact(contact, getPresenceStatus()); - } - - //notify presence listeners for the status change. - fireContactPresenceStatusChangeEvent(contact - , parent - , ZeroconfStatusEnum.OFFLINE); - */} - - - - /** - * Adds a subscription for the presence status of the contact - * corresponding to the specified contactIdentifier. - * - * @param contactIdentifier the identifier of the contact whose status - * updates we are subscribing for. <p> - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * subscribing fails due to errors experienced during network - * communication - */ - public void subscribe(String contactIdentifier) - throws IllegalArgumentException, - IllegalStateException, - OperationFailedException - { - subscribe(contactListRoot, contactIdentifier); - } - - /** - * Removes a subscription for the presence status of the specified - * contact. - * - * @param contact the contact whose status updates we are unsubscribing - * from. - * @throws IllegalArgumentException if <tt>contact</tt> is not a contact - * known to the underlying protocol provider - * @throws IllegalStateException if the underlying protocol provider is - * not registered/signed on a public service. - * @throws OperationFailedException with code NETWORK_FAILURE if - * unsubscribing fails due to errors experienced during network - * communication - */ - public void unsubscribe(Contact contact) - throws IllegalArgumentException, - IllegalStateException, - OperationFailedException - { - String name = contact.getAddress(); - - ContactGroupZeroconfImpl parentGroup - = (ContactGroupZeroconfImpl)((ContactZeroconfImpl)contact) - .getParentContactGroup(); - - //parentGroup.removeContact((ContactZeroconfImpl)contact); - - BonjourService service = - ((ProtocolProviderServiceZeroconfImpl)contact.getProtocolProvider()) - .getBonjourService(); - //TODO: better check with IP - service.removeContact(name,null); - - fireSubscriptionEvent(contact, - ((ContactZeroconfImpl)contact).getParentContactGroup() - , SubscriptionEvent.SUBSCRIPTION_REMOVED); - } - - /** - * Creates and returns a unresolved contact from the specified - * <tt>address</tt> and <tt>persistentData</tt>. The method will not try - * to establish a network connection and resolve the newly created Contact - * against the server. The protocol provider may will later try and resolve - * the contact. When this happens the corresponding event would notify - * interested subscription listeners. - * - * @param address an identifier of the contact that we'll be creating. - * @param persistentData a String returned Contact's getPersistentData() - * method during a previous run and that has been persistently stored - * locally. - * @return the unresolved <tt>Contact</tt> created from the specified - * <tt>address</tt> and <tt>persistentData</tt> - */ - public Contact createUnresolvedContact(String address, - String persistentData) - { - return createUnresolvedContact(address - , persistentData - , getServerStoredContactListRoot()); - } - - /** - * Creates and returns a unresolved contact from the specified - * <tt>address</tt> and <tt>persistentData</tt>. The method will not try - * to establish a network connection and resolve the newly created Contact - * against the server. The protocol provider may will later try and resolve - * the contact. When this happens the corresponding event would notify - * interested subscription listeners. - * - * @param address an identifier of the contact that we'll be creating. - * @param persistentData a String returned Contact's getPersistentData() - * method during a previous run and that has been persistently stored - * locally. - * @param parent the group where the unresolved contact is - * supposed to belong to. - * - * @return the unresolved <tt>Contact</tt> created from the specified - * <tt>address</tt> and <tt>persistentData</tt> - */ - public Contact createUnresolvedContact(String address, - String persistentData, - ContactGroup parent) - { - return null; - } - - /** - * Looks for a zeroconf protocol provider registered for a user id matching - * <tt>zeroconfUserID</tt>. - * - * @param zeroconfUserID the ID of the Zeroconf user whose corresponding - * protocol provider we'd like to find. - * @return ProtocolProviderServiceZeroconfImpl a zeroconf protocol - * provider registered for a user with id <tt>zeroconfUserID</tt> or null - * if there is no such protocol provider. - */ - public ProtocolProviderServiceZeroconfImpl - findProviderForZeroconfUserID(String zeroconfUserID) - { - BundleContext bc = ZeroconfActivator.getBundleContext(); - - String osgiQuery = "(&" + - "(" + ProtocolProviderFactory.PROTOCOL + - "=" + ProtocolNames.ZEROCONF + ")" + - "(" + ProtocolProviderFactory.USER_ID + - "=" + zeroconfUserID + "))"; - - ServiceReference[] refs = null; - try - { - refs = bc.getServiceReferences( - ProtocolProviderService.class.getName(), - osgiQuery); - } - catch (InvalidSyntaxException ex) - { - logger.error("Failed to execute the following osgi query: " - + osgiQuery - , ex); - } - - if(refs != null && refs.length > 0) - { - return (ProtocolProviderServiceZeroconfImpl)bc.getService(refs[0]); - } - - return null; - } - - /** - * Creates and returns a unresolved contact group from the specified - * <tt>address</tt> and <tt>persistentData</tt>. The method will not try - * to establish a network connection and resolve the newly created - * <tt>ContactGroup</tt> against the server or the contact itself. The - * protocol provider will later resolve the contact group. When this happens - * the corresponding event would notify interested subscription listeners. - * - * @param groupUID an identifier, returned by ContactGroup's getGroupUID, - * that the protocol provider may use in order to create the group. - * @param persistentData a String returned ContactGroups's - * getPersistentData() method during a previous run and that has been - * persistently stored locally. - * @param parentGroup the group under which the new group is to be created - * or null if this is group directly underneath the root. - * @return the unresolved <tt>ContactGroup</tt> created from the specified - * <tt>uid</tt> and <tt>persistentData</tt> - */ - public ContactGroup createUnresolvedContactGroup(String groupUID, - String persistentData, ContactGroup parentGroup) - { - ContactGroupZeroconfImpl newGroup - = new ContactGroupZeroconfImpl( - ContactGroupZeroconfImpl.createNameFromUID(groupUID) - , parentProvider); - newGroup.setResolved(false); - - //if parent is null then we're adding under root. - if(parentGroup == null) - parentGroup = getServerStoredContactListRoot(); - - ((ContactGroupZeroconfImpl)parentGroup).addSubgroup(newGroup); - - this.fireServerStoredGroupEvent( - newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT); - - return newGroup; - } - - private class UnregistrationListener - implements RegistrationStateChangeListener - { - /** - * The method is called by a ProtocolProvider implementation whenver - * a change in the registration state of the corresponding provider had - * occurred. The method is particularly interested in events stating - * that the zeroconf provider has unregistered so that it would fire - * status change events for all contacts in our buddy list. - * - * @param evt ProviderStatusChangeEvent the event describing the status - * change. - */ - public void registrationStateChanged(RegistrationStateChangeEvent evt) - { - - if (logger.isDebugEnabled()) - logger.debug("ZEROCONF : The Zeroconf provider changed state from: " - + evt.getOldState() - + " to: " + evt.getNewState()); - - //send event notifications saying that all our buddies are - //offline. The Zeroconf protocol does not implement top level buddies - //nor subgroups for top level groups so a simple nested loop - //would be enough. - Iterator<ContactGroup> groupsIter - = getServerStoredContactListRoot().subgroups(); - while (groupsIter.hasNext()) - { - ContactGroup group = groupsIter.next(); - Iterator<Contact> contactsIter = group.contacts(); - - while (contactsIter.hasNext()) - { - ContactZeroconfImpl contact - = (ContactZeroconfImpl) contactsIter.next(); - - PresenceStatus oldContactStatus - = contact.getPresenceStatus(); - - /* We set contacts to OFFLINE and send an event so that external listeners - * can be aware that the contacts are reachable anymore. Dunno if that's - * a good idea. Can be erased if not. Contacts clean is directly done by the - * contact status change handler. - */ - if (!oldContactStatus.isOnline()) - { - //contact.setPresenceStatus(ZeroconfStatusEnum.OFFLINE); - fireContactPresenceStatusChangeEvent( - contact - , contact.getParentContactGroup() - , oldContactStatus); - } - } - } - } - } - - /** - * Returns the volatile group or null if this group has not yet been - * created. - * - * @return a volatile group existing in our contact list or <tt>null</tt> - * if such a group has not yet been created. - */ - public ContactGroupZeroconfImpl getNonPersistentGroup() - { - for (int i = 0; - i < getServerStoredContactListRoot().countSubgroups(); - i++) - { - ContactGroupZeroconfImpl gr = - (ContactGroupZeroconfImpl)getServerStoredContactListRoot() - .getGroup(i); - - if(!gr.isPersistent()) - return gr; - } - - return null; - } - - - /** - * Creates a non persistent contact for the specified address. This would - * also create (if necessary) a group for volatile contacts that would not - * be added to the server stored contact list. This method would have no - * effect on the server stored contact list. - * @return the newly created volatile contact. - * @param bonjourService BonjourService responsible for the chat with this contact - * @param name Display name of the contact - * @param ip IP address of the contact - * @param port Port declared by the contact for direct chat - * @param contactAddress the address of the volatile contact we'd like to - * create. - */ - public ContactZeroconfImpl createVolatileContact(String contactAddress, - BonjourService bonjourService, - String name, - InetAddress ip, - int port) - { - //First create the new volatile contact; - ContactZeroconfImpl newVolatileContact - = new ContactZeroconfImpl(contactAddress, - this.parentProvider, bonjourService, name, ip, port); - newVolatileContact.setPersistent(false); - - - //Check whether a volatile group already exists and if not create - //one - ContactGroupZeroconfImpl theVolatileGroup = getNonPersistentGroup(); - - - //if the parent volatile group is null then we create it - if (theVolatileGroup == null) - { - theVolatileGroup = new ContactGroupZeroconfImpl( - "Bonjour" - , parentProvider); - theVolatileGroup.setResolved(false); - theVolatileGroup.setPersistent(false); - - this.contactListRoot.addSubgroup(theVolatileGroup); - - fireServerStoredGroupEvent(theVolatileGroup - , ServerStoredGroupEvent.GROUP_CREATED_EVENT); - } - - //now add the volatile contact instide it - theVolatileGroup.addContact(newVolatileContact); - fireSubscriptionEvent(newVolatileContact - , theVolatileGroup - , SubscriptionEvent.SUBSCRIPTION_CREATED); - - return newVolatileContact; - } - - public Contact getLocalContact() - { - return null; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/OperationSetTypingNotificationsZeroconfImpl.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/OperationSetTypingNotificationsZeroconfImpl.java deleted file mode 100644 index db397b8..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/OperationSetTypingNotificationsZeroconfImpl.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import net.java.sip.communicator.service.protocol.*; - -/** - * Implements typing notifications for the Zeroconf protocol. The operation - * set would simply mirror all outgoing typing notifications and make them - * appear as incoming events generated by the contact that we are currently - * writing a message to. - * - * @author Christian Vincenot - * @author Maxime Catelin - * @author Jonathan Martin - */ -public class OperationSetTypingNotificationsZeroconfImpl - extends AbstractOperationSetTypingNotifications<ProtocolProviderServiceZeroconfImpl> -{ - - /** - * Creates a new instance of this operation set and keeps the parent - * provider as a reference. - * - * @param provider a ref to the <tt>ProtocolProviderServiceImpl</tt> - * that created us and that we'll use for retrieving the underlying aim - * connection. - */ - OperationSetTypingNotificationsZeroconfImpl( - ProtocolProviderServiceZeroconfImpl provider) - { - super(provider); - } - - /** - * Sends a notification to <tt>notifiedContatct</tt> that we have entered - * <tt>typingState</tt>. - * - * @param notifiedContact the <tt>Contact</tt> to notify - * @param typingState the typing state that we have entered. - * - * @throws java.lang.IllegalStateException if the underlying stack is - * not registered and initialized. - * @throws java.lang.IllegalArgumentException if <tt>notifiedContact</tt> is - * not an instance belonging to the underlying implementation. - */ - public void sendTypingNotification(Contact notifiedContact, int typingState) - throws IllegalStateException, IllegalArgumentException - { - if( !(notifiedContact instanceof ContactZeroconfImpl) ) - throw new IllegalArgumentException( - "The specified contact is not a Zeroconf contact." - + notifiedContact); - - ContactZeroconfImpl to = (ContactZeroconfImpl)notifiedContact; - - ClientThread thread = to.getClientThread(); - if (thread == null) return;/*throw new IllegalStateException( - "No communication channel opened to chat with this contact");*/ - - if (typingState != STATE_TYPING) - return; - - MessageZeroconfImpl message = - new MessageZeroconfImpl("",null, MessageZeroconfImpl.TYPING); - thread.sendMessage(message); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/ProtocolIconZeroconfImpl.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/ProtocolIconZeroconfImpl.java deleted file mode 100644 index 4c6ce07..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/ProtocolIconZeroconfImpl.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.io.*; -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -import org.jitsi.service.resources.*; -import org.osgi.framework.*; - -/** - * Represents the Zeroconf protocol icon. Implements the <tt>ProtocolIcon</tt> - * interface in order to provide a Zeroconf logo image in two different sizes. - * - * @author Christian Vincenot - * @author Jonathan Martin - */ -public class ProtocolIconZeroconfImpl - implements ProtocolIcon -{ - private static Logger logger - = Logger.getLogger(ProtocolIconZeroconfImpl.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.zeroconf.ZEROCONF_16x16")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_32x32, - getImageInBytes("service.protocol.zeroconf.ZEROCONF_32x32")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_48x48, - getImageInBytes("service.protocol.zeroconf.ZEROCONF_48x48")); - - iconsTable.put(ProtocolIcon.ICON_SIZE_64x64, - getImageInBytes("service.protocol.zeroconf.ZEROCONF_64x64")); - } - - /** - * A hash table containing 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.zeroconf.ZEROCONF_16x16")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_32x32, - getResources().getImagePath( - "service.protocol.zeroconf.ZEROCONF_32x32")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_48x48, - getResources().getImagePath( - "service.protocol.zeroconf.ZEROCONF_48x48")); - - iconPathsTable.put(ProtocolIcon.ICON_SIZE_64x64, - getResources().getImagePath( - "service.protocol.zeroconf.ZEROCONF_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(); - } - - /** - * Returns TRUE if a icon with the given size is supported, FALSE-otherwise. - * @param iconSize Icon size - * @return True if this 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 - * @return Icon image - */ - 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("zeroconfOnlineIcon"); - } - - /** - * 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. - */ - public 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; - } - - /** - * Returns the <tt>ResourceManagementService</tt>. - * - * @return the <tt>ResourceManagementService</tt> - */ - public static ResourceManagementService getResources() - { - if (resourcesService == null) - { - ServiceReference serviceReference = ZeroconfActivator.bundleContext - .getServiceReference(ResourceManagementService.class.getName()); - - if(serviceReference == null) - return null; - - resourcesService - = (ResourceManagementService)ZeroconfActivator.bundleContext - .getService(serviceReference); - } - - return resourcesService; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/ProtocolProviderFactoryZeroconfImpl.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/ProtocolProviderFactoryZeroconfImpl.java deleted file mode 100644 index e68033f..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/ProtocolProviderFactoryZeroconfImpl.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -import org.osgi.framework.*; - -/** - * The Zeroconf protocol provider factory creates instances of the Zeroconf - * protocol provider service. One Service instance corresponds to one account. - * - * - * @author Christian Vincenot - * @author Maxime Catelin - */ -public class ProtocolProviderFactoryZeroconfImpl - extends ProtocolProviderFactory -{ - - /** - * Creates an instance of the ProtocolProviderFactoryZeroconfImpl. - */ - public ProtocolProviderFactoryZeroconfImpl() - { - super(ZeroconfActivator.getBundleContext(), ProtocolNames.ZEROCONF); - } - - /** - * Initializaed and creates an account corresponding to the specified - * accountProperties and registers the resulting ProtocolProvider in the - * <tt>context</tt> BundleContext parameter. - * - * @param userIDStr tha/a 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. - */ - @Override - public AccountID installAccount( String userIDStr, - Map<String, String> accountProperties) - { - BundleContext context - = ZeroconfActivator.getBundleContext(); - if (context == null) - throw new NullPointerException( - "The specified BundleContext was null"); - - if (userIDStr == null) - throw new NullPointerException( - "The specified AccountID was null"); - - if (accountProperties == null) - throw new NullPointerException( - "The specified property map was null"); - - accountProperties.put(USER_ID, userIDStr); - - AccountID accountID = - new ZeroconfAccountID(userIDStr, accountProperties); - - //make sure we haven't seen this account id before. - if (registeredAccounts.containsKey(accountID)) - throw new IllegalStateException( - "An account for id " + userIDStr + " was already installed!"); - - //first store the account and only then load it as the load generates - //an osgi event, the osgi event triggers (through the UI) a call to the - //ProtocolProviderService.register() method and it needs to access - //the configuration service and check for a stored password. - this.storeAccount(accountID, false); - - accountID = loadAccount(accountProperties); - - return accountID; - } - - @Override - protected AccountID createAccountID(String userID, Map<String, String> accountProperties) - { - return new ZeroconfAccountID(userID, accountProperties); - } - - @Override - protected ProtocolProviderService createService(String userID, - AccountID accountID) - { - ProtocolProviderServiceZeroconfImpl service = - new ProtocolProviderServiceZeroconfImpl(); - - service.initialize(userID, accountID); - return service; - } - - @Override - public void modifyAccount( ProtocolProviderService protocolProvider, - Map<String, String> accountProperties) - throws NullPointerException - { - // TODO Auto-generated method stub - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/ProtocolProviderServiceZeroconfImpl.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/ProtocolProviderServiceZeroconfImpl.java deleted file mode 100644 index 7fe916f..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/ProtocolProviderServiceZeroconfImpl.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; - -/** - * An implementation of the protocol provider service over the Zeroconf protocol - * - * @author Christian Vincenot - * @author Maxime Catelin - */ -public class ProtocolProviderServiceZeroconfImpl - extends AbstractProtocolProviderService -{ - /** - * The logger for this class. - */ - private static final Logger logger = - Logger.getLogger(ProtocolProviderServiceZeroconfImpl.class); - - /** - * We use this to lock access to initialization. - */ - private final Object initializationLock = new Object(); - - /** - * The id of the account that this protocol provider represents. - */ - private AccountID accountID = null; - - /** - * Indicates whether or not the provider is initialized and ready for use. - */ - private boolean isInitialized = false; - - /** - * The logo corresponding to the zeroconf protocol. - */ - private final ProtocolIconZeroconfImpl zeroconfIcon - = new ProtocolIconZeroconfImpl(); - - /** - * The registration state that we are currently in. Note that in a real - * world protocol implementation this field won't exist and the registration - * state would be retrieved from the protocol stack. - */ - private RegistrationState currentRegistrationState - = RegistrationState.UNREGISTERED; - - /** - * The BonjourService corresponding to this ProtocolProviderService - */ - - private BonjourService bonjourService; - - /** - * The default constructor for the Zeroconf protocol provider. - */ - public ProtocolProviderServiceZeroconfImpl() - { - if (logger.isTraceEnabled()) - logger.trace("Creating a zeroconf provider."); - } - - /** - * 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 Bonjour Service that handles the Bonjour protocol stack. - * - *@return the Bonjour Service linked with this Protocol Provider - */ - public BonjourService getBonjourService() - { - return bonjourService; - } - - /** - * 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 zeroconf 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 - OperationSetPersistentPresenceZeroconfImpl persistentPresence = - new OperationSetPersistentPresenceZeroconfImpl(this); - - addSupportedOperationSet( - OperationSetPersistentPresence.class, - persistentPresence); - //register it once again for those that simply need presence and - //won't be smart enough to check for a persistent presence - //alternative - addSupportedOperationSet( - OperationSetPresence.class, - persistentPresence); - - //initialize the IM operation set - addSupportedOperationSet( - OperationSetBasicInstantMessaging.class, - new OperationSetBasicInstantMessagingZeroconfImpl( - this, - persistentPresence)); - - //initialize the typing notifications operation set - addSupportedOperationSet( - OperationSetTypingNotifications.class, - new OperationSetTypingNotificationsZeroconfImpl(this)); - - isInitialized = true; - } - } - - /** - * 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 ProtocolNames.ZEROCONF; - } - - /** - * Returns the state of the registration of this protocol provider with - * the corresponding registration service. - * - * @return ProviderRegistrationState - */ - public RegistrationState getRegistrationState() - { - return currentRegistrationState; - } - - /** - * 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 - { - //we don't need a password here since there's no server in - //zeroconf. - - RegistrationState oldState = currentRegistrationState; - currentRegistrationState = RegistrationState.REGISTERED; - - - //ICI : creer le service Zeroconf !! - if (logger.isInfoEnabled()) - logger.info("ZEROCONF: Starting the service"); - this.bonjourService = new BonjourService(5298, this); - - //bonjourService.changeStatus(ZeroconfStatusEnum.ONLINE); - - 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; - } - if (logger.isTraceEnabled()) - logger.trace("Killing the Zeroconf 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; - - if(bonjourService != null) - bonjourService.shutdown(); - - fireRegistrationStateChanged( - oldState - , currentRegistrationState - , RegistrationStateChangeEvent.REASON_USER_REQUEST - , null); - } - - /* - * (non-Javadoc) - * - * @see net.java.sip.communicator.service.protocol.ProtocolProviderService# - * isSignallingTransportSecure() - */ - public boolean isSignalingTransportSecure() - { - return false; - } - - /** - * Returns the "transport" protocol of this instance used to carry the - * control channel for the current protocol service. - * - * @return The "transport" protocol of this instance: TCP. - */ - public TransportProtocol getTransportProtocol() - { - return TransportProtocol.TCP; - } - - /** - * Returns the zeroconf protocol icon. - * @return the zeroconf protocol icon - */ - public ProtocolIcon getProtocolIcon() - { - return zeroconfIcon; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/ZeroconfAccountID.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/ZeroconfAccountID.java deleted file mode 100644 index 64e22c7..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/ZeroconfAccountID.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * The Zeroconf implementation of a sip-communicator AccountID - * - * @author Christian Vincenot - */ -public class ZeroconfAccountID - extends AccountID -{ - /* Firstname, lastname, mail address */ - private String first = null; - private String last = null; - private String mail = null; - - private boolean rememberContacts = false; - - /** - * Creates a zeroconf account id from the specified id and account - * properties. - * @param userID id identifying this account - * @param accountProperties any other properties necessary for the account. - */ - ZeroconfAccountID(String userID, Map<String, String> accountProperties) - { - super(userID, - accountProperties, - ProtocolNames.ZEROCONF, - "zeroconf.org"); - - first = accountProperties.get("first"); - last = accountProperties.get("last"); - mail = accountProperties.get("mail"); - - rememberContacts = - new Boolean(accountProperties.get("rememberContacts")) - .booleanValue(); - } - - /** - * Returns a String representing the firstname of this user. - * @return String representing the firstname of this user. - */ - public String getFirst() - { - return first; - } - - /** - * Returns a String representing the lastname of this user. - * @return String representing the lastname of this user. - */ - public String getLast() - { - return last; - } - - /** - * Returns a String representing the mail address of this user. - * @return String representing the mail address of this user. - */ - public String getMail() - { - return mail; - } - - /** - * Returns a boolean indicating if we store the contacts we meet or not. - * @return boolean indicating if we store the contacts we meet or not. - */ - public boolean isRememberContacts() - { - return rememberContacts; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/ZeroconfActivator.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/ZeroconfActivator.java deleted file mode 100644 index 2544bab..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/ZeroconfActivator.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; - -import org.osgi.framework.*; - -/** - * Loads the Zeroconf provider factory and registers its services in the OSGI - * bundle context. - * - * @author Christian Vincenot - * @author Maxime Catelin - */ -public class ZeroconfActivator - implements BundleActivator -{ - private static final Logger logger - = Logger.getLogger(ZeroconfActivator.class); - - /** - * A reference to the registration of our Zeroconf protocol provider - * factory. - */ - private ServiceRegistration zeroconfPpFactoryServReg = null; - - /** - * A reference to the Zeroconf protocol provider factory. - */ - private static ProtocolProviderFactoryZeroconfImpl - zeroconfProviderFactory = null; - - /** - * The currently valid bundle context. - */ - static BundleContext bundleContext = null; - - - /** - * Called when this bundle is started. In here we'll export the - * zeroconf ProtocolProviderFactory implementation so that it could be - * possible to register accounts with it in SIP Communicator. - * - * @param context The execution context of the bundle being started. - * @throws Exception If this method throws an exception, this bundle is - * marked as stopped and the Framework will remove this bundle's - * listeners, unregister all services registered by this bundle, and - * release all services used by this bundle. - */ - public void start(BundleContext context) - throws Exception - { -// logger.setLevelAll(); - - bundleContext = context; - - Hashtable<String, String> hashtable = new Hashtable<String, String>(); - hashtable.put(ProtocolProviderFactory.PROTOCOL, "Zeroconf"); - - zeroconfProviderFactory = new ProtocolProviderFactoryZeroconfImpl(); - - //register the zeroconf provider factory. - zeroconfPpFactoryServReg = context.registerService( - ProtocolProviderFactory.class.getName(), - zeroconfProviderFactory, - hashtable); - - if (logger.isInfoEnabled()) - logger.info("Zeroconf protocol implementation [STARTED]."); - } - - /** - * Returns a reference to the bundle context that we were started with. - * @return a reference to the BundleContext instance that we were started - * witn. - */ - public static BundleContext getBundleContext() - { - return bundleContext; - } - - /** - * Retrurns a reference to the protocol provider factory that we have - * registered. - * @return a reference to the <tt>ProtocolProviderFactoryJabberImpl</tt> - * instance that we have registered from this package. - */ - public static ProtocolProviderFactoryZeroconfImpl getProtocolProviderFactory() - { - return zeroconfProviderFactory; - } - - - /** - * Called when this bundle is stopped so the Framework can perform the - * bundle-specific activities necessary to stop the bundle. - * - * @param context The execution context of the bundle being stopped. - * @throws Exception If this method throws an exception, the bundle is - * still marked as stopped, and the Framework will remove the bundle's - * listeners, unregister all services registered by the bundle, and - * release all services used by the bundle. - */ - public void stop(BundleContext context) - throws Exception - { - zeroconfProviderFactory.stop(); - zeroconfPpFactoryServReg.unregister(); - - if (logger.isInfoEnabled()) - logger.info("Zeroconf protocol implementation [STOPPED]."); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/ZeroconfStatusEnum.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/ZeroconfStatusEnum.java deleted file mode 100644 index 3fe2745..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/ZeroconfStatusEnum.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright @ 2015 Atlassian Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf; - -import java.util.*; - -import net.java.sip.communicator.service.protocol.*; - -/** - * An implementation of <tt>PresenceStatus</tt> that enumerates all states that - * a Zeroconf contact can fall into. - * - * @author Christian Vincenot - * @author Jonathan Martin - */ -public class ZeroconfStatusEnum - extends PresenceStatus -{ - - /** - * Indicates an Offline status or status with 0 connectivity. - */ - public static final ZeroconfStatusEnum OFFLINE - = new ZeroconfStatusEnum( - 0, - "Offline", - ProtocolIconZeroconfImpl.getImageInBytes( - "service.protocol.zeroconf.OFFLINE_STATUS_ICON")); - - /** - * The DND status. Indicates that the user has connectivity but prefers - * not to be contacted. - */ - public static final ZeroconfStatusEnum DO_NOT_DISTURB - = new ZeroconfStatusEnum( - 30, - "Do Not Disturb",//, "Do Not Disturb", - ProtocolIconZeroconfImpl.getImageInBytes( - "service.protocol.zeroconf.DND_STATUS_ICON")); - - /** - * The Invisible status. Indicates that the user has connectivity even - * though it may appear otherwise to others, to whom she would appear to be - * offline. - */ - public static final ZeroconfStatusEnum INVISIBLE - = new ZeroconfStatusEnum( - 45, - "Invisible", - ProtocolIconZeroconfImpl.getImageInBytes( - "service.protocol.zeroconf.INVISIBLE_STATUS_ICON")); - - /** - * The Online status. Indicate that the user is able and willing to - * communicate. - */ - public static final ZeroconfStatusEnum ONLINE - = new ZeroconfStatusEnum( - 65, - "Available",//, "Online" - ProtocolIconZeroconfImpl.getImageInBytes( - "service.protocol.zeroconf.ONLINE_STATUS_ICON")); - - - /** - * Initialize the list of supported status states. - */ - private static List<PresenceStatus> supportedStatusSet = new LinkedList<PresenceStatus>(); - static - { - supportedStatusSet.add(OFFLINE); - supportedStatusSet.add(DO_NOT_DISTURB); - - /* INVISIBLE STATUS could be supported by unregistering JmDNS and - * accepting unknown contacts' messages */ - //supportedStatusSet.add(INVISIBLE); - - supportedStatusSet.add(ONLINE); - } - - /** - * Creates an instance of <tt>ZeroconfPresneceStatus</tt> with the - * specified parameters. - * @param status the connectivity level of the new presence status instance - * @param statusName the name of the presence status. - * @param statusIcon the icon associated with this status - */ - private ZeroconfStatusEnum(int status, - String statusName, - byte[] statusIcon) - { - super(status, statusName, statusIcon); - } - - /** - * Returns an iterator over all status instances supproted by the zeroconf - * provider. - * @return an <tt>Iterator</tt> over all status instances supported by the - * zeroconf provider. - */ - static Iterator<PresenceStatus> supportedStatusSet() - { - return supportedStatusSet.iterator(); - } - - /** - * @param status String representation of the status - * @return ZeroconfStatusEnum corresponding the supplied String value - */ - static ZeroconfStatusEnum statusOf(String status) - { - Iterator<PresenceStatus> statusIter = supportedStatusSet(); - while (statusIter.hasNext()) - { - ZeroconfStatusEnum state = (ZeroconfStatusEnum)statusIter.next(); - if (state.statusName.equalsIgnoreCase(status)) - return state; - } - return null; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSCache.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSCache.java deleted file mode 100644 index 0baaff4..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSCache.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.util.*; - -import net.java.sip.communicator.util.*; - -/** - * A table of DNS entries. This is a hash table which - * can handle multiple entries with the same name. - * <p/> - * Storing multiple entries with the same name is implemented using a - * linked list of <code>CacheNode</code>'s. - * <p/> - * The current implementation of the API of DNSCache does expose the - * cache nodes to clients. Clients must explicitly deal with the nodes - * when iterating over entries in the cache. Here's how to iterate over - * all entries in the cache: - * <pre> - * for (Iterator i=dnscache.iterator(); i.hasNext(); ) - * { - * for ( DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); - * n != null; - * n.next()) - * { - * DNSEntry entry = n.getValue(); - * ...do something with entry... - * } - * } - * </pre> - * <p/> - * And here's how to iterate over all entries having a given name: - * <pre> - * for ( DNSCache.CacheNode n = (DNSCache.CacheNode) dnscache.find(name); - * n != null; - * n.next()) - * { - * DNSEntry entry = n.getValue(); - * ...do something with entry... - * } - * </pre> - * - * @version %I%, %G% - * @author Arthur van Hoff, Werner Randelshofer, Rick Blair - */ -class DNSCache -{ - private static Logger logger = Logger.getLogger(DNSCache.class.toString()); - // Implementation note: - // We might completely hide the existence of CacheNode's in a future version - // of DNSCache. But this will require to implement two (inner) classes for - // the iterators that will be returned by method <code>iterator()</code> and - // method <code>find(name)</code>. - // Since DNSCache is not a public class, it does not seem worth the effort - // to clean its API up that much. - - // [PJYF Oct 15 2004] This should implements Collections - // that would be amuch cleaner implementation - - /** - * The number of DNSEntry's in the cache. - */ - private int size; - - /** - * The hashtable used internally to store the entries of the cache. - * Keys are instances of String. The String contains an unqualified service - * name. - * Values are linked lists of CacheNode instances. - */ - private HashMap<String, CacheNode> hashtable; - - /** - * Cache nodes are used to implement storage of multiple DNSEntry's of the - * same name in the cache. - */ - public static class CacheNode - { - private DNSEntry value; - private CacheNode next; - - public CacheNode(DNSEntry value) - { - this.value = value; -// String SLevel = System.getProperty("jmdns.debug"); -// if (SLevel == null) -// SLevel = "INFO"; -// logger.setLevel(Level.parse(SLevel)); - } - - public CacheNode next() - { - return next; - } - - public DNSEntry getValue() - { - return value; - } - } - - - /** - * Create a table with a given initial size. - * @param size initial size. - */ - public DNSCache(final int size) - { - hashtable = new HashMap<String, CacheNode>(size); - -// String SLevel = System.getProperty("jmdns.debug"); -// if (SLevel == null) SLevel = "INFO"; -// logger.setLevel(Level.parse(SLevel)); - } - - /** - * Clears the cache. - */ - public synchronized void clear() - { - hashtable.clear(); - size = 0; - } - - /** - * Adds an entry to the table. - * @param entry added to the table. - */ - public synchronized void add(final DNSEntry entry) - { - //logger.log("DNSCache.add("+entry.getName()+")"); - CacheNode newValue = new CacheNode(entry); - CacheNode node = hashtable.get(entry.getName()); - if (node == null) - { - hashtable.put(entry.getName(), newValue); - } - else - { - newValue.next = node.next; - node.next = newValue; - } - size++; - } - - /** - * Remove a specific entry from the table. - * @param entry removed from table. - * @return Returns true if the entry was found. - */ - public synchronized boolean remove(DNSEntry entry) - { - CacheNode node = hashtable.get(entry.getName()); - if (node != null) - { - if (node.value == entry) - { - if (node.next == null) - { - hashtable.remove(entry.getName()); - } - else - { - hashtable.put(entry.getName(), node.next); - } - size--; - return true; - } - - CacheNode previous = node; - node = node.next; - while (node != null) - { - if (node.value == entry) - { - previous.next = node.next; - size--; - return true; - } - previous = node; - node = node.next; - } - ; - } - return false; - } - - /** - * Get a matching DNS entry from the table (using equals). - * @param entry to be found in table. - * @return Returns the entry that was found. - */ - public synchronized DNSEntry get(DNSEntry entry) - { - for (CacheNode node = find(entry.getName()); node != null; node = node.next) - { - if (node.value.equals(entry)) - { - return node.value; - } - } - return null; - } - - /** - * Get a matching DNS entry from the table. - * @param name - * @param type - * @param clazz - * @return Return the entry if found, null otherwise. - */ - public synchronized DNSEntry get(String name, int type, int clazz) - { - for (CacheNode node = find(name); node != null; node = node.next) - { - if (node.value.type == type && node.value.clazz == clazz) - { - return node.value; - } - } - return null; - } - - /** - * Iterates over all cache nodes. - * The iterator returns instances of DNSCache.CacheNode. - * Each instance returned is the first node of a linked list. - * To retrieve all entries, one must iterate over this linked list. See - * code snippets in the header of the class. - * @return Returns iterator with instances of DNSCache.CacheNode. - */ - public Iterator<DNSCache.CacheNode> iterator() - { - return Collections.unmodifiableCollection(hashtable.values()).iterator(); - } - - /** - * Iterate only over items with matching name. - * If an instance is returned, it is the first node of a linked list. - * To retrieve all entries, one must iterate over this linked list. - * @param name to be found. - * @return Returns an instance of DNSCache.CacheNode or null. - */ - public synchronized CacheNode find(String name) - { - return hashtable.get(name); - } - - /** - * List all entries for debugging. - */ - public synchronized void print() - { - for (Iterator<CacheNode> i = iterator(); i.hasNext();) - { - for (CacheNode n = i.next(); n != null; n = n.next) - { - if (logger.isInfoEnabled()) - logger.info(n.value.toString()); - } - } - } - - @Override - public synchronized String toString() - { - StringBuffer aLog = new StringBuffer(); - aLog.append("\t---- cache ----"); - for (Iterator<CacheNode> i = iterator(); i.hasNext();) - { - for (CacheNode n = i.next(); n != null; n = n.next) - { - aLog.append("\n\t\t" + n.value); - } - } - return aLog.toString(); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSConstants.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSConstants.java deleted file mode 100644 index 8792c8d..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSConstants.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff, Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -/** - * DNS constants. - * - * @version %I%, %G% - * @author Arthur van Hoff, Jeff Sonstein, - * Werner Randelshofer, Pierre Frisch, Rick Blair - */ -public final class DNSConstants -{ - - // changed to final class - jeffs - final static String MDNS_GROUP = "224.0.0.251"; - final static String MDNS_GROUP_IPV6 = "FF02::FB"; - final static int MDNS_PORT = 5353; - final static int DNS_PORT = 53; - // default one hour TTL - final static int DNS_TTL = 60 * 60; - // two hour TTL (draft-cheshire-dnsext-multicastdns.txt ch 13) - // final static int DNS_TTL = 120 * 60; - - final static int MAX_MSG_TYPICAL = 1460; - final static int MAX_MSG_ABSOLUTE = 8972; - - final static int FLAGS_QR_MASK = 0x8000; // Query response mask - final static int FLAGS_QR_QUERY = 0x0000; // Query - final static int FLAGS_QR_RESPONSE = 0x8000;// Response - - public final static int FLAGS_AA = 0x0400; // Authorative answer - final static int FLAGS_TC = 0x0200; // Truncated - final static int FLAGS_RD = 0x0100; // Recursion desired - public final static int FLAGS_RA = 0x8000; // Recursion available - - final static int FLAGS_Z = 0x0040; // Zero - final static int FLAGS_AD = 0x0020; // Authentic data - final static int FLAGS_CD = 0x0010; // Checking disabled - - // Final Static Internet - public final static int CLASS_IN = 1; - // CSNET - final static int CLASS_CS = 2; - // CHAOS - final static int CLASS_CH = 3; - // Hesiod - final static int CLASS_HS = 4; - // Used in DNS UPDATE [RFC 2136] - final static int CLASS_NONE = 254; - // Not a DNS class, but a DNS query class, meaning "all classes" - final static int CLASS_ANY = 255; - // Multicast DNS uses the bottom 15 bits to identify the record class... - final static int CLASS_MASK = 0x7FFF; - // ... and the top bit indicates that all other cached records are now invalid - public final static int CLASS_UNIQUE = 0x8000; - - final static int TYPE_IGNORE = 0; // This is a hack to stop further processing - public final static int TYPE_A = 1; // Address - final static int TYPE_NS = 2; // Name Server - final static int TYPE_MD = 3; // Mail Destination - final static int TYPE_MF = 4; // Mail Forwarder - final static int TYPE_CNAME = 5; // Canonical Name - final static int TYPE_SOA = 6; // Start of Authority - final static int TYPE_MB = 7; // Mailbox - final static int TYPE_MG = 8; // Mail Group - final static int TYPE_MR = 9; // Mail Rename - final static int TYPE_NULL = 10; // NULL RR - final static int TYPE_WKS = 11; // Well-known-service - final static int TYPE_PTR = 12; // Domain Name pofinal static inter - final static int TYPE_HINFO = 13; // Host information - final static int TYPE_MINFO = 14; // Mailbox information - final static int TYPE_MX = 15; // Mail exchanger - public final static int TYPE_TXT = 16;// Arbitrary text string - final static int TYPE_RP = 17; // for Responsible Person [RFC1183] - final static int TYPE_AFSDB = 18; // for AFS Data Base location [RFC1183] - final static int TYPE_X25 = 19; // for X.25 PSDN address [RFC1183] - final static int TYPE_ISDN = 20; // for ISDN address [RFC1183] - final static int TYPE_RT = 21; // for Route Through [RFC1183] - final static int TYPE_NSAP = 22; // for NSAP address, NSAP style A record [RFC1706] - final static int TYPE_NSAP_PTR = 23;// - final static int TYPE_SIG = 24; // for security signature [RFC2931] - final static int TYPE_KEY = 25; // for security key [RFC2535] - final static int TYPE_PX = 26; // X.400 mail mapping information [RFC2163] - final static int TYPE_GPOS = 27; // Geographical Position [RFC1712] - final static int TYPE_AAAA = 28; // IP6 Address [Thomson] - final static int TYPE_LOC = 29; // Location Information [Vixie] - final static int TYPE_NXT = 30; // Next Domain - OBSOLETE [RFC2535, RFC3755] - final static int TYPE_EID = 31; // Endpoint Identifier [Patton] - final static int TYPE_NIMLOC = 32; // Nimrod Locator [Patton] - public final static int TYPE_SRV = 33;// Server Selection [RFC2782] - final static int TYPE_ATMA = 34; // ATM Address [Dobrowski] - final static int TYPE_NAPTR = 35; // Naming Authority Pointer [RFC2168, RFC2915] - final static int TYPE_KX = 36; // Key Exchanger [RFC2230] - final static int TYPE_CERT = 37; // CERT [RFC2538] - final static int TYPE_A6 = 38; // A6 [RFC2874] - final static int TYPE_DNAME = 39; // DNAME [RFC2672] - final static int TYPE_SINK = 40; // SINK [Eastlake] - final static int TYPE_OPT = 41; // OPT [RFC2671] - final static int TYPE_APL = 42; // APL [RFC3123] - final static int TYPE_DS = 43; // Delegation Signer [RFC3658] - final static int TYPE_SSHFP = 44; // SSH Key Fingerprint [RFC-ietf-secsh-dns-05.txt] - final static int TYPE_RRSIG = 46; // RRSIG [RFC3755] - final static int TYPE_NSEC = 47; // NSEC [RFC3755] - final static int TYPE_DNSKEY = 48; // DNSKEY [RFC3755] - final static int TYPE_UINFO = 100; // [IANA-Reserved] - final static int TYPE_UID = 101; // [IANA-Reserved] - final static int TYPE_GID = 102; // [IANA-Reserved] - final static int TYPE_UNSPEC = 103; // [IANA-Reserved] - final static int TYPE_TKEY = 249; // Transaction Key [RFC2930] - final static int TYPE_TSIG = 250; // Transaction Signature [RFC2845] - final static int TYPE_IXFR = 251; // Incremental transfer [RFC1995] - final static int TYPE_AXFR = 252; // Transfer of an entire zone [RFC1035] - final static int TYPE_MAILA = 253; // Mailbox-related records (MB, MG or MR) [RFC1035] - final static int TYPE_MAILB = 254; // Mail agent RRs (Obsolete - see MX) [RFC1035] - final static int TYPE_ANY = 255; // Request for all records [RFC1035] - - //Time Intervals for various functions - - //milliseconds before send shared query - final static int SHARED_QUERY_TIME = 20; - //milliseconds between query loops. - final static int QUERY_WAIT_INTERVAL = 225; - //milliseconds between probe loops. - final static int PROBE_WAIT_INTERVAL = 250; - //minimal wait interval for response. - final static int RESPONSE_MIN_WAIT_INTERVAL = 20; - //maximal wait interval for response - final static int RESPONSE_MAX_WAIT_INTERVAL = 115; - //milliseconds to wait after conflict. - final static int PROBE_CONFLICT_INTERVAL = 1000; - //After x tries go 1 time a sec. on probes. - final static int PROBE_THROTTLE_COUNT = 10; - //We only increment the throttle count, if - // the previous increment is inside this interval. - final static int PROBE_THROTTLE_COUNT_INTERVAL = 5000; - //milliseconds between Announce loops. - final static int ANNOUNCE_WAIT_INTERVAL = 1000; - //milliseconds between cache cleanups. - final static int RECORD_REAPER_INTERVAL = 10000; - - final static int KNOWN_ANSWER_TTL = 120; - // 50% of the TTL in milliseconds - final static int ANNOUNCED_RENEWAL_TTL_INTERVAL = DNS_TTL * 500; -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSEntry.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSEntry.java deleted file mode 100644 index 7132756..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSEntry.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.util.logging.*; - -/** - * DNS entry with a name, type, and class. This is the base - * class for questions and records. - * - * @version %I%, %G% - * @author Arthur van Hoff, Pierre Frisch, Rick Blair - * @author Christian Vincenot - */ -public class DNSEntry -{ - private static Logger logger = Logger.getLogger(DNSEntry.class.toString()); - String key; - String name; - int type; - int clazz; - boolean unique; - - /** - * Create an entry. - */ - DNSEntry(String name, int type, int clazz) - { - this.key = name.toLowerCase(); - this.name = name; - this.type = type; - this.clazz = clazz & DNSConstants.CLASS_MASK; - this.unique = (clazz & DNSConstants.CLASS_UNIQUE) != 0; - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - /** - * Check if two entries have exactly the same name, type, and class. - */ - @Override - public boolean equals(Object obj) - { - if (obj instanceof DNSEntry) - { - DNSEntry other = (DNSEntry) obj; - return name.equals(other.name) && - type == other.type && - clazz == other.clazz; - } - return false; - } - - public String getName() - { - return name; - } - - public int getType() - { - return type; - } - - public int getClazz() - { - return clazz; - } - - - public boolean isUnique() - { - return unique; - } - - /** - * Overriden, to return a value which is consistent with the value returned - * by equals(Object). - */ - @Override - public int hashCode() - { - return name.hashCode() + type + clazz; - } - - /** - * Get a string given a clazz. - */ - static String getClazz(int clazz) - { - switch (clazz & DNSConstants.CLASS_MASK) - { - case DNSConstants.CLASS_IN: - return "in"; - case DNSConstants.CLASS_CS: - return "cs"; - case DNSConstants.CLASS_CH: - return "ch"; - case DNSConstants.CLASS_HS: - return "hs"; - case DNSConstants.CLASS_NONE: - return "none"; - case DNSConstants.CLASS_ANY: - return "any"; - default: - return "?"; - } - } - - /** - * Get a string given a type. - */ - static String getType(int type) - { - switch (type) - { - case DNSConstants.TYPE_A: - return "a"; - case DNSConstants.TYPE_AAAA: - return "aaaa"; - case DNSConstants.TYPE_NS: - return "ns"; - case DNSConstants.TYPE_MD: - return "md"; - case DNSConstants.TYPE_MF: - return "mf"; - case DNSConstants.TYPE_CNAME: - return "cname"; - case DNSConstants.TYPE_SOA: - return "soa"; - case DNSConstants.TYPE_MB: - return "mb"; - case DNSConstants.TYPE_MG: - return "mg"; - case DNSConstants.TYPE_MR: - return "mr"; - case DNSConstants.TYPE_NULL: - return "null"; - case DNSConstants.TYPE_WKS: - return "wks"; - case DNSConstants.TYPE_PTR: - return "ptr"; - case DNSConstants.TYPE_HINFO: - return "hinfo"; - case DNSConstants.TYPE_MINFO: - return "minfo"; - case DNSConstants.TYPE_MX: - return "mx"; - case DNSConstants.TYPE_TXT: - return "txt"; - case DNSConstants.TYPE_SRV: - return "srv"; - case DNSConstants.TYPE_ANY: - return "any"; - default: - return "?"; - } - } - - public String toString(String hdr, String other) - { - return hdr + "[" + getType(type) + "," + - getClazz(clazz) + (unique ? "-unique," : ",") + - name + ((other != null) ? "," + - other + "]" : "]"); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSIncoming.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSIncoming.java deleted file mode 100644 index 3dcedc4..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSIncoming.java +++ /dev/null @@ -1,524 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.io.*; -import java.net.*; -import java.util.*; -import java.util.logging.*; - -/** - * Parse an incoming DNS message into its components. - * - * @version %I%, %G% - * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch, Daniel Bobbert - */ -final class DNSIncoming -{ - private static Logger logger = Logger.getLogger(DNSIncoming.class.toString()); - // Implementation note: This vector should be immutable. - // If a client of DNSIncoming changes the contents of this vector, - // we get undesired results. To fix this, we have to migrate to - // the Collections API of Java 1.2. i.e we replace Vector by List. - // final static Vector EMPTY = new Vector(); - - private DatagramPacket packet; - private int off; - private int len; - private byte data[]; - - int id; - private int flags; - private int numQuestions; - int numAnswers; - private int numAuthorities; - private int numAdditionals; - private long receivedTime; - - List<DNSEntry> questions; - List<DNSRecord> answers; - - /** - * Parse a message from a datagram packet. - */ - DNSIncoming(DatagramPacket packet) throws IOException - { - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - - this.packet = packet; - this.data = packet.getData(); - this.len = packet.getLength(); - this.off = packet.getOffset(); - this.questions = new LinkedList<DNSEntry>(); - this.answers = new LinkedList<DNSRecord>(); - this.receivedTime = System.currentTimeMillis(); - - try - { - id = readUnsignedShort(); - flags = readUnsignedShort(); - numQuestions = readUnsignedShort(); - numAnswers = readUnsignedShort(); - numAuthorities = readUnsignedShort(); - numAdditionals = readUnsignedShort(); - - // parse questions - if (numQuestions > 0) - { - questions = - Collections.synchronizedList( - new ArrayList<DNSEntry>(numQuestions)); - for (int i = 0; i < numQuestions; i++) - { - DNSQuestion question = - new DNSQuestion( - readName(), - readUnsignedShort(), - readUnsignedShort()); - - questions.add(question); - } - } - - // parse answers - int n = numAnswers + numAuthorities + numAdditionals; - if (n > 0) - { - //System.out.println("JMDNS received "+n+" answers!"); - answers = Collections.synchronizedList( - new ArrayList<DNSRecord>(n)); - for (int i = 0; i < n; i++) - { - String domain = readName(); - int type = readUnsignedShort(); - int clazz = readUnsignedShort(); - int ttl = readInt(); - int len = readUnsignedShort(); - int end = off + len; - DNSRecord rec = null; - - switch (type) - { - case DNSConstants.TYPE_A: // IPv4 - case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested - rec = new DNSRecord.Address( - domain, type, clazz, ttl, readBytes(off, len)); - break; - case DNSConstants.TYPE_CNAME: - case DNSConstants.TYPE_PTR: - rec = new DNSRecord.Pointer( - domain, type, clazz, ttl, readName()); - break; - case DNSConstants.TYPE_TXT: - rec = new DNSRecord.Text( - domain, type, clazz, ttl, readBytes(off, len)); - break; - case DNSConstants.TYPE_SRV: - //System.out.println("JMDNS: One is a SRV field!!"); - rec = new DNSRecord.Service( domain, - type, - clazz, - ttl, - readUnsignedShort(), - readUnsignedShort(), - readUnsignedShort(), - readName()); - break; - case DNSConstants.TYPE_HINFO: - // Maybe we should do something with those - break; - default : - logger.finer("DNSIncoming() unknown type:" + type); - break; - } - - if (rec != null) - { - // Add a record, if we were able to create one. - answers.add(rec); - } - else - { - // Addjust the numbers for the skipped record - if (answers.size() < numAnswers) - { - numAnswers--; - } - else - { - if (answers.size() < numAnswers + numAuthorities) - { - numAuthorities--; - } - else - { - if (answers.size() < numAnswers + - numAuthorities + - numAdditionals) - { - numAdditionals--; - } - } - } - } - off = end; - } - } - } - catch (IOException e) - { - logger.log(Level.WARNING, - "DNSIncoming() dump " + print(true) + "\n exception ", e); - throw e; - } - } - - /** - * Check if the message is a query. - */ - boolean isQuery() - { - return (flags & DNSConstants.FLAGS_QR_MASK) == - DNSConstants.FLAGS_QR_QUERY; - } - - /** - * Check if the message is truncated. - */ - boolean isTruncated() - { - return (flags & DNSConstants.FLAGS_TC) != 0; - } - - /** - * Check if the message is a response. - */ - boolean isResponse() - { - return (flags & DNSConstants.FLAGS_QR_MASK) == - DNSConstants.FLAGS_QR_RESPONSE; - } - - private int get(int off) throws IOException - { - if ((off < 0) || (off >= len)) - { - throw new IOException("parser error: offset=" + off); - } - return data[off] & 0xFF; - } - - private int readUnsignedShort() throws IOException - { - return (get(off++) << 8) + get(off++); - } - - private int readInt() throws IOException - { - return (readUnsignedShort() << 16) + readUnsignedShort(); - } - - private byte[] readBytes(int off, int len) throws IOException - { - byte bytes[] = new byte[len]; - System.arraycopy(data, off, bytes, 0, len); - return bytes; - } - - private void readUTF(StringBuffer buf, int off, int len) throws IOException - { - for (int end = off + len; off < end;) - { - int ch = get(off++); - switch (ch >> 4) - { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - // 0xxxxxxx - break; - case 12: - case 13: - // 110x xxxx 10xx xxxx - ch = ((ch & 0x1F) << 6) | (get(off++) & 0x3F); - break; - case 14: - // 1110 xxxx 10xx xxxx 10xx xxxx - ch = ((ch & 0x0f) << 12) | - ((get(off++) & 0x3F) << 6) | - (get(off++) & 0x3F); - break; - default: - // 10xx xxxx, 1111 xxxx - ch = ((ch & 0x3F) << 4) | (get(off++) & 0x0f); - break; - } - buf.append((char) ch); - } - } - - private String readName() throws IOException - { - StringBuffer buf = new StringBuffer(); - int off = this.off; - int next = -1; - int first = off; - - while (true) - { - int len = get(off++); - if (len == 0) - { - break; - } - switch (len & 0xC0) - { - case 0x00: - //buf.append("[" + off + "]"); - readUTF(buf, off, len); - off += len; - buf.append('.'); - break; - case 0xC0: - //buf.append("<" + (off - 1) + ">"); - if (next < 0) - { - next = off + 1; - } - off = ((len & 0x3F) << 8) | get(off++); - if (off >= first) - { - throw new IOException( - "bad domain name: possible circular name detected"); - } - first = off; - break; - default: - throw new IOException( - "bad domain name: '" + buf + "' at " + off); - } - } - this.off = (next >= 0) ? next : off; - return buf.toString(); - } - - /** - * Debugging. - */ - String print(boolean dump) - { - StringBuffer buf = new StringBuffer(); - buf.append(toString() + "\n"); - for (Iterator<DNSEntry> iterator = questions.iterator(); - iterator.hasNext();) - { - buf.append(" ques:" + iterator.next() + "\n"); - } - int count = 0; - for (Iterator<DNSRecord> iterator = answers.iterator(); - iterator.hasNext(); - count++) - { - if (count < numAnswers) - { - buf.append(" answ:"); - } - else - { - if (count < numAnswers + numAuthorities) - { - buf.append(" auth:"); - } - else - { - buf.append(" addi:"); - } - } - buf.append(iterator.next() + "\n"); - } - if (dump) - { - for (int off = 0, len = packet.getLength(); off < len; off += 32) - { - int n = Math.min(32, len - off); - if (off < 10) - { - buf.append(' '); - } - if (off < 100) - { - buf.append(' '); - } - buf.append(off); - buf.append(':'); - for (int i = 0; i < n; i++) - { - if ((i % 8) == 0) - { - buf.append(' '); - } - buf.append(Integer.toHexString((data[off + i] & 0xF0) >> 4)); - buf.append(Integer.toHexString((data[off + i] & 0x0F) >> 0)); - } - buf.append("\n"); - buf.append(" "); - for (int i = 0; i < n; i++) - { - if ((i % 8) == 0) - { - buf.append(' '); - } - buf.append(' '); - int ch = data[off + i] & 0xFF; - buf.append(((ch > ' ') && (ch < 127)) ? (char) ch : '.'); - } - buf.append("\n"); - - // limit message size - if (off + 32 >= 256) - { - buf.append("....\n"); - break; - } - } - } - return buf.toString(); - } - - @Override - public String toString() - { - StringBuffer buf = new StringBuffer(); - buf.append(isQuery() ? "dns[query," : "dns[response,"); - if (packet.getAddress() != null) - { - buf.append(packet.getAddress().getHostAddress()); - } - buf.append(':'); - buf.append(packet.getPort()); - buf.append(",len="); - buf.append(packet.getLength()); - buf.append(",id=0x"); - buf.append(Integer.toHexString(id)); - if (flags != 0) - { - buf.append(",flags=0x"); - buf.append(Integer.toHexString(flags)); - if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0) - { - buf.append(":r"); - } - if ((flags & DNSConstants.FLAGS_AA) != 0) - { - buf.append(":aa"); - } - if ((flags & DNSConstants.FLAGS_TC) != 0) - { - buf.append(":tc"); - } - } - if (numQuestions > 0) - { - buf.append(",questions="); - buf.append(numQuestions); - } - if (numAnswers > 0) - { - buf.append(",answers="); - buf.append(numAnswers); - } - if (numAuthorities > 0) - { - buf.append(",authorities="); - buf.append(numAuthorities); - } - if (numAdditionals > 0) - { - buf.append(",additionals="); - buf.append(numAdditionals); - } - buf.append("]"); - return buf.toString(); - } - - /** - * Appends answers to this Incoming. - * - * @throws IllegalArgumentException If not a query or if Truncated. - */ - void append(DNSIncoming that) - { - if (this.isQuery() && this.isTruncated() && that.isQuery()) - { - if (that.numQuestions > 0) { - if (Collections.EMPTY_LIST.equals(this.questions)) - this.questions = - Collections.synchronizedList( - new ArrayList<DNSEntry>(that.numQuestions)); - - this.questions.addAll(that.questions); - this.numQuestions += that.numQuestions; - } - - if (Collections.EMPTY_LIST.equals(answers)) - { - answers = Collections.synchronizedList( - new ArrayList<DNSRecord>()); - } - - if (that.numAnswers > 0) - { - this.answers.addAll(this.numAnswers, - that.answers.subList(0, that.numAnswers)); - this.numAnswers += that.numAnswers; - } - if (that.numAuthorities > 0) - { - this.answers.addAll(this.numAnswers + this.numAuthorities, - that.answers.subList( - that.numAnswers, - that.numAnswers + that.numAuthorities)); - this.numAuthorities += that.numAuthorities; - } - if (that.numAdditionals > 0) - { - this.answers.addAll( - that.answers.subList( - that.numAnswers + that.numAuthorities, - that.numAnswers + that.numAuthorities + that.numAdditionals)); - this.numAdditionals += that.numAdditionals; - } - } - else - { - throw new IllegalArgumentException(); - } - } - - int elapseSinceArrival() - { - return (int) (System.currentTimeMillis() - receivedTime); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSListener.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSListener.java deleted file mode 100644 index d317953..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSListener.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -// REMIND: Listener should follow Java idiom for listener or have a different -// name. - -/** - * DNSListener. - * Listener for record updates. - * - * @author Werner Randelshofer, Rick Blair - * @version 1.0 May 22, 2004 Created. - */ -public interface DNSListener -{ - /** - * Update a DNS record. - * @param jmdns - * @param now - * @param record - */ - public void updateRecord(JmDNS jmdns, long now, DNSRecord record); -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSOutgoing.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSOutgoing.java deleted file mode 100644 index 4d099b8..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSOutgoing.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.io.*; -import java.util.*; -import java.util.logging.*; - -/** - * An outgoing DNS message. - * - * @version %I%, %G% - * @author Arthur van Hoff, Rick Blair, Werner Randelshofer - */ -final class DNSOutgoing -{ - private static Logger logger = - Logger.getLogger(DNSOutgoing.class.toString()); - - int id; - int flags; - private boolean multicast; - private int numQuestions; - private int numAnswers; - private int numAuthorities; - private int numAdditionals; - private Hashtable<String, Integer> names; - - byte data[]; - int off; - int len; - - /** - * Create an outgoing multicast query or response. - */ - DNSOutgoing(int flags) - { - this(flags, true); - - } - - /** - * Create an outgoing query or response. - */ - DNSOutgoing(int flags, boolean multicast) - { - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - - this.flags = flags; - this.multicast = multicast; - names = new Hashtable<String, Integer>(); - data = new byte[DNSConstants.MAX_MSG_TYPICAL]; - off = 12; - } - - /** - * Add a question to the message. - */ - void addQuestion(DNSQuestion rec) throws IOException - { - if (numAnswers > 0 || numAuthorities > 0 || numAdditionals > 0) - { - throw new IllegalStateException("Questions must be added before answers"); - } - numQuestions++; - writeQuestion(rec); - } - - /** - * Add an answer if it is not suppressed. - */ - void addAnswer(DNSIncoming in, DNSRecord rec) throws IOException - { - if (numAuthorities > 0 || numAdditionals > 0) - { - throw new IllegalStateException( - "Answers must be added before authorities and additionals"); - } - if (!rec.suppressedBy(in)) - { - addAnswer(rec, 0); - } - } - - /** - * Add an additional answer to the record. Omit if there is no room. - */ - void addAdditionalAnswer(DNSIncoming in, DNSRecord rec) throws IOException - { - if ((off < DNSConstants.MAX_MSG_TYPICAL - 200) && !rec.suppressedBy(in)) - { - writeRecord(rec, 0); - numAdditionals++; - } - } - - /** - * Add an answer to the message. - */ - void addAnswer(DNSRecord rec, long now) throws IOException - { - if (numAuthorities > 0 || numAdditionals > 0) - { - throw new IllegalStateException( - "Questions must be added before answers"); - } - if (rec != null) - { - if ((now == 0) || !rec.isExpired(now)) - { - writeRecord(rec, now); - numAnswers++; - } - } - } - - private LinkedList<DNSRecord> authorativeAnswers = new LinkedList<DNSRecord>(); - - /** - * Add an authorative answer to the message. - */ - void addAuthorativeAnswer(DNSRecord rec) throws IOException - { - if (numAdditionals > 0) - { - throw new IllegalStateException( - "Authorative answers must be added before additional answers"); - } - authorativeAnswers.add(rec); - writeRecord(rec, 0); - numAuthorities++; - - // VERIFY: - - } - - void writeByte(int value) throws IOException - { - if (off >= data.length) - { - throw new IOException("buffer full"); - } - data[off++] = (byte) value; - } - - void writeBytes(String str, int off, int len) throws IOException - { - for (int i = 0; i < len; i++) - { - writeByte(str.charAt(off + i)); - } - } - - void writeBytes(byte data[]) throws IOException - { - if (data != null) - { - writeBytes(data, 0, data.length); - } - } - - void writeBytes(byte data[], int off, int len) throws IOException - { - for (int i = 0; i < len; i++) - { - writeByte(data[off + i]); - } - } - - void writeShort(int value) throws IOException - { - writeByte(value >> 8); - writeByte(value); - } - - void writeInt(int value) throws IOException - { - writeShort(value >> 16); - writeShort(value); - } - - void writeUTF(String str, int off, int len) throws IOException - { - // compute utf length - int utflen = 0; - for (int i = 0; i < len; i++) - { - int ch = str.charAt(off + i); - if ((ch >= 0x0001) && (ch <= 0x007F)) - { - utflen += 1; - } - else - { - if (ch > 0x07FF) - { - utflen += 3; - } - else - { - utflen += 2; - } - } - } - // write utf length - writeByte(utflen); - // write utf data - for (int i = 0; i < len; i++) - { - int ch = str.charAt(off + i); - if ((ch >= 0x0001) && (ch <= 0x007F)) - { - writeByte(ch); - } - else - { - if (ch > 0x07FF) - { - writeByte(0xE0 | ((ch >> 12) & 0x0F)); - writeByte(0x80 | ((ch >> 6) & 0x3F)); - writeByte(0x80 | ((ch >> 0) & 0x3F)); - } - else - { - writeByte(0xC0 | ((ch >> 6) & 0x1F)); - writeByte(0x80 | ((ch >> 0) & 0x3F)); - } - } - } - } - - void writeName(String name) throws IOException - { - while (true) - { - int n = name.indexOf('.'); - if (n < 0) - { - n = name.length(); - } - if (n <= 0) - { - writeByte(0); - return; - } - Integer offset = names.get(name); - if (offset != null) - { - int val = offset.intValue(); - - if (val > off) - { - logger.log(Level.WARNING, - "DNSOutgoing writeName failed val=" + val + " name=" + name); - } - - writeByte((val >> 8) | 0xC0); - writeByte(val); - return; - } - names.put(name, off); - writeUTF(name, 0, n); - name = name.substring(n); - if (name.startsWith(".")) - { - name = name.substring(1); - } - } - } - - void writeQuestion(DNSQuestion question) throws IOException - { - writeName(question.name); - writeShort(question.type); - writeShort(question.clazz); - } - - void writeRecord(DNSRecord rec, long now) throws IOException - { - int save = off; - try - { - writeName(rec.name); - writeShort(rec.type); - writeShort(rec.clazz | - ((rec.unique && multicast) ? DNSConstants.CLASS_UNIQUE : 0)); - writeInt((now == 0) ? rec.ttl : rec.getRemainingTTL(now)); - writeShort(0); - int start = off; - rec.write(this); - int len = off - start; - data[start - 2] = (byte) (len >> 8); - data[start - 1] = (byte) (len & 0xFF); - } - catch (IOException e) - { - off = save; - throw e; - } - } - - /** - * Finish the message before sending it off. - */ - void finish() throws IOException - { - int save = off; - off = 0; - - writeShort(multicast ? 0 : id); - writeShort(flags); - writeShort(numQuestions); - writeShort(numAnswers); - writeShort(numAuthorities); - writeShort(numAdditionals); - off = save; - } - - boolean isQuery() - { - return (flags & DNSConstants.FLAGS_QR_MASK) == - DNSConstants.FLAGS_QR_QUERY; - } - - public boolean isEmpty() - { - return numQuestions == 0 && numAuthorities == 0 - && numAdditionals == 0 && numAnswers == 0; - } - - - @Override - public String toString() - { - StringBuffer buf = new StringBuffer(); - buf.append(isQuery() ? "dns[query," : "dns[response,"); - //buf.append(packet.getAddress().getHostAddress()); - buf.append(':'); - //buf.append(packet.getPort()); - //buf.append(",len="); - //buf.append(packet.getLength()); - buf.append(",id=0x"); - buf.append(Integer.toHexString(id)); - if (flags != 0) - { - buf.append(",flags=0x"); - buf.append(Integer.toHexString(flags)); - if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0) - { - buf.append(":r"); - } - if ((flags & DNSConstants.FLAGS_AA) != 0) - { - buf.append(":aa"); - } - if ((flags & DNSConstants.FLAGS_TC) != 0) - { - buf.append(":tc"); - } - } - if (numQuestions > 0) - { - buf.append(",questions="); - buf.append(numQuestions); - } - if (numAnswers > 0) - { - buf.append(",answers="); - buf.append(numAnswers); - } - if (numAuthorities > 0) - { - buf.append(",authorities="); - buf.append(numAuthorities); - } - if (numAdditionals > 0) - { - buf.append(",additionals="); - buf.append(numAdditionals); - } - buf.append(",\nnames=" + names); - buf.append(",\nauthorativeAnswers=" + authorativeAnswers); - - buf.append("]"); - return buf.toString(); - } - -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSQuestion.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSQuestion.java deleted file mode 100644 index f6abaa7..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSQuestion.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.util.logging.*; - -/** - * A DNS question. - * - * @version %I%, %G% - * @author Arthur van Hoff - */ -public final class DNSQuestion - extends DNSEntry -{ - private static Logger logger = - Logger.getLogger(DNSQuestion.class.toString()); - - /** - * Create a question. - * @param name - * @param type - * @param clazz - */ - public DNSQuestion(String name, int type, int clazz) - { - super(name, type, clazz); - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - /** - * Check if this question is answered by a given DNS record. - */ - boolean answeredBy(DNSRecord rec) - { - return (clazz == rec.clazz) && - ((type == rec.type) || - (type == DNSConstants.TYPE_ANY)) && - name.equals(rec.name); - } - - /** - * For debugging only. - */ - @Override - public String toString() - { - return toString("question", null); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSRecord.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSRecord.java deleted file mode 100644 index 673bbc4..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSRecord.java +++ /dev/null @@ -1,796 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.io.*; -import java.net.*; -import java.util.logging.*; - -/** - * DNS record - * - * @version %I%, %G% - * @author Arthur van Hoff, Rick Blair, Werner Randelshofer, Pierre Frisch - */ -public abstract class DNSRecord extends DNSEntry -{ - private static Logger logger = - Logger.getLogger(DNSRecord.class.toString()); - int ttl; - private long created; - - /** - * Create a DNSRecord with a name, type, clazz, and ttl. - */ - DNSRecord(String name, int type, int clazz, int ttl) - { - super(name, type, clazz); - this.ttl = ttl; - this.created = System.currentTimeMillis(); - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - /** - * True if this record is the same as some other record. - * @param other obj to be compared to. - */ - @Override - public boolean equals(Object other) - { - return (other instanceof DNSRecord) && sameAs((DNSRecord) other); - } - - /** - * True if this record is the same as some other record. - */ - boolean sameAs(DNSRecord other) - { - return super.equals(other) && sameValue(other); - } - - /** - * True if this record has the same value as some other record. - */ - abstract boolean sameValue(DNSRecord other); - - /** - * True if this record has the same type as some other record. - */ - boolean sameType(DNSRecord other) - { - return type == other.type; - } - - /** - * Handles a query represented by this record. - * - * @return Returns true if a conflict with one of the services registered - * with JmDNS or with the hostname occured. - */ - abstract boolean handleQuery(JmDNS dns, long expirationTime); - - /** - * Handles a responserepresented by this record. - * - * @return Returns true if a conflict with one of the services registered - * with JmDNS or with the hostname occured. - */ - abstract boolean handleResponse(JmDNS dns); - - /** - * Adds this as an answer to the provided outgoing datagram. - */ - abstract DNSOutgoing addAnswer(JmDNS dns, DNSIncoming in, InetAddress addr, - int port, DNSOutgoing out) - throws IOException; - - /** - * True if this record is suppressed by the answers in a message. - */ - boolean suppressedBy(DNSIncoming msg) - { - try - { - for (int i = msg.numAnswers; i-- > 0;) - { - if (suppressedBy(msg.answers.get(i))) - { - return true; - } - } - return false; - } - catch (ArrayIndexOutOfBoundsException e) - { - logger.log(Level.WARNING, - "suppressedBy() message " + msg + " exception ", e); - // msg.print(true); - return false; - } - } - - /** - * True if this record would be supressed by an answer. - * This is the case if this record would not have a - * significantly longer TTL. - */ - boolean suppressedBy(DNSRecord other) - { - if (sameAs(other) && (other.ttl > ttl / 2)) - { - return true; - } - return false; - } - - /** - * Get the expiration time of this record. - */ - long getExpirationTime(int percent) - { - return created + (percent * ttl * 10L); - } - - /** - * Get the remaining TTL for this record. - */ - int getRemainingTTL(long now) - { - return (int) Math.max(0, (getExpirationTime(100) - now) / 1000); - } - - /** - * Check if the record is expired. - */ - boolean isExpired(long now) - { - return getExpirationTime(100) <= now; - } - - /** - * Check if the record is stale, ie it has outlived - * more than half of its TTL. - */ - boolean isStale(long now) - { - return getExpirationTime(50) <= now; - } - - /** - * Reset the TTL of a record. This avoids having to - * update the entire record in the cache. - */ - void resetTTL(DNSRecord other) - { - created = other.created; - ttl = other.ttl; - } - - /** - * Write this record into an outgoing message. - */ - abstract void write(DNSOutgoing out) throws IOException; - - /** - * Address record. - */ - static class Address extends DNSRecord - { - private static Logger logger = - Logger.getLogger(Address.class.toString()); - InetAddress addr; - - Address(String name, int type, int clazz, int ttl, InetAddress addr) - { - super(name, type, clazz, ttl); - this.addr = addr; - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - Address(String name, int type, int clazz, int ttl, byte[] rawAddress) - { - super(name, type, clazz, ttl); - try - { - this.addr = InetAddress.getByAddress(rawAddress); - } - catch (UnknownHostException exception) - { - logger.log(Level.WARNING, "Address() exception ", exception); - } - } - - @Override - void write(DNSOutgoing out) throws IOException - { - if (addr != null) - { - byte[] buffer = addr.getAddress(); - if (DNSConstants.TYPE_A == type) - { - // If we have a type A records we should - // answer with a IPv4 address - if (addr instanceof Inet4Address) - { - // All is good - } - else - { - // Get the last four bytes - byte[] tempbuffer = buffer; - buffer = new byte[4]; - System.arraycopy(tempbuffer, 12, buffer, 0, 4); - } - } - else - { - // If we have a type AAAA records we should - // answer with a IPv6 address - if (addr instanceof Inet4Address) - { - byte[] tempbuffer = buffer; - buffer = new byte[16]; - for (int i = 0; i < 16; i++) - { - if (i < 11) - { - buffer[i] = tempbuffer[i - 12]; - } - else - { - buffer[i] = 0; - } - } - } - } - int length = buffer.length; - out.writeBytes(buffer, 0, length); - } - } - - boolean same(DNSRecord other) - { - return ((sameName(other)) && ((sameValue(other)))); - } - - boolean sameName(DNSRecord other) - { - return name.equalsIgnoreCase(((Address) other).name); - } - - @Override - boolean sameValue(DNSRecord other) - { - return addr.equals(((Address) other).getAddress()); - } - - InetAddress getAddress() - { - return addr; - } - - /** - * Creates a byte array representation of this record. - * This is needed for tie-break tests according to - * draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2. - */ - private byte[] toByteArray() - { - try - { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - DataOutputStream dout = new DataOutputStream(bout); - dout.write(name.getBytes("UTF8")); - dout.writeShort(type); - dout.writeShort(clazz); - //dout.writeInt(len); - byte[] buffer = addr.getAddress(); - for (int i = 0; i < buffer.length; i++) - { - dout.writeByte(buffer[i]); - } - dout.close(); - return bout.toByteArray(); - } - catch (IOException e) - { - throw new InternalError(); - } - } - - /** - * Does a lexicographic comparison of the byte array representation - * of this record and that record. - * This is needed for tie-break tests according to - * draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2. - */ - private int lexCompare(DNSRecord.Address that) - { - byte[] thisBytes = this.toByteArray(); - byte[] thatBytes = that.toByteArray(); - for ( int i = 0, n = Math.min(thisBytes.length, thatBytes.length); - i < n; - i++) - { - if (thisBytes[i] > thatBytes[i]) - { - return 1; - } - else - { - if (thisBytes[i] < thatBytes[i]) - { - return -1; - } - } - } - return thisBytes.length - thatBytes.length; - } - - /** - * Does the necessary actions, when this as a query. - */ - @Override - boolean handleQuery(JmDNS dns, long expirationTime) - { - DNSRecord.Address dnsAddress = - dns.getLocalHost().getDNSAddressRecord(this); - if (dnsAddress != null) - { - if (dnsAddress.sameType(this) && - dnsAddress.sameName(this) && - (!dnsAddress.sameValue(this))) - { - logger.finer( - "handleQuery() Conflicting probe detected. dns state " + - dns.getState() + - " lex compare " + lexCompare(dnsAddress)); - // Tie-breaker test - if (dns.getState().isProbing() && lexCompare(dnsAddress) >= 0) - { - // We lost the tie-break. We have to choose a different name. - dns.getLocalHost().incrementHostName(); - dns.getCache().clear(); - for (ServiceInfo info : dns.services.values()) - info.revertState(); - } - dns.revertState(); - return true; - } - } - return false; - } - - /** - * Does the necessary actions, when this as a response. - */ - @Override - boolean handleResponse(JmDNS dns) - { - DNSRecord.Address dnsAddress = - dns.getLocalHost().getDNSAddressRecord(this); - if (dnsAddress != null) - { - if (dnsAddress.sameType(this) && - dnsAddress.sameName(this) && - (!dnsAddress.sameValue(this))) - { - logger.finer("handleResponse() Denial detected"); - - if (dns.getState().isProbing()) - { - dns.getLocalHost().incrementHostName(); - dns.getCache().clear(); - for (ServiceInfo info : dns.services.values()) - info.revertState(); - } - dns.revertState(); - return true; - } - } - return false; - } - - @Override - DNSOutgoing addAnswer(JmDNS dns, - DNSIncoming in, - InetAddress addr, - int port, - DNSOutgoing out) - throws IOException - { - return out; - } - - @Override - public String toString() - { - return toString(" address '" + - (addr != null ? addr.getHostAddress() : "null") + "'"); - } - - } - - /** - * Pointer record. - */ - static class Pointer extends DNSRecord - { - private static Logger logger = - Logger.getLogger(Pointer.class.toString()); - String alias; - - Pointer(String name, int type, int clazz, int ttl, String alias) - { - super(name, type, clazz, ttl); - this.alias = alias; - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - @Override - void write(DNSOutgoing out) throws IOException - { - out.writeName(alias); - } - - @Override - boolean sameValue(DNSRecord other) - { - return alias.equals(((Pointer) other).alias); - } - - @Override - boolean handleQuery(JmDNS dns, long expirationTime) - { - // Nothing to do (?) - // I think there is no possibility - // for conflicts for this record type? - return false; - } - - @Override - boolean handleResponse(JmDNS dns) - { - // Nothing to do (?) - // I think there is no possibility for conflicts for this record type? - return false; - } - - String getAlias() - { - return alias; - } - - @Override - DNSOutgoing addAnswer(JmDNS dns, - DNSIncoming in, - InetAddress addr, - int port, - DNSOutgoing out) - throws IOException - { - return out; - } - - @Override - public String toString() - { - return toString(alias); - } - } - - static class Text extends DNSRecord - { - private static Logger logger = - Logger.getLogger(Text.class.toString()); - byte text[]; - - Text(String name, int type, int clazz, int ttl, byte text[]) - { - super(name, type, clazz, ttl); - this.text = text; - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - @Override - void write(DNSOutgoing out) throws IOException - { - out.writeBytes(text, 0, text.length); - } - - @Override - boolean sameValue(DNSRecord other) - { - Text txt = (Text) other; - if (txt.text.length != text.length) - { - return false; - } - for (int i = text.length; i-- > 0;) - { - if (txt.text[i] != text[i]) - { - return false; - } - } - return true; - } - - @Override - boolean handleQuery(JmDNS dns, long expirationTime) - { - // Nothing to do (?) - // I think there is no possibility for conflicts for this record type? - return false; - } - - @Override - boolean handleResponse(JmDNS dns) - { - // Nothing to do (?) - // Shouldn't we care if we get a conflict at this level? - /* - ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase()); - if (info != null) - { - if (! Arrays.equals(text,info.text)) - { - info.revertState(); - return true; - } - } - */ - return false; - } - - @Override - DNSOutgoing addAnswer(JmDNS dns, - DNSIncoming in, - InetAddress addr, - int port, - DNSOutgoing out) - throws IOException - { - return out; - } - - @Override - public String toString() - { - return toString((text.length > 10) ? - new String(text, 0, 7) + "..." : - new String(text)); - } - } - - /** - * Service record. - */ - static class Service extends DNSRecord - { - private static Logger logger = - Logger.getLogger(Service.class.toString()); - int priority; - int weight; - int port; - String server; - - Service(String name, - int type, - int clazz, - int ttl, - int priority, - int weight, - int port, - String server) - { - super(name, type, clazz, ttl); - this.priority = priority; - this.weight = weight; - this.port = port; - this.server = server; - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - @Override - void write(DNSOutgoing out) throws IOException - { - out.writeShort(priority); - out.writeShort(weight); - out.writeShort(port); - out.writeName(server); - } - - private byte[] toByteArray() - { - try - { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - DataOutputStream dout = new DataOutputStream(bout); - dout.write(name.getBytes("UTF8")); - dout.writeShort(type); - dout.writeShort(clazz); - //dout.writeInt(len); - dout.writeShort(priority); - dout.writeShort(weight); - dout.writeShort(port); - dout.write(server.getBytes("UTF8")); - dout.close(); - return bout.toByteArray(); - } - catch (IOException e) - { - throw new InternalError(); - } - } - - private int lexCompare(DNSRecord.Service that) - { - byte[] thisBytes = this.toByteArray(); - byte[] thatBytes = that.toByteArray(); - for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); - i < n; - i++) - { - if (thisBytes[i] > thatBytes[i]) - { - return 1; - } - else - { - if (thisBytes[i] < thatBytes[i]) - { - return -1; - } - } - } - return thisBytes.length - thatBytes.length; - } - - @Override - boolean sameValue(DNSRecord other) - { - Service s = (Service) other; - return (priority == s.priority) && - (weight == s.weight) && - (port == s.port) && - server.equals(s.server); - } - - @Override - boolean handleQuery(JmDNS dns, long expirationTime) - { - ServiceInfo info = dns.services.get(name.toLowerCase()); - if (info != null && - (port != info.port || - !server.equalsIgnoreCase(dns.getLocalHost().getName()))) - { - logger.finer("handleQuery() Conflicting probe detected"); - - // Tie breaker test - if (info.getState().isProbing() && - lexCompare(new DNSRecord.Service( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, - DNSConstants.DNS_TTL, - info.priority, - info.weight, - info.port, - dns.getLocalHost().getName())) >= 0) - { - // We lost the tie break - String oldName = info.getQualifiedName().toLowerCase(); - info.setName(dns.incrementName(info.getName())); - dns.services.remove(oldName); - dns.services.put(info.getQualifiedName().toLowerCase(), info); - logger.finer( - "handleQuery() Lost tie break: new unique name chosen:" + info.getName()); - - } - info.revertState(); - return true; - - } - return false; - } - - @Override - boolean handleResponse(JmDNS dns) - { - ServiceInfo info = dns.services.get(name.toLowerCase()); - if (info != null && - (port != info.port || !server.equalsIgnoreCase(dns.getLocalHost().getName()))) - { - logger.finer("handleResponse() Denial detected"); - - if (info.getState().isProbing()) - { - String oldName = info.getQualifiedName().toLowerCase(); - info.setName(dns.incrementName(info.getName())); - dns.services.remove(oldName); - dns.services.put(info.getQualifiedName().toLowerCase(), info); - logger.finer( - "handleResponse() New unique name chose:" + info.getName()); - - } - info.revertState(); - return true; - } - return false; - } - - @Override - DNSOutgoing addAnswer(JmDNS dns, - DNSIncoming in, - InetAddress addr, - int port, - DNSOutgoing out) - throws IOException - { - ServiceInfo info = dns.services.get(name.toLowerCase()); - if (info != null) - { - if (this.port == info.port != server.equals(dns.getLocalHost().getName())) - { - return dns.addAnswer(in, addr, port, out, - new DNSRecord.Service( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, - DNSConstants.DNS_TTL, - info.priority, - info.weight, - info.port, - dns.getLocalHost().getName())); - } - } - return out; - } - - @Override - public String toString() - { - return toString(server + ":" + port); - } - } - - public String toString(String other) - { - return toString("record", ttl + "/" + - getRemainingTTL(System.currentTimeMillis()) -// + "," + other - ); - } -} - diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSState.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSState.java deleted file mode 100644 index 5016050..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/DNSState.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.util.*; -import java.util.logging.*; - -/** - * DNSState defines the possible states for services registered with JmDNS. - * - * @author Werner Randelshofer, Rick Blair - * @version 1.0 May 23, 2004 Created. - */ -public class DNSState - implements Comparable<DNSState> -{ - private static Logger logger = - Logger.getLogger(DNSState.class.toString()); - - private final String name; - - /** - * Ordinal of next state to be created. - */ - private static int nextOrdinal = 0; - /** - * Assign an ordinal to this state. - */ - private final int ordinal = nextOrdinal++; - /** - * Logical sequence of states. - * The sequence is consistent with the ordinal of a state. - * This is used for advancing through states. - */ - private final static ArrayList<DNSState> sequence - = new ArrayList<DNSState>(); - - private DNSState(String name) - { - this.name = name; - sequence.add(this); - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - @Override - public final String toString() - { - return name; - } - - public static final DNSState PROBING_1 = new DNSState("probing 1"); - public static final DNSState PROBING_2 = new DNSState("probing 2"); - public static final DNSState PROBING_3 = new DNSState("probing 3"); - public static final DNSState ANNOUNCING_1 = new DNSState("announcing 1"); - public static final DNSState ANNOUNCING_2 = new DNSState("announcing 2"); - public static final DNSState ANNOUNCED = new DNSState("announced"); - public static final DNSState CANCELED = new DNSState("canceled"); - - /** - * Returns the next advanced state. - * In general, this advances one step in the following sequence: PROBING_1, - * PROBING_2, PROBING_3, ANNOUNCING_1, ANNOUNCING_2, ANNOUNCED. - * Does not advance for ANNOUNCED and CANCELED state. - * @return Returns the next advanced state. - */ - public final DNSState advance() - { - return (isProbing() || isAnnouncing()) ? - sequence.get(ordinal + 1) : - this; - } - - /** - * Returns to the next reverted state. - * All states except CANCELED revert to PROBING_1. - * Status CANCELED does not revert. - * @return Returns to the next reverted state. - */ - public final DNSState revert() - { - return (this == CANCELED) ? this : PROBING_1; - } - - /** - * Returns true, if this is a probing state. - * @return Returns true, if this is a probing state. - */ - public boolean isProbing() - { - return compareTo(PROBING_1) >= 0 && compareTo(PROBING_3) <= 0; - } - - /** - * Returns true, if this is an announcing state. - * @return Returns true, if this is an announcing state. - */ - public boolean isAnnouncing() - { - return compareTo(ANNOUNCING_1) >= 0 && compareTo(ANNOUNCING_2) <= 0; - } - - /** - * Returns true, if this is an announced state. - * @return Returns true, if this is an announced state. - */ - public boolean isAnnounced() - { - return compareTo(ANNOUNCED) == 0; - } - - /** - * Compares two states. - * The states compare as follows: - * PROBING_1 < PROBING_2 < PROBING_3 < ANNOUNCING_1 < - * ANNOUNCING_2 < RESPONDING < ANNOUNCED < CANCELED. - */ - public int compareTo(DNSState state) - { - return ordinal - state.ordinal; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/HostInfo.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/HostInfo.java deleted file mode 100644 index 644afc9..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/HostInfo.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.net.*; -import java.util.logging.*; - - -/** - * HostInfo information on the local host to be able to cope with change of addresses. - * - * @version %I%, %G% - * @author Pierre Frisch, Werner Randelshofer - */ -class HostInfo -{ - private static Logger logger = Logger.getLogger(HostInfo.class.toString()); - protected String name; - protected InetAddress address; - protected NetworkInterface interfaze; - /** - * This is used to create a unique name for the host name. - */ - private int hostNameCount; - - public HostInfo(InetAddress address, String name) - { - super(); - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - - this.address = address; - this.name = name; - if (address != null) - { - try - { - interfaze = NetworkInterface.getByInetAddress(address); - } - catch (Exception exception) - { - // FIXME Shouldn't we take an action here? - logger.log(Level.WARNING, - "LocalHostInfo() exception ", exception); - } - } - } - - public String getName() - { - return name; - } - - public InetAddress getAddress() - { - return address; - } - - public NetworkInterface getInterface() - { - return interfaze; - } - - synchronized String incrementHostName() - { - hostNameCount++; - int plocal = name.indexOf(".local."); - int punder = name.lastIndexOf("-"); - name = name.substring(0, (punder == -1 ? plocal : punder)) + "-" + - hostNameCount + ".local."; - return name; - } - - boolean shouldIgnorePacket(DatagramPacket packet) - { - boolean result = false; - if (getAddress() != null) - { - InetAddress from = packet.getAddress(); - if (from != null) - { - if (from.isLinkLocalAddress() && - (!getAddress().isLinkLocalAddress())) - { - // Ignore linklocal packets on regular interfaces, unless this is - // also a linklocal interface. This is to avoid duplicates. This is - // a terrible hack caused by the lack of an API to get the address - // of the interface on which the packet was received. - result = true; - } - if (from.isLoopbackAddress() && - (!getAddress().isLoopbackAddress())) - { - // Ignore loopback packets on a regular interface unless this is - // also a loopback interface. - result = true; - } - } - } - return result; - } - - DNSRecord.Address getDNSAddressRecord(DNSRecord.Address address) - { - return (DNSConstants.TYPE_AAAA == address.type ? - getDNS6AddressRecord() : - getDNS4AddressRecord()); - } - - DNSRecord.Address getDNS4AddressRecord() - { - if ((getAddress() != null) && - ((getAddress() instanceof Inet4Address) || - ((getAddress() instanceof Inet6Address) && - (((Inet6Address) getAddress()).isIPv4CompatibleAddress())))) - { - return new DNSRecord.Address(getName(), - DNSConstants.TYPE_A, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, getAddress()); - } - return null; - } - - DNSRecord.Address getDNS6AddressRecord() - { - if ((getAddress() != null) && (getAddress() instanceof Inet6Address)) - { - return new DNSRecord.Address( - getName(), - DNSConstants.TYPE_AAAA, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - getAddress()); - } - return null; - } - - @Override - public String toString() - { - StringBuffer buf = new StringBuffer(); - buf.append("local host info["); - buf.append(getName() != null ? getName() : "no name"); - buf.append(", "); - buf.append(getInterface() != null ? - getInterface().getDisplayName() : - "???"); - buf.append(":"); - buf.append(getAddress() != null ? - getAddress().getHostAddress() : - "no address"); - buf.append("]"); - return buf.toString(); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/JmDNS.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/JmDNS.java deleted file mode 100644 index 96420ba..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/JmDNS.java +++ /dev/null @@ -1,3048 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.io.*; -import java.net.*; -import java.util.*; - -import net.java.sip.communicator.util.*; - -// REMIND: multiple IP addresses - -/** - * mDNS implementation in Java. - * - * @version %I%, %G% - * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, - * Werner Randelshofer, Pierre Frisch, Scott Lewis - * @author Christian Vincenot - */ -public class JmDNS -{ - private static final Logger logger - = Logger.getLogger(JmDNS.class); - - /** - * The version of JmDNS. - */ - public static String VERSION = "2.0"; - - /** - * This is the multicast group, we are listening to for - * multicast DNS messages. - */ - private InetAddress group; - /** - * This is our multicast socket. - */ - private MulticastSocket socket; - - /** - * Used to fix live lock problem on unregester. - */ - - protected boolean closed = false; - - /** - * Holds instances of JmDNS.DNSListener. - * Must by a synchronized collection, because it is updated from - * concurrent threads. - */ - private List<DNSListener> listeners; - /** - * Holds instances of ServiceListener's. - * Keys are Strings holding a fully qualified service type. - * Values are LinkedList's of ServiceListener's. - */ - private Map<String, List<ServiceListener>> serviceListeners; - /** - * Holds instances of ServiceTypeListener's. - */ - private List<ServiceTypeListener> typeListeners; - - - /** - * Cache for DNSEntry's. - */ - private DNSCache cache; - - /** - * This hashtable holds the services that have been registered. - * Keys are instances of String which hold an all lower-case version of the - * fully qualified service name. - * Values are instances of ServiceInfo. - */ - Map<String, ServiceInfo> services; - - /** - * This hashtable holds the service types that have been registered or - * that have been received in an incoming datagram. - * Keys are instances of String which hold an all lower-case version of the - * fully qualified service type. - * Values hold the fully qualified service type. - */ - Map<String, String> serviceTypes; - - /** - * Handle on the local host - */ - HostInfo localHost; - - private Thread incomingListener = null; - - /** - * Throttle count. - * This is used to count the overall number of probes sent by JmDNS. - * When the last throttle increment happened . - */ - private int throttle; - /** - * Last throttle increment. - */ - private long lastThrottleIncrement; - - /** - * The timer is used to dispatch all outgoing messages of JmDNS. - * It is also used to dispatch maintenance tasks for the DNS cache. - */ - private Timer timer; - - /** - * The source for random values. - * This is used to introduce random delays in responses. This reduces the - * potential for collisions on the network. - */ - private final static Random random = new Random(); - - /** - * This lock is used to coordinate processing of incoming and outgoing - * messages. This is needed, because the Rendezvous Conformance Test - * does not forgive race conditions. - */ - private Object ioLock = new Object(); - - /** - * If an incoming package which needs an answer is truncated, we store it - * here. We add more incoming DNSRecords to it, until the JmDNS.Responder - * timer picks it up. - * Remind: This does not work well with multiple planned answers for packages - * that came in from different clients. - */ - private DNSIncoming plannedAnswer; - - // State machine - /** - * The state of JmDNS. - * <p/> - * For proper handling of concurrency, this variable must be - * changed only using methods advanceState(), revertState() and cancel(). - */ - private DNSState state = DNSState.PROBING_1; - - /** - * Timer task associated to the host name. - * This is used to prevent from having multiple tasks associated to the host - * name at the same time. - */ - TimerTask task; - - /** - * This hashtable is used to maintain a list of service types being collected - * by this JmDNS instance. - * The key of the hashtable is a service type name, the value is an instance - * of JmDNS.ServiceCollector. - * - * @see #list - */ - private HashMap<String, ServiceCollector> serviceCollectors = new HashMap<String, ServiceCollector>(); - - /** - * Create an instance of JmDNS. - * @throws java.io.IOException - */ - public JmDNS() - throws IOException - { - //String SLevel = System.getProperty("jmdns.debug"); - - if (logger.isDebugEnabled()) - logger.debug("JmDNS instance created"); - try - { - InetAddress addr = InetAddress.getLocalHost(); - // [PJYF Oct 14 2004] Why do we disallow the loopback address? - init(addr.isLoopbackAddress() ? null : addr, addr.getHostName()); - } - catch (IOException exc) - { - logger.error("Failed to get a reference to localhost", exc); - init(null, "computer"); - } - } - - /** - * Create an instance of JmDNS and bind it to a - * specific network interface given its IP-address. - * @param addr - * @throws java.io.IOException - */ - public JmDNS(InetAddress addr) - throws IOException - { - try - { - init(addr, addr.getHostName()); - } - catch (IOException e) - { - init(null, "computer"); - } - } - - /** - * Initialize everything. - * - * @param address The interface to which JmDNS binds to. - * @param name The host name of the interface. - */ - private void init(InetAddress address, String name) throws IOException - { - // A host name with "." is illegal. - // so strip off everything and append .local. - int idx = name.indexOf("."); - if (idx > 0) - { - name = name.substring(0, idx); - } - name += ".local."; - // localHost to IP address binding - localHost = new HostInfo(address, name); - - cache = new DNSCache(100); - - listeners = Collections.synchronizedList(new ArrayList<DNSListener>()); - serviceListeners = new HashMap<String, List<ServiceListener>>(); - typeListeners = new ArrayList<ServiceTypeListener>(); - - services = new Hashtable<String, ServiceInfo>(20); - serviceTypes = new Hashtable<String, String>(20); - - // REMIND: If I could pass in a name for the Timer thread, - // I would pass 'JmDNS.Timer'. - timer = new Timer(); - new RecordReaper().start(); - - incomingListener = new Thread( - new SocketListener(), "JmDNS.SocketListener"); - incomingListener.setDaemon(true); - // Bind to multicast socket - openMulticastSocket(localHost); - start(services.values()); - } - - private void start(Collection<ServiceInfo> serviceInfos) - { - state = DNSState.PROBING_1; - incomingListener.start(); - new Prober().start(); - for (ServiceInfo serviceInfo : serviceInfos) - { - try - { - registerService(new ServiceInfo(serviceInfo)); - } - catch (Exception exception) - { - logger.warn("start() Registration exception ", exception); - } - } - } - - private void openMulticastSocket(HostInfo hostInfo) throws IOException - { - if (group == null) - { - group = InetAddress.getByName(DNSConstants.MDNS_GROUP); - } - if (socket != null) - { - this.closeMulticastSocket(); - } - socket = new MulticastSocket(DNSConstants.MDNS_PORT); - if ((hostInfo != null) && (localHost.getInterface() != null)) - { - socket.setNetworkInterface(hostInfo.getInterface()); - } - socket.setTimeToLive(255); - socket.joinGroup(group); - } - - private void closeMulticastSocket() - { - if (logger.isDebugEnabled()) - logger.debug("closeMulticastSocket()"); - if (socket != null) - { - // close socket - try - { - socket.leaveGroup(group); - socket.close(); - if (incomingListener != null) - { - incomingListener.join(); - } - } - catch (Exception exception) - { - logger.warn("closeMulticastSocket() Close socket exception ", - exception); - } - socket = null; - } - } - - // State machine - /** - * Sets the state and notifies all objects that wait on JmDNS. - */ - synchronized void advanceState() - { - state = state.advance(); - notifyAll(); - } - - /** - * Sets the state and notifies all objects that wait on JmDNS. - */ - synchronized void revertState() - { - state = state.revert(); - notifyAll(); - } - - /** - * Sets the state and notifies all objects that wait on JmDNS. - */ - synchronized void cancel() - { - state = DNSState.CANCELED; - notifyAll(); - } - - /** - * Returns the current state of this info. - */ - DNSState getState() - { - return state; - } - - - /** - * Return the DNSCache associated with the cache variable - */ - DNSCache getCache() - { - return cache; - } - - /** - * Return the HostName associated with this JmDNS instance. - * Note: May not be the same as what started. The host name is subject to - * negotiation. - * @return Return the HostName associated with this JmDNS instance. - */ - public String getHostName() - { - return localHost.getName(); - } - - public HostInfo getLocalHost() - { - return localHost; - } - - /** - * Return the address of the interface to which this instance of JmDNS is - * bound. - * @return Return the address of the interface to which this instance - * of JmDNS is bound. - * @throws java.io.IOException - */ - public InetAddress getInterface() - throws IOException - { - return socket.getInterface(); - } - - /** - * Get service information. If the information is not cached, the method - * will block until updated information is received. - * <p/> - * Usage note: Do not call this method from the AWT event dispatcher thread. - * You will make the user interface unresponsive. - * - * @param type fully qualified service type, - * such as <code>_http._tcp.local.</code> . - * @param name unqualified service name, such as <code>foobar</code> . - * @return null if the service information cannot be obtained - */ - public ServiceInfo getServiceInfo(String type, String name) - { - return getServiceInfo(type, name, 3 * 1000); - } - - /** - * Get service information. If the information is not cached, the method - * will block for the given timeout until updated information is received. - * <p/> - * Usage note: If you call this method from the AWT event dispatcher thread, - * use a small timeout, or you will make the user interface unresponsive. - * - * @param type full qualified service type, - * such as <code>_http._tcp.local.</code> . - * @param name unqualified service name, such as <code>foobar</code> . - * @param timeout timeout in milliseconds - * @return null if the service information cannot be obtained - */ - public ServiceInfo getServiceInfo(String type, String name, int timeout) - { - ServiceInfo info = new ServiceInfo(type, name); - new ServiceInfoResolver(info).start(); - - try - { - long end = System.currentTimeMillis() + timeout; - long delay; - synchronized (info) - { - while (!info.hasData() && - (delay = end - System.currentTimeMillis()) > 0) - { - info.wait(delay); - } - } - } - catch (InterruptedException e) - { - // empty - } - - return (info.hasData()) ? info : null; - } - - /** - * Request service information. The information about the service is - * requested and the ServiceListener.resolveService method is called as soon - * as it is available. - * <p/> - * Usage note: Do not call this method from the AWT event dispatcher thread. - * You will make the user interface unresponsive. - * - * @param type full qualified service type, - * such as <code>_http._tcp.local.</code> . - * @param name unqualified service name, such as <code>foobar</code> . - */ - public void requestServiceInfo(String type, String name) - { - requestServiceInfo(type, name, 3 * 1000); - } - - /** - * Request service information. The information about the service - * is requested and the ServiceListener.resolveService method is - * called as soon as it is available. - * - * @param type full qualified service type, - * such as <code>_http._tcp.local.</code> . - * @param name unqualified service name, such as <code>foobar</code> . - * @param timeout timeout in milliseconds - */ - public void requestServiceInfo(String type, String name, int timeout) - { - registerServiceType(type); - ServiceInfo info = new ServiceInfo(type, name); - new ServiceInfoResolver(info).start(); - - try - { - long end = System.currentTimeMillis() + timeout; - long delay; - synchronized (info) - { - while (!info.hasData() && - (delay = end - System.currentTimeMillis()) > 0) - { - info.wait(delay); - } - } - } - catch (InterruptedException e) - { - // empty - } - } - - void handleServiceResolved(ServiceInfo info) - { - List<ServiceListener> list = serviceListeners.get(info.type.toLowerCase()); - if (list != null) - { - ServiceEvent event = - new ServiceEvent(this, info.type, info.getName(), info); - // Iterate on a copy in case listeners will modify it - List<ServiceListener> listCopy - = new ArrayList<ServiceListener> (list); - for (ServiceListener serviceListener : listCopy) - serviceListener.serviceResolved(event); - } - } - - /** - * Listen for service types. - * - * @param listener listener for service types - * @throws java.io.IOException - */ - public void addServiceTypeListener(ServiceTypeListener listener) - throws IOException - { - synchronized (this) - { - typeListeners.remove(listener); - typeListeners.add(listener); - } - - // report cached service types - for (String serviceType : serviceTypes.values()) - { - listener.serviceTypeAdded( - new ServiceEvent(this, serviceType, null, null)); - } - - new TypeResolver().start(); - } - - /** - * Remove listener for service types. - * - * @param listener listener for service types - */ - public void removeServiceTypeListener(ServiceTypeListener listener) - { - synchronized (this) - { - typeListeners.remove(listener); - } - } - - /** - * Listen for services of a given type. The type has to be a fully - * qualified type name such as <code>_http._tcp.local.</code>. - * - * @param type full qualified service type, - * such as <code>_http._tcp.local.</code>. - * @param listener listener for service updates - */ - public void addServiceListener(String type, ServiceListener listener) - { - String lotype = type.toLowerCase(); - removeServiceListener(lotype, listener); - List<ServiceListener> list = null; - synchronized (this) - { - list = serviceListeners.get(lotype); - if (list == null) - { - list = Collections.synchronizedList(new LinkedList<ServiceListener>()); - serviceListeners.put(lotype, list); - } - list.add(listener); - } - - // report cached service types - for (Iterator<DNSCache.CacheNode> i = cache.iterator(); i.hasNext();) - { - for (DNSCache.CacheNode n = i.next(); n != null; n = n.next()) - { - DNSRecord rec = (DNSRecord) n.getValue(); - if (rec.type == DNSConstants.TYPE_SRV) - { - if (rec.name.endsWith(type)) - { - listener.serviceAdded( - new ServiceEvent( - this, - type, - toUnqualifiedName(type, rec.name), - null)); - } - } - } - } - new ServiceResolver(type).start(); - } - - /** - * Remove listener for services of a given type. - * - * @param type of listener to be removed - * @param listener listener for service updates - */ - public void removeServiceListener(String type, ServiceListener listener) - { - type = type.toLowerCase(); - List<ServiceListener> list = serviceListeners.get(type); - if (list != null) - { - synchronized (this) - { - list.remove(listener); - if (list.size() == 0) - { - serviceListeners.remove(type); - } - } - } - } - - /** - * Register a service. The service is registered - * for access by other jmdns clients. - * The name of the service may be changed to make it unique. - * @param info of service - * @throws java.io.IOException - */ - public void registerService(ServiceInfo info) throws IOException - { - registerServiceType(info.type); - - // bind the service to this address - info.server = localHost.getName(); - info.addr = localHost.getAddress(); - - synchronized (this) - { - makeServiceNameUnique(info); - services.put(info.getQualifiedName().toLowerCase(), info); - } - - new /*Service*/Prober().start(); - try - { - synchronized (info) - { - while (info.getState().compareTo(DNSState.ANNOUNCED) < 0) - { - info.wait(); - } - } - } - catch (InterruptedException e) - { - logger.error(e.getMessage(), e); - } - if (logger.isDebugEnabled()) - logger.debug("registerService() JmDNS registered service as " + info); - } - - /** - * Unregister a service. The service should have been registered. - * @param info of service - */ - public void unregisterService(ServiceInfo info) - { - synchronized (this) - { - services.remove(info.getQualifiedName().toLowerCase()); - } - info.cancel(); - - // Note: We use this lock object to synchronize on it. - // Synchronizing on another object (e.g. the ServiceInfo) does - // not make sense, because the sole purpose of the lock is to - // wait until the canceler has finished. If we synchronized on - // the ServiceInfo or on the Canceler, we would block all - // accesses to synchronized methods on that object. This is not - // what we want! - Object lock = new Object(); - new Canceler(info, lock).start(); - - // Remind: We get a deadlock here, if the Canceler does not run! - try - { - synchronized (lock) - { - lock.wait(); - } - } - catch (InterruptedException e) - { - // empty - } - } - - /** - * Unregister all services. - */ - public void unregisterAllServices() - { - if (logger.isDebugEnabled()) - logger.debug("unregisterAllServices()"); - if (services.size() == 0) - { - return; - } - - Collection<ServiceInfo> list; - synchronized (this) - { - list = new LinkedList<ServiceInfo>(services.values()); - services.clear(); - } - for (Iterator<ServiceInfo> iterator = list.iterator(); iterator.hasNext();) - { - iterator.next().cancel(); - } - - - Object lock = new Object(); - new Canceler(list, lock).start(); - // Remind: We get a livelock here, if the Canceler does not run! - try - { - synchronized (lock) - { - if (!closed) - { - lock.wait(); - } - } - } - catch (InterruptedException e) - { - // empty - } - } - - /** - * Register a service type. If this service type was not already known, - * all service listeners will be notified of the new service type. - * Service types are automatically registered as they are discovered. - * @param type of service - */ - public void registerServiceType(String type) - { - String name = type.toLowerCase(); - if (serviceTypes.get(name) == null) - { - if ((type.indexOf("._mdns._udp.") < 0) && - !type.endsWith(".in-addr.arpa.")) - { - Collection<ServiceTypeListener> list; - synchronized (this) - { - serviceTypes.put(name, type); - list = new LinkedList<ServiceTypeListener>(typeListeners); - } - for (ServiceTypeListener listener : list) - listener - .serviceTypeAdded( - new ServiceEvent(this, type, null, null)); - } - } - } - - /** - * Generate a possibly unique name for a service using the information we - * have in the cache. - * - * @return returns true, if the name of the service info had to be changed. - */ - private boolean makeServiceNameUnique(ServiceInfo info) - { - String originalQualifiedName = info.getQualifiedName(); - long now = System.currentTimeMillis(); - - boolean collision; - do - { - collision = false; - - // Check for collision in cache - for (DNSCache.CacheNode j = cache.find( - info.getQualifiedName().toLowerCase()); - j != null; - j = j.next()) - { - DNSRecord a = (DNSRecord) j.getValue(); - if ((a.type == DNSConstants.TYPE_SRV) && !a.isExpired(now)) - { - DNSRecord.Service s = (DNSRecord.Service) a; - if (s.port != info.port || !s.server.equals(localHost.getName())) - { - if (logger.isDebugEnabled()) - logger.debug("makeServiceNameUnique() " + - "JmDNS.makeServiceNameUnique srv collision:" + - a + " s.server=" + s.server + " " + - localHost.getName() + " equals:" + - (s.server.equals(localHost.getName()))); - info.setName(incrementName(info.getName())); - collision = true; - break; - } - } - } - - // Check for collision with other service infos published by JmDNS - Object selfService = - services.get(info.getQualifiedName().toLowerCase()); - if (selfService != null && selfService != info) - { - info.setName(incrementName(info.getName())); - collision = true; - } - } - while (collision); - - return !(originalQualifiedName.equals(info.getQualifiedName())); - } - - String incrementName(String name) - { - try - { - int l = name.lastIndexOf('('); - int r = name.lastIndexOf(')'); - if ((l >= 0) && (l < r)) - { - name = name.substring(0, l) + "(" + - (Integer.parseInt(name.substring(l + 1, r)) + 1) + ")"; - } - else - { - name += " (2)"; - } - } - catch (NumberFormatException e) - { - name += " (2)"; - } - return name; - } - - /** - * Add a listener for a question. The listener will receive updates - * of answers to the question as they arrive, or from the cache if they - * are already available. - * @param listener to be added - * @param question - which the listener is responsible for. - */ - public void addListener(DNSListener listener, DNSQuestion question) - { - long now = System.currentTimeMillis(); - - // add the new listener - synchronized (this) - { - listeners.add(listener); - } - - // report existing matched records - if (question != null) - { - for (DNSCache.CacheNode i = cache.find(question.name); - i != null; - i = i.next()) - { - DNSRecord c = (DNSRecord) i.getValue(); - if (question.answeredBy(c) && !c.isExpired(now)) - { - listener.updateRecord(this, now, c); - } - } - } - } - - /** - * Remove a listener from all outstanding questions. - * The listener will no longer receive any updates. - */ - void removeListener(DNSListener listener) - { - synchronized (this) - { - listeners.remove(listener); - } - } - - - // Remind: Method updateRecord should receive a better name. - /** - * Notify all listeners that a record was updated. - */ - void updateRecord(long now, DNSRecord rec) - { - // We do not want to block the entire DNS - // while we are updating the record for each listener (service info) - List<DNSListener> listenerList = null; - synchronized (this) - { - listenerList = new ArrayList<DNSListener>(listeners); - } - - //System.out.println("OUT OF MUTEX!!!!!"); - - for (DNSListener listener : listenerList) - listener.updateRecord(this, now, rec); - - if (rec.type == DNSConstants.TYPE_PTR || - rec.type == DNSConstants.TYPE_SRV) - { - List<ServiceListener> serviceListenerList = null; - synchronized (this) - { - serviceListenerList = serviceListeners.get(rec.name.toLowerCase()); - // Iterate on a copy in case listeners will modify it - if (serviceListenerList != null) - { - serviceListenerList = new ArrayList<ServiceListener>(serviceListenerList); - } - } - if (serviceListenerList != null) - { - boolean expired = rec.isExpired(now); - String type = rec.getName(); - String name = ((DNSRecord.Pointer) rec).getAlias(); - // DNSRecord old = (DNSRecord)services.get(name.toLowerCase()); - if (!expired) - { - // new record - ServiceEvent event = - new ServiceEvent( - this, - type, - toUnqualifiedName(type, name), - null); - for (Iterator<ServiceListener> iterator = serviceListenerList.iterator(); - iterator.hasNext();) - { - iterator.next().serviceAdded(event); - } - } - else - { - // expire record - ServiceEvent event = - new ServiceEvent( - this, - type, - toUnqualifiedName(type, name), - null); - for (Iterator<ServiceListener> iterator = serviceListenerList.iterator(); - iterator.hasNext();) - { - iterator.next().serviceRemoved(event); - } - } - } - } - } - - /** - * Handle an incoming response. Cache answers, and pass them on to - * the appropriate questions. - */ - private void handleResponse(DNSIncoming msg) - throws IOException - { - long now = System.currentTimeMillis(); - - boolean hostConflictDetected = false; - boolean serviceConflictDetected = false; - - if (logger.isTraceEnabled()) - logger.trace("JMDNS/handleResponse received " + - msg.answers.size()+ " messages"); - for (DNSRecord rec : msg.answers) - { - if (logger.isTraceEnabled()) - logger.trace("PRINT: "+ rec); - //cache.add(rec); - } - - for (DNSRecord rec : msg.answers) - { - boolean isInformative = false; - boolean expired = rec.isExpired(now); - - if (logger.isTraceEnabled()) - logger.trace("JMDNS received : " + rec + " expired: "+expired); - - // update the cache - DNSRecord c = (DNSRecord) cache.get(rec); - if (c != null) - { - if (logger.isTraceEnabled()) - logger.trace("JMDNS has found "+rec+" in cache"); - if (expired) - { - isInformative = true; - cache.remove(c); - } - else - { - /* Special case for SIP Communicator. - * We want to be informed if a cache entry is modified - */ -// if ((c.isUnique() -// && c.getType() == DNSConstants.TYPE_TXT -// && ((c.getClazz() & DNSConstants.CLASS_IN) != 0))) -// isInformative = true; -// c.resetTTL(rec); -// rec = c; - if (logger.isTraceEnabled()) - logger.trace( - new Boolean(c.isUnique()).toString() + - c.getType()+c.getClazz() + "/" + - DNSConstants.TYPE_TXT + " "+DNSConstants.CLASS_IN); - - if ((rec.isUnique() - && ((rec.getType() & DNSConstants.TYPE_TXT) != 0) - && ((rec.getClazz() & DNSConstants.CLASS_IN) != 0))) - { - if (logger.isTraceEnabled()) - logger.trace("UPDATING CACHE !! "); - isInformative = true; - cache.remove(c); - cache.add(rec); - } - else - { - c.resetTTL(rec); - rec = c; - } - } - } - else - { - if (!expired) - { - isInformative = true; - if (logger.isTraceEnabled()) - logger.trace("Adding "+rec+" to the cache"); - cache.add(rec); - } - } - switch (rec.type) - { - case DNSConstants.TYPE_PTR: - // handle _mdns._udp records - if (rec.getName().indexOf("._mdns._udp.") >= 0) - { - if (!expired && - rec.name.startsWith("_services._mdns._udp.")) - { - isInformative = true; - registerServiceType(((DNSRecord.Pointer)rec).alias); - } - continue; - } - registerServiceType(rec.name); - break; - } - - - if ((rec.getType() == DNSConstants.TYPE_A) || - (rec.getType() == DNSConstants.TYPE_AAAA)) - { - hostConflictDetected |= rec.handleResponse(this); - } - else - { - serviceConflictDetected |= rec.handleResponse(this); - } - - // notify the listeners - if (isInformative) - { - updateRecord(now, rec); - } - - - } - - if (hostConflictDetected || serviceConflictDetected) - { - new Prober().start(); - } - } - - /** - * Handle an incoming query. See if we can answer any part of it - * given our service infos. - */ - private void handleQuery(DNSIncoming in, InetAddress addr, int port) - throws IOException - { - // Track known answers - boolean hostConflictDetected = false; - boolean serviceConflictDetected = false; - long expirationTime = System.currentTimeMillis() + - DNSConstants.KNOWN_ANSWER_TTL; - for (DNSRecord answer : in.answers) - { - if ((answer.getType() == DNSConstants.TYPE_A) || - (answer.getType() == DNSConstants.TYPE_AAAA)) - { - hostConflictDetected |= - answer.handleQuery(this, expirationTime); - } - else - { - serviceConflictDetected |= - answer.handleQuery(this, expirationTime); - } - } - - if (plannedAnswer != null) - { - plannedAnswer.append(in); - } - else - { - if (in.isTruncated()) - { - plannedAnswer = in; - } - - new Responder(in, addr, port).start(); - } - - if (hostConflictDetected || serviceConflictDetected) - { - new Prober().start(); - } - } - - /** - * Add an answer to a question. Deal with the case when the - * outgoing packet overflows - */ - DNSOutgoing addAnswer(DNSIncoming in, - InetAddress addr, - int port, - DNSOutgoing out, - DNSRecord rec) - throws IOException - { - if (out == null) - { - out = new DNSOutgoing( - DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); - } - try - { - out.addAnswer(in, rec); - } - catch (IOException e) - { - out.flags |= DNSConstants.FLAGS_TC; - out.id = in.id; - out.finish(); - send(out); - - out = new DNSOutgoing( - DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); - out.addAnswer(in, rec); - } - return out; - } - - - /** - * Send an outgoing multicast DNS message. - */ - private void send(DNSOutgoing out) throws IOException - { - out.finish(); - if (!out.isEmpty()) - { - DatagramPacket packet = - new DatagramPacket( - out.data, out.off, group, DNSConstants.MDNS_PORT); - - try - { - DNSIncoming msg = new DNSIncoming(packet); - if (logger.isTraceEnabled()) - logger.trace("send() JmDNS out:" + msg.print(true)); - } - catch (IOException exc) - { - logger.error( - "send(DNSOutgoing) - JmDNS can not parse what it sends!!!", - exc); - } - socket.send(packet); - } - } - - /** - * Listen for multicast packets. - */ - class SocketListener implements Runnable - { - public void run() - { - try - { - byte buf[] = new byte[DNSConstants.MAX_MSG_ABSOLUTE]; - DatagramPacket packet = new DatagramPacket(buf, buf.length); - while (state != DNSState.CANCELED) - { - packet.setLength(buf.length); - socket.receive(packet); - if (state == DNSState.CANCELED) - { - break; - } - try - { - if (localHost.shouldIgnorePacket(packet)) - { - continue; - } - - DNSIncoming msg = new DNSIncoming(packet); - if (logger.isTraceEnabled()) - logger.trace("SocketListener.run() JmDNS in:" + - msg.print(true)); - - synchronized (ioLock) - { - if (msg.isQuery()) - { - if (packet.getPort() != DNSConstants.MDNS_PORT) - { - handleQuery(msg, - packet.getAddress(), - packet.getPort()); - } - handleQuery(msg, group, DNSConstants.MDNS_PORT); - } - else - { - handleResponse(msg); - } - } - } - catch (IOException e) - { - logger.warn( "run() exception ", e); - } - } - } - catch (IOException e) - { - if (state != DNSState.CANCELED) - { - logger.warn( "run() exception ", e); - recover(); - } - } - } - } - - - /** - * Periodicaly removes expired entries from the cache. - */ - private class RecordReaper extends TimerTask - { - public void start() - { - timer.schedule( this, - DNSConstants.RECORD_REAPER_INTERVAL, - DNSConstants.RECORD_REAPER_INTERVAL); - } - - @Override - public void run() - { - synchronized (JmDNS.this) - { - if (state == DNSState.CANCELED) - { - return; - } - if (logger.isTraceEnabled()) - logger.trace("run() JmDNS reaping cache"); - - // Remove expired answers from the cache - // ------------------------------------- - // To prevent race conditions, we defensively copy all cache - // entries into a list. - List<DNSEntry> list = new ArrayList<DNSEntry>(); - synchronized (cache) - { - for (Iterator<DNSCache.CacheNode> i = cache.iterator(); - i.hasNext();) - { - for (DNSCache.CacheNode n = i.next(); - n != null; - n = n.next()) - { - list.add(n.getValue()); - } - } - } - // Now, we remove them. - long now = System.currentTimeMillis(); - for (Iterator<DNSEntry> i = list.iterator(); i.hasNext();) - { - DNSRecord c = (DNSRecord)i.next(); - if (c.isExpired(now)) - { - updateRecord(now, c); - cache.remove(c); - } - } - } - } - } - - - /** - * The Prober sends three consecutive probes for all service infos - * that needs probing as well as for the host name. - * The state of each service info of the host name is advanced, - * when a probe has been sent for it. - * When the prober has run three times, it launches an Announcer. - * <p/> - * If a conflict during probes occurs, the affected service - * infos (and affected host name) are taken away from the prober. - * This eventually causes the prober tho cancel itself. - */ - private class Prober extends TimerTask - { - /** - * The state of the prober. - */ - DNSState taskState = DNSState.PROBING_1; - - public Prober() - { - // Associate the host name to this, if it needs probing - if (state == DNSState.PROBING_1) - { - task = this; - } - // Associate services to this, if they need probing - synchronized (JmDNS.this) - { - for (Iterator<ServiceInfo> iterator = services.values().iterator(); - iterator.hasNext();) - { - ServiceInfo info = iterator.next(); - if (info.getState() == DNSState.PROBING_1) - { - info.task = this; - } - } - } - } - - - public void start() - { - long now = System.currentTimeMillis(); - if (now - lastThrottleIncrement < - DNSConstants.PROBE_THROTTLE_COUNT_INTERVAL) - { - throttle++; - } - else - { - throttle = 1; - } - lastThrottleIncrement = now; - - if (state == DNSState.ANNOUNCED && - throttle < DNSConstants.PROBE_THROTTLE_COUNT) - { - timer.schedule(this, - random.nextInt(1 + DNSConstants.PROBE_WAIT_INTERVAL), - DNSConstants.PROBE_WAIT_INTERVAL); - } - else - { - timer.schedule(this, - DNSConstants.PROBE_CONFLICT_INTERVAL, - DNSConstants.PROBE_CONFLICT_INTERVAL); - } - } - - @Override - public boolean cancel() - { - // Remove association from host name to this - if (task == this) - { - task = null; - } - - // Remove associations from services to this - synchronized (JmDNS.this) - { - for (Iterator<ServiceInfo> i = services.values().iterator(); - i.hasNext();) - { - ServiceInfo info = i.next(); - if (info.task == this) - { - info.task = null; - } - } - } - - return super.cancel(); - } - - @Override - public void run() - { - synchronized (ioLock) - { - DNSOutgoing out = null; - try - { - // send probes for JmDNS itself - if (state == taskState && task == this) - { - if (out == null) - { - out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); - } - out.addQuestion( - new DNSQuestion( - localHost.getName(), - DNSConstants.TYPE_ANY, - DNSConstants.CLASS_IN)); - DNSRecord answer = localHost.getDNS4AddressRecord(); - if (answer != null) - { - out.addAuthorativeAnswer(answer); - } - answer = localHost.getDNS6AddressRecord(); - if (answer != null) - { - out.addAuthorativeAnswer(answer); - } - advanceState(); - } - // send probes for services - // Defensively copy the services into a local list, - // to prevent race conditions with methods registerService - // and unregisterService. - List<ServiceInfo> list; - synchronized (JmDNS.this) - { - list = new LinkedList<ServiceInfo>(services.values()); - } - for (Iterator<ServiceInfo> i = list.iterator(); i.hasNext();) - { - ServiceInfo info = i.next(); - - synchronized (info) - { - if (info.getState() == taskState && - info.task == this) - { - info.advanceState(); - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS probing " + - info.getQualifiedName() + " state " + - info.getState()); - - if (out == null) - { - out = new DNSOutgoing( - DNSConstants.FLAGS_QR_QUERY); - out.addQuestion( - new DNSQuestion( - info.getQualifiedName(), - DNSConstants.TYPE_ANY, - DNSConstants.CLASS_IN)); - } - out.addAuthorativeAnswer( - new DNSRecord.Service( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.priority, - info.weight, - info.port, - localHost.getName())); - } - } - } - if (out != null) - { - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS probing #" + taskState); - send(out); - } - else - { - // If we have nothing to send, another timer taskState - // ahead of us has done the job for us. We can cancel. - cancel(); - return; - } - } - catch (Throwable e) - { - logger.warn( "run() exception ", e); - recover(); - } - - taskState = taskState.advance(); - if (!taskState.isProbing()) - { - cancel(); - - new Announcer().start(); - } - } - } - - } - - /** - * The Announcer sends an accumulated query of all announces, and advances - * the state of all serviceInfos, for which it has sent an announce. - * The Announcer also sends announcements and advances the state of JmDNS - * itself. - * <p/> - * When the announcer has run two times, it finishes. - */ - private class Announcer extends TimerTask - { - /** - * The state of the announcer. - */ - DNSState taskState = DNSState.ANNOUNCING_1; - - public Announcer() - { - // Associate host to this, if it needs announcing - if (state == DNSState.ANNOUNCING_1) - { - task = this; - } - // Associate services to this, if they need announcing - synchronized (JmDNS.this) - { - for (Iterator<ServiceInfo> s = services.values().iterator(); s.hasNext();) - { - ServiceInfo info = s.next(); - if (info.getState() == DNSState.ANNOUNCING_1) - { - info.task = this; - } - } - } - } - - public void start() - { - timer.schedule(this, - DNSConstants.ANNOUNCE_WAIT_INTERVAL, - DNSConstants.ANNOUNCE_WAIT_INTERVAL); - } - - @Override - public boolean cancel() - { - // Remove association from host to this - if (task == this) - { - task = null; - } - - // Remove associations from services to this - synchronized (JmDNS.this) - { - for (Iterator<ServiceInfo> i = services.values().iterator(); - i.hasNext();) - { - ServiceInfo info = i.next(); - if (info.task == this) - { - info.task = null; - } - } - } - - return super.cancel(); - } - - @Override - public void run() - { - DNSOutgoing out = null; - try - { - // send probes for JmDNS itself - if (state == taskState) - { - if (out == null) - { - out = new DNSOutgoing( - DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); - } - DNSRecord answer = localHost.getDNS4AddressRecord(); - if (answer != null) - { - out.addAnswer(answer, 0); - } - answer = localHost.getDNS6AddressRecord(); - if (answer != null) - { - out.addAnswer(answer, 0); - } - advanceState(); - } - // send announces for services - // Defensively copy the services into a local list, - // to prevent race conditions with methods registerService - // and unregisterService. - List<ServiceInfo> list; - synchronized (JmDNS.this) - { - list = new ArrayList<ServiceInfo>(services.values()); - } - for (Iterator<ServiceInfo> i = list.iterator(); i.hasNext();) - { - ServiceInfo info = i.next(); - synchronized (info) - { - if (info.getState() == taskState && info.task == this) - { - info.advanceState(); - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS announcing " + - info.getQualifiedName() + - " state " + info.getState()); - - if (out == null) - { - out = new DNSOutgoing( - DNSConstants.FLAGS_QR_RESPONSE | - DNSConstants.FLAGS_AA); - } - out.addAnswer( - new DNSRecord.Pointer( - info.type, - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.getQualifiedName()), 0); - out.addAnswer( - new DNSRecord.Service( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.priority, - info.weight, - info.port, - localHost.getName()), 0); - out.addAnswer( - new DNSRecord.Text( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.text), 0); - } - } - } - if (out != null) - { - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS announcing #" + taskState); - send(out); - } - else - { - // If we have nothing to send, another timer taskState ahead - // of us has done the job for us. We can cancel. - cancel(); - } - } - catch (Throwable e) - { - logger.warn( "run() exception ", e); - recover(); - } - - taskState = taskState.advance(); - if (!taskState.isAnnouncing()) - { - cancel(); - - new Renewer().start(); - } - } - } - - /** - * The Renewer is there to send renewal announcment - * when the record expire for ours infos. - */ - private class Renewer extends TimerTask - { - /** - * The state of the announcer. - */ - DNSState taskState = DNSState.ANNOUNCED; - - public Renewer() - { - // Associate host to this, if it needs renewal - if (state == DNSState.ANNOUNCED) - { - task = this; - } - // Associate services to this, if they need renewal - synchronized (JmDNS.this) - { - for (Iterator<ServiceInfo> s = services.values().iterator(); s.hasNext();) - { - ServiceInfo info = s.next(); - if (info.getState() == DNSState.ANNOUNCED) - { - info.task = this; - } - } - } - } - - public void start() - { - timer.schedule(this, - DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL, - DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL); - } - - @Override - public boolean cancel() - { - // Remove association from host to this - if (task == this) - { - task = null; - } - - // Remove associations from services to this - synchronized (JmDNS.this) - { - for (Iterator<ServiceInfo> i = services.values().iterator(); - i.hasNext();) - { - ServiceInfo info = i.next(); - if (info.task == this) - { - info.task = null; - } - } - } - - return super.cancel(); - } - - @Override - public void run() - { - DNSOutgoing out = null; - try - { - // send probes for JmDNS itself - if (state == taskState) - { - if (out == null) - { - out = new DNSOutgoing( - DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); - } - DNSRecord answer = localHost.getDNS4AddressRecord(); - if (answer != null) - { - out.addAnswer(answer, 0); - } - answer = localHost.getDNS6AddressRecord(); - if (answer != null) - { - out.addAnswer(answer, 0); - } - advanceState(); - } - // send announces for services - // Defensively copy the services into a local list, - // to prevent race conditions with methods registerService - // and unregisterService. - List<ServiceInfo> list; - synchronized (JmDNS.this) - { - list = new ArrayList<ServiceInfo>(services.values()); - } - for (Iterator<ServiceInfo> i = list.iterator(); i.hasNext();) - { - ServiceInfo info = i.next(); - synchronized (info) - { - if (info.getState() == taskState && info.task == this) - { - info.advanceState(); - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS announced " + - info.getQualifiedName() + " state " + info.getState()); - - if (out == null) - { - out = new DNSOutgoing( - DNSConstants.FLAGS_QR_RESPONSE | - DNSConstants.FLAGS_AA); - } - out.addAnswer( - new DNSRecord.Pointer( - info.type, - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.getQualifiedName()), 0); - out.addAnswer( - new DNSRecord.Service( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.priority, - info.weight, - info.port, - localHost.getName()), 0); - out.addAnswer( - new DNSRecord.Text( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.text), 0); - } - } - } - if (out != null) - { - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS announced"); - send(out); - } - else - { - // If we have nothing to send, another timer taskState ahead - // of us has done the job for us. We can cancel. - cancel(); - } - } - catch (Throwable e) - { - logger.warn( "run() exception ", e); - recover(); - } - - taskState = taskState.advance(); - if (!taskState.isAnnounced()) - { - cancel(); - - } - } - } - - /** - * The Responder sends a single answer for the specified service infos - * and for the host name. - */ - private class Responder extends TimerTask - { - private DNSIncoming in; - private InetAddress addr; - private int port; - - public Responder(DNSIncoming in, InetAddress addr, int port) - { - this.in = in; - this.addr = addr; - this.port = port; - } - - public void start() - { - // According to draft-cheshire-dnsext-multicastdns.txt - // chapter "8 Responding": - // We respond immediately if we know for sure, that we are - // the only one who can respond to the query. - // In all other cases, we respond within 20-120 ms. - // - // According to draft-cheshire-dnsext-multicastdns.txt - // chapter "7.2 Multi-Packet Known Answer Suppression": - // We respond after 20-120 ms if the query is truncated. - - boolean iAmTheOnlyOne = true; - for (DNSEntry entry : in.questions) - { - if (entry instanceof DNSQuestion) - { - DNSQuestion q = (DNSQuestion) entry; - if (logger.isTraceEnabled()) - logger.trace("start() question=" + q); - iAmTheOnlyOne &= (q.type == DNSConstants.TYPE_SRV - || q.type == DNSConstants.TYPE_TXT - || q.type == DNSConstants.TYPE_A - || q.type == DNSConstants.TYPE_AAAA - || localHost.getName().equalsIgnoreCase(q.name) - || services.containsKey(q.name.toLowerCase())); - if (!iAmTheOnlyOne) - { - break; - } - } - } - int delay = (iAmTheOnlyOne && !in.isTruncated()) ? - 0 : - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + - random.nextInt( - DNSConstants.RESPONSE_MAX_WAIT_INTERVAL - - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + 1) - - in.elapseSinceArrival(); - if (delay < 0) - { - delay = 0; - } - if (logger.isTraceEnabled()) - logger.trace("start() Responder chosen delay=" + delay); - timer.schedule(this, delay); - } - - @Override - public void run() - { - synchronized (ioLock) - { - if (plannedAnswer == in) - { - plannedAnswer = null; - } - - // We use these sets to prevent duplicate records - // FIXME - This should be moved into DNSOutgoing - HashSet<DNSQuestion> questions = new HashSet<DNSQuestion>(); - HashSet<DNSRecord> answers = new HashSet<DNSRecord>(); - - - if (state == DNSState.ANNOUNCED) - { - try - { - boolean isUnicast = (port != DNSConstants.MDNS_PORT); - - - // Answer questions - for (Iterator<DNSEntry> iterator = in.questions.iterator(); - iterator.hasNext();) - { - DNSEntry entry = iterator.next(); - if (entry instanceof DNSQuestion) - { - DNSQuestion q = (DNSQuestion) entry; - - // for unicast responses the question - // must be included - if (isUnicast) - { - //out.addQuestion(q); - questions.add(q); - } - - int type = q.type; - if (type == DNSConstants.TYPE_ANY || - type == DNSConstants.TYPE_SRV) - { // I ama not sure of why there is a special - // case here [PJYF Oct 15 2004] - if (localHost.getName(). - equalsIgnoreCase(q.getName())) - { - // type = DNSConstants.TYPE_A; - DNSRecord answer = - localHost.getDNS4AddressRecord(); - if (answer != null) - { - answers.add(answer); - } - answer = localHost.getDNS6AddressRecord(); - if (answer != null) - { - answers.add(answer); - } - type = DNSConstants.TYPE_IGNORE; - } - else - { - if (serviceTypes.containsKey( - q.getName().toLowerCase())) - { - type = DNSConstants.TYPE_PTR; - } - } - } - - switch (type) - { - case DNSConstants.TYPE_A: - { - // Answer a query for a domain name - //out = addAnswer( in, addr, port, out, host ); - DNSRecord answer = - localHost.getDNS4AddressRecord(); - if (answer != null) - { - answers.add(answer); - } - break; - } - case DNSConstants.TYPE_AAAA: - { - // Answer a query for a domain name - DNSRecord answer = - localHost.getDNS6AddressRecord(); - if (answer != null) - { - answers.add(answer); - } - break; - } - case DNSConstants.TYPE_PTR: - { - // Answer a query for services of a given type - - // find matching services - for (Iterator<ServiceInfo> serviceIterator = - services.values().iterator(); - serviceIterator.hasNext();) - { - ServiceInfo info = serviceIterator.next(); - if (info.getState() == DNSState.ANNOUNCED) - { - if (q.name.equalsIgnoreCase(info.type)) - { - DNSRecord answer = - localHost.getDNS4AddressRecord(); - if (answer != null) - { - answers.add(answer); - } - answer = - localHost.getDNS6AddressRecord(); - if (answer != null) - { - answers.add(answer); - } - answers.add( - new DNSRecord.Pointer( - info.type, - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.getQualifiedName())); - answers.add( - new DNSRecord.Service( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, - DNSConstants.DNS_TTL, - info.priority, - info.weight, - info.port, - localHost.getName())); - answers.add( - new DNSRecord.Text( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, - DNSConstants.DNS_TTL, - info.text)); - } - } - } - if (q.name.equalsIgnoreCase("_services._mdns._udp.local.")) - { - for (Iterator<String> serviceTypeIterator = serviceTypes.values().iterator(); - serviceTypeIterator.hasNext();) - { - answers.add( - new DNSRecord.Pointer( - "_services._mdns._udp.local.", - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - serviceTypeIterator.next())); - } - } - break; - } - case DNSConstants.TYPE_SRV: - case DNSConstants.TYPE_ANY: - case DNSConstants.TYPE_TXT: - { - ServiceInfo info = services.get(q.name.toLowerCase()); - if (info != null && - info.getState() == DNSState.ANNOUNCED) - { - DNSRecord answer = - localHost.getDNS4AddressRecord(); - if (answer != null) - { - answers.add(answer); - } - answer = - localHost.getDNS6AddressRecord(); - if (answer != null) - { - answers.add(answer); - } - answers.add( - new DNSRecord.Pointer( - info.type, - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.getQualifiedName())); - answers.add( - new DNSRecord.Service( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, - DNSConstants.DNS_TTL, - info.priority, - info.weight, - info.port, - localHost.getName())); - answers.add( - new DNSRecord.Text( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, - DNSConstants.DNS_TTL, - info.text)); - } - break; - } - default : - { - //System.out.println("JmDNSResponder.unhandled query:"+q); - break; - } - } - } - } - - - // remove known answers, if the ttl is at least half of - // the correct value. (See Draft Cheshire chapter 7.1.). - for (DNSRecord knownAnswer : in.answers) - { - if (knownAnswer.ttl > DNSConstants.DNS_TTL / 2 && - answers.remove(knownAnswer)) - { - if (logger.isDebugEnabled()) - logger.debug( - "JmDNS Responder Known Answer Removed"); - } - } - - - // responde if we have answers - if (answers.size() != 0) - { - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS responding"); - DNSOutgoing out = null; - if (isUnicast) - { - out = new DNSOutgoing( - DNSConstants.FLAGS_QR_RESPONSE - | DNSConstants.FLAGS_AA, - false); - } - - for (Iterator<DNSQuestion> i = questions.iterator(); - i.hasNext();) - { - out.addQuestion(i.next()); - } - for (Iterator<DNSRecord> i = answers.iterator(); - i.hasNext();) - { - out = addAnswer(in, addr, port, out, i.next()); - } - send(out); - } - this.cancel(); - } - catch (Throwable e) - { - logger.warn( "run() exception ", e); - close(); - } - } - } - } - } - - /** - * Helper class to resolve service types. - * <p/> - * The TypeResolver queries three times consecutively for service types, and then - * removes itself from the timer. - * <p/> - * The TypeResolver will run only if JmDNS is in state ANNOUNCED. - */ - private class TypeResolver extends TimerTask - { - public void start() - { - timer.schedule(this, - DNSConstants.QUERY_WAIT_INTERVAL, - DNSConstants.QUERY_WAIT_INTERVAL); - } - - /** - * Counts the number of queries that were sent. - */ - int count = 0; - - @Override - public void run() - { - try - { - if (state == DNSState.ANNOUNCED) - { - if (count++ < 3) - { - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS querying type"); - DNSOutgoing out = - new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); - out.addQuestion( - new DNSQuestion( - "_services._mdns._udp.local.", - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN)); - for (String serviceType : serviceTypes.values()) - { - out.addAnswer( - new DNSRecord.Pointer( - "_services._mdns._udp.local.", - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - serviceType), 0); - } - send(out); - } - else - { - // After three queries, we can quit. - this.cancel(); - } - } - else - { - if (state == DNSState.CANCELED) - { - this.cancel(); - } - } - } - catch (Throwable e) - { - logger.warn( "run() exception ", e); - recover(); - } - } - } - - /** - * The ServiceResolver queries three times consecutively for services of - * a given type, and then removes itself from the timer. - * <p/> - * The ServiceResolver will run only if JmDNS is in state ANNOUNCED. - * REMIND: Prevent having multiple service resolvers for the same type in the - * timer queue. - */ - private class ServiceResolver extends TimerTask - { - /** - * Counts the number of queries being sent. - */ - int count = 0; - private String type; - - public ServiceResolver(String type) - { - this.type = type; - } - - public void start() - { - timer.schedule(this, - DNSConstants.QUERY_WAIT_INTERVAL, - DNSConstants.QUERY_WAIT_INTERVAL); - } - - @Override - public void run() - { - try - { - if (state == DNSState.ANNOUNCED) - { - if (count++ < 3) - { - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS querying service"); - long now = System.currentTimeMillis(); - DNSOutgoing out = - new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); - out.addQuestion( - new DNSQuestion( - type, - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN)); - for (Iterator<ServiceInfo> s = services.values().iterator(); s.hasNext();) - { - final ServiceInfo info = s.next(); - try - { - out.addAnswer( - new DNSRecord.Pointer( - info.type, - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN, - DNSConstants.DNS_TTL, - info.getQualifiedName()), now); - } - catch (IOException ee) - { - break; - } - } - send(out); - } - else - { - // After three queries, we can quit. - this.cancel(); - } - } - else - { - if (state == DNSState.CANCELED) - { - this.cancel(); - } - } - } - catch (Throwable e) - { - logger.warn( "run() exception ", e); - recover(); - } - } - } - - /** - * The ServiceInfoResolver queries up to three times consecutively for - * a service info, and then removes itself from the timer. - * <p/> - * The ServiceInfoResolver will run only if JmDNS is in state ANNOUNCED. - * REMIND: Prevent having multiple service resolvers for the same info in the - * timer queue. - */ - private class ServiceInfoResolver extends TimerTask - { - /** - * Counts the number of queries being sent. - */ - int count = 0; - private ServiceInfo info; - - public ServiceInfoResolver(ServiceInfo info) - { - this.info = info; - info.dns = JmDNS.this; - addListener(info, - new DNSQuestion( - info.getQualifiedName(), - DNSConstants.TYPE_ANY, - DNSConstants.CLASS_IN)); - } - - public void start() - { - timer.schedule(this, - DNSConstants.QUERY_WAIT_INTERVAL, - DNSConstants.QUERY_WAIT_INTERVAL); - } - - @Override - public void run() - { - try - { - if (state == DNSState.ANNOUNCED) - { - if (count++ < 3 && !info.hasData()) - { - long now = System.currentTimeMillis(); - DNSOutgoing out = - new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); - out.addQuestion( - new DNSQuestion( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN)); - out.addQuestion( - new DNSQuestion( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN)); - if (info.server != null) - { - out.addQuestion( - new DNSQuestion( - info.server, - DNSConstants.TYPE_A, - DNSConstants.CLASS_IN)); - } - out.addAnswer((DNSRecord) cache.get( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN), now); - out.addAnswer((DNSRecord) cache.get( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN), now); - if (info.server != null) - { - out.addAnswer((DNSRecord) cache.get( - info.server, - DNSConstants.TYPE_A, - DNSConstants.CLASS_IN), now); - } - send(out); - } - else - { - // After three queries, we can quit. - this.cancel(); - removeListener(info); - } - } - else - { - if (state == DNSState.CANCELED) - { - this.cancel(); - removeListener(info); - } - } - } - catch (Throwable e) - { - logger.warn( "run() exception ", e); - recover(); - } - } - } - - /** - * The Canceler sends two announces with TTL=0 for the specified services. - */ - /* TODO: Clarify whether 2 or 3 announces should be sent. The header says 2, - * run() uses the (misleading) ++count < 3 (while all other tasks use count++ < 3) - * and the comment in the else block in run() says: "After three successful..." - */ - public class Canceler extends TimerTask - { - /** - * Counts the number of announces being sent. - */ - int count = 0; - /** - * The services that need cancelling. - * Note: We have to use a local variable here, because the services - * that are canceled, are removed immediately from variable JmDNS.services. - */ - private ServiceInfo[] infos; - /** - * We call notifyAll() on the lock object, when we have canceled the - * service infos. - * This is used by method JmDNS.unregisterService() and - * JmDNS.unregisterAllServices, to ensure that the JmDNS - * socket stays open until the Canceler has canceled all services. - * <p/> - * Note: We need this lock, because ServiceInfos do the transition from - * state ANNOUNCED to state CANCELED before we get here. We could get - * rid of this lock, if we added a state named CANCELLING to DNSState. - */ - private Object lock; - int ttl = 0; - - public Canceler(ServiceInfo info, Object lock) - { - this.infos = new ServiceInfo[]{info}; - this.lock = lock; - addListener(info, - new DNSQuestion( - info.getQualifiedName(), - DNSConstants.TYPE_ANY, - DNSConstants.CLASS_IN)); - } - - public Canceler(ServiceInfo[] infos, Object lock) - { - this.infos = infos; - this.lock = lock; - } - - public Canceler(Collection<ServiceInfo> infos, Object lock) - { - this.infos = infos.toArray(new ServiceInfo[infos.size()]); - this.lock = lock; - } - - public void start() - { - timer.schedule(this, 0, DNSConstants.ANNOUNCE_WAIT_INTERVAL); - } - - @Override - public void run() - { - try - { - if (++count < 3) - { - if (logger.isDebugEnabled()) - logger.debug("run() JmDNS canceling service"); - // announce the service - //long now = System.currentTimeMillis(); - DNSOutgoing out = - new DNSOutgoing( - DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); - for (int i = 0; i < infos.length; i++) - { - ServiceInfo info = infos[i]; - out.addAnswer( - new DNSRecord.Pointer( - info.type, - DNSConstants.TYPE_PTR, - DNSConstants.CLASS_IN, - ttl, - info.getQualifiedName()), 0); - out.addAnswer( - new DNSRecord.Service( - info.getQualifiedName(), - DNSConstants.TYPE_SRV, - DNSConstants.CLASS_IN, - ttl, - info.priority, - info.weight, - info.port, - localHost.getName()), 0); - out.addAnswer( - new DNSRecord.Text( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN, - ttl, - info.text), 0); - DNSRecord answer = localHost.getDNS4AddressRecord(); - if (answer != null) - { - out.addAnswer(answer, 0); - } - answer = localHost.getDNS6AddressRecord(); - if (answer != null) - { - out.addAnswer(answer, 0); - } - } - send(out); - } - else - { - // After three successful announcements, we are finished. - synchronized (lock) - { - closed=true; - lock.notifyAll(); - } - this.cancel(); - } - } - catch (Throwable e) - { - logger.warn( "run() exception ", e); - recover(); - } - } - } - - /** - * Recover jmdns when there is an error. - */ - protected void recover() - { - if (logger.isDebugEnabled()) - logger.debug("recover()"); - // We have an IO error so lets try to recover if anything happens lets close it. - // This should cover the case of the IP address changing under our feet - if (DNSState.CANCELED != state) - { - synchronized (this) - { // Synchronize only if we are not already in process to prevent dead locks - // - if (logger.isDebugEnabled()) - logger.debug("recover() Cleanning up"); - // Stop JmDNS - state = DNSState.CANCELED; // This protects against recursive calls - - // We need to keep a copy for reregistration - Collection<ServiceInfo> oldServiceInfos = new ArrayList<ServiceInfo>(services.values()); - - // Cancel all services - unregisterAllServices(); - disposeServiceCollectors(); - // - // close multicast socket - closeMulticastSocket(); - // - cache.clear(); - if (logger.isDebugEnabled()) - logger.debug("recover() All is clean"); - // - // All is clear now start the services - // - try - { - openMulticastSocket(localHost); - start(oldServiceInfos); - } - catch (Exception exception) - { - logger.warn( - "recover() Start services exception ", exception); - } - logger.warn( "recover() We are back!"); - } - } - } - - /** - * Close down jmdns. Release all resources and unregister all services. - */ - public void close() - { - if (state != DNSState.CANCELED) - { - synchronized (this) - { // Synchronize only if we are not already in process to prevent dead locks - // Stop JmDNS - state = DNSState.CANCELED; // This protects against recursive calls - - unregisterAllServices(); - disposeServiceCollectors(); - - // close socket - closeMulticastSocket(); - - // Stop the timer - timer.cancel(); - } - } - } - - /** - * List cache entries, for debugging only. - */ - void print() - { - if (logger.isInfoEnabled()) - logger.info("---- cache ----\n"); - cache.print(); - if (logger.isInfoEnabled()) - logger.info("\n"); - } - - /** - * List Services and serviceTypes. - * Debugging Only - */ - - public void printServices() - { - if (logger.isInfoEnabled()) - logger.info(toString()); - } - - @Override - public String toString() - { - StringBuffer aLog = new StringBuffer(); - aLog.append("\t---- Services -----"); - if (services != null) - { - for (Map.Entry<String, ServiceInfo> entry : services.entrySet()) - { - aLog.append("\n\t\tService: " + entry.getKey() + ": " - + entry.getValue()); - } - } - aLog.append("\n"); - aLog.append("\t---- Types ----"); - if (serviceTypes != null) - { - for (Map.Entry<String, String> entry : serviceTypes.entrySet()) - { - aLog.append("\n\t\tType: " + entry.getKey() + ": " - + entry.getValue()); - } - } - aLog.append("\n"); - aLog.append(cache.toString()); - aLog.append("\n"); - aLog.append("\t---- Service Collectors ----"); - if (serviceCollectors != null) - { - synchronized (serviceCollectors) - { - for (Map.Entry<String, ServiceCollector> entry - : serviceCollectors.entrySet()) - { - aLog.append("\n\t\tService Collector: " + entry.getKey() - + ": " + entry.getValue()); - } - serviceCollectors.clear(); - } - } - return aLog.toString(); - } - - /** - * Returns a list of service infos of the specified type. - * - * @param type Service type name, such as <code>_http._tcp.local.</code>. - * @return An array of service instance names. - */ - public ServiceInfo[] list(String type) - { - // Implementation note: The first time a list for a given type is - // requested, a ServiceCollector is created which collects service - // infos. This greatly speeds up the performance of subsequent calls - // to this method. The caveats are, that 1) the first call to this method - // for a given type is slow, and 2) we spawn a ServiceCollector - // instance for each service type which increases network traffic a - // little. - - ServiceCollector collector; - - boolean newCollectorCreated; - synchronized (serviceCollectors) - { - collector = serviceCollectors.get(type); - if (collector == null) - { - collector = new ServiceCollector(type); - serviceCollectors.put(type, collector); - addServiceListener(type, collector); - newCollectorCreated = true; - } - else - { - newCollectorCreated = false; - } - } - - // After creating a new ServiceCollector, we collect service infos for - // 200 milliseconds. This should be enough time, to get some service - // infos from the network. - if (newCollectorCreated) - { - try - { - Thread.sleep(200); - } - catch (InterruptedException e) - { - } - } - - return collector.list(); - } - - /** - * This method disposes all ServiceCollector instances which have been - * created by calls to method <code>list(type)</code>. - * - * @see #list - */ - private void disposeServiceCollectors() - { - if (logger.isDebugEnabled()) - logger.debug("disposeServiceCollectors()"); - synchronized (serviceCollectors) - { - for (Iterator<ServiceCollector> i = serviceCollectors.values().iterator(); i.hasNext();) - { - ServiceCollector collector = i.next(); - removeServiceListener(collector.type, collector); - } - serviceCollectors.clear(); - } - } - - /** - * Instances of ServiceCollector are used internally to speed up the - * performance of method <code>list(type)</code>. - * - * @see #list - */ - private static class ServiceCollector implements ServiceListener - { - - /** - * A set of collected service instance names. - */ - private Map<String, ServiceInfo> infos = Collections.synchronizedMap(new HashMap<String, ServiceInfo>()); - - public String type; - - public ServiceCollector(String type) - { - this.type = type; - } - - /** - * A service has been added. - */ - public void serviceAdded(ServiceEvent event) - { - synchronized (infos) - { - event.getDNS().requestServiceInfo( - event.getType(), event.getName(), 0); - } - } - - /** - * A service has been removed. - */ - public void serviceRemoved(ServiceEvent event) - { - synchronized (infos) - { - infos.remove(event.getName()); - } - } - - /** - * A service hase been resolved. Its details are now available in the - * ServiceInfo record. - */ - public void serviceResolved(ServiceEvent event) - { - synchronized (infos) - { - infos.put(event.getName(), event.getInfo()); - } - } - - /** - * Returns an array of all service infos which have been collected by this - * ServiceCollector. - * @return - */ - public ServiceInfo[] list() - { - synchronized (infos) - { - return infos.values(). - toArray(new ServiceInfo[infos.size()]); - } - } - - @Override - public String toString() - { - StringBuffer aLog = new StringBuffer(); - synchronized (infos) - { - for (Map.Entry<String, ServiceInfo> entry : infos.entrySet()) - { - aLog.append("\n\t\tService: " + entry.getKey() + ": " - + entry.getValue()); - } - } - return aLog.toString(); - } - }; - - private static String toUnqualifiedName(String type, String qualifiedName) - { - if (qualifiedName.endsWith(type)) - { - return qualifiedName.substring(0, - qualifiedName.length() - type.length() - 1); - } - else - { - return qualifiedName; - } - } - - /** - * SC-Bonjour Implementation : Method used to update the corresponding DNS - * entry in the cache of JmDNS with the new information in this ServiceInfo. - * A call to getLocalService must first be issued to get the - * ServiceInfo object to be modified. - * THIS METHOD MUST BE USED INSTEAD OF ANY DIRECT ACCESS TO JMDNS' CACHE!! - * This is used in the implementation of Zeroconf in SIP Communicator - * to be able to change fields declared by the local contact (status, etc). - * @param info Updated service data to be used to replace the old - * stuff contained in JmDNS' cache - * @param old info bytes - */ - public void updateInfos(ServiceInfo info, byte[] old) - { - - DNSOutgoing out, out2; - synchronized (JmDNS.this) - { - //list = new ArrayList(services.values()); - services.put(info.getQualifiedName().toLowerCase(), info); - } - - synchronized (info) - { - if (logger.isDebugEnabled()) - logger.debug("updateInfos() JmDNS updating " + - info.getQualifiedName() + " state " + - info.getState()); - - out = new DNSOutgoing( - /*DNSConstants.FLAGS_QR_RESPONSE*/ - DNSConstants.FLAGS_RA | DNSConstants.FLAGS_AA); - out2 = new DNSOutgoing( - /*DNSConstants.FLAGS_QR_RESPONSE*/ - DNSConstants.FLAGS_RA | DNSConstants.FLAGS_AA); - - - try - { - //out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), 0); - //out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()), 0); - //out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()), 0); -// out.addAnswer( -// new DNSRecord.Text( -// info.getQualifiedName(), -// DNSConstants.TYPE_TXT, -// DNSConstants.CLASS_IN , -// DNSConstants.DNS_TTL, -// info.text), 0); - out.addAnswer( - new DNSRecord.Text( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN , - 0, - old), 0); - out.addAnswer( - new DNSRecord.Text( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, - DNSConstants.DNS_TTL, - info.text), 0); - - out2.addAnswer( - new DNSRecord.Text( - info.getQualifiedName(), - DNSConstants.TYPE_TXT, - DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, - DNSConstants.DNS_TTL, - info.text), 0); - - if (logger.isDebugEnabled()) - logger.debug("updateInfos() JmDNS updated infos for "+info); - - send(out); - Thread.sleep(1000); - send(out2); - Thread.sleep(2000); - send(out2); - } - catch( Exception e) - { - logger.warn( "", e); - } - } - } - - - /** - * SC-Bonjour Implementation: Method to retrieve the DNS Entry corresponding to a service - * that has been declared and return it as a ServiceInfo structure. - * It is used in the implementation of Bonjour in SIP Communicator to retrieve the information - * concerning the service declared by the local contact. THIS METHOD MUST BE USED INSTEAD OF ANY - * LOCAL COPY SAVED BEFORE SERVICE REGISTRATION!! - * @return information corresponding to the specified service - * @param FQN String representing the Fully Qualified name of the service we want info about - */ - public ServiceInfo getLocalService(String FQN) - { - return services.get(FQN); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceEvent.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceEvent.java deleted file mode 100644 index ff922d1..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceEvent.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.util.*; -import java.util.logging.*; - -/** - * ServiceEvent. - * - * @author Werner Randelshofer, Rick Blair - * @version %I%, %G% - */ -public class ServiceEvent - extends EventObject -{ - private static Logger logger = - Logger.getLogger(ServiceEvent.class.toString()); - /** - * The type name of the service. - */ - private String type; - /** - * The instance name of the service. Or null, if the event was - * fired to a service type listener. - */ - private String name; - /** - * The service info record, or null if the service could be be resolved. - * This is also null, if the event was fired to a service type listener. - */ - private ServiceInfo info; - - /** - * Creates a new instance. - * - * @param source the JmDNS instance which originated the event. - * @param type the type name of the service. - * @param name the instance name of the service. - * @param info the service info record, or null if the - * service could be be resolved. - */ - public ServiceEvent(JmDNS source, String type, String name, ServiceInfo info) - { - super(source); - this.type = type; - this.name = name; - this.info = info; - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - /** - * Returns the JmDNS instance which originated the event. - * @return Returns the JmDNS instance which originated the event. - */ - public JmDNS getDNS() - { - return (JmDNS) getSource(); - } - - /** - * Returns the fully qualified type of the service. - * @return Returns the fully qualified type of the service. - */ - public String getType() - { - return type; - } - - /** - * Returns the instance name of the service. - * Always returns null, if the event is sent to a service type listener. - * @return Returns the instance name of the service. - */ - public String getName() - { - return name; - } - - /** - * Returns the service info record, or null if the service could not be - * resolved. - * Always returns null, if the event is sent to a service type listener. - * @return Returns the service info record. - */ - public ServiceInfo getInfo() - { - return info; - } - - @Override - public String toString() - { - StringBuffer buf = new StringBuffer(); - buf.append("<" + getClass().getName() + "> "); - buf.append(super.toString()); - buf.append(" name "); - buf.append(getName()); - buf.append(" type "); - buf.append(getType()); - buf.append(" info "); - buf.append(getInfo()); - return buf.toString(); - } - -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceInfo.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceInfo.java deleted file mode 100644 index b7f7f2d..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceInfo.java +++ /dev/null @@ -1,785 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.io.*; -import java.net.*; -import java.util.*; -import java.util.logging.*; - -/** - * JmDNS service information. - * - * @version %I%, %G% - * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer - * @author Christian Vincenot - */ -public class ServiceInfo implements DNSListener -{ - private static Logger logger = - Logger.getLogger(ServiceInfo.class.toString()); - public final static byte[] NO_VALUE = new byte[0]; - JmDNS dns; - - // State machine - /** - * The state of this service info. - * This is used only for services announced by JmDNS. - * <p/> - * For proper handling of concurrency, this variable must be - * changed only using methods advanceState(), revertState() and cancel(). - */ - private DNSState state = DNSState.PROBING_1; - - /** - * Task associated to this service info. - * Possible tasks are JmDNS.Prober, JmDNS.Announcer, JmDNS.Responder, - * JmDNS.Canceler. - */ - TimerTask task; - - String type; - private String name; - String server; - int port; - int weight; - int priority; - byte text[]; - private Map<String, Object> props; - InetAddress addr; - - - /** - * Construct a service description for registrating with JmDNS. - * - * @param type fully qualified service type name, - * such as <code>_http._tcp.local.</code>. - * @param name unqualified service instance name, - * such as <code>foobar</code> - * @param port the local port on which the service runs - * @param text string describing the service - */ - public ServiceInfo(String type, String name, int port, String text) - { - this(type, name, port, 0, 0, text); - } - - /** - * Construct a service description for registrating with JmDNS. - * - * @param type fully qualified service type name, - * such as <code>_http._tcp.local.</code>. - * @param name unqualified service instance name, - * such as <code>foobar</code> - * @param port the local port on which the service runs - * @param weight weight of the service - * @param priority priority of the service - * @param text string describing the service - */ - public ServiceInfo(String type, String name, - int port, int weight, - int priority, String text) - { - this(type, name, port, weight, priority, (byte[]) null); - try - { - ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); - writeUTF(out, text); - this.text = out.toByteArray(); - } - catch (IOException e) - { - throw new RuntimeException("unexpected exception: " + e); - } - } - - /** - * Construct a service description for registrating with JmDNS. The properties hashtable must - * map property names to either Strings or byte arrays describing the property values. - * - * @param type fully qualified service type name, such as <code>_http._tcp.local.</code>. - * @param name unqualified service instance name, such as <code>foobar</code> - * @param port the local port on which the service runs - * @param weight weight of the service - * @param priority priority of the service - * @param props properties describing the service - */ - public ServiceInfo(String type, String name, - int port, int weight, - int priority, Map<String, Object> props) - { - this(type, name, port, weight, priority, new byte[0]); - if (props != null) - { - try - { - ByteArrayOutputStream out = new ByteArrayOutputStream(256); - for (Map.Entry<String, Object> prop : props.entrySet()) - { - String key = prop.getKey(); - Object val = prop.getValue(); - ByteArrayOutputStream out2 = new ByteArrayOutputStream(100); - writeUTF(out2, key); - if (val instanceof String) - { - out2.write('='); - writeUTF(out2, (String) val); - } - else - { - if (val instanceof byte[]) - { - out2.write('='); - byte[] bval = (byte[]) val; - out2.write(bval, 0, bval.length); - } - else - { - if (val != NO_VALUE) - { - throw new IllegalArgumentException( - "invalid property value: " + val); - } - } - } - byte data[] = out2.toByteArray(); - out.write(data.length); - out.write(data, 0, data.length); - } - this.text = out.toByteArray(); - } - catch (IOException e) - { - throw new RuntimeException("unexpected exception: " + e); - } - } - } - - /** - * Construct a service description for registrating with JmDNS. - * - * @param type fully qualified service type name, - * such as <code>_http._tcp.local.</code>. - * @param name unqualified service instance name, - * such as <code>foobar</code> - * @param port the local port on which the service runs - * @param weight weight of the service - * @param priority priority of the service - * @param text bytes describing the service - */ - public ServiceInfo(String type, String name, - int port, int weight, - int priority, byte text[]) - { - this.type = type; - this.name = name; - this.port = port; - this.weight = weight; - this.priority = priority; - this.text = text; - - String SLevel = System.getProperty("jmdns.debug"); - if (SLevel == null) SLevel = "INFO"; - logger.setLevel(Level.parse(SLevel)); - } - - /** - * Construct a service record during service discovery. - */ - ServiceInfo(String type, String name) - { - if (!type.endsWith(".")) - { - throw new IllegalArgumentException( - "type must be fully qualified DNS name ending in '.': " + type); - } - - this.type = type; - this.name = name; - } - - /** - * During recovery we need to duplicate service info to reregister them - */ - ServiceInfo(ServiceInfo info) - { - if (info != null) - { - this.type = info.type; - this.name = info.name; - this.port = info.port; - this.weight = info.weight; - this.priority = info.priority; - this.text = info.text; - } - } - - /** - * Fully qualified service type name, - * such as <code>_http._tcp.local.</code> . - * @return Returns fully qualified service type name. - */ - public String getType() - { - return type; - } - - /** - * Unqualified service instance name, - * such as <code>foobar</code> . - * @return Returns unqualified service instance name. - */ - public String getName() - { - return name; - } - - /** - * Sets the service instance name. - * - * @param name unqualified service instance name, - * such as <code>foobar</code> - */ - void setName(String name) - { - this.name = name; - } - - /** - * Fully qualified service name, - * such as <code>foobar._http._tcp.local.</code> . - * @return Returns fully qualified service name. - */ - public String getQualifiedName() - { - return name + "." + type; - } - - /** - * Get the name of the server. - * @return Returns name of the server. - */ - public String getServer() - { - return server; - } - - /** - * Get the host address of the service (ie X.X.X.X). - * @return Returns host address of the service. - */ - public String getHostAddress() - { - return (addr != null ? addr.getHostAddress() : ""); - } - - public InetAddress getAddress() - { - return addr; - } - - /** - * Get the InetAddress of the service. - * @return Returns the InetAddress of the service. - */ - public InetAddress getInetAddress() - { - return addr; - } - - /** - * Get the port for the service. - * @return Returns port for the service. - */ - public int getPort() - { - return port; - } - - /** - * Get the priority of the service. - * @return Returns the priority of the service. - */ - public int getPriority() - { - return priority; - } - - /** - * Get the weight of the service. - * @return Returns the weight of the service. - */ - public int getWeight() - { - return weight; - } - - /** - * Get the text for the serivce as raw bytes. - * @return Returns the text for the serivce as raw bytes. - */ - public byte[] getTextBytes() - { - return text; - } - - /** - * Get the text for the service. This will interpret the text bytes - * as a UTF8 encoded string. Will return null if the bytes are not - * a valid UTF8 encoded string. - * @return Returns the text for the service. - */ - public String getTextString() - { - if ((text == null) || - (text.length == 0) || - ((text.length == 1) && (text[0] == 0))) - { - return null; - } - return readUTF(text, 0, text.length); - } - - /** - * Get the URL for this service. An http URL is created by - * combining the address, port, and path properties. - * @return Returns the URL for this service. - */ - public String getURL() - { - return getURL("http"); - } - - /** - * Get the URL for this service. An URL is created by - * combining the protocol, address, port, and path properties. - * @param protocol - * @return Returns URL for this service. - */ - public String getURL(String protocol) - { - String url = protocol + "://" + getHostAddress() + ":" + getPort(); - String path = getPropertyString("path"); - if (path != null) - { - if (path.indexOf("://") >= 0) - { - url = path; - } - else - { - url += path.startsWith("/") ? path : "/" + path; - } - } - return url; - } - - /** - * Get a property of the service. This involves decoding the - * text bytes into a property list. Returns null if the property - * is not found or the text data could not be decoded correctly. - * @param name - * @return Returns property of the service as bytes. - */ - public synchronized byte[] getPropertyBytes(String name) - { - return (byte[]) getProperties().get(name); - } - - /** - * Get a property of the service. This involves decoding the - * text bytes into a property list. Returns null if the property - * is not found, the text data could not be decoded correctly, or - * the resulting bytes are not a valid UTF8 string. - * @param name - * @return Returns property of the service as string. - */ - public synchronized String getPropertyString(String name) - { - byte data[] = (byte[]) getProperties().get(name); - - if (data == null) - { - return null; - } - if (data == NO_VALUE) - { - return "true"; - } - String res = readUTF(data, 0, data.length); - - return res; - } - - /** - * Iterator<String> of the property names. - * @return Iterator<String> of the property names. - */ - public Iterator<String> getPropertyNames() - { - Map<String, Object> properties = getProperties(); - Iterable<String> propertyNames - = (properties != null) ? properties.keySet() : new Vector<String>(); - return propertyNames.iterator(); - } - - /** - * Write a UTF string with a length to a stream. - */ - void writeUTF(OutputStream out, String str) throws IOException - { - for (int i = 0, len = str.length(); i < len; i++) - { - int c = str.charAt(i); - if ((c >= 0x0001) && (c <= 0x007F)) - { - out.write(c); - } - else - { - if (c > 0x07FF) - { - out.write(0xE0 | ((c >> 12) & 0x0F)); - out.write(0x80 | ((c >> 6) & 0x3F)); - out.write(0x80 | ((c >> 0) & 0x3F)); - } - else - { - out.write(0xC0 | ((c >> 6) & 0x1F)); - out.write(0x80 | ((c >> 0) & 0x3F)); - } - } - } - } - - /** - * Read data bytes as a UTF stream. - */ - String readUTF(byte data[], int off, int len) - { - StringBuffer buf = new StringBuffer(); - for (int end = off + len; off < end;) - { - int ch = data[off++] & 0xFF; - switch (ch >> 4) - { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - // 0xxxxxxx - break; - case 12: - case 13: - if (off >= len) - { - return null; - } - // 110x xxxx 10xx xxxx - ch = ((ch & 0x1F) << 6) | (data[off++] & 0x3F); - break; - case 14: - if (off + 2 >= len) - { - return null; - } - // 1110 xxxx 10xx xxxx 10xx xxxx - ch = ((ch & 0x0f) << 12) | - ((data[off++] & 0x3F) << 6) | - (data[off++] & 0x3F); - break; - default: - if (off + 1 >= len) - { - return null; - } - // 10xx xxxx, 1111 xxxx - ch = ((ch & 0x3F) << 4) | (data[off++] & 0x0f); - break; - } - buf.append((char) ch); - } - return buf.toString(); - } - - synchronized Map<String, Object> getProperties() - { - if ((props == null) && (text != null)) - { - Map<String, Object> props = new Hashtable<String, Object>(); - int off = 0; - while (off < text.length) - { - // length of the next key value pair - int len = text[off++] & 0xFF; - if ((len == 0) || (off + len > text.length)) - { - props.clear(); - break; - } - // look for the '=' - int i = 0; - for (; (i < len) && (text[off + i] != '='); i++) - { - ; - } - - // get the property name - String name = readUTF(text, off, i); - if (name == null) - { - props.clear(); - break; - } - if (i == len) - { - props.put(name, NO_VALUE); - } - else - { - byte value[] = new byte[len - ++i]; - System.arraycopy(text, off + i, value, 0, len - i); - props.put(name, value); - off += len; - } - } - this.props = props; - } - return props; - } - - - /** - * JmDNS callback to update a DNS record. - * @param rec - */ - public void updateRecord(JmDNS jmdns, long now, DNSRecord rec) - { - if ((rec != null) && !rec.isExpired(now)) - { - switch (rec.type) - { - case DNSConstants.TYPE_A: // IPv4 - case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested - if (rec.name.equals(server)) - { - addr = ((DNSRecord.Address) rec).getAddress(); - - } - break; - case DNSConstants.TYPE_SRV: - if (rec.name.equals(getQualifiedName())) - { - DNSRecord.Service srv = (DNSRecord.Service) rec; - server = srv.server; - port = srv.port; - weight = srv.weight; - priority = srv.priority; - addr = null; - // changed to use getCache() instead - jeffs - // updateRecord(jmdns, now, (DNSRecord)jmdns.cache.get(server, TYPE_A, CLASS_IN)); - updateRecord(jmdns, - now, - (DNSRecord) jmdns.getCache().get( - server, - DNSConstants.TYPE_A, - DNSConstants.CLASS_IN)); - } - break; - case DNSConstants.TYPE_TXT: - if (rec.name.equals(getQualifiedName())) - { - DNSRecord.Text txt = (DNSRecord.Text) rec; - text = txt.text; - } - break; - } - // Future Design Pattern - // This is done, to notify the wait loop in method - // JmDNS.getServiceInfo(type, name, timeout); - if (hasData() && dns != null) - { - dns.handleServiceResolved(this); - dns = null; - } - synchronized (this) - { - notifyAll(); - } - } - } - - /** - * Returns true if the service info is filled with data. - */ - boolean hasData() - { - return server != null && addr != null && text != null; - } - - - // State machine - /** - * Sets the state and notifies all objects that wait on the ServiceInfo. - */ - synchronized void advanceState() - { - state = state.advance(); - notifyAll(); - } - - /** - * Sets the state and notifies all objects that wait on the ServiceInfo. - */ - synchronized void revertState() - { - state = state.revert(); - notifyAll(); - } - - /** - * Sets the state and notifies all objects that wait on the ServiceInfo. - */ - synchronized void cancel() - { - state = DNSState.CANCELED; - notifyAll(); - } - - /** - * Returns the current state of this info. - */ - DNSState getState() - { - return state; - } - - - @Override - public int hashCode() - { - return getQualifiedName().hashCode(); - } - - @Override - public boolean equals(Object obj) - { - return (obj instanceof ServiceInfo) && - getQualifiedName().equals(((ServiceInfo) obj).getQualifiedName()); - } - - public String getNiceTextString() - { - StringBuffer buf = new StringBuffer(); - for (int i = 0, len = text.length; i < len; i++) - { - if (i >= 20) - { - buf.append("..."); - break; - } - int ch = text[i] & 0xFF; - if ((ch < ' ') || (ch > 127)) - { - buf.append("\\0"); - buf.append(Integer.toString(ch, 8)); - } - else - { - buf.append((char) ch); - } - } - return buf.toString(); - } - - @Override - public String toString() - { - StringBuffer buf = new StringBuffer(); - buf.append("service["); - buf.append(getQualifiedName()); - buf.append(','); - buf.append(getAddress()); - buf.append(':'); - buf.append(port); - buf.append(','); - buf.append(getNiceTextString()); - buf.append(']'); - return buf.toString(); - } - - /** - * SC-Bonjour Implementation: Method used to set the properties of an existing ServiceInfo. - * This is used in the implementation of Bonjour in SIP Communicator to be able to replace - * old properties of the service we've declared to announce the local user with new properties - * (for example in case of a status change). - * @param props Hashtable containing all the new properties to set - */ - public void setProps(Map<String, Object> props) - { - if (props != null) - { - try - { - ByteArrayOutputStream out = new ByteArrayOutputStream(256); - for (Map.Entry<String, Object> prop : props.entrySet()) - { - String key = prop.getKey(); - Object val = prop.getValue(); - - ByteArrayOutputStream out2 = new ByteArrayOutputStream(100); - writeUTF(out2, key); - if (val instanceof String) - { - out2.write('='); - writeUTF(out2, (String) val); - } - else - { - if (val instanceof byte[]) - { - out2.write('='); - byte[] bval = (byte[]) val; - out2.write(bval, 0, bval.length); - } - else - { - if (val != NO_VALUE) - { - throw new IllegalArgumentException( - "invalid property value: " + val); - } - } - } - byte data[] = out2.toByteArray(); - out.write(data.length); - out.write(data, 0, data.length); - } - this.text = out.toByteArray(); - } - catch (IOException e) - { - throw new RuntimeException("unexpected exception: " + e); - } - } - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceListener.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceListener.java deleted file mode 100644 index 1c34adf..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceListener.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.util.*; - -/** - * Listener for service updates. - * - * @version %I%, %G% - * @author Arthur van Hoff, Werner Randelshofer - */ -public interface ServiceListener extends EventListener -{ - /** - * A service has been added. - * - * @param event The ServiceEvent providing the name and fully qualified type - * of the service. - */ - - void serviceAdded(ServiceEvent event); - - /** - * A service has been removed. - * - * @param event The ServiceEvent providing the name and fully qualified type - * of the service. - */ - void serviceRemoved(ServiceEvent event); - - /** - * A service has been resolved. Its details are now available in the - * ServiceInfo record. - * - * @param event The ServiceEvent providing the name, the fully qualified - * type of the service, and the service info record, - * or null if the service could not be resolved. - */ - - void serviceResolved(ServiceEvent event); -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceTypeListener.java b/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceTypeListener.java deleted file mode 100644 index 84e5c59..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/jmdns/ServiceTypeListener.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Copyright 2003-2005 Arthur van Hoff Rick Blair - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.java.sip.communicator.impl.protocol.zeroconf.jmdns; - -import java.util.*; - -/** - * Listener for service types. - * - * @version %I%, %G% - * @author Arthur van Hoff, Werner Randelshofer - */ -public interface ServiceTypeListener extends EventListener -{ - /** - * A new service type was discovered. - * - * @param event The service event providing the fully qualified type of - * the service. - */ - void serviceTypeAdded(ServiceEvent event); -} diff --git a/src/net/java/sip/communicator/impl/protocol/zeroconf/zeroconf.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/zeroconf/zeroconf.provider.manifest.mf deleted file mode 100644 index 24daba0..0000000 --- a/src/net/java/sip/communicator/impl/protocol/zeroconf/zeroconf.provider.manifest.mf +++ /dev/null @@ -1,12 +0,0 @@ -Bundle-Activator: net.java.sip.communicator.impl.protocol.zeroconf.ZeroconfActivator -Bundle-Name: Zeroconf Protocol Provider -Bundle-Description: A bundle providing support for the Zeroconf protocol. -Bundle-Vendor: jitsi.org -Bundle-Version: 0.0.1 -Bundle-SymbolicName: net.java.sip.communicator.protocol.zeroconf -Import-Package: org.osgi.framework, - org.jitsi.service.configuration, - org.jitsi.service.resources, net.java.sip.communicator.service.resources, - net.java.sip.communicator.util, - net.java.sip.communicator.service.protocol, - net.java.sip.communicator.service.protocol.event diff --git a/src/net/java/sip/communicator/impl/replacement/directimage/ReplacementServiceDirectImageImpl.java b/src/net/java/sip/communicator/impl/replacement/directimage/ReplacementServiceDirectImageImpl.java index 48bb362..3bdaaa4 100644 --- a/src/net/java/sip/communicator/impl/replacement/directimage/ReplacementServiceDirectImageImpl.java +++ b/src/net/java/sip/communicator/impl/replacement/directimage/ReplacementServiceDirectImageImpl.java @@ -45,7 +45,7 @@ public class ReplacementServiceDirectImageImpl * The regex used to match the link in the message. */ public static final String URL_PATTERN = - "https?\\:\\/\\/(www\\.)*.*\\.(?:jpg|png|gif)"; + "https?\\:\\/\\/.*\\.(?:jpg|png|gif)"; /** * Configuration label shown in the config form. diff --git a/src/net/java/sip/communicator/impl/resources/ResourceManagementActivator.java b/src/net/java/sip/communicator/impl/resources/ResourceManagementActivator.java index 6c76906..1e7e7e4 100644 --- a/src/net/java/sip/communicator/impl/resources/ResourceManagementActivator.java +++ b/src/net/java/sip/communicator/impl/resources/ResourceManagementActivator.java @@ -19,6 +19,7 @@ package net.java.sip.communicator.impl.resources; import net.java.sip.communicator.util.*; +import org.jitsi.service.configuration.*; import org.jitsi.service.resources.*; import org.osgi.framework.*; @@ -31,6 +32,7 @@ public class ResourceManagementActivator extends SimpleServiceActivator<ResourceManagementServiceImpl> { static BundleContext bundleContext; + private static ConfigurationService configService; /** * Creates new instance of <tt>ResourceManagementActivator</tt> @@ -68,4 +70,22 @@ public class ResourceManagementActivator { return new ResourceManagementServiceImpl(); } + + /** + * Returns the <tt>ConfigurationService</tt> obtained from the bundle + * context. + * @return the <tt>ConfigurationService</tt> obtained from the bundle + * context + */ + public static ConfigurationService getConfigService() + { + if(configService == null) + { + configService + = ServiceUtils.getService( + bundleContext, + ConfigurationService.class); + } + return configService; + } } diff --git a/src/net/java/sip/communicator/impl/resources/ResourceManagementServiceImpl.java b/src/net/java/sip/communicator/impl/resources/ResourceManagementServiceImpl.java index d116fea..1cc2dcf 100644 --- a/src/net/java/sip/communicator/impl/resources/ResourceManagementServiceImpl.java +++ b/src/net/java/sip/communicator/impl/resources/ResourceManagementServiceImpl.java @@ -344,4 +344,24 @@ public class ResourceManagementServiceImpl { return SkinJarBuilder.createBundleFromZip(zipFile, getImagePack()); } + + /** + * Gets the specified setting from the config service if present, otherwise + * from the embedded resources (resources/config/defaults.properties). + * + * @param key The setting to lookup. + * @return The setting for the key or {@code null} if not found. + */ + @Override + public String getSettingsString(String key) + { + Object configValue = ResourceManagementActivator + .getConfigService().getProperty(key); + if (configValue == null) + { + configValue = super.getSettingsString(key); + } + + return configValue == null ? null : configValue.toString(); + } } diff --git a/src/net/java/sip/communicator/impl/sysactivity/DBusNetworkManager.java b/src/net/java/sip/communicator/impl/sysactivity/DBusNetworkManager.java index 4c148df..c75e941 100644 --- a/src/net/java/sip/communicator/impl/sysactivity/DBusNetworkManager.java +++ b/src/net/java/sip/communicator/impl/sysactivity/DBusNetworkManager.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,133 +15,133 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.sysactivity;
-
-import org.freedesktop.dbus.*;
-import org.freedesktop.dbus.exceptions.*;
-
-/**
- * NetworkManager D-Bus Interface
- *
- * @author Damian Minkov
- * @author Ingo Bauersachs
- */
-@DBusInterfaceName("org.freedesktop.NetworkManager")
-public interface DBusNetworkManager
- extends DBusInterface
-{
- /*
- * Types of NetworkManager states for versions < 0.9
- */
- public static final int NM_STATE_UNKNOWN = 0;
- public static final int NM_STATE_ASLEEP = 1;
- public static final int NM_STATE_CONNECTING = 2;
- public static final int NM_STATE_CONNECTED = 3;
- public static final int NM_STATE_DISCONNECTED = 4;
-
- /*
- * Types of NetworkManager states for versions >= 0.9
- */
- public static final int NM9_STATE_UNKNOWN = 0;
- public static final int NM9_STATE_ASLEEP = 10;
- public static final int NM9_STATE_DISCONNECTED = 20;
- public static final int NM9_STATE_DISCONNECTING = 30;
- public static final int NM9_STATE_CONNECTING = 40;
- public static final int NM9_STATE_CONNECTED_LOCAL = 50;
- public static final int NM9_STATE_CONNECTED_SITE = 60;
- public static final int NM9_STATE_CONNECTED_GLOBAL = 70;
-
- /**
- * State change signal.
- */
- public class StateChange extends DBusSignal
- {
- /**
- * The name of the signal.
- */
- public final String name;
-
- /**
- * The current status it holds.
- */
- public final UInt32 status;
-
- /**
- * Creates status change.
- * @param path the path
- * @param status the status
- * @throws DBusException
- */
- public StateChange(String path, UInt32 status)
- throws DBusException
- {
- super(path, status);
- name = path;
- this.status = status;
- }
-
- /**
- * The current status.
- * @return the current status
- */
- public int getStatus()
- {
- return status.intValue();
- }
-
- /**
- * Returns status description
- * @return the status description
- */
- public String getStatusName()
- {
- switch(status.intValue())
- {
- case NM_STATE_ASLEEP : return "Asleep";
- case NM_STATE_CONNECTING : return "Connecting";
- case NM_STATE_CONNECTED : return "Connected";
- case NM_STATE_DISCONNECTED : return "Disconnected";
- default : return "Unknown";
- }
- }
- }
-
- /**
- * State changed signal.
- */
- public static class StateChanged extends StateChange
- {
- /**
- * Creates status changed.
- * @param path the path
- * @param status the status
- * @throws DBusException
- */
- public StateChanged(String path, UInt32 status)
- throws DBusException
- {
- super(path, status);
- }
-
- /**
- * Returns status description
- * @return the status name
- */
- @Override
- public String getStatusName()
- {
- switch(status.intValue())
- {
- case NM9_STATE_UNKNOWN: return "Unknown";
- case NM9_STATE_ASLEEP: return "Asleep";
- case NM9_STATE_DISCONNECTED: return "Disconnected";
- case NM9_STATE_DISCONNECTING: return "Disconnecting";
- case NM9_STATE_CONNECTING: return "Connecting";
- case NM9_STATE_CONNECTED_LOCAL: return "LocalConnectivity";
- case NM9_STATE_CONNECTED_SITE: return "SiteConnectivity";
- case NM9_STATE_CONNECTED_GLOBAL: return "GlobalConnectivity";
- default : return "Unknown";
- }
- }
- }
-}
+package net.java.sip.communicator.impl.sysactivity; + +import org.freedesktop.dbus.*; +import org.freedesktop.dbus.exceptions.*; + +/** + * NetworkManager D-Bus Interface + * + * @author Damian Minkov + * @author Ingo Bauersachs + */ +@DBusInterfaceName("org.freedesktop.NetworkManager") +public interface DBusNetworkManager + extends DBusInterface +{ + /* + * Types of NetworkManager states for versions < 0.9 + */ + public static final int NM_STATE_UNKNOWN = 0; + public static final int NM_STATE_ASLEEP = 1; + public static final int NM_STATE_CONNECTING = 2; + public static final int NM_STATE_CONNECTED = 3; + public static final int NM_STATE_DISCONNECTED = 4; + + /* + * Types of NetworkManager states for versions >= 0.9 + */ + public static final int NM9_STATE_UNKNOWN = 0; + public static final int NM9_STATE_ASLEEP = 10; + public static final int NM9_STATE_DISCONNECTED = 20; + public static final int NM9_STATE_DISCONNECTING = 30; + public static final int NM9_STATE_CONNECTING = 40; + public static final int NM9_STATE_CONNECTED_LOCAL = 50; + public static final int NM9_STATE_CONNECTED_SITE = 60; + public static final int NM9_STATE_CONNECTED_GLOBAL = 70; + + /** + * State change signal. + */ + public class StateChange extends DBusSignal + { + /** + * The name of the signal. + */ + public final String name; + + /** + * The current status it holds. + */ + public final UInt32 status; + + /** + * Creates status change. + * @param path the path + * @param status the status + * @throws DBusException + */ + public StateChange(String path, UInt32 status) + throws DBusException + { + super(path, status); + name = path; + this.status = status; + } + + /** + * The current status. + * @return the current status + */ + public int getStatus() + { + return status.intValue(); + } + + /** + * Returns status description + * @return the status description + */ + public String getStatusName() + { + switch(status.intValue()) + { + case NM_STATE_ASLEEP : return "Asleep"; + case NM_STATE_CONNECTING : return "Connecting"; + case NM_STATE_CONNECTED : return "Connected"; + case NM_STATE_DISCONNECTED : return "Disconnected"; + default : return "Unknown"; + } + } + } + + /** + * State changed signal. + */ + public static class StateChanged extends StateChange + { + /** + * Creates status changed. + * @param path the path + * @param status the status + * @throws DBusException + */ + public StateChanged(String path, UInt32 status) + throws DBusException + { + super(path, status); + } + + /** + * Returns status description + * @return the status name + */ + @Override + public String getStatusName() + { + switch(status.intValue()) + { + case NM9_STATE_UNKNOWN: return "Unknown"; + case NM9_STATE_ASLEEP: return "Asleep"; + case NM9_STATE_DISCONNECTED: return "Disconnected"; + case NM9_STATE_DISCONNECTING: return "Disconnecting"; + case NM9_STATE_CONNECTING: return "Connecting"; + case NM9_STATE_CONNECTED_LOCAL: return "LocalConnectivity"; + case NM9_STATE_CONNECTED_SITE: return "SiteConnectivity"; + case NM9_STATE_CONNECTED_GLOBAL: return "GlobalConnectivity"; + default : return "Unknown"; + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/sysactivity/NetworkManagerListenerImpl.java b/src/net/java/sip/communicator/impl/sysactivity/NetworkManagerListenerImpl.java index 952645a..94f8748 100644 --- a/src/net/java/sip/communicator/impl/sysactivity/NetworkManagerListenerImpl.java +++ b/src/net/java/sip/communicator/impl/sysactivity/NetworkManagerListenerImpl.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,164 +15,164 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.sysactivity;
-
-import net.java.sip.communicator.service.sysactivity.event.*;
-import net.java.sip.communicator.util.*;
-
-import org.freedesktop.*;
-import org.freedesktop.dbus.*;
-import org.freedesktop.dbus.exceptions.*;
-
-/**
- * Responsible for subscribe and dispatch signals from NetworkManager.
- * Uses dbus to connect.
- *
- * @author Damian Minkov
- */
-@SuppressWarnings("rawtypes")
-public class NetworkManagerListenerImpl
- implements DBusSigHandler,
- SystemActivityManager
-{
- /**
- * The logger.
- */
- private Logger logger = Logger.getLogger(
- NetworkManagerListenerImpl.class.getName());
-
- /**
- * Dbus connection we use.
- */
- private DBusConnection dbusConn;
-
- /**
- * Make only one instance.
- */
- public NetworkManagerListenerImpl()
- {
- try
- {
- dbusConn = DBusConnection.getConnection(DBusConnection.SYSTEM);
- }
- catch(DBusException e)
- {
- logger.error("Cannot obtain dbus connection", e);
- }
- }
-
- /**
- * Starts
- */
- @SuppressWarnings("unchecked")
- public void start()
- {
- // on error connecting to dbus do nothing
- if(dbusConn == null)
- return;
-
- try
- {
- dbusConn.addSigHandler(DBus.NameOwnerChanged.class, this);
- dbusConn.addSigHandler(DBusNetworkManager.StateChange.class, this);
- dbusConn.addSigHandler(DBusNetworkManager.StateChanged.class, this);
- }
- catch(DBusException e)
- {
- logger.error("Error adding dbus signal handlers", e);
- }
- }
-
- /**
- * Stops.
- */
- @SuppressWarnings("unchecked")
- public void stop()
- {
- // on error connecting to dbus do nothing
- if(dbusConn == null)
- return;
-
- try
- {
- dbusConn.removeSigHandler(DBus.NameOwnerChanged.class, this);
- dbusConn.removeSigHandler(
- DBusNetworkManager.StateChange.class, this);
- dbusConn.removeSigHandler(
- DBusNetworkManager.StateChanged.class, this);
- }
- catch(DBusException e)
- {
- logger.error("Error removing dbus signal handlers", e);
- }
- }
-
- /**
- * Receives signals and dispatch them.
- * @param dBusSignal signal to handle.
- */
- public void handle(DBusSignal dBusSignal)
- {
- if(dBusSignal instanceof DBus.NameOwnerChanged)
- {
- DBus.NameOwnerChanged nameOwnerChanged =
- (DBus.NameOwnerChanged)dBusSignal;
-
- if(nameOwnerChanged.name.equals("org.freedesktop.NetworkManager"))
- {
- boolean b1 = nameOwnerChanged.old_owner != null
- && nameOwnerChanged.old_owner.length() > 0;
- boolean b2 = nameOwnerChanged.new_owner != null
- && nameOwnerChanged.new_owner.length() > 0;
-
- if(b1 && !b2)
- {
- SystemActivityEvent evt = new SystemActivityEvent(
- SysActivityActivator.getSystemActivityService(),
- SystemActivityEvent.EVENT_NETWORK_CHANGE);
- SysActivityActivator.getSystemActivityService()
- .fireSystemActivityEvent(evt);
- }
- }
- }
- else if(dBusSignal instanceof DBusNetworkManager.StateChange)
- {
- DBusNetworkManager.StateChange stateChange =
- (DBusNetworkManager.StateChange)dBusSignal;
-
- SystemActivityEvent evt = null;
- switch(stateChange.getStatus())
- {
- case DBusNetworkManager.NM_STATE_CONNECTED:
- case DBusNetworkManager.NM_STATE_DISCONNECTED:
- case DBusNetworkManager.NM9_STATE_DISCONNECTED:
- case DBusNetworkManager.NM9_STATE_CONNECTED_LOCAL:
- case DBusNetworkManager.NM9_STATE_CONNECTED_SITE:
- case DBusNetworkManager.NM9_STATE_CONNECTED_GLOBAL:
- evt = new SystemActivityEvent(
- SysActivityActivator.getSystemActivityService(),
- SystemActivityEvent.EVENT_NETWORK_CHANGE);
- break;
- case DBusNetworkManager.NM_STATE_ASLEEP:
- case DBusNetworkManager.NM9_STATE_ASLEEP:
- evt = new SystemActivityEvent(
- SysActivityActivator.getSystemActivityService(),
- SystemActivityEvent.EVENT_SLEEP);
- break;
- }
-
- if(evt != null)
- SysActivityActivator.getSystemActivityService()
- .fireSystemActivityEvent(evt);
- }
- }
-
- /**
- * Whether we are connected to the network manager through dbus.
- * @return whether we are connected to the network manager.
- */
- public boolean isConnected()
- {
- return dbusConn != null;
- }
-}
+package net.java.sip.communicator.impl.sysactivity; + +import net.java.sip.communicator.service.sysactivity.event.*; +import net.java.sip.communicator.util.*; + +import org.freedesktop.*; +import org.freedesktop.dbus.*; +import org.freedesktop.dbus.exceptions.*; + +/** + * Responsible for subscribe and dispatch signals from NetworkManager. + * Uses dbus to connect. + * + * @author Damian Minkov + */ +@SuppressWarnings("rawtypes") +public class NetworkManagerListenerImpl + implements DBusSigHandler, + SystemActivityManager +{ + /** + * The logger. + */ + private Logger logger = Logger.getLogger( + NetworkManagerListenerImpl.class.getName()); + + /** + * Dbus connection we use. + */ + private DBusConnection dbusConn; + + /** + * Make only one instance. + */ + public NetworkManagerListenerImpl() + { + try + { + dbusConn = DBusConnection.getConnection(DBusConnection.SYSTEM); + } + catch(DBusException e) + { + logger.error("Cannot obtain dbus connection", e); + } + } + + /** + * Starts + */ + @SuppressWarnings("unchecked") + public void start() + { + // on error connecting to dbus do nothing + if(dbusConn == null) + return; + + try + { + dbusConn.addSigHandler(DBus.NameOwnerChanged.class, this); + dbusConn.addSigHandler(DBusNetworkManager.StateChange.class, this); + dbusConn.addSigHandler(DBusNetworkManager.StateChanged.class, this); + } + catch(DBusException e) + { + logger.error("Error adding dbus signal handlers", e); + } + } + + /** + * Stops. + */ + @SuppressWarnings("unchecked") + public void stop() + { + // on error connecting to dbus do nothing + if(dbusConn == null) + return; + + try + { + dbusConn.removeSigHandler(DBus.NameOwnerChanged.class, this); + dbusConn.removeSigHandler( + DBusNetworkManager.StateChange.class, this); + dbusConn.removeSigHandler( + DBusNetworkManager.StateChanged.class, this); + } + catch(DBusException e) + { + logger.error("Error removing dbus signal handlers", e); + } + } + + /** + * Receives signals and dispatch them. + * @param dBusSignal signal to handle. + */ + public void handle(DBusSignal dBusSignal) + { + if(dBusSignal instanceof DBus.NameOwnerChanged) + { + DBus.NameOwnerChanged nameOwnerChanged = + (DBus.NameOwnerChanged)dBusSignal; + + if(nameOwnerChanged.name.equals("org.freedesktop.NetworkManager")) + { + boolean b1 = nameOwnerChanged.old_owner != null + && nameOwnerChanged.old_owner.length() > 0; + boolean b2 = nameOwnerChanged.new_owner != null + && nameOwnerChanged.new_owner.length() > 0; + + if(b1 && !b2) + { + SystemActivityEvent evt = new SystemActivityEvent( + SysActivityActivator.getSystemActivityService(), + SystemActivityEvent.EVENT_NETWORK_CHANGE); + SysActivityActivator.getSystemActivityService() + .fireSystemActivityEvent(evt); + } + } + } + else if(dBusSignal instanceof DBusNetworkManager.StateChange) + { + DBusNetworkManager.StateChange stateChange = + (DBusNetworkManager.StateChange)dBusSignal; + + SystemActivityEvent evt = null; + switch(stateChange.getStatus()) + { + case DBusNetworkManager.NM_STATE_CONNECTED: + case DBusNetworkManager.NM_STATE_DISCONNECTED: + case DBusNetworkManager.NM9_STATE_DISCONNECTED: + case DBusNetworkManager.NM9_STATE_CONNECTED_LOCAL: + case DBusNetworkManager.NM9_STATE_CONNECTED_SITE: + case DBusNetworkManager.NM9_STATE_CONNECTED_GLOBAL: + evt = new SystemActivityEvent( + SysActivityActivator.getSystemActivityService(), + SystemActivityEvent.EVENT_NETWORK_CHANGE); + break; + case DBusNetworkManager.NM_STATE_ASLEEP: + case DBusNetworkManager.NM9_STATE_ASLEEP: + evt = new SystemActivityEvent( + SysActivityActivator.getSystemActivityService(), + SystemActivityEvent.EVENT_SLEEP); + break; + } + + if(evt != null) + SysActivityActivator.getSystemActivityService() + .fireSystemActivityEvent(evt); + } + } + + /** + * Whether we are connected to the network manager through dbus. + * @return whether we are connected to the network manager. + */ + public boolean isConnected() + { + return dbusConn != null; + } +} diff --git a/src/net/java/sip/communicator/impl/sysactivity/SysActivityActivator.java b/src/net/java/sip/communicator/impl/sysactivity/SysActivityActivator.java index 6a46e18..96cea29 100644 --- a/src/net/java/sip/communicator/impl/sysactivity/SysActivityActivator.java +++ b/src/net/java/sip/communicator/impl/sysactivity/SysActivityActivator.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,101 +15,101 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.sysactivity;
-
-import net.java.sip.communicator.service.sysactivity.*;
-import net.java.sip.communicator.util.*;
-
-import org.osgi.framework.*;
-
-/**
- * Listens for system activity changes like sleep, network change, inactivity
- * and informs all its listeners.
- *
- * @author Damian Minkov
- */
-public class SysActivityActivator
- implements BundleActivator
-{
- /**
- * The <tt>Logger</tt> used by this <tt>SysActivityActivator</tt> for
- * logging output.
- */
- private final Logger logger = Logger.getLogger(SysActivityActivator.class);
-
- /**
- * The OSGi <tt>BundleContext</tt>.
- */
- private static BundleContext bundleContext = null;
-
- /**
- * The system activity service impl.
- */
- private static SystemActivityNotificationsServiceImpl
- sysActivitiesServiceImpl;
-
- /**
- * Called when this bundle is started so the Framework can perform the
- * bundle-specific activities necessary to start this bundle.
- *
- * @param bundleContext The execution context of the bundle being started.
- * @throws Exception If this method throws an exception, this bundle is
- * marked as stopped and the Framework will remove this bundle's listeners,
- * unregister all services registered by this bundle, and release all
- * services used by this bundle.
- */
- public void start(BundleContext bundleContext)
- throws Exception
- {
- SysActivityActivator.bundleContext = bundleContext;
-
- if (logger.isDebugEnabled())
- logger.debug("Started.");
-
- sysActivitiesServiceImpl = new SystemActivityNotificationsServiceImpl();
- sysActivitiesServiceImpl.start();
-
- bundleContext.registerService(
- SystemActivityNotificationsService.class.getName(),
- sysActivitiesServiceImpl,
- null);
- }
-
- /**
- * Returns a reference to the bundle context that we were started with.
- * @return a reference to the BundleContext instance that we were started
- * with.
- */
- public static SystemActivityNotificationsServiceImpl
- getSystemActivityService()
- {
- return sysActivitiesServiceImpl;
- }
-
- /**
- * Called when this bundle is stopped so the Framework can perform the
- * bundle-specific activities necessary to stop the bundle.
- *
- * @param bundleContext The execution context of the bundle being stopped.
- * @throws Exception If this method throws an exception, the bundle is still
- * marked as stopped, and the Framework will remove the bundle's listeners,
- * unregister all services registered by the bundle, and release all
- * services used by the bundle.
- */
- public void stop(BundleContext bundleContext)
- throws Exception
- {
- if (sysActivitiesServiceImpl != null)
- sysActivitiesServiceImpl.stop();
- }
-
- /**
- * Returns a reference to the bundle context that we were started with.
- * @return a reference to the BundleContext instance that we were started
- * with.
- */
- public static BundleContext getBundleContext()
- {
- return bundleContext;
- }
-}
+package net.java.sip.communicator.impl.sysactivity; + +import net.java.sip.communicator.service.sysactivity.*; +import net.java.sip.communicator.util.*; + +import org.osgi.framework.*; + +/** + * Listens for system activity changes like sleep, network change, inactivity + * and informs all its listeners. + * + * @author Damian Minkov + */ +public class SysActivityActivator + implements BundleActivator +{ + /** + * The <tt>Logger</tt> used by this <tt>SysActivityActivator</tt> for + * logging output. + */ + private final Logger logger = Logger.getLogger(SysActivityActivator.class); + + /** + * The OSGi <tt>BundleContext</tt>. + */ + private static BundleContext bundleContext = null; + + /** + * The system activity service impl. + */ + private static SystemActivityNotificationsServiceImpl + sysActivitiesServiceImpl; + + /** + * Called when this bundle is started so the Framework can perform the + * bundle-specific activities necessary to start this bundle. + * + * @param bundleContext The execution context of the bundle being started. + * @throws Exception If this method throws an exception, this bundle is + * marked as stopped and the Framework will remove this bundle's listeners, + * unregister all services registered by this bundle, and release all + * services used by this bundle. + */ + public void start(BundleContext bundleContext) + throws Exception + { + SysActivityActivator.bundleContext = bundleContext; + + if (logger.isDebugEnabled()) + logger.debug("Started."); + + sysActivitiesServiceImpl = new SystemActivityNotificationsServiceImpl(); + sysActivitiesServiceImpl.start(); + + bundleContext.registerService( + SystemActivityNotificationsService.class.getName(), + sysActivitiesServiceImpl, + null); + } + + /** + * Returns a reference to the bundle context that we were started with. + * @return a reference to the BundleContext instance that we were started + * with. + */ + public static SystemActivityNotificationsServiceImpl + getSystemActivityService() + { + return sysActivitiesServiceImpl; + } + + /** + * Called when this bundle is stopped so the Framework can perform the + * bundle-specific activities necessary to stop the bundle. + * + * @param bundleContext The execution context of the bundle being stopped. + * @throws Exception If this method throws an exception, the bundle is still + * marked as stopped, and the Framework will remove the bundle's listeners, + * unregister all services registered by the bundle, and release all + * services used by the bundle. + */ + public void stop(BundleContext bundleContext) + throws Exception + { + if (sysActivitiesServiceImpl != null) + sysActivitiesServiceImpl.stop(); + } + + /** + * Returns a reference to the bundle context that we were started with. + * @return a reference to the BundleContext instance that we were started + * with. + */ + public static BundleContext getBundleContext() + { + return bundleContext; + } +} diff --git a/src/net/java/sip/communicator/impl/sysactivity/SystemActivityNotifications.java b/src/net/java/sip/communicator/impl/sysactivity/SystemActivityNotifications.java index a6ff936..e2fb17a 100644 --- a/src/net/java/sip/communicator/impl/sysactivity/SystemActivityNotifications.java +++ b/src/net/java/sip/communicator/impl/sysactivity/SystemActivityNotifications.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,235 +15,237 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.sysactivity;
-
-import net.java.sip.communicator.util.*;
-
-/**
- * @author Damian Minkov
- */
-public class SystemActivityNotifications
-{
- /**
- * The <tt>Logger</tt> used by the <tt>SystemActivityNotifications</tt>
- * class to log debugging information.
- */
- private static final Logger logger
- = Logger.getLogger(SystemActivityNotifications.class);
-
- /**
- * Computer display has stand by.
- */
- public static final int NOTIFY_DISPLAY_SLEEP = 2;
-
- /**
- * Computer display wakes up after stand by.
- */
- public static final int NOTIFY_DISPLAY_WAKE = 3;
-
- /**
- * A change in dns configuration has occurred.
- */
- public static final int NOTIFY_DNS_CHANGE = 10;
-
- /**
- * All processes have been informed about ending session, now notify for
- * the actual end session.
- */
- public static final int NOTIFY_ENDSESSION = 12;
-
- /**
- * A change in network configuration has occurred.
- */
- public static final int NOTIFY_NETWORK_CHANGE = 9;
-
- /**
- * Notifies for start of process of ending desktop session,
- * logoff or shutdown.
- */
- public static final int NOTIFY_QUERY_ENDSESSION = 11;
-
- /**
- * Screen has been locked.
- */
- public static final int NOTIFY_SCREEN_LOCKED = 7;
-
- /**
- * Screen has been unlocked.
- */
- public static final int NOTIFY_SCREEN_UNLOCKED = 8;
-
- /**
- * Screensaver has been started.
- */
- public static final int NOTIFY_SCREENSAVER_START = 4;
-
- /**
- * Screensaver has been stopped.
- */
- public static final int NOTIFY_SCREENSAVER_STOP = 6;
-
- /**
- * Screensaver will stop.
- */
- public static final int NOTIFY_SCREENSAVER_WILL_STOP = 5;
-
- /**
- * Notify that computers is going to sleep.
- */
- public static final int NOTIFY_SLEEP = 0;
-
- /**
- * Notify that computer is wakeing up after stand by.
- */
- public static final int NOTIFY_WAKE = 1;
-
- /**
- * The native instance.
- */
- private static long ptr;
-
- /**
- * Init native library.
- */
- static
- {
- try
- {
- // Don't load native library on Android to prevent the exception
- if(!org.jitsi.util.OSUtils.IS_ANDROID)
- {
- System.loadLibrary("sysactivitynotifications");
-
- ptr = allocAndInit();
- if (ptr == -1)
- ptr = 0;
- }
- }
- catch (Throwable t)
- {
- if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
- else
- logger.warn("Failed to initialize native counterpart", t);
- }
- }
-
- /**
- * Allocate native resources and gets a pointer.
- *
- * @return
- */
- private static native long allocAndInit();
-
- /**
- * Returns the when was last input in milliseconds. The time when there was
- * any activity on the computer.
- *
- * @return the last input in milliseconds
- */
- public static native long getLastInput();
-
- /**
- * Whether native library is loaded.
- *
- * @return whether native library is loaded.
- */
- public static boolean isLoaded()
- {
- return (ptr != 0);
- }
-
- /**
- * Release native resources.
- *
- * @param ptr
- */
- private static native void release(long ptr);
-
- /**
- * Sets notifier delegate.
- *
- * @param ptr
- * @param delegate
- */
- public static native void setDelegate(
- long ptr,
- NotificationsDelegate delegate);
-
- /**
- * Sets delegate.
- *
- * @param delegate
- */
- public static void setDelegate(NotificationsDelegate delegate)
- {
- if (ptr != 0)
- setDelegate(ptr, delegate);
- }
-
- /**
- * Start.
- */
- public static void start()
- {
- if (ptr != 0)
- start(ptr);
- }
-
- /**
- * Start processing.
- *
- * @param ptr
- */
- private static native void start(long ptr);
-
- /**
- * Stop.
- */
- public static void stop()
- {
- if (ptr != 0)
- {
- stop(ptr);
- release(ptr);
- ptr = 0;
- }
- }
-
- /**
- * Stop processing.
- *
- * @param ptr
- */
- private static native void stop(long ptr);
-
- /**
- * Delegate class to be notified about changes.
- */
- public interface NotificationsDelegate
- {
- /**
- * Callback method when receiving notifications.
- *
- * @param type
- */
- public void notify(int type);
-
- /**
- * Callback method when receiving special network notifications.
- *
- * @param family family of network change (ipv6, ipv4)
- * @param luidIndex unique index of interface
- * @param name name of the interface
- * @param type of the interface
- * @param connected whether interface is connected or not.
- */
- public void notifyNetworkChange(
- int family,
- long luidIndex,
- String name,
- long type,
- boolean connected);
- }
-}
+package net.java.sip.communicator.impl.sysactivity; + +import net.java.sip.communicator.util.Logger; +import org.jitsi.util.*; + +/** + * @author Damian Minkov + */ +public class SystemActivityNotifications +{ + /** + * The <tt>Logger</tt> used by the <tt>SystemActivityNotifications</tt> + * class to log debugging information. + */ + private static final Logger logger + = Logger.getLogger(SystemActivityNotifications.class); + + /** + * Computer display has stand by. + */ + public static final int NOTIFY_DISPLAY_SLEEP = 2; + + /** + * Computer display wakes up after stand by. + */ + public static final int NOTIFY_DISPLAY_WAKE = 3; + + /** + * A change in dns configuration has occurred. + */ + public static final int NOTIFY_DNS_CHANGE = 10; + + /** + * All processes have been informed about ending session, now notify for + * the actual end session. + */ + public static final int NOTIFY_ENDSESSION = 12; + + /** + * A change in network configuration has occurred. + */ + public static final int NOTIFY_NETWORK_CHANGE = 9; + + /** + * Notifies for start of process of ending desktop session, + * logoff or shutdown. + */ + public static final int NOTIFY_QUERY_ENDSESSION = 11; + + /** + * Screen has been locked. + */ + public static final int NOTIFY_SCREEN_LOCKED = 7; + + /** + * Screen has been unlocked. + */ + public static final int NOTIFY_SCREEN_UNLOCKED = 8; + + /** + * Screensaver has been started. + */ + public static final int NOTIFY_SCREENSAVER_START = 4; + + /** + * Screensaver has been stopped. + */ + public static final int NOTIFY_SCREENSAVER_STOP = 6; + + /** + * Screensaver will stop. + */ + public static final int NOTIFY_SCREENSAVER_WILL_STOP = 5; + + /** + * Notify that computers is going to sleep. + */ + public static final int NOTIFY_SLEEP = 0; + + /** + * Notify that computer is wakeing up after stand by. + */ + public static final int NOTIFY_WAKE = 1; + + /** + * The native instance. + */ + private static long ptr; + + /** + * Init native library. + */ + static + { + try + { + // Don't load native library on Android to prevent the exception + if(!org.jitsi.util.OSUtils.IS_ANDROID) + { + JNIUtils.loadLibrary("sysactivitynotifications", + SystemActivityNotifications.class); + + ptr = allocAndInit(); + if (ptr == -1) + ptr = 0; + } + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + logger.warn("Failed to initialize native counterpart", t); + } + } + + /** + * Allocate native resources and gets a pointer. + * + * @return + */ + private static native long allocAndInit(); + + /** + * Returns the when was last input in milliseconds. The time when there was + * any activity on the computer. + * + * @return the last input in milliseconds + */ + public static native long getLastInput(); + + /** + * Whether native library is loaded. + * + * @return whether native library is loaded. + */ + public static boolean isLoaded() + { + return (ptr != 0); + } + + /** + * Release native resources. + * + * @param ptr + */ + private static native void release(long ptr); + + /** + * Sets notifier delegate. + * + * @param ptr + * @param delegate + */ + public static native void setDelegate( + long ptr, + NotificationsDelegate delegate); + + /** + * Sets delegate. + * + * @param delegate + */ + public static void setDelegate(NotificationsDelegate delegate) + { + if (ptr != 0) + setDelegate(ptr, delegate); + } + + /** + * Start. + */ + public static void start() + { + if (ptr != 0) + start(ptr); + } + + /** + * Start processing. + * + * @param ptr + */ + private static native void start(long ptr); + + /** + * Stop. + */ + public static void stop() + { + if (ptr != 0) + { + stop(ptr); + release(ptr); + ptr = 0; + } + } + + /** + * Stop processing. + * + * @param ptr + */ + private static native void stop(long ptr); + + /** + * Delegate class to be notified about changes. + */ + public interface NotificationsDelegate + { + /** + * Callback method when receiving notifications. + * + * @param type + */ + public void notify(int type); + + /** + * Callback method when receiving special network notifications. + * + * @param family family of network change (ipv6, ipv4) + * @param luidIndex unique index of interface + * @param name name of the interface + * @param type of the interface + * @param connected whether interface is connected or not. + */ + public void notifyNetworkChange( + int family, + long luidIndex, + String name, + long type, + boolean connected); + } +} diff --git a/src/net/java/sip/communicator/impl/sysactivity/SystemActivityNotificationsServiceImpl.java b/src/net/java/sip/communicator/impl/sysactivity/SystemActivityNotificationsServiceImpl.java index c25504f..c58805b 100644 --- a/src/net/java/sip/communicator/impl/sysactivity/SystemActivityNotificationsServiceImpl.java +++ b/src/net/java/sip/communicator/impl/sysactivity/SystemActivityNotificationsServiceImpl.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,639 +15,639 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.java.sip.communicator.impl.sysactivity;
-
-import java.util.*;
-
-import net.java.sip.communicator.service.sysactivity.*;
-import net.java.sip.communicator.service.sysactivity.event.*;
-import net.java.sip.communicator.util.Logger;
-
-import org.jitsi.util.*;
-
-/**
- * Service implementation listens for computer changes as sleeping, network
- * change, inactivity.
- *
- * @author Damian Minkov
- */
-public class SystemActivityNotificationsServiceImpl
- implements SystemActivityNotifications.NotificationsDelegate,
- SystemActivityNotificationsService,
- Runnable
-{
- /**
- * The <tt>Logger</tt> used by this
- * <tt>SystemActivityNotificationsServiceImpl</tt> for logging output.
- */
- private final Logger logger
- = Logger.getLogger(SystemActivityNotificationsServiceImpl.class);
-
- /**
- * The thread dispatcher of network change events.
- */
- private final SystemActivityEventDispatcher eventDispatcher
- = new SystemActivityEventDispatcher();
-
- /**
- * A list of listeners registered for idle events.
- */
- private final Map<SystemActivityChangeListener,Long> idleChangeListeners
- = new HashMap<SystemActivityChangeListener, Long>();
-
- /**
- * Listeners which are fired for idle state and which will be fired
- * with idle end when needed.
- */
- private final List<SystemActivityChangeListener> listenersInIdleState
- = new ArrayList<SystemActivityChangeListener>();
-
- /**
- * The interval between checks when not idle.
- */
- private static final int CHECK_FOR_IDLE_DEFAULT = 30 * 1000;
-
- /**
- * The interval between checks when idle. The interval is shorter
- * so we can react almost immediately when we are active again.
- */
- private static final int CHECK_FOR_IDLE_WHEN_IDLE = 1000;
-
- /**
- * The time in milliseconds between two checks for system idle.
- */
- private static int idleStateCheckDelay = CHECK_FOR_IDLE_DEFAULT;
-
- /**
- * Whether current service is started or stopped.
- */
- private boolean running = false;
-
- /**
- * The time when we received latest network change event.
- */
- private long lastNetworkChange = -1;
-
- /**
- * Sometimes (on windows) we got several network change events
- * this is the time after which latest event we will skip next events.
- */
- private static final long NETWORK_EVENT_SILENT_TIME = 10*1000;
-
- /**
- * Whether network is currently connected.
- */
- private Boolean networkIsConnected = null;
-
- /**
- * The linux impl class name.
- */
- private static final String SYSTEM_ACTIVITY_MANAGER_LINUX_CLASS
- = "net.java.sip.communicator.impl.sysactivity.NetworkManagerListenerImpl";
-
- /**
- * The android impl class name.
- */
- private static final String SYSTEM_ACTIVITY_MANAGER_ANDROID_CLASS
- = "net.java.sip.communicator.impl.sysactivity.ConnectivityManagerListenerImpl";
-
- /**
- * The currently instantiated and working manager.
- */
- private SystemActivityManager currentRunningManager = null;
-
- /**
- * Init and start notifications.
- */
- public void start()
- {
- running = true;
-
- // set the delegate and start notification in new thread
- // make sure we don't block startup process
- Thread notifystartThread
- = new Thread(
- new Runnable()
- {
- public void run()
- {
- SystemActivityNotifications.setDelegate(
- SystemActivityNotificationsServiceImpl.this);
- SystemActivityNotifications.start();
- }
- },
- "SystemActivityNotificationsServiceImpl");
- notifystartThread.setDaemon(true);
- notifystartThread.start();
-
- if(isSupported(SystemActivityEvent.EVENT_SYSTEM_IDLE))
- {
- // a thread periodically checks system idle state and if it pass the
- // idle time for a particular listener, will inform it.
- Thread idleNotifyThread = new Thread(
- this,
- "SystemActivityNotificationsServiceImpl.IdleNotifyThread");
- idleNotifyThread.setDaemon(true);
- idleNotifyThread.start();
- }
-
- if (getCurrentRunningManager() != null)
- getCurrentRunningManager().start();
- }
-
- /**
- * Stop notifications.
- */
- public void stop()
- {
- SystemActivityNotifications.stop();
-
- if (getCurrentRunningManager() != null)
- getCurrentRunningManager().stop();
-
- eventDispatcher.stop();
-
- running = false;
-
- synchronized(this)
- {
- this.notifyAll();
- }
- }
-
- /**
- * Registers a listener that would be notified of changes that have occurred
- * in the underlying system.
- *
- * @param listener the listener that we'd like to register for changes in
- * the underlying system.
- */
- public void addSystemActivityChangeListener(
- SystemActivityChangeListener listener)
- {
- eventDispatcher.addSystemActivityChangeListener(listener);
- }
-
- /**
- * Remove the specified listener so that it won't receive further
- * notifications of changes that occur in the underlying system
- *
- * @param listener the listener to remove.
- */
- public void removeSystemActivityChangeListener(
- SystemActivityChangeListener listener)
- {
- eventDispatcher.removeSystemActivityChangeListener(listener);
- }
-
- /**
- * Registers a listener that would be notified for idle of the system
- * for <tt>idleTime</tt>.
- *
- * @param idleTime the time in milliseconds after which we will consider
- * system to be idle. This doesn't count when system seems idle as
- * monitor is off or screensaver is on, or desktop is locked.
- * @param listener the listener that we'd like to register for changes in
- * the underlying system.
- */
- public void addIdleSystemChangeListener(
- long idleTime,
- SystemActivityChangeListener listener)
- {
- synchronized (idleChangeListeners)
- {
- if (idleTime > 0
- && !idleChangeListeners.containsKey(listener))
- idleChangeListeners.put(listener, idleTime);
- }
- }
-
- /**
- * Remove the specified listener so that it won't receive further
- * notifications for idle system.
- *
- * @param listener the listener to remove.
- */
- public void removeIdleSystemChangeListener(
- SystemActivityChangeListener listener)
- {
- synchronized (idleChangeListeners)
- {
- idleChangeListeners.remove(listener);
- }
- }
-
- /**
- * The time since last user input. The time the system has been idle.
- * @return time the system has been idle.
- */
- public long getTimeSinceLastInput()
- {
- if(SystemActivityNotifications.isLoaded())
- return SystemActivityNotifications.getLastInput();
- else
- return -1;
- }
-
- /**
- * Callback method when receiving notifications.
- *
- * @param type type of the notification.
- */
- public void notify(int type)
- {
- SystemActivityEvent evt = null;
- switch(type)
- {
- case SystemActivityNotifications.NOTIFY_SLEEP :
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_SLEEP);
- break;
- case SystemActivityNotifications.NOTIFY_WAKE :
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_WAKE);
- break;
- case SystemActivityNotifications.NOTIFY_DISPLAY_SLEEP :
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_DISPLAY_SLEEP);
- break;
- case SystemActivityNotifications.NOTIFY_DISPLAY_WAKE :
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_DISPLAY_WAKE);
- break;
- case SystemActivityNotifications.NOTIFY_SCREENSAVER_START :
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_SCREENSAVER_START);
- break;
- case SystemActivityNotifications.NOTIFY_SCREENSAVER_WILL_STOP :
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_SCREENSAVER_WILL_STOP);
- break;
- case SystemActivityNotifications.NOTIFY_SCREENSAVER_STOP :
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_SCREENSAVER_STOP);
- break;
- case SystemActivityNotifications.NOTIFY_SCREEN_LOCKED :
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_SCREEN_LOCKED);
- break;
- case SystemActivityNotifications.NOTIFY_SCREEN_UNLOCKED :
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_SCREEN_UNLOCKED);
- break;
- case SystemActivityNotifications.NOTIFY_NETWORK_CHANGE :
- {
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_NETWORK_CHANGE);
- break;
- }
- case SystemActivityNotifications.NOTIFY_DNS_CHANGE :
- {
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_DNS_CHANGE);
- break;
- }
- case SystemActivityNotifications.NOTIFY_QUERY_ENDSESSION :
- {
- // both events QUERY_ENDSESSION and ENDSESSION
- // depend on the result one after another
- // we don't put them in new thread in order to give control
- // in the bundles using this events.
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_QUERY_ENDSESSION);
- eventDispatcher.fireSystemActivityEventCurrentThread(evt);
-
- return;
- }
- case SystemActivityNotifications.NOTIFY_ENDSESSION :
- {
- // both events QUERY_ENDSESSION and ENDSESSION
- // depend on the result one after another
- // we don't put them in new thread in order to give control
- // in the bundles using this events.
- evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_ENDSESSION);
- eventDispatcher.fireSystemActivityEventCurrentThread(evt);
-
- return;
- }
- }
-
- if (evt != null)
- fireSystemActivityEvent(evt);
- }
-
- /**
- * Callback method when receiving special network notifications.
- *
- * @param family family of network change (ipv6, ipv4)
- * AF_UNSPEC = 0 (The address family is unspecified.)
- * AF_INET = 2 (The Internet Protocol version 4 (IPv4) address family)
- * AF_INET6 = 23 (The Internet Protocol version 6 (IPv6) address family)
- * @param luidIndex unique index of interface
- * @param name name of the interface
- * @param type of the interface
- * Possible values for the interface type are listed in the Ipifcons.h file.
- * common values:
- * IF_TYPE_OTHER = 1 (Some other type of network interface.)
- * IF_TYPE_ETHERNET_CSMACD = 6 (An Ethernet network interface.)
- * IF_TYPE_ISO88025_TOKENRING = 9 (A token ring network interface.)
- * IF_TYPE_PPP = 23 (A PPP network interface.)
- * IF_TYPE_SOFTWARE_LOOPBACK = 24 (A software loopback network interface.)
- * IF_TYPE_IEEE80211 = 71 (An IEEE 802.11 wireless network interface.)
- * IF_TYPE_TUNNEL = 131 (A tunnel type encapsulation network interface.)
- * IF_TYPE_IEEE1394 = 144 (An IEEE 1394 (Firewire) high performance
- * serial bus network interface.)
- * @param connected whether interface is connected or not.
- */
- public void notifyNetworkChange(
- int family,
- long luidIndex,
- String name,
- long type,
- boolean connected)
- {
- long current = System.currentTimeMillis();
- if(current - lastNetworkChange <= NETWORK_EVENT_SILENT_TIME
- && (networkIsConnected != null && networkIsConnected.equals(connected)))
- {
- networkIsConnected = connected;
- return;
- }
-
- lastNetworkChange = current;
- networkIsConnected = connected;
-
- SystemActivityEvent evt = new SystemActivityEvent(this,
- SystemActivityEvent.EVENT_NETWORK_CHANGE);
- fireSystemActivityEvent(evt);
- }
-
- /**
- * The thread run method that handles idle notifies.
- *
- * @see Thread#run()
- */
- public void run()
- {
- while(running)
- {
- try
- {
- long idleTime = 0;
- if(idleChangeListeners.size() > 0)
- {
- // check
- idleTime = SystemActivityNotifications.getLastInput();
-
- if((idleTime < idleStateCheckDelay)
- && (listenersInIdleState.size() > 0))
- {
- for(SystemActivityChangeListener l
- : listenersInIdleState)
- {
- fireSystemIdleEndEvent(l);
- }
- listenersInIdleState.clear();
- }
-
- for(Map.Entry<SystemActivityChangeListener, Long> entry
- : idleChangeListeners.entrySet())
- {
- SystemActivityChangeListener listener =
- entry.getKey();
-
- if(!listenersInIdleState.contains(listener)
- && (entry.getValue() <= idleTime))
- {
- fireSystemIdleEvent(listener);
-
- listenersInIdleState.add(listener);
- }
- }
- }
-
- // if the minimum check for idle is X minutes
- // we will wait before checking (X - Y + 1sec)
- // where Y is the last idle time returned by OS
- if(listenersInIdleState.size() > 0)
- {
- idleStateCheckDelay = CHECK_FOR_IDLE_WHEN_IDLE;
- }
- else if(idleTime != 0)
- {
- long minIdleSetting = CHECK_FOR_IDLE_DEFAULT;
-
- if(!idleChangeListeners.isEmpty())
- minIdleSetting =
- Collections.min(idleChangeListeners.values());
-
- int newSetting = (int)(minIdleSetting - idleTime) + 1000;
-
- if(newSetting > 0)
- idleStateCheckDelay = newSetting;
- else
- idleStateCheckDelay = CHECK_FOR_IDLE_DEFAULT;
- }
- else
- {
- idleStateCheckDelay = CHECK_FOR_IDLE_DEFAULT;
- }
-
- // wait for the specified time
- synchronized(this)
- {
- this.wait(idleStateCheckDelay);
- }
- }
- catch(UnsatisfiedLinkError t)
- {
- logger.error("Missing native impl", t);
- return;
- }
- catch(Throwable t)
- {
- logger.error("Error checking for idle", t);
- }
- }
- }
-
- /**
- * Delivers the specified event to all registered listeners.
- *
- * @param evt the <tt>SystemActivityEvent</tt> that we'd like delivered to
- * all registered message listeners.
- */
- protected void fireSystemActivityEvent(SystemActivityEvent evt)
- {
- int eventID = evt.getEventID();
-
- // Add network activity info to track wake up problems.
- if (logger.isInfoEnabled()
- && ((eventID == SystemActivityEvent.EVENT_NETWORK_CHANGE)
- || (eventID == SystemActivityEvent.EVENT_DNS_CHANGE)))
- {
- logger.info("Received system activity event: " + evt);
- }
-
- if (eventID == SystemActivityEvent.EVENT_NETWORK_CHANGE)
- {
- // Give time to Java to dispatch same event and populate its network
- // interfaces.
- eventDispatcher.fireSystemActivityEvent(evt, 500);
- }
- else
- eventDispatcher.fireSystemActivityEvent(evt);
- }
-
- /**
- * Delivers the specified event to all registered listeners.
- *
- * @param listener listener to inform
- */
- protected void fireSystemIdleEvent(SystemActivityChangeListener listener)
- {
- SystemActivityEvent evt
- = new SystemActivityEvent(
- this,
- SystemActivityEvent.EVENT_SYSTEM_IDLE);
-
- if (logger.isDebugEnabled())
- logger.debug("Dispatching SystemActivityEvent evt=" + evt);
-
- try
- {
- listener.activityChanged(evt);
- }
- catch (Throwable t)
- {
- if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
- else
- logger.error("Error delivering event", t);
- }
- }
-
- /**
- * Delivers the specified event to listener.
- *
- * @param listener listener to inform
- */
- protected void fireSystemIdleEndEvent(
- SystemActivityChangeListener listener)
- {
- SystemActivityEvent evt
- = new SystemActivityEvent(
- this,
- SystemActivityEvent.EVENT_SYSTEM_IDLE_END);
-
- if (logger.isDebugEnabled())
- logger.debug("Dispatching SystemActivityEvent evt=" + evt);
-
- try
- {
- listener.activityChanged(evt);
- }
- catch (Throwable t)
- {
- if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
- else
- logger.error("Error delivering event", t);
- }
- }
-
- /**
- * Can check whether an event id is supported on
- * current operation system.
- * Simple return what is implemented in native, and checks
- * are made when possible, for example linux cannot connect
- * to NM through dbus.
- * @param eventID the event to check.
- * @return whether the supplied event id is supported.
- */
- public boolean isSupported(int eventID)
- {
- if(OSUtils.IS_WINDOWS)
- {
- switch(eventID)
- {
- case SystemActivityEvent.EVENT_SLEEP:
- case SystemActivityEvent.EVENT_WAKE:
- case SystemActivityEvent.EVENT_NETWORK_CHANGE:
- case SystemActivityEvent.EVENT_SYSTEM_IDLE:
- case SystemActivityEvent.EVENT_SYSTEM_IDLE_END:
- return SystemActivityNotifications.isLoaded();
- default:
- return false;
- }
- }
- else if(OSUtils.IS_MAC)
- {
- return SystemActivityNotifications.isLoaded();
- }
- else if(OSUtils.IS_LINUX)
- {
- switch(eventID)
- {
- case SystemActivityEvent.EVENT_SLEEP:
- case SystemActivityEvent.EVENT_NETWORK_CHANGE:
- {
- SystemActivityManager currentRunningManager
- = getCurrentRunningManager();
-
- return
- (currentRunningManager == null)
- ? false
- : currentRunningManager.isConnected();
- }
- case SystemActivityEvent.EVENT_SYSTEM_IDLE:
- case SystemActivityEvent.EVENT_SYSTEM_IDLE_END:
- return SystemActivityNotifications.isLoaded();
- default:
- return false;
- }
- }
- else if(OSUtils.IS_ANDROID)
- {
- return (eventID == SystemActivityEvent.EVENT_NETWORK_CHANGE);
- }
- else
- {
- return false;
- }
- }
-
- /**
- * Returns or instantiate the manager.
- * @return
- */
- private SystemActivityManager getCurrentRunningManager()
- {
- if(currentRunningManager == null)
- {
- try
- {
- String className = null;
- if(OSUtils.IS_LINUX)
- {
- className = SYSTEM_ACTIVITY_MANAGER_LINUX_CLASS;
- }
- else if(OSUtils.IS_ANDROID)
- {
- className = SYSTEM_ACTIVITY_MANAGER_ANDROID_CLASS;
- }
-
- if(className != null)
- currentRunningManager = (SystemActivityManager)
- Class.forName(className).newInstance();
- }
- catch(Throwable t)
- {
- logger.error("Error creating manager", t);
- }
- }
-
- return currentRunningManager;
- }
-}
+package net.java.sip.communicator.impl.sysactivity; + +import java.util.*; + +import net.java.sip.communicator.service.sysactivity.*; +import net.java.sip.communicator.service.sysactivity.event.*; +import net.java.sip.communicator.util.Logger; + +import org.jitsi.util.*; + +/** + * Service implementation listens for computer changes as sleeping, network + * change, inactivity. + * + * @author Damian Minkov + */ +public class SystemActivityNotificationsServiceImpl + implements SystemActivityNotifications.NotificationsDelegate, + SystemActivityNotificationsService, + Runnable +{ + /** + * The <tt>Logger</tt> used by this + * <tt>SystemActivityNotificationsServiceImpl</tt> for logging output. + */ + private final Logger logger + = Logger.getLogger(SystemActivityNotificationsServiceImpl.class); + + /** + * The thread dispatcher of network change events. + */ + private final SystemActivityEventDispatcher eventDispatcher + = new SystemActivityEventDispatcher(); + + /** + * A list of listeners registered for idle events. + */ + private final Map<SystemActivityChangeListener,Long> idleChangeListeners + = new HashMap<SystemActivityChangeListener, Long>(); + + /** + * Listeners which are fired for idle state and which will be fired + * with idle end when needed. + */ + private final List<SystemActivityChangeListener> listenersInIdleState + = new ArrayList<SystemActivityChangeListener>(); + + /** + * The interval between checks when not idle. + */ + private static final int CHECK_FOR_IDLE_DEFAULT = 30 * 1000; + + /** + * The interval between checks when idle. The interval is shorter + * so we can react almost immediately when we are active again. + */ + private static final int CHECK_FOR_IDLE_WHEN_IDLE = 1000; + + /** + * The time in milliseconds between two checks for system idle. + */ + private static int idleStateCheckDelay = CHECK_FOR_IDLE_DEFAULT; + + /** + * Whether current service is started or stopped. + */ + private boolean running = false; + + /** + * The time when we received latest network change event. + */ + private long lastNetworkChange = -1; + + /** + * Sometimes (on windows) we got several network change events + * this is the time after which latest event we will skip next events. + */ + private static final long NETWORK_EVENT_SILENT_TIME = 10*1000; + + /** + * Whether network is currently connected. + */ + private Boolean networkIsConnected = null; + + /** + * The linux impl class name. + */ + private static final String SYSTEM_ACTIVITY_MANAGER_LINUX_CLASS + = "net.java.sip.communicator.impl.sysactivity.NetworkManagerListenerImpl"; + + /** + * The android impl class name. + */ + private static final String SYSTEM_ACTIVITY_MANAGER_ANDROID_CLASS + = "net.java.sip.communicator.impl.sysactivity.ConnectivityManagerListenerImpl"; + + /** + * The currently instantiated and working manager. + */ + private SystemActivityManager currentRunningManager = null; + + /** + * Init and start notifications. + */ + public void start() + { + running = true; + + // set the delegate and start notification in new thread + // make sure we don't block startup process + Thread notifystartThread + = new Thread( + new Runnable() + { + public void run() + { + SystemActivityNotifications.setDelegate( + SystemActivityNotificationsServiceImpl.this); + SystemActivityNotifications.start(); + } + }, + "SystemActivityNotificationsServiceImpl"); + notifystartThread.setDaemon(true); + notifystartThread.start(); + + if(isSupported(SystemActivityEvent.EVENT_SYSTEM_IDLE)) + { + // a thread periodically checks system idle state and if it pass the + // idle time for a particular listener, will inform it. + Thread idleNotifyThread = new Thread( + this, + "SystemActivityNotificationsServiceImpl.IdleNotifyThread"); + idleNotifyThread.setDaemon(true); + idleNotifyThread.start(); + } + + if (getCurrentRunningManager() != null) + getCurrentRunningManager().start(); + } + + /** + * Stop notifications. + */ + public void stop() + { + SystemActivityNotifications.stop(); + + if (getCurrentRunningManager() != null) + getCurrentRunningManager().stop(); + + eventDispatcher.stop(); + + running = false; + + synchronized(this) + { + this.notifyAll(); + } + } + + /** + * Registers a listener that would be notified of changes that have occurred + * in the underlying system. + * + * @param listener the listener that we'd like to register for changes in + * the underlying system. + */ + public void addSystemActivityChangeListener( + SystemActivityChangeListener listener) + { + eventDispatcher.addSystemActivityChangeListener(listener); + } + + /** + * Remove the specified listener so that it won't receive further + * notifications of changes that occur in the underlying system + * + * @param listener the listener to remove. + */ + public void removeSystemActivityChangeListener( + SystemActivityChangeListener listener) + { + eventDispatcher.removeSystemActivityChangeListener(listener); + } + + /** + * Registers a listener that would be notified for idle of the system + * for <tt>idleTime</tt>. + * + * @param idleTime the time in milliseconds after which we will consider + * system to be idle. This doesn't count when system seems idle as + * monitor is off or screensaver is on, or desktop is locked. + * @param listener the listener that we'd like to register for changes in + * the underlying system. + */ + public void addIdleSystemChangeListener( + long idleTime, + SystemActivityChangeListener listener) + { + synchronized (idleChangeListeners) + { + if (idleTime > 0 + && !idleChangeListeners.containsKey(listener)) + idleChangeListeners.put(listener, idleTime); + } + } + + /** + * Remove the specified listener so that it won't receive further + * notifications for idle system. + * + * @param listener the listener to remove. + */ + public void removeIdleSystemChangeListener( + SystemActivityChangeListener listener) + { + synchronized (idleChangeListeners) + { + idleChangeListeners.remove(listener); + } + } + + /** + * The time since last user input. The time the system has been idle. + * @return time the system has been idle. + */ + public long getTimeSinceLastInput() + { + if(SystemActivityNotifications.isLoaded()) + return SystemActivityNotifications.getLastInput(); + else + return -1; + } + + /** + * Callback method when receiving notifications. + * + * @param type type of the notification. + */ + public void notify(int type) + { + SystemActivityEvent evt = null; + switch(type) + { + case SystemActivityNotifications.NOTIFY_SLEEP : + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_SLEEP); + break; + case SystemActivityNotifications.NOTIFY_WAKE : + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_WAKE); + break; + case SystemActivityNotifications.NOTIFY_DISPLAY_SLEEP : + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_DISPLAY_SLEEP); + break; + case SystemActivityNotifications.NOTIFY_DISPLAY_WAKE : + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_DISPLAY_WAKE); + break; + case SystemActivityNotifications.NOTIFY_SCREENSAVER_START : + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_SCREENSAVER_START); + break; + case SystemActivityNotifications.NOTIFY_SCREENSAVER_WILL_STOP : + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_SCREENSAVER_WILL_STOP); + break; + case SystemActivityNotifications.NOTIFY_SCREENSAVER_STOP : + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_SCREENSAVER_STOP); + break; + case SystemActivityNotifications.NOTIFY_SCREEN_LOCKED : + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_SCREEN_LOCKED); + break; + case SystemActivityNotifications.NOTIFY_SCREEN_UNLOCKED : + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_SCREEN_UNLOCKED); + break; + case SystemActivityNotifications.NOTIFY_NETWORK_CHANGE : + { + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_NETWORK_CHANGE); + break; + } + case SystemActivityNotifications.NOTIFY_DNS_CHANGE : + { + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_DNS_CHANGE); + break; + } + case SystemActivityNotifications.NOTIFY_QUERY_ENDSESSION : + { + // both events QUERY_ENDSESSION and ENDSESSION + // depend on the result one after another + // we don't put them in new thread in order to give control + // in the bundles using this events. + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_QUERY_ENDSESSION); + eventDispatcher.fireSystemActivityEventCurrentThread(evt); + + return; + } + case SystemActivityNotifications.NOTIFY_ENDSESSION : + { + // both events QUERY_ENDSESSION and ENDSESSION + // depend on the result one after another + // we don't put them in new thread in order to give control + // in the bundles using this events. + evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_ENDSESSION); + eventDispatcher.fireSystemActivityEventCurrentThread(evt); + + return; + } + } + + if (evt != null) + fireSystemActivityEvent(evt); + } + + /** + * Callback method when receiving special network notifications. + * + * @param family family of network change (ipv6, ipv4) + * AF_UNSPEC = 0 (The address family is unspecified.) + * AF_INET = 2 (The Internet Protocol version 4 (IPv4) address family) + * AF_INET6 = 23 (The Internet Protocol version 6 (IPv6) address family) + * @param luidIndex unique index of interface + * @param name name of the interface + * @param type of the interface + * Possible values for the interface type are listed in the Ipifcons.h file. + * common values: + * IF_TYPE_OTHER = 1 (Some other type of network interface.) + * IF_TYPE_ETHERNET_CSMACD = 6 (An Ethernet network interface.) + * IF_TYPE_ISO88025_TOKENRING = 9 (A token ring network interface.) + * IF_TYPE_PPP = 23 (A PPP network interface.) + * IF_TYPE_SOFTWARE_LOOPBACK = 24 (A software loopback network interface.) + * IF_TYPE_IEEE80211 = 71 (An IEEE 802.11 wireless network interface.) + * IF_TYPE_TUNNEL = 131 (A tunnel type encapsulation network interface.) + * IF_TYPE_IEEE1394 = 144 (An IEEE 1394 (Firewire) high performance + * serial bus network interface.) + * @param connected whether interface is connected or not. + */ + public void notifyNetworkChange( + int family, + long luidIndex, + String name, + long type, + boolean connected) + { + long current = System.currentTimeMillis(); + if(current - lastNetworkChange <= NETWORK_EVENT_SILENT_TIME + && (networkIsConnected != null && networkIsConnected.equals(connected))) + { + networkIsConnected = connected; + return; + } + + lastNetworkChange = current; + networkIsConnected = connected; + + SystemActivityEvent evt = new SystemActivityEvent(this, + SystemActivityEvent.EVENT_NETWORK_CHANGE); + fireSystemActivityEvent(evt); + } + + /** + * The thread run method that handles idle notifies. + * + * @see Thread#run() + */ + public void run() + { + while(running) + { + try + { + long idleTime = 0; + if(idleChangeListeners.size() > 0) + { + // check + idleTime = SystemActivityNotifications.getLastInput(); + + if((idleTime < idleStateCheckDelay) + && (listenersInIdleState.size() > 0)) + { + for(SystemActivityChangeListener l + : listenersInIdleState) + { + fireSystemIdleEndEvent(l); + } + listenersInIdleState.clear(); + } + + for(Map.Entry<SystemActivityChangeListener, Long> entry + : idleChangeListeners.entrySet()) + { + SystemActivityChangeListener listener = + entry.getKey(); + + if(!listenersInIdleState.contains(listener) + && (entry.getValue() <= idleTime)) + { + fireSystemIdleEvent(listener); + + listenersInIdleState.add(listener); + } + } + } + + // if the minimum check for idle is X minutes + // we will wait before checking (X - Y + 1sec) + // where Y is the last idle time returned by OS + if(listenersInIdleState.size() > 0) + { + idleStateCheckDelay = CHECK_FOR_IDLE_WHEN_IDLE; + } + else if(idleTime != 0) + { + long minIdleSetting = CHECK_FOR_IDLE_DEFAULT; + + if(!idleChangeListeners.isEmpty()) + minIdleSetting = + Collections.min(idleChangeListeners.values()); + + int newSetting = (int)(minIdleSetting - idleTime) + 1000; + + if(newSetting > 0) + idleStateCheckDelay = newSetting; + else + idleStateCheckDelay = CHECK_FOR_IDLE_DEFAULT; + } + else + { + idleStateCheckDelay = CHECK_FOR_IDLE_DEFAULT; + } + + // wait for the specified time + synchronized(this) + { + this.wait(idleStateCheckDelay); + } + } + catch(UnsatisfiedLinkError t) + { + logger.error("Missing native impl", t); + return; + } + catch(Throwable t) + { + logger.error("Error checking for idle", t); + } + } + } + + /** + * Delivers the specified event to all registered listeners. + * + * @param evt the <tt>SystemActivityEvent</tt> that we'd like delivered to + * all registered message listeners. + */ + protected void fireSystemActivityEvent(SystemActivityEvent evt) + { + int eventID = evt.getEventID(); + + // Add network activity info to track wake up problems. + if (logger.isInfoEnabled() + && ((eventID == SystemActivityEvent.EVENT_NETWORK_CHANGE) + || (eventID == SystemActivityEvent.EVENT_DNS_CHANGE))) + { + logger.info("Received system activity event: " + evt); + } + + if (eventID == SystemActivityEvent.EVENT_NETWORK_CHANGE) + { + // Give time to Java to dispatch same event and populate its network + // interfaces. + eventDispatcher.fireSystemActivityEvent(evt, 500); + } + else + eventDispatcher.fireSystemActivityEvent(evt); + } + + /** + * Delivers the specified event to all registered listeners. + * + * @param listener listener to inform + */ + protected void fireSystemIdleEvent(SystemActivityChangeListener listener) + { + SystemActivityEvent evt + = new SystemActivityEvent( + this, + SystemActivityEvent.EVENT_SYSTEM_IDLE); + + if (logger.isDebugEnabled()) + logger.debug("Dispatching SystemActivityEvent evt=" + evt); + + try + { + listener.activityChanged(evt); + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + logger.error("Error delivering event", t); + } + } + + /** + * Delivers the specified event to listener. + * + * @param listener listener to inform + */ + protected void fireSystemIdleEndEvent( + SystemActivityChangeListener listener) + { + SystemActivityEvent evt + = new SystemActivityEvent( + this, + SystemActivityEvent.EVENT_SYSTEM_IDLE_END); + + if (logger.isDebugEnabled()) + logger.debug("Dispatching SystemActivityEvent evt=" + evt); + + try + { + listener.activityChanged(evt); + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + logger.error("Error delivering event", t); + } + } + + /** + * Can check whether an event id is supported on + * current operation system. + * Simple return what is implemented in native, and checks + * are made when possible, for example linux cannot connect + * to NM through dbus. + * @param eventID the event to check. + * @return whether the supplied event id is supported. + */ + public boolean isSupported(int eventID) + { + if(OSUtils.IS_WINDOWS) + { + switch(eventID) + { + case SystemActivityEvent.EVENT_SLEEP: + case SystemActivityEvent.EVENT_WAKE: + case SystemActivityEvent.EVENT_NETWORK_CHANGE: + case SystemActivityEvent.EVENT_SYSTEM_IDLE: + case SystemActivityEvent.EVENT_SYSTEM_IDLE_END: + return SystemActivityNotifications.isLoaded(); + default: + return false; + } + } + else if(OSUtils.IS_MAC) + { + return SystemActivityNotifications.isLoaded(); + } + else if(OSUtils.IS_LINUX) + { + switch(eventID) + { + case SystemActivityEvent.EVENT_SLEEP: + case SystemActivityEvent.EVENT_NETWORK_CHANGE: + { + SystemActivityManager currentRunningManager + = getCurrentRunningManager(); + + return + (currentRunningManager == null) + ? false + : currentRunningManager.isConnected(); + } + case SystemActivityEvent.EVENT_SYSTEM_IDLE: + case SystemActivityEvent.EVENT_SYSTEM_IDLE_END: + return SystemActivityNotifications.isLoaded(); + default: + return false; + } + } + else if(OSUtils.IS_ANDROID) + { + return (eventID == SystemActivityEvent.EVENT_NETWORK_CHANGE); + } + else + { + return false; + } + } + + /** + * Returns or instantiate the manager. + * @return + */ + private SystemActivityManager getCurrentRunningManager() + { + if(currentRunningManager == null) + { + try + { + String className = null; + if(OSUtils.IS_LINUX) + { + className = SYSTEM_ACTIVITY_MANAGER_LINUX_CLASS; + } + else if(OSUtils.IS_ANDROID) + { + className = SYSTEM_ACTIVITY_MANAGER_ANDROID_CLASS; + } + + if(className != null) + currentRunningManager = (SystemActivityManager) + Class.forName(className).newInstance(); + } + catch(Throwable t) + { + logger.error("Error creating manager", t); + } + } + + return currentRunningManager; + } +} diff --git a/src/net/java/sip/communicator/impl/version/VersionImpl.java b/src/net/java/sip/communicator/impl/version/VersionImpl.java index 8bed4ec..74e199c 100644 --- a/src/net/java/sip/communicator/impl/version/VersionImpl.java +++ b/src/net/java/sip/communicator/impl/version/VersionImpl.java @@ -42,7 +42,7 @@ public class VersionImpl * number changes when a relatively extensive set of new features and * possibly rearchitecturing have been applied to the Jitsi. */ - public static final int VERSION_MINOR = 9; + public static final int VERSION_MINOR = 11; /** * Indicates whether this version represents a prerelease (i.e. a |