aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/service/protocol
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/java/sip/communicator/service/protocol')
-rw-r--r--src/net/java/sip/communicator/service/protocol/AbstractOperationSetBasicInstantMessaging.java8
-rw-r--r--src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java13
-rw-r--r--src/net/java/sip/communicator/service/protocol/AccountManager.java2166
-rw-r--r--src/net/java/sip/communicator/service/protocol/AccountManagerUtils.java332
-rw-r--r--src/net/java/sip/communicator/service/protocol/AdHocChatRoom.java3
-rw-r--r--src/net/java/sip/communicator/service/protocol/Call.java12
-rw-r--r--src/net/java/sip/communicator/service/protocol/CallConference.java1858
-rw-r--r--src/net/java/sip/communicator/service/protocol/OperationSetBasicInstantMessaging.java16
-rw-r--r--src/net/java/sip/communicator/service/protocol/OperationSetJitsiMeetTools.java11
-rw-r--r--src/net/java/sip/communicator/service/protocol/OperationSetSecureSDesTelephony.java26
-rw-r--r--src/net/java/sip/communicator/service/protocol/OperationSetSecureZrtpTelephony.java26
-rw-r--r--src/net/java/sip/communicator/service/protocol/OperationSetTelephonyBLF.java18
-rw-r--r--src/net/java/sip/communicator/service/protocol/PhoneNumberI18nService.java104
-rw-r--r--src/net/java/sip/communicator/service/protocol/ProtocolNames.java35
-rw-r--r--src/net/java/sip/communicator/service/protocol/ProtocolProviderActivator.java586
-rw-r--r--src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java2609
-rw-r--r--src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java24
-rw-r--r--src/net/java/sip/communicator/service/protocol/RegistrationState.java6
-rw-r--r--src/net/java/sip/communicator/service/protocol/ServerStoredDetails.java6
-rw-r--r--src/net/java/sip/communicator/service/protocol/WhiteboardPoint.java8
-rw-r--r--src/net/java/sip/communicator/service/protocol/event/AccountManagerEvent.java168
-rw-r--r--src/net/java/sip/communicator/service/protocol/event/AccountManagerListener.java52
-rw-r--r--src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityMessageEvent.java15
-rw-r--r--src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityStatusEvent.java15
-rw-r--r--src/net/java/sip/communicator/service/protocol/jabber/AbstractSmackInteroperabilityLayer.java141
-rw-r--r--src/net/java/sip/communicator/service/protocol/jabber/JabberAccountID.java29
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java62
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java2
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java1286
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java2490
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/TransportManager.java1756
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/protocol.media.manifest.mf1
-rw-r--r--src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf4
-rw-r--r--src/net/java/sip/communicator/service/protocol/sip/SIPAccountRegistration.java38
-rw-r--r--src/net/java/sip/communicator/service/protocol/yahooconstants/YahooStatusEnum.java200
35 files changed, 7125 insertions, 7001 deletions
diff --git a/src/net/java/sip/communicator/service/protocol/AbstractOperationSetBasicInstantMessaging.java b/src/net/java/sip/communicator/service/protocol/AbstractOperationSetBasicInstantMessaging.java
index 4270a78..6f038b5 100644
--- a/src/net/java/sip/communicator/service/protocol/AbstractOperationSetBasicInstantMessaging.java
+++ b/src/net/java/sip/communicator/service/protocol/AbstractOperationSetBasicInstantMessaging.java
@@ -137,14 +137,6 @@ public abstract class AbstractOperationSetBasicInstantMessaging
return createMessage(messageText);
}
- /**
- * {@inheritDoc}
- */
- public Message createMessageWithUID(String messageText, String messageUID)
- {
- return createMessage(messageText);
- }
-
public abstract Message createMessage(
String content, String contentType, String encoding, String subject);
diff --git a/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java b/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java
index 9a78eec..897b74b 100644
--- a/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java
+++ b/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java
@@ -245,6 +245,19 @@ public abstract class AbstractProtocolProviderService
}
/**
+ * Default implementation that always returns true.
+ *
+ * @param contactId ignored.
+ * @param result ignored
+ * @return true
+ */
+ @Override
+ public boolean validateContactAddress(String contactId, List<String> result)
+ {
+ return true;
+ }
+
+ /**
* Returns an array containing all operation sets supported by the current
* implementation. When querying this method users must be prepared to
* receive any subset of the OperationSet-s defined by this service. They
diff --git a/src/net/java/sip/communicator/service/protocol/AccountManager.java b/src/net/java/sip/communicator/service/protocol/AccountManager.java
index c608092..3ce85dc 100644
--- a/src/net/java/sip/communicator/service/protocol/AccountManager.java
+++ b/src/net/java/sip/communicator/service/protocol/AccountManager.java
@@ -1,1083 +1,1083 @@
-/*
- * 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.service.protocol;
-
-import java.util.*;
-
-import net.java.sip.communicator.service.credentialsstorage.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.util.*;
-import net.java.sip.communicator.util.Base64; //disambiguate from java.util.Base64
-
-import org.jitsi.service.configuration.*;
-import org.osgi.framework.*;
-
-/**
- * Represents an implementation of <tt>AccountManager</tt> which loads the
- * accounts in a separate thread.
- *
- * @author Lyubomir Marinov
- * @author Yana Stamcheva
- */
-public class AccountManager
-{
- /**
- * The delay in milliseconds the background <tt>Thread</tt> loading the
- * stored accounts should wait before dying so that it doesn't get recreated
- * for each <tt>ProtocolProviderFactory</tt> registration.
- */
- private static final long LOAD_STORED_ACCOUNTS_TIMEOUT = 30000;
-
- /**
- * The <tt>BundleContext</tt> this service is registered in.
- */
- private final BundleContext bundleContext;
-
- /**
- * The <tt>AccountManagerListener</tt>s currently interested in the
- * events fired by this manager.
- */
- private final List<AccountManagerListener> listeners =
- new LinkedList<AccountManagerListener>();
-
- /**
- * The queue of <tt>ProtocolProviderFactory</tt> services awaiting their
- * stored accounts to be loaded.
- */
- private final Queue<ProtocolProviderFactory> loadStoredAccountsQueue =
- new LinkedList<ProtocolProviderFactory>();
-
- /**
- * The <tt>Thread</tt> loading the stored accounts of the
- * <tt>ProtocolProviderFactory</tt> services waiting in
- * {@link #loadStoredAccountsQueue}.
- */
- private Thread loadStoredAccountsThread;
-
- /**
- * The <tt>Logger</tt> used by this <tt>AccountManagerImpl</tt> instance for
- * logging output.
- */
- private final Logger logger = Logger.getLogger(AccountManager.class);
-
- /**
- * The list of <tt>AccountID</tt>s, corresponding to all stored accounts.
- */
- private final Vector<AccountID> storedAccounts = new Vector<AccountID>();
-
- /**
- * The prefix of the account unique identifier.
- */
- private static final String ACCOUNT_UID_PREFIX = "acc";
-
- /**
- * Initializes a new <tt>AccountManagerImpl</tt> instance loaded in a
- * specific <tt>BundleContext</tt> (in which the caller will usually
- * later register it).
- *
- * @param bundleContext the <tt>BundleContext</tt> in which the new
- * instance is loaded (and in which the caller will usually later
- * register it as a service)
- */
- public AccountManager(BundleContext bundleContext)
- {
- this.bundleContext = bundleContext;
-
- this.bundleContext.addServiceListener(new ServiceListener()
- {
- public void serviceChanged(ServiceEvent serviceEvent)
- {
- AccountManager.this.serviceChanged(serviceEvent);
- }
- });
- }
-
- /**
- * Implements AccountManager#addListener(AccountManagerListener).
- * @param listener the <tt>AccountManagerListener</tt> to add
- */
- public void addListener(AccountManagerListener listener)
- {
- synchronized (listeners)
- {
- if (!listeners.contains(listener))
- listeners.add(listener);
- }
- }
-
- /**
- * Loads the accounts stored for a specific
- * <tt>ProtocolProviderFactory</tt>.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> to load the
- * stored accounts of
- */
- private void doLoadStoredAccounts(ProtocolProviderFactory factory)
- {
- ConfigurationService configService
- = ProtocolProviderActivator.getConfigurationService();
- String factoryPackage = getFactoryImplPackageName(factory);
- List<String> accounts
- = configService.getPropertyNamesByPrefix(factoryPackage, true);
-
- if (logger.isDebugEnabled())
- logger.debug("Discovered " + accounts.size() + " stored "
- + factoryPackage + " accounts");
-
- for (Iterator<String> storedAccountIter = accounts.iterator();
- storedAccountIter.hasNext();)
- {
- String storedAccount = storedAccountIter.next();
-
- // If the property is not related to an account we skip it.
- int dotIndex = storedAccount.lastIndexOf(".");
- if (!storedAccount.substring(dotIndex + 1)
- .startsWith(ACCOUNT_UID_PREFIX))
- continue;
-
- if (logger.isDebugEnabled())
- logger.debug("Loading account " + storedAccount);
-
- List<String> storedAccountProperties =
- configService.getPropertyNamesByPrefix(storedAccount, false);
- Map<String, String> accountProperties =
- new Hashtable<String, String>();
- boolean disabled = false;
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
-
- int prefLen = storedAccount.length() + 1;
- for (Iterator<String> storedAccountPropertyIter
- = storedAccountProperties.iterator();
- storedAccountPropertyIter.hasNext();)
- {
- String property = storedAccountPropertyIter.next();
- String value = configService.getString(property);
-
- //strip the package prefix
- if(prefLen > property.length())
- continue;
-
- property = property.substring(prefLen);
-
- if (ProtocolProviderFactory.IS_ACCOUNT_DISABLED.equals(property))
- disabled = Boolean.parseBoolean(value);
- // Decode passwords.
- else if (ProtocolProviderFactory.PASSWORD.equals(property)
- && !credentialsStorage.isStoredEncrypted(storedAccount))
- {
- if ((value != null) && value.length() != 0)
- {
-
- /*
- * TODO Converting byte[] to String using the platform's
- * default charset may result in an invalid password.
- */
- value = new String(Base64.decode(value));
- }
- }
-
- if (value != null)
- accountProperties.put(property, value);
- }
-
- try
- {
- AccountID accountID = factory.createAccount(accountProperties);
-
- // If for some reason the account id is not created we move to
- // the next account.
- if (accountID == null)
- continue;
-
- synchronized (storedAccounts)
- {
- storedAccounts.add(accountID);
- }
- if (!disabled)
- factory.loadAccount(accountID);
- }
- catch (Exception ex)
- {
- /*
- * Swallow the exception in order to prevent a single account
- * from halting the loading of subsequent accounts.
- */
- logger.error("Failed to load account " + accountProperties, ex);
- }
- catch (ExceptionInInitializerError ex)
- {
- // In case we fail to instantiate the ProtocolProviderService.
- logger.error(
- "Failed to create account service instance for account "
- + accountProperties, ex);
- }
- }
- }
-
- /**
- * Notifies the registered {@link #listeners} that the stored accounts of a
- * specific <tt>ProtocolProviderFactory</tt> have just been loaded.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> which had its
- * stored accounts just loaded
- */
- private void fireStoredAccountsLoaded(ProtocolProviderFactory factory)
- {
- AccountManagerListener[] listeners;
- synchronized (this.listeners)
- {
- listeners =
- this.listeners
- .toArray(new AccountManagerListener[this.listeners.size()]);
- }
-
- int listenerCount = listeners.length;
- if (listenerCount > 0)
- {
- AccountManagerEvent event =
- new AccountManagerEvent(this,
- AccountManagerEvent.STORED_ACCOUNTS_LOADED, factory);
-
- for (int listenerIndex = 0;
- listenerIndex < listenerCount; listenerIndex++)
- {
- listeners[listenerIndex].handleAccountManagerEvent(event);
- }
- }
- }
-
- /**
- * Returns the package name of the <tt>factory</tt>.
- * @param factory the factory which package will be returned.
- * @return the package name of the <tt>factory</tt>.
- */
- public String getFactoryImplPackageName(ProtocolProviderFactory factory)
- {
- String className = factory.getClass().getName();
-
- return className.substring(0, className.lastIndexOf('.'));
- }
-
- /**
- * Check for stored accounts for the supplied <tt>protocolName</tt>.
- * @param protocolName the protocol name to check for
- * @param includeHidden whether to include hidden providers
- * @return <tt>true</tt> if there is any account stored in configuration
- * service with <tt>protocolName</tt>, <tt>false</tt> otherwise.
- */
- public boolean hasStoredAccounts(String protocolName, boolean includeHidden)
- {
- return hasStoredAccount(protocolName, includeHidden, null);
- }
-
- /**
- * Checks whether a stored account with <tt>userID</tt> is stored
- * in configuration.
- *
- * @param protocolName the protocol name
- * @param includeHidden whether to check hidden providers
- * @param userID the user id to check.
- * @return <tt>true</tt> if there is any account stored in configuration
- * service with <tt>protocolName</tt> and <tt>userID</tt>,
- * <tt>false</tt> otherwise.
- */
- public boolean hasStoredAccount(String protocolName,
- boolean includeHidden,
- String userID)
- {
- Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs
- = ServiceUtils.getServiceReferences(
- bundleContext,
- ProtocolProviderFactory.class);
- boolean hasStoredAccounts = false;
-
- if (!factoryRefs.isEmpty())
- {
- ConfigurationService configService
- = ProtocolProviderActivator.getConfigurationService();
-
- for (ServiceReference<ProtocolProviderFactory> factoryRef
- : factoryRefs)
- {
- ProtocolProviderFactory factory
- = bundleContext.getService(factoryRef);
-
- if ((protocolName != null)
- && !protocolName.equals(factory.getProtocolName()))
- {
- continue;
- }
-
- String factoryPackage = getFactoryImplPackageName(factory);
- List<String> storedAccounts
- = configService
- .getPropertyNamesByPrefix(factoryPackage + ".acc",
- false);
-
- /* Ignore the hidden accounts. */
- for (Iterator<String> storedAccountIter =
- storedAccounts.iterator(); storedAccountIter.hasNext();)
- {
- String storedAccount = storedAccountIter.next();
- List<String> storedAccountProperties =
- configService.getPropertyNamesByPrefix(storedAccount,
- true);
- boolean hidden = false;
- String accountUserID = null;
-
- if (!includeHidden || userID != null)
- {
- for (Iterator<String> storedAccountPropertyIter =
- storedAccountProperties.iterator();
- storedAccountPropertyIter.hasNext();)
- {
- String property = storedAccountPropertyIter.next();
- String value = configService.getString(property);
-
- property = stripPackagePrefix(property);
-
- if (ProtocolProviderFactory.IS_PROTOCOL_HIDDEN
- .equals(property))
- {
- hidden = (value != null);
- }
- else if (ProtocolProviderFactory.USER_ID
- .equals(property))
- {
- accountUserID = value;
- }
- }
- }
-
- if (includeHidden || !hidden)
- {
- if(accountUserID != null
- && userID != null
- && userID.equals(accountUserID))
- {
- hasStoredAccounts = true;
- break;
- }
- else if(userID == null)
- {
- hasStoredAccounts = true;
- break;
- }
- }
- }
-
- if (hasStoredAccounts || (protocolName != null))
- {
- break;
- }
- }
- }
- return hasStoredAccounts;
- }
-
- /**
- * Searches for stored account with <tt>uid</tt> in stored
- * configuration. The <tt>uid</tt> is the one generated when creating
- * accounts with prefix <tt>ACCOUNT_UID_PREFIX</tt>.
- *
- * @return <tt>AccountID</tt> if there is any account stored in configuration
- * service with <tt>uid</tt>,
- * <tt>null</tt> otherwise.
- */
- public AccountID findAccountID(String uid)
- {
- Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs
- = ServiceUtils.getServiceReferences(
- bundleContext,
- ProtocolProviderFactory.class);
-
- if (!factoryRefs.isEmpty())
- {
- ConfigurationService configService
- = ProtocolProviderActivator.getConfigurationService();
-
- for (ServiceReference<ProtocolProviderFactory> factoryRef
- : factoryRefs)
- {
- ProtocolProviderFactory factory
- = bundleContext.getService(factoryRef);
-
- String factoryPackage = getFactoryImplPackageName(factory);
- List<String> storedAccountsProps
- = configService
- .getPropertyNamesByPrefix(factoryPackage, true);
-
- for (Iterator<String> storedAccountIter =
- storedAccountsProps.iterator();
- storedAccountIter.hasNext();)
- {
- String storedAccount = storedAccountIter.next();
-
- if(!storedAccount.endsWith(uid))
- continue;
-
- String accountUID = configService.getString(
- storedAccount //node id
- + "." + ProtocolProviderFactory.ACCOUNT_UID);// propname
-
- for(AccountID acc : storedAccounts)
- {
- if(acc.getAccountUniqueID().equals(accountUID))
- return acc;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Loads the accounts stored for a specific
- * <tt>ProtocolProviderFactory</tt> and notifies the registered
- * {@link #listeners} that the stored accounts of the specified
- * <tt>factory</tt> have just been loaded
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> to load the
- * stored accounts of
- */
- private void loadStoredAccounts(ProtocolProviderFactory factory)
- {
- doLoadStoredAccounts(factory);
-
- fireStoredAccountsLoaded(factory);
- }
-
- /**
- * Notifies this manager that a specific
- * <tt>ProtocolProviderFactory</tt> has been registered as a service.
- * The current implementation queues the specified <tt>factory</tt> to
- * have its stored accounts as soon as possible.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> which has been
- * registered as a service.
- */
- private void protocolProviderFactoryRegistered(
- ProtocolProviderFactory factory)
- {
- queueLoadStoredAccounts(factory);
- }
-
- /**
- * Queues a specific <tt>ProtocolProviderFactory</tt> to have its stored
- * accounts loaded as soon as possible.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> to be queued for
- * loading its stored accounts as soon as possible
- */
- private void queueLoadStoredAccounts(ProtocolProviderFactory factory)
- {
- synchronized (loadStoredAccountsQueue)
- {
- loadStoredAccountsQueue.add(factory);
- loadStoredAccountsQueue.notifyAll();
-
- if (loadStoredAccountsThread == null)
- {
- loadStoredAccountsThread = new Thread()
- {
- @Override
- public void run()
- {
- runInLoadStoredAccountsThread();
- }
- };
- loadStoredAccountsThread.setDaemon(true);
- loadStoredAccountsThread.setName(
- "AccountManager.loadStoredAccounts");
- loadStoredAccountsThread.start();
- }
- }
- }
-
- /**
- * Implements AccountManager#removeListener(AccountManagerListener).
- * @param listener the <tt>AccountManagerListener</tt> to remove
- */
- public void removeListener(AccountManagerListener listener)
- {
- synchronized (listeners)
- {
- listeners.remove(listener);
- }
- }
-
- /**
- * Running in {@link #loadStoredAccountsThread}, loads the stored accounts
- * of the <tt>ProtocolProviderFactory</tt> services waiting in
- * {@link #loadStoredAccountsQueue}
- */
- private void runInLoadStoredAccountsThread()
- {
- boolean interrupted = false;
- while (!interrupted)
- {
- try
- {
- ProtocolProviderFactory factory;
-
- synchronized (loadStoredAccountsQueue)
- {
- factory = loadStoredAccountsQueue.poll();
- if (factory == null)
- {
- /*
- * Technically, we should be handing spurious wakeups.
- * However, we cannot check the condition in a queue.
- * Anyway, we just want to keep this Thread alive long
- * enough to allow it to not be re-created multiple
- * times and not handing a spurious wakeup will just
- * cause such an inconvenience.
- */
- try
- {
- loadStoredAccountsQueue
- .wait(LOAD_STORED_ACCOUNTS_TIMEOUT);
- }
- catch (InterruptedException ex)
- {
- logger
- .warn(
- "The loading of the stored accounts has"
- + " been interrupted",
- ex);
- interrupted = true;
- break;
- }
- factory = loadStoredAccountsQueue.poll();
- }
- if (factory != null)
- loadStoredAccountsQueue.notifyAll();
- }
-
- if (factory != null)
- {
- try
- {
- loadStoredAccounts(factory);
- }
- catch (Exception ex)
- {
-
- /*
- * Swallow the exception in order to prevent a single
- * factory from halting the loading of subsequent
- * factories.
- */
- logger.error("Failed to load accounts for " + factory,
- ex);
- }
- }
- }
- finally
- {
- synchronized (loadStoredAccountsQueue)
- {
- if (!interrupted && (loadStoredAccountsQueue.size() <= 0))
- {
- if (loadStoredAccountsThread == Thread.currentThread())
- {
- loadStoredAccountsThread = null;
- loadStoredAccountsQueue.notifyAll();
- }
- break;
- }
- }
- }
- }
- }
-
- /**
- * Notifies this manager that an OSGi service has changed. The current
- * implementation tracks the registrations of
- * <tt>ProtocolProviderFactory</tt> services in order to queue them for
- * loading their stored accounts.
- *
- * @param serviceEvent the <tt>ServiceEvent</tt> containing the event
- * data
- */
- private void serviceChanged(ServiceEvent serviceEvent)
- {
- switch (serviceEvent.getType())
- {
- case ServiceEvent.REGISTERED:
- Object service
- = bundleContext.getService(serviceEvent.getServiceReference());
-
- if (service instanceof ProtocolProviderFactory)
- {
- protocolProviderFactoryRegistered(
- (ProtocolProviderFactory) service);
- }
- break;
- default:
- break;
- }
- }
-
- /**
- * Stores an account represented in the form of an <tt>AccountID</tt>
- * created by a specific <tt>ProtocolProviderFactory</tt>.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> which created the
- * account to be stored
- * @param accountID the account in the form of <tt>AccountID</tt> to be
- * stored
- * @throws OperationFailedException if anything goes wrong while storing the
- * account
- */
- public void storeAccount(
- ProtocolProviderFactory factory,
- AccountID accountID)
- throws OperationFailedException
- {
- synchronized (storedAccounts)
- {
- if (!storedAccounts.contains(accountID))
- storedAccounts.add(accountID);
- }
-
- ConfigurationService configurationService
- = ProtocolProviderActivator.getConfigurationService();
- String factoryPackage = getFactoryImplPackageName(factory);
-
- String accountNodeName
- = getAccountNodeName( factory,
- accountID.getAccountUniqueID() );
-
- Map<String, Object> configurationProperties
- = new HashMap<String, Object>();
-
- // Create a unique node name of the properties node that will contain
- // this account's properties.
- if (accountNodeName == null)
- {
- accountNodeName
- = ACCOUNT_UID_PREFIX + Long.toString(System.currentTimeMillis());
-
- // set a value for the persistent node so that we could later
- // retrieve it as a property
- configurationProperties.put(
- factoryPackage /* prefix */ + "." + accountNodeName,
- accountNodeName);
-
- // register the account in the configuration service.
- // we register all the properties in the following hierarchy
- //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
- configurationProperties.put(factoryPackage// prefix
- + "." + accountNodeName // node name for the account id
- + "." + ProtocolProviderFactory.ACCOUNT_UID, // propname
- accountID.getAccountUniqueID()); // value
- }
-
- // store the rest of the properties
- Map<String, String> accountProperties = accountID.getAccountProperties();
-
- for (Map.Entry<String, String> entry : accountProperties.entrySet())
- {
- String property = entry.getKey();
- String value = entry.getValue();
- String secureStorePrefix = null;
-
- // If the property is a password, store it securely.
- if (property.equals(ProtocolProviderFactory.PASSWORD))
- {
- String accountPrefix = factoryPackage + "." + accountNodeName;
- secureStorePrefix = accountPrefix;
- }
- else if(property.endsWith("." + ProtocolProviderFactory.PASSWORD))
- {
- secureStorePrefix = factoryPackage + "." + accountNodeName +
- "." + property.substring(0, property.lastIndexOf("."));
- }
-
- if(secureStorePrefix != null)
- {
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
-
- // encrypt and store
- if ((value != null)
- && (value.length() != 0)
- && !credentialsStorage.storePassword(
- secureStorePrefix,
- value))
- {
- throw
- new OperationFailedException(
- "CredentialsStorageService failed to"
- + " storePassword",
- OperationFailedException.GENERAL_ERROR);
- }
- }
- else
- {
- configurationProperties.put(
- factoryPackage // prefix
- + "." + accountNodeName // a unique node name for the account id
- + "." + property, // propname
- value); // value
- }
- }
-
- // clear the password if missing property, modification can request
- // password delete
- if(!accountProperties.containsKey(ProtocolProviderFactory.PASSWORD)
- && // And only if it's not stored already in encrypted form.
- // Account registration object clears also this property
- // in order to forget the password
- !configurationProperties.containsKey(
- factoryPackage+"."+accountNodeName+".ENCRYPTED_PASSWORD"))
- {
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
- credentialsStorage.removePassword(
- factoryPackage + "." + accountNodeName);
- }
-
- if (configurationProperties.size() > 0)
- configurationService.setProperties(configurationProperties);
-
- if (logger.isDebugEnabled())
- logger.debug("Stored account for id " + accountID.getAccountUniqueID()
- + " for package " + factoryPackage);
- }
-
- /**
- * Gets account node name under which account configuration properties are
- * stored.
- *
- * @param factory account's protocol provider factory
- * @param accountUID account for which the prefix will be returned
- * @return configuration prefix for given <tt>accountID</tt> if exists or
- * <tt>null</tt> otherwise
- */
- public String getAccountNodeName( ProtocolProviderFactory factory,
- String accountUID )
- {
- ConfigurationService configurationService
- = ProtocolProviderActivator.getConfigurationService();
- String factoryPackage = getFactoryImplPackageName(factory);
-
- // First check if such accountID already exists in the configuration.
- List<String> storedAccounts =
- configurationService.getPropertyNamesByPrefix(factoryPackage, true);
- String accountNodeName = null;
-
- for (Iterator<String> storedAccountIter = storedAccounts.iterator();
- storedAccountIter.hasNext();)
- {
- String storedAccount = storedAccountIter.next();
-
- // If the property is not related to an account we skip it.
- int dotIndex = storedAccount.lastIndexOf(".");
- if (!storedAccount.substring(dotIndex + 1)
- .startsWith(ACCOUNT_UID_PREFIX))
- continue;
-
- String storedAccountUID
- = configurationService.getString(
- storedAccount + "." + ProtocolProviderFactory.ACCOUNT_UID);
-
- if(storedAccountUID == null)
- continue;
-
- if (storedAccountUID.equals(accountUID))
- accountNodeName = configurationService.getString(storedAccount);
- }
- return accountNodeName;
- }
-
- /**
- * Removes the account with <tt>accountID</tt> from the set of accounts
- * that are persistently stored inside the configuration service.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> which created the
- * account to be stored
- * @param accountID the AccountID of the account to remove.
- * @return true if an account has been removed and false otherwise.
- */
- public boolean removeStoredAccount(ProtocolProviderFactory factory,
- AccountID accountID)
- {
- synchronized (storedAccounts)
- {
- if (storedAccounts.contains(accountID))
- storedAccounts.remove(accountID);
- }
-
- /*
- * We're already doing it in #unloadAccount(AccountID) - we're figuring
- * out the ProtocolProviderFactory by the AccountID.
- */
- if (factory == null)
- {
- factory
- = ProtocolProviderActivator.getProtocolProviderFactory(
- accountID.getProtocolName());
- }
-
- String factoryPackage = getFactoryImplPackageName(factory);
-
- // remove the stored password explicitly using credentials service
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
- String accountPrefix =
- ProtocolProviderFactory.findAccountPrefix(bundleContext, accountID,
- factoryPackage);
-
- credentialsStorage.removePassword(accountPrefix);
-
- ConfigurationService configurationService
- = ServiceUtils.getService(
- bundleContext,
- ConfigurationService.class);
- //first retrieve all accounts that we've registered
- List<String> storedAccounts
- = configurationService.getPropertyNamesByPrefix(
- factoryPackage, true);
-
- //find an account with the corresponding id.
- for (String accountRootPropertyName : storedAccounts)
- {
- //unregister the account in the configuration service.
- //all the properties must have been registered in the following
- //hierarchy:
- //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
- String accountUID = configurationService.getString(
- accountRootPropertyName //node id
- + "." + ProtocolProviderFactory.ACCOUNT_UID); // propname
-
- if (accountID.getAccountUniqueID().equals(accountUID))
- {
- //retrieve the names of all properties registered for the
- //current account.
- List<String> accountPropertyNames
- = configurationService.getPropertyNamesByPrefix(
- accountRootPropertyName, false);
-
- //set all account properties to null in order to remove them.
- for (String propName : accountPropertyNames)
- configurationService.setProperty(propName, null);
-
- //and now remove the parent too.
- configurationService.setProperty(accountRootPropertyName, null);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Removes all accounts which have been persistently stored.
- *
- * @see #removeStoredAccount(ProtocolProviderFactory, AccountID)
- */
- public void removeStoredAccounts()
- {
- synchronized (loadStoredAccountsQueue)
- {
- /*
- * Wait for the Thread which loads the stored account to complete so
- * that we can be sure later on that it will not load a stored
- * account while we are deleting it or another one for that matter.
- */
- boolean interrupted = false;
-
- while (loadStoredAccountsThread != null)
- try
- {
- loadStoredAccountsQueue.wait(LOAD_STORED_ACCOUNTS_TIMEOUT);
- }
- catch (InterruptedException ie)
- {
- interrupted = true;
- }
- if (interrupted)
- Thread.currentThread().interrupt();
-
- synchronized (this.storedAccounts)
- {
- AccountID[] storedAccounts
- = this.storedAccounts.toArray(
- new AccountID[this.storedAccounts.size()]);
-
- for (AccountID storedAccount : storedAccounts)
- {
- ProtocolProviderFactory ppf
- = ProtocolProviderActivator.getProtocolProviderFactory(
- storedAccount.getProtocolName());
-
- if (ppf != null)
- ppf.uninstallAccount(storedAccount);
- }
- }
- }
- }
-
- /**
- * Returns an <tt>Iterator</tt> over a list of all stored
- * <tt>AccountID</tt>s. The list of stored accounts include all registered
- * accounts and all disabled accounts. In other words in this list we could
- * find accounts that aren't loaded.
- * <p>
- * In order to check if an account is already loaded please use the
- * #isAccountLoaded(AccountID accountID) method. To load an account use the
- * #loadAccount(AccountID accountID) method.
- *
- * @return an <tt>Iterator</tt> over a list of all stored
- * <tt>AccountID</tt>s
- */
- public Collection<AccountID> getStoredAccounts()
- {
- synchronized (storedAccounts)
- {
- return new Vector<AccountID>(storedAccounts);
- }
- }
-
- /**
- * Loads the account corresponding to the given <tt>AccountID</tt>. An
- * account is loaded when its <tt>ProtocolProviderService</tt> is registered
- * in the bundle context. This method is meant to load the account through
- * the corresponding <tt>ProtocolProviderFactory</tt>.
- *
- * @param accountID the identifier of the account to load
- * @throws OperationFailedException if anything goes wrong while loading the
- * account corresponding to the specified <tt>accountID</tt>
- */
- public void loadAccount(AccountID accountID)
- throws OperationFailedException
- {
- // If the account with the given id is already loaded we have nothing
- // to do here.
- if (isAccountLoaded(accountID))
- return;
-
- ProtocolProviderFactory providerFactory
- = ProtocolProviderActivator.getProtocolProviderFactory(
- accountID.getProtocolName());
-
- if(providerFactory.loadAccount(accountID))
- {
- accountID.putAccountProperty(
- ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
- String.valueOf(false));
- // Finally store the modified properties.
- storeAccount(providerFactory, accountID);
- }
- }
-
- /**
- * Unloads the account corresponding to the given <tt>AccountID</tt>. An
- * account is unloaded when its <tt>ProtocolProviderService</tt> is
- * unregistered in the bundle context. This method is meant to unload the
- * account through the corresponding <tt>ProtocolProviderFactory</tt>.
- *
- * @param accountID the identifier of the account to load
- * @throws OperationFailedException if anything goes wrong while unloading
- * the account corresponding to the specified <tt>accountID</tt>
- */
- public void unloadAccount(AccountID accountID)
- throws OperationFailedException
- {
- // If the account with the given id is already unloaded we have nothing
- // to do here.
- if (!isAccountLoaded(accountID))
- return;
-
- ProtocolProviderFactory providerFactory
- = ProtocolProviderActivator.getProtocolProviderFactory(
- accountID.getProtocolName());
-
- // Obtain the protocol provider.
- ServiceReference<ProtocolProviderService> serRef
- = providerFactory.getProviderForAccount(accountID);
-
- // If there's no such provider we have nothing to do here.
- if (serRef == null)
- return;
-
- ProtocolProviderService protocolProvider
- = bundleContext.getService(serRef);
-
- // Set the account icon path for unloaded accounts.
- String iconPathProperty = accountID.getAccountPropertyString(
- ProtocolProviderFactory.ACCOUNT_ICON_PATH);
-
- if (iconPathProperty == null)
- {
- accountID.putAccountProperty(
- ProtocolProviderFactory.ACCOUNT_ICON_PATH,
- protocolProvider.getProtocolIcon()
- .getIconPath(ProtocolIcon.ICON_SIZE_32x32));
- }
-
- accountID.putAccountProperty(
- ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
- String.valueOf(true));
-
- if (!providerFactory.unloadAccount(accountID))
- {
- accountID.putAccountProperty(
- ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
- String.valueOf(false));
- }
- // Finally store the modified properties.
- storeAccount(providerFactory, accountID);
- }
-
- /**
- * Checks if the account corresponding to the given <tt>accountID</tt> is
- * loaded. An account is loaded if its <tt>ProtocolProviderService</tt> is
- * registered in the bundle context. By default all accounts are loaded.
- * However the user could manually unload an account, which would be
- * unregistered from the bundle context, but would remain in the
- * configuration file.
- *
- * @param accountID the identifier of the account to load
- * @return <tt>true</tt> to indicate that the account with the given
- * <tt>accountID</tt> is loaded, <tt>false</tt> - otherwise
- */
- public boolean isAccountLoaded(AccountID accountID)
- {
- return storedAccounts.contains(accountID) && accountID.isEnabled();
- }
-
- private String stripPackagePrefix(String property)
- {
- int packageEndIndex = property.lastIndexOf('.');
-
- if (packageEndIndex != -1)
- property = property.substring(packageEndIndex + 1);
- return property;
- }
-}
+/*
+ * 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.service.protocol;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.credentialsstorage.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.Base64; //disambiguate from java.util.Base64
+
+import org.jitsi.service.configuration.*;
+import org.osgi.framework.*;
+
+/**
+ * Represents an implementation of <tt>AccountManager</tt> which loads the
+ * accounts in a separate thread.
+ *
+ * @author Lyubomir Marinov
+ * @author Yana Stamcheva
+ */
+public class AccountManager
+{
+ /**
+ * The delay in milliseconds the background <tt>Thread</tt> loading the
+ * stored accounts should wait before dying so that it doesn't get recreated
+ * for each <tt>ProtocolProviderFactory</tt> registration.
+ */
+ private static final long LOAD_STORED_ACCOUNTS_TIMEOUT = 30000;
+
+ /**
+ * The <tt>BundleContext</tt> this service is registered in.
+ */
+ private final BundleContext bundleContext;
+
+ /**
+ * The <tt>AccountManagerListener</tt>s currently interested in the
+ * events fired by this manager.
+ */
+ private final List<AccountManagerListener> listeners =
+ new LinkedList<AccountManagerListener>();
+
+ /**
+ * The queue of <tt>ProtocolProviderFactory</tt> services awaiting their
+ * stored accounts to be loaded.
+ */
+ private final Queue<ProtocolProviderFactory> loadStoredAccountsQueue =
+ new LinkedList<ProtocolProviderFactory>();
+
+ /**
+ * The <tt>Thread</tt> loading the stored accounts of the
+ * <tt>ProtocolProviderFactory</tt> services waiting in
+ * {@link #loadStoredAccountsQueue}.
+ */
+ private Thread loadStoredAccountsThread;
+
+ /**
+ * The <tt>Logger</tt> used by this <tt>AccountManagerImpl</tt> instance for
+ * logging output.
+ */
+ private final Logger logger = Logger.getLogger(AccountManager.class);
+
+ /**
+ * The list of <tt>AccountID</tt>s, corresponding to all stored accounts.
+ */
+ private final Vector<AccountID> storedAccounts = new Vector<AccountID>();
+
+ /**
+ * The prefix of the account unique identifier.
+ */
+ private static final String ACCOUNT_UID_PREFIX = "acc";
+
+ /**
+ * Initializes a new <tt>AccountManagerImpl</tt> instance loaded in a
+ * specific <tt>BundleContext</tt> (in which the caller will usually
+ * later register it).
+ *
+ * @param bundleContext the <tt>BundleContext</tt> in which the new
+ * instance is loaded (and in which the caller will usually later
+ * register it as a service)
+ */
+ public AccountManager(BundleContext bundleContext)
+ {
+ this.bundleContext = bundleContext;
+
+ this.bundleContext.addServiceListener(new ServiceListener()
+ {
+ public void serviceChanged(ServiceEvent serviceEvent)
+ {
+ AccountManager.this.serviceChanged(serviceEvent);
+ }
+ });
+ }
+
+ /**
+ * Implements AccountManager#addListener(AccountManagerListener).
+ * @param listener the <tt>AccountManagerListener</tt> to add
+ */
+ public void addListener(AccountManagerListener listener)
+ {
+ synchronized (listeners)
+ {
+ if (!listeners.contains(listener))
+ listeners.add(listener);
+ }
+ }
+
+ /**
+ * Loads the accounts stored for a specific
+ * <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> to load the
+ * stored accounts of
+ */
+ private void doLoadStoredAccounts(ProtocolProviderFactory factory)
+ {
+ ConfigurationService configService
+ = ProtocolProviderActivator.getConfigurationService();
+ String factoryPackage = getFactoryImplPackageName(factory);
+ List<String> accounts
+ = configService.getPropertyNamesByPrefix(factoryPackage, true);
+
+ if (logger.isDebugEnabled())
+ logger.debug("Discovered " + accounts.size() + " stored "
+ + factoryPackage + " accounts");
+
+ for (Iterator<String> storedAccountIter = accounts.iterator();
+ storedAccountIter.hasNext();)
+ {
+ String storedAccount = storedAccountIter.next();
+
+ // If the property is not related to an account we skip it.
+ int dotIndex = storedAccount.lastIndexOf(".");
+ if (!storedAccount.substring(dotIndex + 1)
+ .startsWith(ACCOUNT_UID_PREFIX))
+ continue;
+
+ if (logger.isDebugEnabled())
+ logger.debug("Loading account " + storedAccount);
+
+ List<String> storedAccountProperties =
+ configService.getPropertyNamesByPrefix(storedAccount, false);
+ Map<String, String> accountProperties =
+ new Hashtable<String, String>();
+ boolean disabled = false;
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+
+ int prefLen = storedAccount.length() + 1;
+ for (Iterator<String> storedAccountPropertyIter
+ = storedAccountProperties.iterator();
+ storedAccountPropertyIter.hasNext();)
+ {
+ String property = storedAccountPropertyIter.next();
+ String value = configService.getString(property);
+
+ //strip the package prefix
+ if(prefLen > property.length())
+ continue;
+
+ property = property.substring(prefLen);
+
+ if (ProtocolProviderFactory.IS_ACCOUNT_DISABLED.equals(property))
+ disabled = Boolean.parseBoolean(value);
+ // Decode passwords.
+ else if (ProtocolProviderFactory.PASSWORD.equals(property)
+ && !credentialsStorage.isStoredEncrypted(storedAccount))
+ {
+ if ((value != null) && value.length() != 0)
+ {
+
+ /*
+ * TODO Converting byte[] to String using the platform's
+ * default charset may result in an invalid password.
+ */
+ value = new String(Base64.decode(value));
+ }
+ }
+
+ if (value != null)
+ accountProperties.put(property, value);
+ }
+
+ try
+ {
+ AccountID accountID = factory.createAccount(accountProperties);
+
+ // If for some reason the account id is not created we move to
+ // the next account.
+ if (accountID == null)
+ continue;
+
+ synchronized (storedAccounts)
+ {
+ storedAccounts.add(accountID);
+ }
+ if (!disabled)
+ factory.loadAccount(accountID);
+ }
+ catch (Exception ex)
+ {
+ /*
+ * Swallow the exception in order to prevent a single account
+ * from halting the loading of subsequent accounts.
+ */
+ logger.error("Failed to load account " + accountProperties, ex);
+ }
+ catch (ExceptionInInitializerError ex)
+ {
+ // In case we fail to instantiate the ProtocolProviderService.
+ logger.error(
+ "Failed to create account service instance for account "
+ + accountProperties, ex);
+ }
+ }
+ }
+
+ /**
+ * Notifies the registered {@link #listeners} that the stored accounts of a
+ * specific <tt>ProtocolProviderFactory</tt> have just been loaded.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> which had its
+ * stored accounts just loaded
+ */
+ private void fireStoredAccountsLoaded(ProtocolProviderFactory factory)
+ {
+ AccountManagerListener[] listeners;
+ synchronized (this.listeners)
+ {
+ listeners =
+ this.listeners
+ .toArray(new AccountManagerListener[this.listeners.size()]);
+ }
+
+ int listenerCount = listeners.length;
+ if (listenerCount > 0)
+ {
+ AccountManagerEvent event =
+ new AccountManagerEvent(this,
+ AccountManagerEvent.STORED_ACCOUNTS_LOADED, factory);
+
+ for (int listenerIndex = 0;
+ listenerIndex < listenerCount; listenerIndex++)
+ {
+ listeners[listenerIndex].handleAccountManagerEvent(event);
+ }
+ }
+ }
+
+ /**
+ * Returns the package name of the <tt>factory</tt>.
+ * @param factory the factory which package will be returned.
+ * @return the package name of the <tt>factory</tt>.
+ */
+ public String getFactoryImplPackageName(ProtocolProviderFactory factory)
+ {
+ String className = factory.getClass().getName();
+
+ return className.substring(0, className.lastIndexOf('.'));
+ }
+
+ /**
+ * Check for stored accounts for the supplied <tt>protocolName</tt>.
+ * @param protocolName the protocol name to check for
+ * @param includeHidden whether to include hidden providers
+ * @return <tt>true</tt> if there is any account stored in configuration
+ * service with <tt>protocolName</tt>, <tt>false</tt> otherwise.
+ */
+ public boolean hasStoredAccounts(String protocolName, boolean includeHidden)
+ {
+ return hasStoredAccount(protocolName, includeHidden, null);
+ }
+
+ /**
+ * Checks whether a stored account with <tt>userID</tt> is stored
+ * in configuration.
+ *
+ * @param protocolName the protocol name
+ * @param includeHidden whether to check hidden providers
+ * @param userID the user id to check.
+ * @return <tt>true</tt> if there is any account stored in configuration
+ * service with <tt>protocolName</tt> and <tt>userID</tt>,
+ * <tt>false</tt> otherwise.
+ */
+ public boolean hasStoredAccount(String protocolName,
+ boolean includeHidden,
+ String userID)
+ {
+ Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs
+ = ServiceUtils.getServiceReferences(
+ bundleContext,
+ ProtocolProviderFactory.class);
+ boolean hasStoredAccounts = false;
+
+ if (!factoryRefs.isEmpty())
+ {
+ ConfigurationService configService
+ = ProtocolProviderActivator.getConfigurationService();
+
+ for (ServiceReference<ProtocolProviderFactory> factoryRef
+ : factoryRefs)
+ {
+ ProtocolProviderFactory factory
+ = bundleContext.getService(factoryRef);
+
+ if ((protocolName != null)
+ && !protocolName.equals(factory.getProtocolName()))
+ {
+ continue;
+ }
+
+ String factoryPackage = getFactoryImplPackageName(factory);
+ List<String> storedAccounts
+ = configService
+ .getPropertyNamesByPrefix(factoryPackage + ".acc",
+ false);
+
+ /* Ignore the hidden accounts. */
+ for (Iterator<String> storedAccountIter =
+ storedAccounts.iterator(); storedAccountIter.hasNext();)
+ {
+ String storedAccount = storedAccountIter.next();
+ List<String> storedAccountProperties =
+ configService.getPropertyNamesByPrefix(storedAccount,
+ true);
+ boolean hidden = false;
+ String accountUserID = null;
+
+ if (!includeHidden || userID != null)
+ {
+ for (Iterator<String> storedAccountPropertyIter =
+ storedAccountProperties.iterator();
+ storedAccountPropertyIter.hasNext();)
+ {
+ String property = storedAccountPropertyIter.next();
+ String value = configService.getString(property);
+
+ property = stripPackagePrefix(property);
+
+ if (ProtocolProviderFactory.IS_PROTOCOL_HIDDEN
+ .equals(property))
+ {
+ hidden = (value != null);
+ }
+ else if (ProtocolProviderFactory.USER_ID
+ .equals(property))
+ {
+ accountUserID = value;
+ }
+ }
+ }
+
+ if (includeHidden || !hidden)
+ {
+ if(accountUserID != null
+ && userID != null
+ && userID.equals(accountUserID))
+ {
+ hasStoredAccounts = true;
+ break;
+ }
+ else if(userID == null)
+ {
+ hasStoredAccounts = true;
+ break;
+ }
+ }
+ }
+
+ if (hasStoredAccounts || (protocolName != null))
+ {
+ break;
+ }
+ }
+ }
+ return hasStoredAccounts;
+ }
+
+ /**
+ * Searches for stored account with <tt>uid</tt> in stored
+ * configuration. The <tt>uid</tt> is the one generated when creating
+ * accounts with prefix <tt>ACCOUNT_UID_PREFIX</tt>.
+ *
+ * @return <tt>AccountID</tt> if there is any account stored in configuration
+ * service with <tt>uid</tt>,
+ * <tt>null</tt> otherwise.
+ */
+ public AccountID findAccountID(String uid)
+ {
+ Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs
+ = ServiceUtils.getServiceReferences(
+ bundleContext,
+ ProtocolProviderFactory.class);
+
+ if (!factoryRefs.isEmpty())
+ {
+ ConfigurationService configService
+ = ProtocolProviderActivator.getConfigurationService();
+
+ for (ServiceReference<ProtocolProviderFactory> factoryRef
+ : factoryRefs)
+ {
+ ProtocolProviderFactory factory
+ = bundleContext.getService(factoryRef);
+
+ String factoryPackage = getFactoryImplPackageName(factory);
+ List<String> storedAccountsProps
+ = configService
+ .getPropertyNamesByPrefix(factoryPackage, true);
+
+ for (Iterator<String> storedAccountIter =
+ storedAccountsProps.iterator();
+ storedAccountIter.hasNext();)
+ {
+ String storedAccount = storedAccountIter.next();
+
+ if(!storedAccount.endsWith(uid))
+ continue;
+
+ String accountUID = configService.getString(
+ storedAccount //node id
+ + "." + ProtocolProviderFactory.ACCOUNT_UID);// propname
+
+ for(AccountID acc : storedAccounts)
+ {
+ if(acc.getAccountUniqueID().equals(accountUID))
+ return acc;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Loads the accounts stored for a specific
+ * <tt>ProtocolProviderFactory</tt> and notifies the registered
+ * {@link #listeners} that the stored accounts of the specified
+ * <tt>factory</tt> have just been loaded
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> to load the
+ * stored accounts of
+ */
+ private void loadStoredAccounts(ProtocolProviderFactory factory)
+ {
+ doLoadStoredAccounts(factory);
+
+ fireStoredAccountsLoaded(factory);
+ }
+
+ /**
+ * Notifies this manager that a specific
+ * <tt>ProtocolProviderFactory</tt> has been registered as a service.
+ * The current implementation queues the specified <tt>factory</tt> to
+ * have its stored accounts as soon as possible.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> which has been
+ * registered as a service.
+ */
+ private void protocolProviderFactoryRegistered(
+ ProtocolProviderFactory factory)
+ {
+ queueLoadStoredAccounts(factory);
+ }
+
+ /**
+ * Queues a specific <tt>ProtocolProviderFactory</tt> to have its stored
+ * accounts loaded as soon as possible.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> to be queued for
+ * loading its stored accounts as soon as possible
+ */
+ private void queueLoadStoredAccounts(ProtocolProviderFactory factory)
+ {
+ synchronized (loadStoredAccountsQueue)
+ {
+ loadStoredAccountsQueue.add(factory);
+ loadStoredAccountsQueue.notifyAll();
+
+ if (loadStoredAccountsThread == null)
+ {
+ loadStoredAccountsThread = new Thread()
+ {
+ @Override
+ public void run()
+ {
+ runInLoadStoredAccountsThread();
+ }
+ };
+ loadStoredAccountsThread.setDaemon(true);
+ loadStoredAccountsThread.setName(
+ "AccountManager.loadStoredAccounts");
+ loadStoredAccountsThread.start();
+ }
+ }
+ }
+
+ /**
+ * Implements AccountManager#removeListener(AccountManagerListener).
+ * @param listener the <tt>AccountManagerListener</tt> to remove
+ */
+ public void removeListener(AccountManagerListener listener)
+ {
+ synchronized (listeners)
+ {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Running in {@link #loadStoredAccountsThread}, loads the stored accounts
+ * of the <tt>ProtocolProviderFactory</tt> services waiting in
+ * {@link #loadStoredAccountsQueue}
+ */
+ private void runInLoadStoredAccountsThread()
+ {
+ boolean interrupted = false;
+ while (!interrupted)
+ {
+ try
+ {
+ ProtocolProviderFactory factory;
+
+ synchronized (loadStoredAccountsQueue)
+ {
+ factory = loadStoredAccountsQueue.poll();
+ if (factory == null)
+ {
+ /*
+ * Technically, we should be handing spurious wakeups.
+ * However, we cannot check the condition in a queue.
+ * Anyway, we just want to keep this Thread alive long
+ * enough to allow it to not be re-created multiple
+ * times and not handing a spurious wakeup will just
+ * cause such an inconvenience.
+ */
+ try
+ {
+ loadStoredAccountsQueue
+ .wait(LOAD_STORED_ACCOUNTS_TIMEOUT);
+ }
+ catch (InterruptedException ex)
+ {
+ logger
+ .warn(
+ "The loading of the stored accounts has"
+ + " been interrupted",
+ ex);
+ interrupted = true;
+ break;
+ }
+ factory = loadStoredAccountsQueue.poll();
+ }
+ if (factory != null)
+ loadStoredAccountsQueue.notifyAll();
+ }
+
+ if (factory != null)
+ {
+ try
+ {
+ loadStoredAccounts(factory);
+ }
+ catch (Exception ex)
+ {
+
+ /*
+ * Swallow the exception in order to prevent a single
+ * factory from halting the loading of subsequent
+ * factories.
+ */
+ logger.error("Failed to load accounts for " + factory,
+ ex);
+ }
+ }
+ }
+ finally
+ {
+ synchronized (loadStoredAccountsQueue)
+ {
+ if (!interrupted && (loadStoredAccountsQueue.size() <= 0))
+ {
+ if (loadStoredAccountsThread == Thread.currentThread())
+ {
+ loadStoredAccountsThread = null;
+ loadStoredAccountsQueue.notifyAll();
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies this manager that an OSGi service has changed. The current
+ * implementation tracks the registrations of
+ * <tt>ProtocolProviderFactory</tt> services in order to queue them for
+ * loading their stored accounts.
+ *
+ * @param serviceEvent the <tt>ServiceEvent</tt> containing the event
+ * data
+ */
+ private void serviceChanged(ServiceEvent serviceEvent)
+ {
+ switch (serviceEvent.getType())
+ {
+ case ServiceEvent.REGISTERED:
+ Object service
+ = bundleContext.getService(serviceEvent.getServiceReference());
+
+ if (service instanceof ProtocolProviderFactory)
+ {
+ protocolProviderFactoryRegistered(
+ (ProtocolProviderFactory) service);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Stores an account represented in the form of an <tt>AccountID</tt>
+ * created by a specific <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> which created the
+ * account to be stored
+ * @param accountID the account in the form of <tt>AccountID</tt> to be
+ * stored
+ * @throws OperationFailedException if anything goes wrong while storing the
+ * account
+ */
+ public void storeAccount(
+ ProtocolProviderFactory factory,
+ AccountID accountID)
+ throws OperationFailedException
+ {
+ synchronized (storedAccounts)
+ {
+ if (!storedAccounts.contains(accountID))
+ storedAccounts.add(accountID);
+ }
+
+ ConfigurationService configurationService
+ = ProtocolProviderActivator.getConfigurationService();
+ String factoryPackage = getFactoryImplPackageName(factory);
+
+ String accountNodeName
+ = getAccountNodeName( factory,
+ accountID.getAccountUniqueID() );
+
+ Map<String, Object> configurationProperties
+ = new HashMap<String, Object>();
+
+ // Create a unique node name of the properties node that will contain
+ // this account's properties.
+ if (accountNodeName == null)
+ {
+ accountNodeName
+ = ACCOUNT_UID_PREFIX + Long.toString(System.currentTimeMillis());
+
+ // set a value for the persistent node so that we could later
+ // retrieve it as a property
+ configurationProperties.put(
+ factoryPackage /* prefix */ + "." + accountNodeName,
+ accountNodeName);
+
+ // register the account in the configuration service.
+ // we register all the properties in the following hierarchy
+ //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
+ configurationProperties.put(factoryPackage// prefix
+ + "." + accountNodeName // node name for the account id
+ + "." + ProtocolProviderFactory.ACCOUNT_UID, // propname
+ accountID.getAccountUniqueID()); // value
+ }
+
+ // store the rest of the properties
+ Map<String, String> accountProperties = accountID.getAccountProperties();
+
+ for (Map.Entry<String, String> entry : accountProperties.entrySet())
+ {
+ String property = entry.getKey();
+ String value = entry.getValue();
+ String secureStorePrefix = null;
+
+ // If the property is a password, store it securely.
+ if (property.equals(ProtocolProviderFactory.PASSWORD))
+ {
+ String accountPrefix = factoryPackage + "." + accountNodeName;
+ secureStorePrefix = accountPrefix;
+ }
+ else if(property.endsWith("." + ProtocolProviderFactory.PASSWORD))
+ {
+ secureStorePrefix = factoryPackage + "." + accountNodeName +
+ "." + property.substring(0, property.lastIndexOf("."));
+ }
+
+ if(secureStorePrefix != null)
+ {
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+
+ // encrypt and store
+ if ((value != null)
+ && (value.length() != 0)
+ && !credentialsStorage.storePassword(
+ secureStorePrefix,
+ value))
+ {
+ throw
+ new OperationFailedException(
+ "CredentialsStorageService failed to"
+ + " storePassword",
+ OperationFailedException.GENERAL_ERROR);
+ }
+ }
+ else
+ {
+ configurationProperties.put(
+ factoryPackage // prefix
+ + "." + accountNodeName // a unique node name for the account id
+ + "." + property, // propname
+ value); // value
+ }
+ }
+
+ // clear the password if missing property, modification can request
+ // password delete
+ if(!accountProperties.containsKey(ProtocolProviderFactory.PASSWORD)
+ && // And only if it's not stored already in encrypted form.
+ // Account registration object clears also this property
+ // in order to forget the password
+ !configurationProperties.containsKey(
+ factoryPackage+"."+accountNodeName+".ENCRYPTED_PASSWORD"))
+ {
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+ credentialsStorage.removePassword(
+ factoryPackage + "." + accountNodeName);
+ }
+
+ if (configurationProperties.size() > 0)
+ configurationService.setProperties(configurationProperties);
+
+ if (logger.isDebugEnabled())
+ logger.debug("Stored account for id " + accountID.getAccountUniqueID()
+ + " for package " + factoryPackage);
+ }
+
+ /**
+ * Gets account node name under which account configuration properties are
+ * stored.
+ *
+ * @param factory account's protocol provider factory
+ * @param accountUID account for which the prefix will be returned
+ * @return configuration prefix for given <tt>accountID</tt> if exists or
+ * <tt>null</tt> otherwise
+ */
+ public String getAccountNodeName( ProtocolProviderFactory factory,
+ String accountUID )
+ {
+ ConfigurationService configurationService
+ = ProtocolProviderActivator.getConfigurationService();
+ String factoryPackage = getFactoryImplPackageName(factory);
+
+ // First check if such accountID already exists in the configuration.
+ List<String> storedAccounts =
+ configurationService.getPropertyNamesByPrefix(factoryPackage, true);
+ String accountNodeName = null;
+
+ for (Iterator<String> storedAccountIter = storedAccounts.iterator();
+ storedAccountIter.hasNext();)
+ {
+ String storedAccount = storedAccountIter.next();
+
+ // If the property is not related to an account we skip it.
+ int dotIndex = storedAccount.lastIndexOf(".");
+ if (!storedAccount.substring(dotIndex + 1)
+ .startsWith(ACCOUNT_UID_PREFIX))
+ continue;
+
+ String storedAccountUID
+ = configurationService.getString(
+ storedAccount + "." + ProtocolProviderFactory.ACCOUNT_UID);
+
+ if(storedAccountUID == null)
+ continue;
+
+ if (storedAccountUID.equals(accountUID))
+ accountNodeName = configurationService.getString(storedAccount);
+ }
+ return accountNodeName;
+ }
+
+ /**
+ * Removes the account with <tt>accountID</tt> from the set of accounts
+ * that are persistently stored inside the configuration service.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> which created the
+ * account to be stored
+ * @param accountID the AccountID of the account to remove.
+ * @return true if an account has been removed and false otherwise.
+ */
+ public boolean removeStoredAccount(ProtocolProviderFactory factory,
+ AccountID accountID)
+ {
+ synchronized (storedAccounts)
+ {
+ if (storedAccounts.contains(accountID))
+ storedAccounts.remove(accountID);
+ }
+
+ /*
+ * We're already doing it in #unloadAccount(AccountID) - we're figuring
+ * out the ProtocolProviderFactory by the AccountID.
+ */
+ if (factory == null)
+ {
+ factory
+ = ProtocolProviderActivator.getProtocolProviderFactory(
+ accountID.getProtocolName());
+ }
+
+ String factoryPackage = getFactoryImplPackageName(factory);
+
+ // remove the stored password explicitly using credentials service
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+ String accountPrefix =
+ ProtocolProviderFactory.findAccountPrefix(bundleContext, accountID,
+ factoryPackage);
+
+ credentialsStorage.removePassword(accountPrefix);
+
+ ConfigurationService configurationService
+ = ServiceUtils.getService(
+ bundleContext,
+ ConfigurationService.class);
+ //first retrieve all accounts that we've registered
+ List<String> storedAccounts
+ = configurationService.getPropertyNamesByPrefix(
+ factoryPackage, true);
+
+ //find an account with the corresponding id.
+ for (String accountRootPropertyName : storedAccounts)
+ {
+ //unregister the account in the configuration service.
+ //all the properties must have been registered in the following
+ //hierarchy:
+ //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
+ String accountUID = configurationService.getString(
+ accountRootPropertyName //node id
+ + "." + ProtocolProviderFactory.ACCOUNT_UID); // propname
+
+ if (accountID.getAccountUniqueID().equals(accountUID))
+ {
+ //retrieve the names of all properties registered for the
+ //current account.
+ List<String> accountPropertyNames
+ = configurationService.getPropertyNamesByPrefix(
+ accountRootPropertyName, false);
+
+ //set all account properties to null in order to remove them.
+ for (String propName : accountPropertyNames)
+ configurationService.setProperty(propName, null);
+
+ //and now remove the parent too.
+ configurationService.setProperty(accountRootPropertyName, null);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes all accounts which have been persistently stored.
+ *
+ * @see #removeStoredAccount(ProtocolProviderFactory, AccountID)
+ */
+ public void removeStoredAccounts()
+ {
+ synchronized (loadStoredAccountsQueue)
+ {
+ /*
+ * Wait for the Thread which loads the stored account to complete so
+ * that we can be sure later on that it will not load a stored
+ * account while we are deleting it or another one for that matter.
+ */
+ boolean interrupted = false;
+
+ while (loadStoredAccountsThread != null)
+ try
+ {
+ loadStoredAccountsQueue.wait(LOAD_STORED_ACCOUNTS_TIMEOUT);
+ }
+ catch (InterruptedException ie)
+ {
+ interrupted = true;
+ }
+ if (interrupted)
+ Thread.currentThread().interrupt();
+
+ synchronized (this.storedAccounts)
+ {
+ AccountID[] storedAccounts
+ = this.storedAccounts.toArray(
+ new AccountID[this.storedAccounts.size()]);
+
+ for (AccountID storedAccount : storedAccounts)
+ {
+ ProtocolProviderFactory ppf
+ = ProtocolProviderActivator.getProtocolProviderFactory(
+ storedAccount.getProtocolName());
+
+ if (ppf != null)
+ ppf.uninstallAccount(storedAccount);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns an <tt>Iterator</tt> over a list of all stored
+ * <tt>AccountID</tt>s. The list of stored accounts include all registered
+ * accounts and all disabled accounts. In other words in this list we could
+ * find accounts that aren't loaded.
+ * <p>
+ * In order to check if an account is already loaded please use the
+ * #isAccountLoaded(AccountID accountID) method. To load an account use the
+ * #loadAccount(AccountID accountID) method.
+ *
+ * @return an <tt>Iterator</tt> over a list of all stored
+ * <tt>AccountID</tt>s
+ */
+ public Collection<AccountID> getStoredAccounts()
+ {
+ synchronized (storedAccounts)
+ {
+ return new Vector<AccountID>(storedAccounts);
+ }
+ }
+
+ /**
+ * Loads the account corresponding to the given <tt>AccountID</tt>. An
+ * account is loaded when its <tt>ProtocolProviderService</tt> is registered
+ * in the bundle context. This method is meant to load the account through
+ * the corresponding <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param accountID the identifier of the account to load
+ * @throws OperationFailedException if anything goes wrong while loading the
+ * account corresponding to the specified <tt>accountID</tt>
+ */
+ public void loadAccount(AccountID accountID)
+ throws OperationFailedException
+ {
+ // If the account with the given id is already loaded we have nothing
+ // to do here.
+ if (isAccountLoaded(accountID))
+ return;
+
+ ProtocolProviderFactory providerFactory
+ = ProtocolProviderActivator.getProtocolProviderFactory(
+ accountID.getProtocolName());
+
+ if(providerFactory.loadAccount(accountID))
+ {
+ accountID.putAccountProperty(
+ ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
+ String.valueOf(false));
+ // Finally store the modified properties.
+ storeAccount(providerFactory, accountID);
+ }
+ }
+
+ /**
+ * Unloads the account corresponding to the given <tt>AccountID</tt>. An
+ * account is unloaded when its <tt>ProtocolProviderService</tt> is
+ * unregistered in the bundle context. This method is meant to unload the
+ * account through the corresponding <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param accountID the identifier of the account to load
+ * @throws OperationFailedException if anything goes wrong while unloading
+ * the account corresponding to the specified <tt>accountID</tt>
+ */
+ public void unloadAccount(AccountID accountID)
+ throws OperationFailedException
+ {
+ // If the account with the given id is already unloaded we have nothing
+ // to do here.
+ if (!isAccountLoaded(accountID))
+ return;
+
+ ProtocolProviderFactory providerFactory
+ = ProtocolProviderActivator.getProtocolProviderFactory(
+ accountID.getProtocolName());
+
+ // Obtain the protocol provider.
+ ServiceReference<ProtocolProviderService> serRef
+ = providerFactory.getProviderForAccount(accountID);
+
+ // If there's no such provider we have nothing to do here.
+ if (serRef == null)
+ return;
+
+ ProtocolProviderService protocolProvider
+ = bundleContext.getService(serRef);
+
+ // Set the account icon path for unloaded accounts.
+ String iconPathProperty = accountID.getAccountPropertyString(
+ ProtocolProviderFactory.ACCOUNT_ICON_PATH);
+
+ if (iconPathProperty == null)
+ {
+ accountID.putAccountProperty(
+ ProtocolProviderFactory.ACCOUNT_ICON_PATH,
+ protocolProvider.getProtocolIcon()
+ .getIconPath(ProtocolIcon.ICON_SIZE_32x32));
+ }
+
+ accountID.putAccountProperty(
+ ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
+ String.valueOf(true));
+
+ if (!providerFactory.unloadAccount(accountID))
+ {
+ accountID.putAccountProperty(
+ ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
+ String.valueOf(false));
+ }
+ // Finally store the modified properties.
+ storeAccount(providerFactory, accountID);
+ }
+
+ /**
+ * Checks if the account corresponding to the given <tt>accountID</tt> is
+ * loaded. An account is loaded if its <tt>ProtocolProviderService</tt> is
+ * registered in the bundle context. By default all accounts are loaded.
+ * However the user could manually unload an account, which would be
+ * unregistered from the bundle context, but would remain in the
+ * configuration file.
+ *
+ * @param accountID the identifier of the account to load
+ * @return <tt>true</tt> to indicate that the account with the given
+ * <tt>accountID</tt> is loaded, <tt>false</tt> - otherwise
+ */
+ public boolean isAccountLoaded(AccountID accountID)
+ {
+ return storedAccounts.contains(accountID) && accountID.isEnabled();
+ }
+
+ private String stripPackagePrefix(String property)
+ {
+ int packageEndIndex = property.lastIndexOf('.');
+
+ if (packageEndIndex != -1)
+ property = property.substring(packageEndIndex + 1);
+ return property;
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/AccountManagerUtils.java b/src/net/java/sip/communicator/service/protocol/AccountManagerUtils.java
index 5b64e06..1400a2d 100644
--- a/src/net/java/sip/communicator/service/protocol/AccountManagerUtils.java
+++ b/src/net/java/sip/communicator/service/protocol/AccountManagerUtils.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,168 +15,168 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol;
-
-import java.util.*;
-
-import net.java.sip.communicator.service.protocol.event.*;
-
-import org.osgi.framework.*;
-
-/**
- * Provides utilities to aid the manipulation of {@link AccountManager}.
- *
- * @author Lyubomir Marinov
- */
-public final class AccountManagerUtils
-{
- private static AccountManager getAccountManager(BundleContext bundleContext)
- {
- return
- bundleContext.getService(
- bundleContext.getServiceReference(AccountManager.class));
- }
-
- /**
- * Starts a specific <code>Bundle</code> and waits for the
- * <code>AccountManager</code> available in a specific
- * <code>BundleContext</code> to load the stored accounts of a
- * <code>ProtocolProviderFactory</code> with a specific protocol name.
- *
- * @param bundleContextWithAccountManager
- * the <code>BundleContext</code> in which an
- * <code>AccountManager</code> service is registered
- * @param bundleToStart
- * the <code>Bundle</code> to be started
- * @param protocolNameToWait
- * the protocol name of a <code>ProtocolProviderFactory</code> to
- * wait the end of the loading of the stored accounts for
- * @throws BundleException
- * @throws InterruptedException
- * if any thread interrupted the current thread before or while
- * the current thread was waiting for the loading of the stored
- * accounts
- */
- public static void startBundleAndWaitStoredAccountsLoaded(
- BundleContext bundleContextWithAccountManager,
- final Bundle bundleToStart,
- final String protocolNameToWait)
- throws BundleException,
- InterruptedException
- {
- AccountManager accountManager
- = getAccountManager(bundleContextWithAccountManager);
- final boolean[] storedAccountsAreLoaded = new boolean[1];
- AccountManagerListener listener = new AccountManagerListener()
- {
- public void handleAccountManagerEvent(AccountManagerEvent event)
- {
- if (AccountManagerEvent.STORED_ACCOUNTS_LOADED
- != event.getType())
- return;
-
- ProtocolProviderFactory factory = event.getFactory();
-
- /*
- * If the event is for a factory with a protocol name other than
- * protocolNameToWait, it's not the one we're waiting for.
- */
- if ((factory != null)
- && !protocolNameToWait
- .equals(factory.getProtocolName()))
- return;
-
- /*
- * If the event if for a factory which is no longer registered,
- * then it's not the one we're waiting for because we're waiting
- * for the specified bundle to start and register a factory.
- */
- if (factory != null)
- {
- BundleContext bundleContext
- = bundleToStart.getBundleContext();
-
- /*
- * If the specified bundle still hasn't started, the event
- * cannot be the one we're waiting for.
- */
- if (bundleContext == null)
- return;
-
- Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs;
-
- try
- {
- factoryRefs
- = bundleContext.getServiceReferences(
- ProtocolProviderFactory.class,
- "("
- + ProtocolProviderFactory.PROTOCOL
- + "="
- + protocolNameToWait
- + ")");
- }
- catch (InvalidSyntaxException isex)
- {
- /*
- * Not likely so ignore it and assume the event is for
- * a valid factory.
- */
- factoryRefs = null;
- }
- if ((factoryRefs != null) && !factoryRefs.isEmpty())
- {
- boolean factoryIsRegistered = false;
-
- for (ServiceReference<ProtocolProviderFactory> factoryRef
- : factoryRefs)
- {
- if (factory == bundleContext.getService(factoryRef))
- {
- factoryIsRegistered = true;
- break;
- }
- }
- if (!factoryIsRegistered)
- return;
- }
- }
-
- synchronized (storedAccountsAreLoaded)
- {
- storedAccountsAreLoaded[0] = true;
- storedAccountsAreLoaded.notify();
- }
- }
- };
-
- accountManager.addListener(listener);
- try
- {
- bundleToStart.start();
-
- while (true)
- {
- synchronized (storedAccountsAreLoaded)
- {
- if (storedAccountsAreLoaded[0])
- {
- break;
- }
- storedAccountsAreLoaded.wait();
- }
- }
- }
- finally
- {
- accountManager.removeListener(listener);
- }
- }
-
- /**
- * Prevents the creation of <code>AccountManagerUtils</code> instances.
- */
- private AccountManagerUtils()
- {
- }
-}
+package net.java.sip.communicator.service.protocol;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.event.*;
+
+import org.osgi.framework.*;
+
+/**
+ * Provides utilities to aid the manipulation of {@link AccountManager}.
+ *
+ * @author Lyubomir Marinov
+ */
+public final class AccountManagerUtils
+{
+ private static AccountManager getAccountManager(BundleContext bundleContext)
+ {
+ return
+ bundleContext.getService(
+ bundleContext.getServiceReference(AccountManager.class));
+ }
+
+ /**
+ * Starts a specific <code>Bundle</code> and waits for the
+ * <code>AccountManager</code> available in a specific
+ * <code>BundleContext</code> to load the stored accounts of a
+ * <code>ProtocolProviderFactory</code> with a specific protocol name.
+ *
+ * @param bundleContextWithAccountManager
+ * the <code>BundleContext</code> in which an
+ * <code>AccountManager</code> service is registered
+ * @param bundleToStart
+ * the <code>Bundle</code> to be started
+ * @param protocolNameToWait
+ * the protocol name of a <code>ProtocolProviderFactory</code> to
+ * wait the end of the loading of the stored accounts for
+ * @throws BundleException
+ * @throws InterruptedException
+ * if any thread interrupted the current thread before or while
+ * the current thread was waiting for the loading of the stored
+ * accounts
+ */
+ public static void startBundleAndWaitStoredAccountsLoaded(
+ BundleContext bundleContextWithAccountManager,
+ final Bundle bundleToStart,
+ final String protocolNameToWait)
+ throws BundleException,
+ InterruptedException
+ {
+ AccountManager accountManager
+ = getAccountManager(bundleContextWithAccountManager);
+ final boolean[] storedAccountsAreLoaded = new boolean[1];
+ AccountManagerListener listener = new AccountManagerListener()
+ {
+ public void handleAccountManagerEvent(AccountManagerEvent event)
+ {
+ if (AccountManagerEvent.STORED_ACCOUNTS_LOADED
+ != event.getType())
+ return;
+
+ ProtocolProviderFactory factory = event.getFactory();
+
+ /*
+ * If the event is for a factory with a protocol name other than
+ * protocolNameToWait, it's not the one we're waiting for.
+ */
+ if ((factory != null)
+ && !protocolNameToWait
+ .equals(factory.getProtocolName()))
+ return;
+
+ /*
+ * If the event if for a factory which is no longer registered,
+ * then it's not the one we're waiting for because we're waiting
+ * for the specified bundle to start and register a factory.
+ */
+ if (factory != null)
+ {
+ BundleContext bundleContext
+ = bundleToStart.getBundleContext();
+
+ /*
+ * If the specified bundle still hasn't started, the event
+ * cannot be the one we're waiting for.
+ */
+ if (bundleContext == null)
+ return;
+
+ Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs;
+
+ try
+ {
+ factoryRefs
+ = bundleContext.getServiceReferences(
+ ProtocolProviderFactory.class,
+ "("
+ + ProtocolProviderFactory.PROTOCOL
+ + "="
+ + protocolNameToWait
+ + ")");
+ }
+ catch (InvalidSyntaxException isex)
+ {
+ /*
+ * Not likely so ignore it and assume the event is for
+ * a valid factory.
+ */
+ factoryRefs = null;
+ }
+ if ((factoryRefs != null) && !factoryRefs.isEmpty())
+ {
+ boolean factoryIsRegistered = false;
+
+ for (ServiceReference<ProtocolProviderFactory> factoryRef
+ : factoryRefs)
+ {
+ if (factory == bundleContext.getService(factoryRef))
+ {
+ factoryIsRegistered = true;
+ break;
+ }
+ }
+ if (!factoryIsRegistered)
+ return;
+ }
+ }
+
+ synchronized (storedAccountsAreLoaded)
+ {
+ storedAccountsAreLoaded[0] = true;
+ storedAccountsAreLoaded.notify();
+ }
+ }
+ };
+
+ accountManager.addListener(listener);
+ try
+ {
+ bundleToStart.start();
+
+ while (true)
+ {
+ synchronized (storedAccountsAreLoaded)
+ {
+ if (storedAccountsAreLoaded[0])
+ {
+ break;
+ }
+ storedAccountsAreLoaded.wait();
+ }
+ }
+ }
+ finally
+ {
+ accountManager.removeListener(listener);
+ }
+ }
+
+ /**
+ * Prevents the creation of <code>AccountManagerUtils</code> instances.
+ */
+ private AccountManagerUtils()
+ {
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/AdHocChatRoom.java b/src/net/java/sip/communicator/service/protocol/AdHocChatRoom.java
index 6f07bd1..e2af0a8 100644
--- a/src/net/java/sip/communicator/service/protocol/AdHocChatRoom.java
+++ b/src/net/java/sip/communicator/service/protocol/AdHocChatRoom.java
@@ -25,8 +25,7 @@ import net.java.sip.communicator.service.protocol.event.*;
* Represents an ad-hoc rendez-vous point where multiple chat users could
* communicate together. This interface describes the main methods used by some
* protocols for multi user chat, without useless methods (such as kicking a
- * participant) which aren't supported by these protocols (MSN, ICQ, Yahoo!,
- * etc.).
+ * participant) which aren't supported by these protocols (MSN, ICQ, etc.).
*
* <tt>AdHocChatRoom</tt> acts like a simplified <tt>ChatRoom</tt>.
*
diff --git a/src/net/java/sip/communicator/service/protocol/Call.java b/src/net/java/sip/communicator/service/protocol/Call.java
index d342373..a952cfd 100644
--- a/src/net/java/sip/communicator/service/protocol/Call.java
+++ b/src/net/java/sip/communicator/service/protocol/Call.java
@@ -111,6 +111,13 @@ public abstract class Call
private boolean isAutoAnswer = false;
/**
+ * The indicator which determines whether any telephony conference
+ * represented by this instance is mixing or relaying.
+ * By default what can be mixed is mixed (audio) and rest is relayed.
+ */
+ protected final boolean useTranslator;
+
+ /**
* Creates a new Call instance.
*
* @param sourceProvider the proto provider that created us.
@@ -133,6 +140,11 @@ public abstract class Call
= accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_SIPZRTP_ATTRIBUTE,
true);
+
+ useTranslator
+ = accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.USE_TRANSLATOR_IN_CONFERENCE,
+ false);
}
/**
diff --git a/src/net/java/sip/communicator/service/protocol/CallConference.java b/src/net/java/sip/communicator/service/protocol/CallConference.java
index 890b0cb..b6752cd 100644
--- a/src/net/java/sip/communicator/service/protocol/CallConference.java
+++ b/src/net/java/sip/communicator/service/protocol/CallConference.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,931 +15,931 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol;
-
-import java.util.*;
-
-import net.java.sip.communicator.service.protocol.event.*;
-
-import org.jitsi.util.event.*;
-
-/**
- * Represents the telephony conference-related state of a <tt>Call</tt>.
- * Multiple <tt>Call</tt> instances share a single <tt>CallConference</tt>
- * instance when the former are into a telephony conference i.e. the local
- * peer/user is the conference focus. <tt>CallConference</tt> is
- * protocol-agnostic and thus enables cross-protocol conferences. Since a
- * non-conference <tt>Call</tt> may be converted into a conference <tt>Call</tt>
- * at any time, every <tt>Call</tt> instance maintains a <tt>CallConference</tt>
- * instance regardless of whether the <tt>Call</tt> in question is participating
- * in a telephony conference.
- *
- * @author Lyubomir Marinov
- */
-public class CallConference
- extends PropertyChangeNotifier
-{
- /**
- * The name of the <tt>CallConference</tt> property which specifies the list
- * of <tt>Call</tt>s participating in a telephony conference. A change in
- * the value of the property is delivered in the form of a
- * <tt>PropertyChangeEvent</tt> which has its <tt>oldValue</tt> or
- * <tt>newValue</tt> set to the <tt>Call</tt> which has been removed or
- * added to the list of <tt>Call</tt>s participating in the telephony
- * conference.
- */
- public static final String CALLS = "calls";
-
- /**
- * Gets the number of <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in the telephony conference-related state of a specific
- * <tt>Call</tt>.
- *
- * @param call the <tt>Call</tt> for which the number of <tt>CallPeer</tt>s
- * associated with the <tt>Call</tt>s participating in its associated
- * telephony conference-related state
- * @return the number of <tt>CallPeer</tt>s associated with the
- * <tt>Call</tt>s participating in the telephony conference-related state
- * of the specified <tt>Call</tt>
- */
- public static int getCallPeerCount(Call call)
- {
- CallConference conference = call.getConference();
-
- /*
- * A Call instance is supposed to always maintain a CallConference
- * instance. Anyway, if it turns out that it is not the case, we will
- * consider the Call as a representation of a telephony conference.
- */
- return
- (conference == null)
- ? call.getCallPeerCount()
- : conference.getCallPeerCount();
- }
-
- /**
- * Gets a list of the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in the telephony conference in which a specific
- * <tt>Call</tt> is participating.
- *
- * @param call the <tt>Call</tt> which specifies the telephony conference
- * the <tt>CallPeer</tt>s of which are to be retrieved
- * @return a list of the <tt>CallPeer</tt>s associated with the
- * <tt>Call</tt>s participating in the telephony conference in which the
- * specified <tt>call</tt> is participating
- */
- public static List<CallPeer> getCallPeers(Call call)
- {
- CallConference conference = call.getConference();
- List<CallPeer> callPeers = new ArrayList<CallPeer>();
-
- if (conference == null)
- {
- Iterator<? extends CallPeer> callPeerIt = call.getCallPeers();
-
- while (callPeerIt.hasNext())
- callPeers.add(callPeerIt.next());
- }
- else
- conference.getCallPeers(callPeers);
- return callPeers;
- }
-
- /**
- * Gets the list of <tt>Call</tt>s participating in the telephony conference
- * in which a specific <tt>Call</tt> is participating.
- *
- * @param call the <tt>Call</tt> which participates in the telephony
- * conference the list of participating <tt>Call</tt>s of which is to be
- * returned
- * @return the list of <tt>Call</tt>s participating in the telephony
- * conference in which the specified <tt>call</tt> is participating
- */
- public static List<Call> getCalls(Call call)
- {
- CallConference conference = call.getConference();
- List<Call> calls;
-
- if (conference == null)
- calls = Collections.emptyList();
- else
- calls = conference.getCalls();
- return calls;
- }
-
- /**
- * Determines whether a <tt>CallConference</tt> is to report the local
- * peer/user as a conference focus judging by a specific list of
- * <tt>Call</tt>s.
- *
- * @param calls the list of <tt>Call</tt> which are to be judged whether
- * the local peer/user that they represent is to be considered as a
- * conference focus
- * @return <tt>true</tt> if the local peer/user represented by the specified
- * <tt>calls</tt> is judged to be a conference focus; otherwise,
- * <tt>false</tt>
- */
- private static boolean isConferenceFocus(List<Call> calls)
- {
- int callCount = calls.size();
- boolean conferenceFocus;
-
- if (callCount < 1)
- conferenceFocus = false;
- else if (callCount > 1)
- conferenceFocus = true;
- else
- conferenceFocus = (calls.get(0).getCallPeerCount() > 1);
- return conferenceFocus;
- }
-
- /**
- * The <tt>CallChangeListener</tt> which listens to changes in the
- * <tt>Call</tt>s participating in this telephony conference.
- */
- private final CallChangeListener callChangeListener
- = new CallChangeListener()
- {
- @Override
- public void callPeerAdded(CallPeerEvent ev)
- {
- CallConference.this.onCallPeerEvent(ev);
- }
-
- @Override
- public void callPeerRemoved(CallPeerEvent ev)
- {
- CallConference.this.onCallPeerEvent(ev);
- }
-
- @Override
- public void callStateChanged(CallChangeEvent ev)
- {
- CallConference.this.callStateChanged(ev);
- }
- };
-
- /**
- * The list of <tt>CallChangeListener</tt>s added to the <tt>Call</tt>s
- * participating in this telephony conference via
- * {@link #addCallChangeListener(CallChangeListener)}.
- */
- private final List<CallChangeListener> callChangeListeners
- = new LinkedList<CallChangeListener>();
-
- /**
- * The <tt>CallPeerConferenceListener</tt> which listens to the
- * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
- * this telephony conference.
- */
- private final CallPeerConferenceListener callPeerConferenceListener
- = new CallPeerConferenceAdapter()
- {
- /**
- * {@inheritDoc}
- *
- * Invokes
- * {@link CallConference#onCallPeerConferenceEvent(
- * CallPeerConferenceEvent)}.
- */
- @Override
- protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
- {
- CallConference.this.onCallPeerConferenceEvent(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Invokes
- * {@link CallConference#onCallPeerConferenceEvent(
- * CallPeerConferenceEvent)}.
- */
- @Override
- public void conferenceMemberErrorReceived(
- CallPeerConferenceEvent ev)
- {
- CallConference.this.onCallPeerConferenceEvent(ev);
- }
- };
-
- /**
- * The list of <tt>CallPeerConferenceListener</tt>s added to the
- * <tt>CallPeer</tt>s associated with the <tt>CallPeer</tt>s participating
- * in this telephony conference via
- * {@link #addCallPeerConferenceListener}.
- */
- private final List<CallPeerConferenceListener> callPeerConferenceListeners
- = new LinkedList<CallPeerConferenceListener>();
-
- /**
- * The synchronization root/<tt>Object</tt> which protects the access to
- * {@link #immutableCalls} and {@link #mutableCalls}.
- */
- private final Object callsSyncRoot = new Object();
-
- /**
- * The indicator which determines whether the local peer represented by this
- * instance and the <tt>Call</tt>s participating in it is acting as a
- * conference focus. The SIP protocol, for example, will add the
- * &quot;isfocus&quot; parameter to the Contact headers of its outgoing
- * signaling if <tt>true</tt>.
- */
- private boolean conferenceFocus = false;
-
- /**
- * The list of <tt>Call</tt>s participating in this telephony conference as
- * an immutable <tt>List</tt> which can be exposed out of this instance
- * without the need to make a copy. In other words, it is an unmodifiable
- * view of {@link #mutableCalls}.
- */
- private List<Call> immutableCalls;
-
- /**
- * The indicator which determines whether the telephony conference
- * represented by this instance is utilizing the Jitsi Videobridge
- * server-side telephony conferencing technology.
- */
- private final boolean jitsiVideobridge;
-
- /**
- * The list of <tt>Call</tt>s participating in this telephony conference as
- * a mutable <tt>List</tt> which should not be exposed out of this instance.
- */
- private List<Call> mutableCalls;
-
- /**
- * Initializes a new <tt>CallConference</tt> instance.
- */
- public CallConference()
- {
- this(false);
- }
-
- /**
- * Initializes a new <tt>CallConference</tt> instance which is to optionally
- * utilize the Jitsi Videobridge server-side telephony conferencing
- * technology.
- *
- * @param jitsiVideobridge <tt>true</tt> if the telephony conference
- * represented by the new instance is to utilize the Jitsi Videobridge
- * server-side telephony conferencing technology; otherwise, <tt>false</tt>
- */
- public CallConference(boolean jitsiVideobridge)
- {
- this.jitsiVideobridge = jitsiVideobridge;
-
- mutableCalls = new ArrayList<Call>();
- immutableCalls = Collections.unmodifiableList(mutableCalls);
- }
-
- /**
- * Adds a specific <tt>Call</tt> to the list of <tt>Call</tt>s participating
- * in this telephony conference.
- *
- * @param call the <tt>Call</tt> to add to the list of <tt>Call</tt>s
- * participating in this telephony conference
- * @return <tt>true</tt> if the list of <tt>Call</tt>s participating in this
- * telephony conference changed as a result of the method call; otherwise,
- * <tt>false</tt>
- * @throws NullPointerException if <tt>call</tt> is <tt>null</tt>
- */
- boolean addCall(Call call)
- {
- if (call == null)
- throw new NullPointerException("call");
-
- synchronized (callsSyncRoot)
- {
- if (mutableCalls.contains(call))
- return false;
-
- /*
- * Implement the List of Calls participating in this telephony
- * conference as a copy-on-write storage in order to optimize the
- * getCalls method which is likely to be executed much more often
- * than the addCall and removeCall methods.
- */
- List<Call> newMutableCalls = new ArrayList<Call>(mutableCalls);
-
- if (newMutableCalls.add(call))
- {
- mutableCalls = newMutableCalls;
- immutableCalls = Collections.unmodifiableList(mutableCalls);
- }
- else
- return false;
- }
-
- callAdded(call);
- return true;
- }
-
- /**
- * Adds a <tt>CallChangeListener</tt> to the <tt>Call</tt>s participating in
- * this telephony conference. The method is a convenience that takes on the
- * responsibility of tracking the <tt>Call</tt>s that get added/removed
- * to/from this telephony conference.
- *
- * @param listener the <tt>CallChangeListner</tt> to be added to the
- * <tt>Call</tt>s participating in this telephony conference
- * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
- */
- public void addCallChangeListener(CallChangeListener listener)
- {
- if (listener == null)
- throw new NullPointerException("listener");
- else
- {
- synchronized (callChangeListeners)
- {
- if (!callChangeListeners.contains(listener))
- callChangeListeners.add(listener);
- }
- }
- }
-
- /**
- * Adds {@link #callPeerConferenceListener} to the <tt>CallPeer</tt>s
- * associated with a specific <tt>Call</tt>.
- *
- * @param call the <tt>Call</tt> to whose associated <tt>CallPeer</tt>s
- * <tt>callPeerConferenceListener</tt> is to be added
- */
- private void addCallPeerConferenceListener(Call call)
- {
- Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
-
- while (callPeerIter.hasNext())
- {
- callPeerIter.next().addCallPeerConferenceListener(
- callPeerConferenceListener);
- }
- }
-
- /**
- * Adds a <tt>CallPeerConferenceListener</tt> to the <tt>CallPeer</tt>s
- * associated with the <tt>Call</tt>s participating in this telephony
- * conference. The method is a convenience that takes on the responsibility
- * of tracking the <tt>Call</tt>s that get added/removed to/from this
- * telephony conference and the <tt>CallPeer</tt> that get added/removed
- * to/from these <tt>Call</tt>s.
- *
- * @param listener the <tt>CallPeerConferenceListener</tt> to be added to
- * the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating
- * in this telephony conference
- * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
- */
- public void addCallPeerConferenceListener(
- CallPeerConferenceListener listener)
- {
- if (listener == null)
- throw new NullPointerException("listener");
- else
- {
- synchronized (callPeerConferenceListeners)
- {
- if (!callPeerConferenceListeners.contains(listener))
- callPeerConferenceListeners.add(listener);
- }
- }
- }
-
- /**
- * Notifies this <tt>CallConference</tt> that a specific <tt>Call</tt> has
- * been added to the list of <tt>Call</tt>s participating in this telephony
- * conference.
- *
- * @param call the <tt>Call</tt> which has been added to the list of
- * <tt>Call</tt>s participating in this telephony conference
- */
- protected void callAdded(Call call)
- {
- call.addCallChangeListener(callChangeListener);
- addCallPeerConferenceListener(call);
-
- /*
- * Update the conferenceFocus state. Because the public
- * setConferenceFocus method allows forcing a specific value on the
- * state in question and because it does not sound right to have the
- * adding of a Call set conferenceFocus to false, only update it if the
- * new conferenceFocus value is true,
- */
- boolean conferenceFocus = isConferenceFocus(getCalls());
-
- if (conferenceFocus)
- setConferenceFocus(conferenceFocus);
-
- firePropertyChange(CALLS, null, call);
- }
-
- /**
- * Notifies this <tt>CallConference</tt> that a specific <tt>Call</tt> has
- * been removed from the list of <tt>Call</tt>s participating in this
- * telephony conference.
- *
- * @param call the <tt>Call</tt> which has been removed from the list of
- * <tt>Call</tt>s participating in this telephony conference
- */
- protected void callRemoved(Call call)
- {
- call.removeCallChangeListener(callChangeListener);
- removeCallPeerConferenceListener(call);
-
- /*
- * Update the conferenceFocus state. Following the line of thinking
- * expressed in the callAdded method, only update it if the new
- * conferenceFocus value is false.
- */
- boolean conferenceFocus = isConferenceFocus(getCalls());
-
- if (!conferenceFocus)
- setConferenceFocus(conferenceFocus);
-
- firePropertyChange(CALLS, call, null);
- }
-
- /**
- * Notifies this telephony conference that the <tt>CallState</tt> of a
- * <tt>Call</tt> has changed.
- *
- * @param ev a <tt>CallChangeEvent</tt> which specifies the <tt>Call</tt>
- * which had its <tt>CallState</tt> changed and the old and new
- * <tt>CallState</tt>s of that <tt>Call</tt>
- */
- private void callStateChanged(CallChangeEvent ev)
- {
- Call call = ev.getSourceCall();
-
- if (containsCall(call))
- {
- try
- {
- // Forward the CallChangeEvent to the callChangeListeners.
- for (CallChangeListener l : getCallChangeListeners())
- l.callStateChanged(ev);
- }
- finally
- {
- if (CallChangeEvent.CALL_STATE_CHANGE
- .equals(ev.getPropertyName())
- && CallState.CALL_ENDED.equals(ev.getNewValue()))
- {
- /*
- * Should not be vital because Call will remove itself.
- * Anyway, do it for the sake of completeness.
- */
- removeCall(call);
- }
- }
- }
- }
-
- /**
- * Notifies this <tt>CallConference</tt> that the value of its
- * <tt>conferenceFocus</tt> property has changed from a specific old value
- * to a specific new value.
- *
- * @param oldValue the value of the <tt>conferenceFocus</tt> property of
- * this instance before the change
- * @param newValue the value of the <tt>conferenceFocus</tt> property of
- * this instance after the change
- */
- protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
- {
- firePropertyChange(Call.CONFERENCE_FOCUS, oldValue, newValue);
- }
-
- /**
- * Determines whether a specific <tt>Call</tt> is participating in this
- * telephony conference.
- *
- * @param call the <tt>Call</tt> which is to be checked whether it is
- * participating in this telephony conference
- * @return <tt>true</tt> if the specified <tt>call</tt> is participating in
- * this telephony conference
- */
- public boolean containsCall(Call call)
- {
- synchronized (callsSyncRoot)
- {
- return mutableCalls.contains(call);
- }
- }
-
- /**
- * Gets the list of <tt>CallChangeListener</tt>s added to the <tt>Call</tt>s
- * participating in this telephony conference via
- * {@link #addCallChangeListener(CallChangeListener)}.
- *
- * @return the list of <tt>CallChangeListener</tt>s added to the
- * <tt>Call</tt>s participating in this telephony conference via
- * {@link #addCallChangeListener(CallChangeListener)}
- */
- private CallChangeListener[] getCallChangeListeners()
- {
- synchronized (callChangeListeners)
- {
- return
- callChangeListeners.toArray(
- new CallChangeListener[callChangeListeners.size()]);
- }
- }
-
- /**
- * Gets the number of <tt>Call</tt>s that are participating in this
- * telephony conference.
- *
- * @return the number of <tt>Call</tt>s that are participating in this
- * telephony conference
- */
- public int getCallCount()
- {
- synchronized (callsSyncRoot)
- {
- return mutableCalls.size();
- }
- }
-
- /**
- * Gets the list of <tt>CallPeerConferenceListener</tt>s added to the
- * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
- * this telephony conference via
- * {@link #addCallPeerConferenceListener(CallPeerConferenceListener)}.
- *
- * @return the list of <tt>CallPeerConferenceListener</tt>s added to the
- * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
- * this telephony conference via
- * {@link #addCallPeerConferenceListener(CallPeerConferenceListener)}
- */
- private CallPeerConferenceListener[] getCallPeerConferenceListeners()
- {
- synchronized (callPeerConferenceListeners)
- {
- return
- callPeerConferenceListeners.toArray(
- new CallPeerConferenceListener[
- callPeerConferenceListeners.size()]);
- }
- }
-
- /**
- * Gets the number of <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in this telephony conference.
- *
- * @return the number of <tt>CallPeer</tt>s associated with the
- * <tt>Call</tt>s participating in this telephony conference
- */
- public int getCallPeerCount()
- {
- int callPeerCount = 0;
-
- for (Call call : getCalls())
- callPeerCount += call.getCallPeerCount();
- return callPeerCount;
- }
-
- /**
- * Gets a list of the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in this telephony conference.
- *
- * @return a list of the <tt>CallPeer</tt>s associated with the
- * <tt>Call</tt>s participating in this telephony conference
- */
- public List<CallPeer> getCallPeers()
- {
- List<CallPeer> callPeers = new ArrayList<CallPeer>();
-
- getCallPeers(callPeers);
- return callPeers;
- }
-
- /**
- * Adds the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in this telephony conference into a specific <tt>List</tt>.
- *
- * @param callPeers a <tt>List</tt> into which the <tt>CallPeer</tt>s
- * associated with the <tt>Call</tt>s participating in this telephony
- * conference are to be added
- */
- protected void getCallPeers(List<CallPeer> callPeers)
- {
- for (Call call : getCalls())
- {
- Iterator<? extends CallPeer> callPeerIt = call.getCallPeers();
-
- while (callPeerIt.hasNext())
- callPeers.add(callPeerIt.next());
- }
- }
-
- /**
- * Gets the list of <tt>Call</tt> participating in this telephony
- * conference.
- *
- * @return the list of <tt>Call</tt>s participating in this telephony
- * conference. An empty array of <tt>Call</tt> element type is returned if
- * there are no <tt>Call</tt>s in this telephony conference-related state.
- */
- public List<Call> getCalls()
- {
- synchronized (callsSyncRoot)
- {
- return immutableCalls;
- }
- }
-
- /**
- * Determines whether the local peer/user associated with this instance and
- * represented by the <tt>Call</tt>s participating into it is acting as a
- * conference focus.
- *
- * @return <tt>true</tt> if the local peer/user associated by this instance
- * is acting as a conference focus; otherwise, <tt>false</tt>
- */
- public boolean isConferenceFocus()
- {
- return conferenceFocus;
- }
-
- /**
- * Determines whether the current state of this instance suggests that the
- * telephony conference it represents has ended. Iterates over the
- * <tt>Call</tt>s participating in this telephony conference and looks for a
- * <tt>Call</tt> which is not in the {@link CallState#CALL_ENDED} state.
- *
- * @return <tt>true</tt> if the current state of this instance suggests that
- * the telephony conference it represents has ended; otherwise,
- * <tt>false</tt>
- */
- public boolean isEnded()
- {
- for (Call call : getCalls())
- {
- if (!CallState.CALL_ENDED.equals(call.getCallState()))
- return false;
- }
- return true;
- }
-
- /**
- * Determines whether the telephony conference represented by this instance
- * is utilizing the Jitsi Videobridge server-side telephony conferencing
- * technology.
- *
- * @return <tt>true</tt> if the telephony conference represented by this
- * instance is utilizing the Jitsi Videobridge server-side telephony
- * conferencing technology
- */
- public boolean isJitsiVideobridge()
- {
- return jitsiVideobridge;
- }
-
- /**
- * Notifies this telephony conference that a
- * <tt>CallPeerConferenceEvent</tt> was fired by a <tt>CallPeer</tt>
- * associated with a <tt>Call</tt> participating in this telephony
- * conference. Forwards the specified <tt>CallPeerConferenceEvent</tt> to
- * {@link #callPeerConferenceListeners}.
- *
- * @param ev the <tt>CallPeerConferenceEvent</tt> which was fired
- */
- private void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
- {
- int eventID = ev.getEventID();
-
- for (CallPeerConferenceListener l : getCallPeerConferenceListeners())
- {
- switch (eventID)
- {
- case CallPeerConferenceEvent.CONFERENCE_FOCUS_CHANGED:
- l.conferenceFocusChanged(ev);
- break;
- case CallPeerConferenceEvent.CONFERENCE_MEMBER_ADDED:
- l.conferenceMemberAdded(ev);
- break;
- case CallPeerConferenceEvent.CONFERENCE_MEMBER_REMOVED:
- l.conferenceMemberRemoved(ev);
- break;
- case CallPeerConferenceEvent.CONFERENCE_MEMBER_ERROR_RECEIVED:
- l.conferenceMemberErrorReceived(ev);
- break;
- default:
- throw new UnsupportedOperationException(
- "Unsupported CallPeerConferenceEvent eventID.");
- }
- }
- }
-
- /**
- * Notifies this telephony conference about a specific
- * <tt>CallPeerEvent</tt> i.e. that a <tt>CallPeer</tt> was either added to
- * or removed from a <tt>Call</tt>.
- *
- * @param ev a <tt>CallPeerEvent</tt> which specifies the <tt>CallPeer</tt>
- * which was added or removed and the <tt>Call</tt> to which it was added or
- * from which is was removed
- */
- private void onCallPeerEvent(CallPeerEvent ev)
- {
- Call call = ev.getSourceCall();
-
- if (containsCall(call))
- {
- /*
- * Update the conferenceFocus state. Following the line of thinking
- * expressed in the callAdded and callRemoved methods, only update
- * it if the new conferenceFocus value is in accord with the
- * expectations.
- */
- int eventID = ev.getEventID();
- boolean conferenceFocus = isConferenceFocus(getCalls());
-
- switch (eventID)
- {
- case CallPeerEvent.CALL_PEER_ADDED:
- if (conferenceFocus)
- setConferenceFocus(conferenceFocus);
- break;
- case CallPeerEvent.CALL_PEER_REMOVED:
- if (!conferenceFocus)
- setConferenceFocus(conferenceFocus);
- break;
- default:
- /*
- * We're interested in the adding and removing of CallPeers
- * only.
- */
- break;
- }
-
- try
- {
- // Forward the CallPeerEvent to the callChangeListeners.
- for (CallChangeListener l : getCallChangeListeners())
- {
- switch (eventID)
- {
- case CallPeerEvent.CALL_PEER_ADDED:
- l.callPeerAdded(ev);
- break;
- case CallPeerEvent.CALL_PEER_REMOVED:
- l.callPeerRemoved(ev);
- break;
- default:
- break;
- }
- }
- }
- finally
- {
- /*
- * Add/remove the callPeerConferenceListener to/from the source
- * CallPeer (for the purposes of the
- * addCallPeerConferenceListener method of this CallConference).
- */
- CallPeer callPeer = ev.getSourceCallPeer();
-
- switch (eventID)
- {
- case CallPeerEvent.CALL_PEER_ADDED:
- callPeer.addCallPeerConferenceListener(
- callPeerConferenceListener);
- break;
- case CallPeerEvent.CALL_PEER_REMOVED:
- callPeer.removeCallPeerConferenceListener(
- callPeerConferenceListener);
- break;
- default:
- break;
- }
- }
- }
- }
-
- /**
- * Removes a specific <tt>Call</tt> from the list of <tt>Call</tt>s
- * participating in this telephony conference.
- *
- * @param call the <tt>Call</tt> to remove from the list of <tt>Call</tt>s
- * participating in this telephony conference
- * @return <tt>true</tt> if the list of <tt>Call</tt>s participating in this
- * telephony conference changed as a result of the method call; otherwise,
- * <tt>false</tt>
- */
- boolean removeCall(Call call)
- {
- if (call == null)
- return false;
-
- synchronized (callsSyncRoot)
- {
- if (!mutableCalls.contains(call))
- return false;
-
- /*
- * Implement the List of Calls participating in this telephony
- * conference as a copy-on-write storage in order to optimize the
- * getCalls method which is likely to be executed much more often
- * than the addCall and removeCall methods.
- */
- List<Call> newMutableCalls = new ArrayList<Call>(mutableCalls);
-
- if (newMutableCalls.remove(call))
- {
- mutableCalls = newMutableCalls;
- immutableCalls = Collections.unmodifiableList(mutableCalls);
- }
- else
- return false;
- }
-
- callRemoved(call);
- return true;
- }
-
- /**
- * Removes a <tt>CallChangeListener</tt> from the <tt>Call</tt>s
- * participating in this telephony conference.
- *
- * @param listener the <tt>CallChangeListener</tt> to be removed from the
- * <tt>Call</tt>s participating in this telephony conference
- * @see #addCallChangeListener(CallChangeListener)
- */
- public void removeCallChangeListener(CallChangeListener listener)
- {
- if (listener != null)
- {
- synchronized (callChangeListeners)
- {
- callChangeListeners.remove(listener);
- }
- }
- }
-
- /**
- * Removes {@link #callPeerConferenceListener} from the <tt>CallPeer</tt>s
- * associated with a specific <tt>Call</tt>.
- *
- * @param call the <tt>Call</tt> from whose associated <tt>CallPeer</tt>s
- * <tt>callPeerConferenceListener</tt> is to be removed
- */
- private void removeCallPeerConferenceListener(Call call)
- {
- Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
-
- while (callPeerIter.hasNext())
- {
- callPeerIter.next().removeCallPeerConferenceListener(
- callPeerConferenceListener);
- }
- }
-
- /**
- * Removes a <tt>CallPeerConferenceListener</tt> from the <tt>CallPeer</tt>s
- * associated with the <tt>Call</tt>s participating in this telephony
- * conference.
- *
- * @param listener the <tt>CallPeerConferenceListener</tt> to be removed
- * from the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in this telephony conference
- * @see #addCallPeerConferenceListener(CallPeerConferenceListener)
- */
- public void removeCallPeerConferenceListener(
- CallPeerConferenceListener listener)
- {
- if (listener != null)
- {
- synchronized (callPeerConferenceListeners)
- {
- callPeerConferenceListeners.remove(listener);
- }
- }
- }
-
- /**
- * Sets the indicator which determines whether the local peer represented by
- * this instance and the <tt>Call</tt>s participating in it is acting as a
- * conference focus (and thus may, for example, need to send the
- * corresponding parameters in its outgoing signaling).
- *
- * @param conferenceFocus <tt>true</tt> if the local peer represented by
- * this instance and the <tt>Call</tt>s participating in it is to act as a
- * conference focus; otherwise, <tt>false</tt>
- */
- public void setConferenceFocus(boolean conferenceFocus)
- {
- if (this.conferenceFocus != conferenceFocus)
- {
- boolean oldValue = isConferenceFocus();
-
- this.conferenceFocus = conferenceFocus;
-
- boolean newValue = isConferenceFocus();
-
- if (oldValue != newValue)
- conferenceFocusChanged(oldValue, newValue);
- }
- }
-}
+package net.java.sip.communicator.service.protocol;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.event.*;
+
+import org.jitsi.util.event.*;
+
+/**
+ * Represents the telephony conference-related state of a <tt>Call</tt>.
+ * Multiple <tt>Call</tt> instances share a single <tt>CallConference</tt>
+ * instance when the former are into a telephony conference i.e. the local
+ * peer/user is the conference focus. <tt>CallConference</tt> is
+ * protocol-agnostic and thus enables cross-protocol conferences. Since a
+ * non-conference <tt>Call</tt> may be converted into a conference <tt>Call</tt>
+ * at any time, every <tt>Call</tt> instance maintains a <tt>CallConference</tt>
+ * instance regardless of whether the <tt>Call</tt> in question is participating
+ * in a telephony conference.
+ *
+ * @author Lyubomir Marinov
+ */
+public class CallConference
+ extends PropertyChangeNotifier
+{
+ /**
+ * The name of the <tt>CallConference</tt> property which specifies the list
+ * of <tt>Call</tt>s participating in a telephony conference. A change in
+ * the value of the property is delivered in the form of a
+ * <tt>PropertyChangeEvent</tt> which has its <tt>oldValue</tt> or
+ * <tt>newValue</tt> set to the <tt>Call</tt> which has been removed or
+ * added to the list of <tt>Call</tt>s participating in the telephony
+ * conference.
+ */
+ public static final String CALLS = "calls";
+
+ /**
+ * Gets the number of <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in the telephony conference-related state of a specific
+ * <tt>Call</tt>.
+ *
+ * @param call the <tt>Call</tt> for which the number of <tt>CallPeer</tt>s
+ * associated with the <tt>Call</tt>s participating in its associated
+ * telephony conference-related state
+ * @return the number of <tt>CallPeer</tt>s associated with the
+ * <tt>Call</tt>s participating in the telephony conference-related state
+ * of the specified <tt>Call</tt>
+ */
+ public static int getCallPeerCount(Call call)
+ {
+ CallConference conference = call.getConference();
+
+ /*
+ * A Call instance is supposed to always maintain a CallConference
+ * instance. Anyway, if it turns out that it is not the case, we will
+ * consider the Call as a representation of a telephony conference.
+ */
+ return
+ (conference == null)
+ ? call.getCallPeerCount()
+ : conference.getCallPeerCount();
+ }
+
+ /**
+ * Gets a list of the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in the telephony conference in which a specific
+ * <tt>Call</tt> is participating.
+ *
+ * @param call the <tt>Call</tt> which specifies the telephony conference
+ * the <tt>CallPeer</tt>s of which are to be retrieved
+ * @return a list of the <tt>CallPeer</tt>s associated with the
+ * <tt>Call</tt>s participating in the telephony conference in which the
+ * specified <tt>call</tt> is participating
+ */
+ public static List<CallPeer> getCallPeers(Call call)
+ {
+ CallConference conference = call.getConference();
+ List<CallPeer> callPeers = new ArrayList<CallPeer>();
+
+ if (conference == null)
+ {
+ Iterator<? extends CallPeer> callPeerIt = call.getCallPeers();
+
+ while (callPeerIt.hasNext())
+ callPeers.add(callPeerIt.next());
+ }
+ else
+ conference.getCallPeers(callPeers);
+ return callPeers;
+ }
+
+ /**
+ * Gets the list of <tt>Call</tt>s participating in the telephony conference
+ * in which a specific <tt>Call</tt> is participating.
+ *
+ * @param call the <tt>Call</tt> which participates in the telephony
+ * conference the list of participating <tt>Call</tt>s of which is to be
+ * returned
+ * @return the list of <tt>Call</tt>s participating in the telephony
+ * conference in which the specified <tt>call</tt> is participating
+ */
+ public static List<Call> getCalls(Call call)
+ {
+ CallConference conference = call.getConference();
+ List<Call> calls;
+
+ if (conference == null)
+ calls = Collections.emptyList();
+ else
+ calls = conference.getCalls();
+ return calls;
+ }
+
+ /**
+ * Determines whether a <tt>CallConference</tt> is to report the local
+ * peer/user as a conference focus judging by a specific list of
+ * <tt>Call</tt>s.
+ *
+ * @param calls the list of <tt>Call</tt> which are to be judged whether
+ * the local peer/user that they represent is to be considered as a
+ * conference focus
+ * @return <tt>true</tt> if the local peer/user represented by the specified
+ * <tt>calls</tt> is judged to be a conference focus; otherwise,
+ * <tt>false</tt>
+ */
+ private static boolean isConferenceFocus(List<Call> calls)
+ {
+ int callCount = calls.size();
+ boolean conferenceFocus;
+
+ if (callCount < 1)
+ conferenceFocus = false;
+ else if (callCount > 1)
+ conferenceFocus = true;
+ else
+ conferenceFocus = (calls.get(0).getCallPeerCount() > 1);
+ return conferenceFocus;
+ }
+
+ /**
+ * The <tt>CallChangeListener</tt> which listens to changes in the
+ * <tt>Call</tt>s participating in this telephony conference.
+ */
+ private final CallChangeListener callChangeListener
+ = new CallChangeListener()
+ {
+ @Override
+ public void callPeerAdded(CallPeerEvent ev)
+ {
+ CallConference.this.onCallPeerEvent(ev);
+ }
+
+ @Override
+ public void callPeerRemoved(CallPeerEvent ev)
+ {
+ CallConference.this.onCallPeerEvent(ev);
+ }
+
+ @Override
+ public void callStateChanged(CallChangeEvent ev)
+ {
+ CallConference.this.callStateChanged(ev);
+ }
+ };
+
+ /**
+ * The list of <tt>CallChangeListener</tt>s added to the <tt>Call</tt>s
+ * participating in this telephony conference via
+ * {@link #addCallChangeListener(CallChangeListener)}.
+ */
+ private final List<CallChangeListener> callChangeListeners
+ = new LinkedList<CallChangeListener>();
+
+ /**
+ * The <tt>CallPeerConferenceListener</tt> which listens to the
+ * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
+ * this telephony conference.
+ */
+ private final CallPeerConferenceListener callPeerConferenceListener
+ = new CallPeerConferenceAdapter()
+ {
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes
+ * {@link CallConference#onCallPeerConferenceEvent(
+ * CallPeerConferenceEvent)}.
+ */
+ @Override
+ protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
+ {
+ CallConference.this.onCallPeerConferenceEvent(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes
+ * {@link CallConference#onCallPeerConferenceEvent(
+ * CallPeerConferenceEvent)}.
+ */
+ @Override
+ public void conferenceMemberErrorReceived(
+ CallPeerConferenceEvent ev)
+ {
+ CallConference.this.onCallPeerConferenceEvent(ev);
+ }
+ };
+
+ /**
+ * The list of <tt>CallPeerConferenceListener</tt>s added to the
+ * <tt>CallPeer</tt>s associated with the <tt>CallPeer</tt>s participating
+ * in this telephony conference via
+ * {@link #addCallPeerConferenceListener}.
+ */
+ private final List<CallPeerConferenceListener> callPeerConferenceListeners
+ = new LinkedList<CallPeerConferenceListener>();
+
+ /**
+ * The synchronization root/<tt>Object</tt> which protects the access to
+ * {@link #immutableCalls} and {@link #mutableCalls}.
+ */
+ private final Object callsSyncRoot = new Object();
+
+ /**
+ * The indicator which determines whether the local peer represented by this
+ * instance and the <tt>Call</tt>s participating in it is acting as a
+ * conference focus. The SIP protocol, for example, will add the
+ * &quot;isfocus&quot; parameter to the Contact headers of its outgoing
+ * signaling if <tt>true</tt>.
+ */
+ private boolean conferenceFocus = false;
+
+ /**
+ * The list of <tt>Call</tt>s participating in this telephony conference as
+ * an immutable <tt>List</tt> which can be exposed out of this instance
+ * without the need to make a copy. In other words, it is an unmodifiable
+ * view of {@link #mutableCalls}.
+ */
+ private List<Call> immutableCalls;
+
+ /**
+ * The indicator which determines whether the telephony conference
+ * represented by this instance is utilizing the Jitsi Videobridge
+ * server-side telephony conferencing technology.
+ */
+ private final boolean jitsiVideobridge;
+
+ /**
+ * The list of <tt>Call</tt>s participating in this telephony conference as
+ * a mutable <tt>List</tt> which should not be exposed out of this instance.
+ */
+ private List<Call> mutableCalls;
+
+ /**
+ * Initializes a new <tt>CallConference</tt> instance.
+ */
+ public CallConference()
+ {
+ this(false);
+ }
+
+ /**
+ * Initializes a new <tt>CallConference</tt> instance which is to optionally
+ * utilize the Jitsi Videobridge server-side telephony conferencing
+ * technology.
+ *
+ * @param jitsiVideobridge <tt>true</tt> if the telephony conference
+ * represented by the new instance is to utilize the Jitsi Videobridge
+ * server-side telephony conferencing technology; otherwise, <tt>false</tt>
+ */
+ public CallConference(boolean jitsiVideobridge)
+ {
+ this.jitsiVideobridge = jitsiVideobridge;
+
+ mutableCalls = new ArrayList<Call>();
+ immutableCalls = Collections.unmodifiableList(mutableCalls);
+ }
+
+ /**
+ * Adds a specific <tt>Call</tt> to the list of <tt>Call</tt>s participating
+ * in this telephony conference.
+ *
+ * @param call the <tt>Call</tt> to add to the list of <tt>Call</tt>s
+ * participating in this telephony conference
+ * @return <tt>true</tt> if the list of <tt>Call</tt>s participating in this
+ * telephony conference changed as a result of the method call; otherwise,
+ * <tt>false</tt>
+ * @throws NullPointerException if <tt>call</tt> is <tt>null</tt>
+ */
+ boolean addCall(Call call)
+ {
+ if (call == null)
+ throw new NullPointerException("call");
+
+ synchronized (callsSyncRoot)
+ {
+ if (mutableCalls.contains(call))
+ return false;
+
+ /*
+ * Implement the List of Calls participating in this telephony
+ * conference as a copy-on-write storage in order to optimize the
+ * getCalls method which is likely to be executed much more often
+ * than the addCall and removeCall methods.
+ */
+ List<Call> newMutableCalls = new ArrayList<Call>(mutableCalls);
+
+ if (newMutableCalls.add(call))
+ {
+ mutableCalls = newMutableCalls;
+ immutableCalls = Collections.unmodifiableList(mutableCalls);
+ }
+ else
+ return false;
+ }
+
+ callAdded(call);
+ return true;
+ }
+
+ /**
+ * Adds a <tt>CallChangeListener</tt> to the <tt>Call</tt>s participating in
+ * this telephony conference. The method is a convenience that takes on the
+ * responsibility of tracking the <tt>Call</tt>s that get added/removed
+ * to/from this telephony conference.
+ *
+ * @param listener the <tt>CallChangeListner</tt> to be added to the
+ * <tt>Call</tt>s participating in this telephony conference
+ * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
+ */
+ public void addCallChangeListener(CallChangeListener listener)
+ {
+ if (listener == null)
+ throw new NullPointerException("listener");
+ else
+ {
+ synchronized (callChangeListeners)
+ {
+ if (!callChangeListeners.contains(listener))
+ callChangeListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Adds {@link #callPeerConferenceListener} to the <tt>CallPeer</tt>s
+ * associated with a specific <tt>Call</tt>.
+ *
+ * @param call the <tt>Call</tt> to whose associated <tt>CallPeer</tt>s
+ * <tt>callPeerConferenceListener</tt> is to be added
+ */
+ private void addCallPeerConferenceListener(Call call)
+ {
+ Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
+
+ while (callPeerIter.hasNext())
+ {
+ callPeerIter.next().addCallPeerConferenceListener(
+ callPeerConferenceListener);
+ }
+ }
+
+ /**
+ * Adds a <tt>CallPeerConferenceListener</tt> to the <tt>CallPeer</tt>s
+ * associated with the <tt>Call</tt>s participating in this telephony
+ * conference. The method is a convenience that takes on the responsibility
+ * of tracking the <tt>Call</tt>s that get added/removed to/from this
+ * telephony conference and the <tt>CallPeer</tt> that get added/removed
+ * to/from these <tt>Call</tt>s.
+ *
+ * @param listener the <tt>CallPeerConferenceListener</tt> to be added to
+ * the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating
+ * in this telephony conference
+ * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
+ */
+ public void addCallPeerConferenceListener(
+ CallPeerConferenceListener listener)
+ {
+ if (listener == null)
+ throw new NullPointerException("listener");
+ else
+ {
+ synchronized (callPeerConferenceListeners)
+ {
+ if (!callPeerConferenceListeners.contains(listener))
+ callPeerConferenceListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Notifies this <tt>CallConference</tt> that a specific <tt>Call</tt> has
+ * been added to the list of <tt>Call</tt>s participating in this telephony
+ * conference.
+ *
+ * @param call the <tt>Call</tt> which has been added to the list of
+ * <tt>Call</tt>s participating in this telephony conference
+ */
+ protected void callAdded(Call call)
+ {
+ call.addCallChangeListener(callChangeListener);
+ addCallPeerConferenceListener(call);
+
+ /*
+ * Update the conferenceFocus state. Because the public
+ * setConferenceFocus method allows forcing a specific value on the
+ * state in question and because it does not sound right to have the
+ * adding of a Call set conferenceFocus to false, only update it if the
+ * new conferenceFocus value is true,
+ */
+ boolean conferenceFocus = isConferenceFocus(getCalls());
+
+ if (conferenceFocus)
+ setConferenceFocus(conferenceFocus);
+
+ firePropertyChange(CALLS, null, call);
+ }
+
+ /**
+ * Notifies this <tt>CallConference</tt> that a specific <tt>Call</tt> has
+ * been removed from the list of <tt>Call</tt>s participating in this
+ * telephony conference.
+ *
+ * @param call the <tt>Call</tt> which has been removed from the list of
+ * <tt>Call</tt>s participating in this telephony conference
+ */
+ protected void callRemoved(Call call)
+ {
+ call.removeCallChangeListener(callChangeListener);
+ removeCallPeerConferenceListener(call);
+
+ /*
+ * Update the conferenceFocus state. Following the line of thinking
+ * expressed in the callAdded method, only update it if the new
+ * conferenceFocus value is false.
+ */
+ boolean conferenceFocus = isConferenceFocus(getCalls());
+
+ if (!conferenceFocus)
+ setConferenceFocus(conferenceFocus);
+
+ firePropertyChange(CALLS, call, null);
+ }
+
+ /**
+ * Notifies this telephony conference that the <tt>CallState</tt> of a
+ * <tt>Call</tt> has changed.
+ *
+ * @param ev a <tt>CallChangeEvent</tt> which specifies the <tt>Call</tt>
+ * which had its <tt>CallState</tt> changed and the old and new
+ * <tt>CallState</tt>s of that <tt>Call</tt>
+ */
+ private void callStateChanged(CallChangeEvent ev)
+ {
+ Call call = ev.getSourceCall();
+
+ if (containsCall(call))
+ {
+ try
+ {
+ // Forward the CallChangeEvent to the callChangeListeners.
+ for (CallChangeListener l : getCallChangeListeners())
+ l.callStateChanged(ev);
+ }
+ finally
+ {
+ if (CallChangeEvent.CALL_STATE_CHANGE
+ .equals(ev.getPropertyName())
+ && CallState.CALL_ENDED.equals(ev.getNewValue()))
+ {
+ /*
+ * Should not be vital because Call will remove itself.
+ * Anyway, do it for the sake of completeness.
+ */
+ removeCall(call);
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies this <tt>CallConference</tt> that the value of its
+ * <tt>conferenceFocus</tt> property has changed from a specific old value
+ * to a specific new value.
+ *
+ * @param oldValue the value of the <tt>conferenceFocus</tt> property of
+ * this instance before the change
+ * @param newValue the value of the <tt>conferenceFocus</tt> property of
+ * this instance after the change
+ */
+ protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
+ {
+ firePropertyChange(Call.CONFERENCE_FOCUS, oldValue, newValue);
+ }
+
+ /**
+ * Determines whether a specific <tt>Call</tt> is participating in this
+ * telephony conference.
+ *
+ * @param call the <tt>Call</tt> which is to be checked whether it is
+ * participating in this telephony conference
+ * @return <tt>true</tt> if the specified <tt>call</tt> is participating in
+ * this telephony conference
+ */
+ public boolean containsCall(Call call)
+ {
+ synchronized (callsSyncRoot)
+ {
+ return mutableCalls.contains(call);
+ }
+ }
+
+ /**
+ * Gets the list of <tt>CallChangeListener</tt>s added to the <tt>Call</tt>s
+ * participating in this telephony conference via
+ * {@link #addCallChangeListener(CallChangeListener)}.
+ *
+ * @return the list of <tt>CallChangeListener</tt>s added to the
+ * <tt>Call</tt>s participating in this telephony conference via
+ * {@link #addCallChangeListener(CallChangeListener)}
+ */
+ private CallChangeListener[] getCallChangeListeners()
+ {
+ synchronized (callChangeListeners)
+ {
+ return
+ callChangeListeners.toArray(
+ new CallChangeListener[callChangeListeners.size()]);
+ }
+ }
+
+ /**
+ * Gets the number of <tt>Call</tt>s that are participating in this
+ * telephony conference.
+ *
+ * @return the number of <tt>Call</tt>s that are participating in this
+ * telephony conference
+ */
+ public int getCallCount()
+ {
+ synchronized (callsSyncRoot)
+ {
+ return mutableCalls.size();
+ }
+ }
+
+ /**
+ * Gets the list of <tt>CallPeerConferenceListener</tt>s added to the
+ * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
+ * this telephony conference via
+ * {@link #addCallPeerConferenceListener(CallPeerConferenceListener)}.
+ *
+ * @return the list of <tt>CallPeerConferenceListener</tt>s added to the
+ * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
+ * this telephony conference via
+ * {@link #addCallPeerConferenceListener(CallPeerConferenceListener)}
+ */
+ private CallPeerConferenceListener[] getCallPeerConferenceListeners()
+ {
+ synchronized (callPeerConferenceListeners)
+ {
+ return
+ callPeerConferenceListeners.toArray(
+ new CallPeerConferenceListener[
+ callPeerConferenceListeners.size()]);
+ }
+ }
+
+ /**
+ * Gets the number of <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in this telephony conference.
+ *
+ * @return the number of <tt>CallPeer</tt>s associated with the
+ * <tt>Call</tt>s participating in this telephony conference
+ */
+ public int getCallPeerCount()
+ {
+ int callPeerCount = 0;
+
+ for (Call call : getCalls())
+ callPeerCount += call.getCallPeerCount();
+ return callPeerCount;
+ }
+
+ /**
+ * Gets a list of the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in this telephony conference.
+ *
+ * @return a list of the <tt>CallPeer</tt>s associated with the
+ * <tt>Call</tt>s participating in this telephony conference
+ */
+ public List<CallPeer> getCallPeers()
+ {
+ List<CallPeer> callPeers = new ArrayList<CallPeer>();
+
+ getCallPeers(callPeers);
+ return callPeers;
+ }
+
+ /**
+ * Adds the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in this telephony conference into a specific <tt>List</tt>.
+ *
+ * @param callPeers a <tt>List</tt> into which the <tt>CallPeer</tt>s
+ * associated with the <tt>Call</tt>s participating in this telephony
+ * conference are to be added
+ */
+ protected void getCallPeers(List<CallPeer> callPeers)
+ {
+ for (Call call : getCalls())
+ {
+ Iterator<? extends CallPeer> callPeerIt = call.getCallPeers();
+
+ while (callPeerIt.hasNext())
+ callPeers.add(callPeerIt.next());
+ }
+ }
+
+ /**
+ * Gets the list of <tt>Call</tt> participating in this telephony
+ * conference.
+ *
+ * @return the list of <tt>Call</tt>s participating in this telephony
+ * conference. An empty array of <tt>Call</tt> element type is returned if
+ * there are no <tt>Call</tt>s in this telephony conference-related state.
+ */
+ public List<Call> getCalls()
+ {
+ synchronized (callsSyncRoot)
+ {
+ return immutableCalls;
+ }
+ }
+
+ /**
+ * Determines whether the local peer/user associated with this instance and
+ * represented by the <tt>Call</tt>s participating into it is acting as a
+ * conference focus.
+ *
+ * @return <tt>true</tt> if the local peer/user associated by this instance
+ * is acting as a conference focus; otherwise, <tt>false</tt>
+ */
+ public boolean isConferenceFocus()
+ {
+ return conferenceFocus;
+ }
+
+ /**
+ * Determines whether the current state of this instance suggests that the
+ * telephony conference it represents has ended. Iterates over the
+ * <tt>Call</tt>s participating in this telephony conference and looks for a
+ * <tt>Call</tt> which is not in the {@link CallState#CALL_ENDED} state.
+ *
+ * @return <tt>true</tt> if the current state of this instance suggests that
+ * the telephony conference it represents has ended; otherwise,
+ * <tt>false</tt>
+ */
+ public boolean isEnded()
+ {
+ for (Call call : getCalls())
+ {
+ if (!CallState.CALL_ENDED.equals(call.getCallState()))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Determines whether the telephony conference represented by this instance
+ * is utilizing the Jitsi Videobridge server-side telephony conferencing
+ * technology.
+ *
+ * @return <tt>true</tt> if the telephony conference represented by this
+ * instance is utilizing the Jitsi Videobridge server-side telephony
+ * conferencing technology
+ */
+ public boolean isJitsiVideobridge()
+ {
+ return jitsiVideobridge;
+ }
+
+ /**
+ * Notifies this telephony conference that a
+ * <tt>CallPeerConferenceEvent</tt> was fired by a <tt>CallPeer</tt>
+ * associated with a <tt>Call</tt> participating in this telephony
+ * conference. Forwards the specified <tt>CallPeerConferenceEvent</tt> to
+ * {@link #callPeerConferenceListeners}.
+ *
+ * @param ev the <tt>CallPeerConferenceEvent</tt> which was fired
+ */
+ private void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
+ {
+ int eventID = ev.getEventID();
+
+ for (CallPeerConferenceListener l : getCallPeerConferenceListeners())
+ {
+ switch (eventID)
+ {
+ case CallPeerConferenceEvent.CONFERENCE_FOCUS_CHANGED:
+ l.conferenceFocusChanged(ev);
+ break;
+ case CallPeerConferenceEvent.CONFERENCE_MEMBER_ADDED:
+ l.conferenceMemberAdded(ev);
+ break;
+ case CallPeerConferenceEvent.CONFERENCE_MEMBER_REMOVED:
+ l.conferenceMemberRemoved(ev);
+ break;
+ case CallPeerConferenceEvent.CONFERENCE_MEMBER_ERROR_RECEIVED:
+ l.conferenceMemberErrorReceived(ev);
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "Unsupported CallPeerConferenceEvent eventID.");
+ }
+ }
+ }
+
+ /**
+ * Notifies this telephony conference about a specific
+ * <tt>CallPeerEvent</tt> i.e. that a <tt>CallPeer</tt> was either added to
+ * or removed from a <tt>Call</tt>.
+ *
+ * @param ev a <tt>CallPeerEvent</tt> which specifies the <tt>CallPeer</tt>
+ * which was added or removed and the <tt>Call</tt> to which it was added or
+ * from which is was removed
+ */
+ private void onCallPeerEvent(CallPeerEvent ev)
+ {
+ Call call = ev.getSourceCall();
+
+ if (containsCall(call))
+ {
+ /*
+ * Update the conferenceFocus state. Following the line of thinking
+ * expressed in the callAdded and callRemoved methods, only update
+ * it if the new conferenceFocus value is in accord with the
+ * expectations.
+ */
+ int eventID = ev.getEventID();
+ boolean conferenceFocus = isConferenceFocus(getCalls());
+
+ switch (eventID)
+ {
+ case CallPeerEvent.CALL_PEER_ADDED:
+ if (conferenceFocus)
+ setConferenceFocus(conferenceFocus);
+ break;
+ case CallPeerEvent.CALL_PEER_REMOVED:
+ if (!conferenceFocus)
+ setConferenceFocus(conferenceFocus);
+ break;
+ default:
+ /*
+ * We're interested in the adding and removing of CallPeers
+ * only.
+ */
+ break;
+ }
+
+ try
+ {
+ // Forward the CallPeerEvent to the callChangeListeners.
+ for (CallChangeListener l : getCallChangeListeners())
+ {
+ switch (eventID)
+ {
+ case CallPeerEvent.CALL_PEER_ADDED:
+ l.callPeerAdded(ev);
+ break;
+ case CallPeerEvent.CALL_PEER_REMOVED:
+ l.callPeerRemoved(ev);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ finally
+ {
+ /*
+ * Add/remove the callPeerConferenceListener to/from the source
+ * CallPeer (for the purposes of the
+ * addCallPeerConferenceListener method of this CallConference).
+ */
+ CallPeer callPeer = ev.getSourceCallPeer();
+
+ switch (eventID)
+ {
+ case CallPeerEvent.CALL_PEER_ADDED:
+ callPeer.addCallPeerConferenceListener(
+ callPeerConferenceListener);
+ break;
+ case CallPeerEvent.CALL_PEER_REMOVED:
+ callPeer.removeCallPeerConferenceListener(
+ callPeerConferenceListener);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>Call</tt> from the list of <tt>Call</tt>s
+ * participating in this telephony conference.
+ *
+ * @param call the <tt>Call</tt> to remove from the list of <tt>Call</tt>s
+ * participating in this telephony conference
+ * @return <tt>true</tt> if the list of <tt>Call</tt>s participating in this
+ * telephony conference changed as a result of the method call; otherwise,
+ * <tt>false</tt>
+ */
+ boolean removeCall(Call call)
+ {
+ if (call == null)
+ return false;
+
+ synchronized (callsSyncRoot)
+ {
+ if (!mutableCalls.contains(call))
+ return false;
+
+ /*
+ * Implement the List of Calls participating in this telephony
+ * conference as a copy-on-write storage in order to optimize the
+ * getCalls method which is likely to be executed much more often
+ * than the addCall and removeCall methods.
+ */
+ List<Call> newMutableCalls = new ArrayList<Call>(mutableCalls);
+
+ if (newMutableCalls.remove(call))
+ {
+ mutableCalls = newMutableCalls;
+ immutableCalls = Collections.unmodifiableList(mutableCalls);
+ }
+ else
+ return false;
+ }
+
+ callRemoved(call);
+ return true;
+ }
+
+ /**
+ * Removes a <tt>CallChangeListener</tt> from the <tt>Call</tt>s
+ * participating in this telephony conference.
+ *
+ * @param listener the <tt>CallChangeListener</tt> to be removed from the
+ * <tt>Call</tt>s participating in this telephony conference
+ * @see #addCallChangeListener(CallChangeListener)
+ */
+ public void removeCallChangeListener(CallChangeListener listener)
+ {
+ if (listener != null)
+ {
+ synchronized (callChangeListeners)
+ {
+ callChangeListeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Removes {@link #callPeerConferenceListener} from the <tt>CallPeer</tt>s
+ * associated with a specific <tt>Call</tt>.
+ *
+ * @param call the <tt>Call</tt> from whose associated <tt>CallPeer</tt>s
+ * <tt>callPeerConferenceListener</tt> is to be removed
+ */
+ private void removeCallPeerConferenceListener(Call call)
+ {
+ Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
+
+ while (callPeerIter.hasNext())
+ {
+ callPeerIter.next().removeCallPeerConferenceListener(
+ callPeerConferenceListener);
+ }
+ }
+
+ /**
+ * Removes a <tt>CallPeerConferenceListener</tt> from the <tt>CallPeer</tt>s
+ * associated with the <tt>Call</tt>s participating in this telephony
+ * conference.
+ *
+ * @param listener the <tt>CallPeerConferenceListener</tt> to be removed
+ * from the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in this telephony conference
+ * @see #addCallPeerConferenceListener(CallPeerConferenceListener)
+ */
+ public void removeCallPeerConferenceListener(
+ CallPeerConferenceListener listener)
+ {
+ if (listener != null)
+ {
+ synchronized (callPeerConferenceListeners)
+ {
+ callPeerConferenceListeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Sets the indicator which determines whether the local peer represented by
+ * this instance and the <tt>Call</tt>s participating in it is acting as a
+ * conference focus (and thus may, for example, need to send the
+ * corresponding parameters in its outgoing signaling).
+ *
+ * @param conferenceFocus <tt>true</tt> if the local peer represented by
+ * this instance and the <tt>Call</tt>s participating in it is to act as a
+ * conference focus; otherwise, <tt>false</tt>
+ */
+ public void setConferenceFocus(boolean conferenceFocus)
+ {
+ if (this.conferenceFocus != conferenceFocus)
+ {
+ boolean oldValue = isConferenceFocus();
+
+ this.conferenceFocus = conferenceFocus;
+
+ boolean newValue = isConferenceFocus();
+
+ if (oldValue != newValue)
+ conferenceFocusChanged(oldValue, newValue);
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetBasicInstantMessaging.java b/src/net/java/sip/communicator/service/protocol/OperationSetBasicInstantMessaging.java
index c0918cb..55dec9a 100644
--- a/src/net/java/sip/communicator/service/protocol/OperationSetBasicInstantMessaging.java
+++ b/src/net/java/sip/communicator/service/protocol/OperationSetBasicInstantMessaging.java
@@ -80,22 +80,6 @@ public interface OperationSetBasicInstantMessaging
String messageText, String contentType, String messageUID);
/**
- * Create a Message instance with the specified UID and a default
- * (text/plain) content type and encoding.
- * This method can be useful when message correction is required. One can
- * construct the corrected message to have the same UID as the message
- * before correction.
- *
- * @param messageText the string content of the message.
- * @param messageUID the unique identifier of this message.
- * @return Message the newly created message
- *
- * @deprecated Method will be removed once OTR bundle is updated on Android.
- */
- @Deprecated
- public Message createMessageWithUID(String messageText, String messageUID);
-
- /**
* Sends the <tt>message</tt> to the destination indicated by the
* <tt>to</tt> contact.
*
diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetJitsiMeetTools.java b/src/net/java/sip/communicator/service/protocol/OperationSetJitsiMeetTools.java
index 08c8cff..41af2b4 100644
--- a/src/net/java/sip/communicator/service/protocol/OperationSetJitsiMeetTools.java
+++ b/src/net/java/sip/communicator/service/protocol/OperationSetJitsiMeetTools.java
@@ -56,6 +56,17 @@ public interface OperationSetJitsiMeetTools
PacketExtension extension);
/**
+ * Removes given <tt>PacketExtension</tt> from the multi user chat presence
+ * and sends presence update packet to the chat room.
+ * @param chatRoom the <tt>ChatRoom</tt> for which the presence will be
+ * updated.
+ * @param extension the <tt>PacketExtension</tt> to be removed from the MUC
+ * presence.
+ */
+ public void removePresenceExtension(ChatRoom chatRoom,
+ PacketExtension extension);
+
+ /**
* Sets the status message of our MUC presence and sends presence status
* update packet to the server.
* @param chatRoom the <tt>ChatRoom</tt> for which the presence status
diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetSecureSDesTelephony.java b/src/net/java/sip/communicator/service/protocol/OperationSetSecureSDesTelephony.java
index e0c75fd..f336de1 100644
--- a/src/net/java/sip/communicator/service/protocol/OperationSetSecureSDesTelephony.java
+++ b/src/net/java/sip/communicator/service/protocol/OperationSetSecureSDesTelephony.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,15 +15,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol;
-
-/**
- * Marker interface to indicate that a protocol supports SDES encryption.
- *
- * @author Ingo Bauersachs
- */
-public interface OperationSetSecureSDesTelephony
- extends OperationSetSecureTelephony
-{
-
-}
+package net.java.sip.communicator.service.protocol;
+
+/**
+ * Marker interface to indicate that a protocol supports SDES encryption.
+ *
+ * @author Ingo Bauersachs
+ */
+public interface OperationSetSecureSDesTelephony
+ extends OperationSetSecureTelephony
+{
+
+}
diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetSecureZrtpTelephony.java b/src/net/java/sip/communicator/service/protocol/OperationSetSecureZrtpTelephony.java
index 7213af4..eb5dcdd 100644
--- a/src/net/java/sip/communicator/service/protocol/OperationSetSecureZrtpTelephony.java
+++ b/src/net/java/sip/communicator/service/protocol/OperationSetSecureZrtpTelephony.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,15 +15,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol;
-
-/**
- * Marker interface to indicate that a protocol supports ZRTP encryption.
- *
- * @author Ingo Bauersachs
- */
-public interface OperationSetSecureZrtpTelephony
- extends OperationSetSecureTelephony
-{
-
-}
+package net.java.sip.communicator.service.protocol;
+
+/**
+ * Marker interface to indicate that a protocol supports ZRTP encryption.
+ *
+ * @author Ingo Bauersachs
+ */
+public interface OperationSetSecureZrtpTelephony
+ extends OperationSetSecureTelephony
+{
+
+}
diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetTelephonyBLF.java b/src/net/java/sip/communicator/service/protocol/OperationSetTelephonyBLF.java
index 028c2ae..f6a50dc 100644
--- a/src/net/java/sip/communicator/service/protocol/OperationSetTelephonyBLF.java
+++ b/src/net/java/sip/communicator/service/protocol/OperationSetTelephonyBLF.java
@@ -80,6 +80,11 @@ public interface OperationSetTelephonyBLF
private String group;
/**
+ * Asterisk pickup prefix.
+ */
+ private String pickupTemplate;
+
+ /**
* The parent provider.
*/
private ProtocolProviderService provider;
@@ -90,14 +95,16 @@ public interface OperationSetTelephonyBLF
* @param address the address of the line.
* @param name the display name if any
* @param group the group name if any
+ * @param pickup the pickup dial template
* @param provider the parent provider.
*/
- public Line(String address, String name, String group,
+ public Line(String address, String name, String group, String pickup,
ProtocolProviderService provider)
{
this.address = address;
this.name = name;
this.group = group;
+ this.pickupTemplate = pickup;
this.provider = provider;
}
@@ -129,6 +136,15 @@ public interface OperationSetTelephonyBLF
}
/**
+ * The pickup template.
+ * @return the pickup template.
+ */
+ public String getPickupTemplate()
+ {
+ return pickupTemplate;
+ }
+
+ /**
* The provider.
* @return the provider.
*/
diff --git a/src/net/java/sip/communicator/service/protocol/PhoneNumberI18nService.java b/src/net/java/sip/communicator/service/protocol/PhoneNumberI18nService.java
index df71e0c..521afc5 100644
--- a/src/net/java/sip/communicator/service/protocol/PhoneNumberI18nService.java
+++ b/src/net/java/sip/communicator/service/protocol/PhoneNumberI18nService.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,49 +15,59 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol;
-
-/**
- * Implements <tt>PhoneNumberI18nService</tt> which aids the parsing, formatting
- * and validating of international phone numbers.
- *
- * @author Lyubomir Marinov
- * @author Vincent Lucas
- * @author Damian Minkov
- */
-public interface PhoneNumberI18nService
-{
- /**
- * Normalizes a <tt>String</tt> which may be a phone number or a identifier
- * by removing useless characters and, if necessary, replacing the alpahe
- * characters in corresponding dial pad numbers.
- *
- * @param possibleNumber a <tt>String</tt> which may represents a phone
- * number or an identifier to normalize.
- *
- * @return a <tt>String</tt> which is a normalized form of the specified
- * <tt>possibleNumber</tt>.
- */
- public String normalize(String possibleNumber);
-
- /**
- * Determines whether two <tt>String</tt> phone numbers match.
- *
- * @param aPhoneNumber a <tt>String</tt> which represents a phone number to
- * match to <tt>bPhoneNumber</tt>
- * @param bPhoneNumber a <tt>String</tt> which represents a phone number to
- * match to <tt>aPhoneNumber</tt>
- * @return <tt>true</tt> if the specified <tt>String</tt>s match as phone
- * numbers; otherwise, <tt>false</tt>
- */
- public boolean phoneNumbersMatch(String aPhoneNumber, String bPhoneNumber);
-
- /**
- * Indicates if the given string is possibly a phone number.
- *
- * @param possibleNumber the string to be verified
- * @return <tt>true</tt> if the possibleNumber is a phone number,
- * <tt>false</tt> - otherwise
- */
- public boolean isPhoneNumber(String possibleNumber);
-}
+package net.java.sip.communicator.service.protocol;
+
+/**
+ * Implements <tt>PhoneNumberI18nService</tt> which aids the parsing, formatting
+ * and validating of international phone numbers.
+ *
+ * @author Lyubomir Marinov
+ * @author Vincent Lucas
+ * @author Damian Minkov
+ */
+public interface PhoneNumberI18nService
+{
+ /**
+ * Normalizes a <tt>String</tt> which may be a phone number or a identifier
+ * by removing useless characters and, if necessary, replacing the alpahe
+ * characters in corresponding dial pad numbers.
+ *
+ * @param possibleNumber a <tt>String</tt> which may represents a phone
+ * number or an identifier to normalize.
+ *
+ * @return a <tt>String</tt> which is a normalized form of the specified
+ * <tt>possibleNumber</tt>.
+ */
+ public String normalize(String possibleNumber);
+
+ /**
+ * 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);
+
+ /**
+ * Determines whether two <tt>String</tt> phone numbers match.
+ *
+ * @param aPhoneNumber a <tt>String</tt> which represents a phone number to
+ * match to <tt>bPhoneNumber</tt>
+ * @param bPhoneNumber a <tt>String</tt> which represents a phone number to
+ * match to <tt>aPhoneNumber</tt>
+ * @return <tt>true</tt> if the specified <tt>String</tt>s match as phone
+ * numbers; otherwise, <tt>false</tt>
+ */
+ public boolean phoneNumbersMatch(String aPhoneNumber, String bPhoneNumber);
+
+ /**
+ * Indicates if the given string is possibly a phone number.
+ *
+ * @param possibleNumber the string to be verified
+ * @return <tt>true</tt> if the possibleNumber is a phone number,
+ * <tt>false</tt> - otherwise
+ */
+ public boolean isPhoneNumber(String possibleNumber);
+}
diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolNames.java b/src/net/java/sip/communicator/service/protocol/ProtocolNames.java
index 8332557..ba51c91 100644
--- a/src/net/java/sip/communicator/service/protocol/ProtocolNames.java
+++ b/src/net/java/sip/communicator/service/protocol/ProtocolNames.java
@@ -44,16 +44,6 @@ public interface ProtocolNames
public static final String IRC = "IRC";
/**
- * The Gadu-Gadu protocol.
- */
- public static final String GADU_GADU = "Gadu-Gadu";
-
- /**
- * The GroupWise protocol.
- */
- public static final String GROUP_WISE = "GroupWise";
-
- /**
* The ICQ service protocol.
*/
public static final String ICQ = "ICQ";
@@ -64,26 +54,11 @@ public interface ProtocolNames
public static final String AIM = "AIM";
/**
- * The Yahoo! messenger protocol.
- */
- public static final String YAHOO = "Yahoo!";
-
- /**
- * The Skype protocol.
- */
- public static final String SKYPE = "Skype";
-
- /**
* The SIP Communicator MOCK protocol.
*/
public static final String SIP_COMMUNICATOR_MOCK = "sip-communicator-mock";
/**
- * The Zeroconf protocol.
- */
- public static final String ZEROCONF = "Zeroconf";
-
- /**
* The SSH protocol.
*/
public static final String SSH = "SSH";
@@ -92,14 +67,4 @@ public interface ProtocolNames
* The Gibberish protocol.
*/
public static final String GIBBERISH = "Gibberish";
-
- /**
- * The Dict protocol.
- */
- public static final String DICT = "Dict";
-
- /**
- * The Facebook protocol.
- */
- public static final String FACEBOOK = "Facebook";
}
diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolProviderActivator.java b/src/net/java/sip/communicator/service/protocol/ProtocolProviderActivator.java
index 60cdf5c..a9d7c7c 100644
--- a/src/net/java/sip/communicator/service/protocol/ProtocolProviderActivator.java
+++ b/src/net/java/sip/communicator/service/protocol/ProtocolProviderActivator.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,295 +15,295 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol;
-
-import net.java.sip.communicator.service.calendar.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.configuration.*;
-import org.jitsi.service.resources.*;
-import org.osgi.framework.*;
-
-import java.util.*;
-
-/**
- * Implements <code>BundleActivator</code> for the purposes of
- * protocol.jar/protocol.provider.manifest.mf and in order to register and start
- * services independent of the specifics of a particular protocol.
- *
- * @author Lubomir Marinov
- * @author Yana Stamcheva
- */
-public class ProtocolProviderActivator
- implements BundleActivator
-{
- /**
- * The object used for logging.
- */
- private final static Logger logger
- = Logger.getLogger(ProtocolProviderActivator.class);
-
- /**
- * The <code>ServiceRegistration</code> of the <code>AccountManager</code>
- * implementation registered as a service by this activator and cached so
- * that the service in question can be properly disposed of upon stopping
- * this activator.
- */
- private ServiceRegistration accountManagerServiceRegistration;
-
- /**
- * The account manager.
- */
- private static AccountManager accountManager;
-
- /**
- * The <code>BundleContext</code> of the one and only
- * <code>ProtocolProviderActivator</code> instance which is currently
- * started.
- */
- private static BundleContext bundleContext;
-
- /**
- * The <code>ConfigurationService</code> used by the classes in the bundle
- * represented by <code>ProtocolProviderActivator</code>.
- */
- private static ConfigurationService configurationService;
-
- /**
- * The resource service through which we obtain localized strings.
- */
- private static ResourceManagementService resourceService;
-
- /**
- * The calendar service instance.
- */
- private static CalendarService calendarService;
-
- /**
- * The <code>SingleCallInProgressPolicy</code> making sure that the
- * <code>Call</code>s accessible in the <code>BundleContext</code> of this
- * activator will obey to the rule that a new <code>Call</code> should put
- * the other existing <code>Call</code>s on hold.
- */
- private SingleCallInProgressPolicy singleCallInProgressPolicy;
-
- /**
- * Gets the <code>ConfigurationService</code> to be used by the classes in
- * the bundle represented by <code>ProtocolProviderActivator</code>.
- *
- * @return the <code>ConfigurationService</code> to be used by the classes
- * in the bundle represented by
- * <code>ProtocolProviderActivator</code>
- */
- public static ConfigurationService getConfigurationService()
- {
- if (configurationService == null)
- {
- configurationService
- = (ConfigurationService)
- bundleContext.getService(
- bundleContext.getServiceReference(
- ConfigurationService.class.getName()));
- }
- return configurationService;
- }
-
- /**
- * Gets the <code>ResourceManagementService</code> to be used by the classes
- * in the bundle represented by <code>ProtocolProviderActivator</code>.
- *
- * @return the <code>ResourceManagementService</code> to be used by the
- * classes in the bundle represented by
- * <code>ProtocolProviderActivator</code>
- */
- public static ResourceManagementService getResourceService()
- {
- if (resourceService == null)
- {
- resourceService
- = (ResourceManagementService)
- bundleContext.getService(
- bundleContext.getServiceReference(
- ResourceManagementService.class.getName()));
- }
- return resourceService;
- }
-
- /**
- * Gets the <code>CalendarService</code> to be used by the classes
- * in the bundle represented by <code>ProtocolProviderActivator</code>.
- *
- * @return the <code>CalendarService</code> to be used by the
- * classes in the bundle represented by
- * <code>ProtocolProviderActivator</code>
- */
- public static CalendarService getCalendarService()
- {
- if (calendarService == null)
- {
- ServiceReference serviceReference
- = bundleContext.getServiceReference(
- CalendarService.class.getName());
- if(serviceReference == null)
- return null;
- calendarService
- = (CalendarService)
- bundleContext.getService(serviceReference);
- }
- return calendarService;
- }
-
- /**
- * Returns a <tt>ProtocolProviderFactory</tt> for a given protocol
- * provider.
- * @param protocolName the name of the protocol, which factory we're
- * looking for
- * @return a <tt>ProtocolProviderFactory</tt> for a given protocol
- * provider
- */
- public static ProtocolProviderFactory getProtocolProviderFactory(
- String protocolName)
- {
- String osgiFilter
- = "(" + ProtocolProviderFactory.PROTOCOL + "=" + protocolName + ")";
- ProtocolProviderFactory protocolProviderFactory = null;
-
- try
- {
- ServiceReference[] serRefs
- = bundleContext.getServiceReferences(
- ProtocolProviderFactory.class.getName(),
- osgiFilter);
-
- if ((serRefs != null) && (serRefs.length != 0))
- {
- protocolProviderFactory
- = (ProtocolProviderFactory)
- bundleContext.getService(serRefs[0]);
- }
- }
- catch (InvalidSyntaxException ex)
- {
- if (logger.isInfoEnabled())
- logger.info("ProtocolProviderActivator : " + ex);
- }
-
- return protocolProviderFactory;
- }
-
- /**
- * Registers a new <code>AccountManagerImpl</code> instance as an
- * <code>AccountManager</code> service and starts a new
- * <code>SingleCallInProgressPolicy</code> instance to ensure that only one
- * of the <code>Call</code>s accessible in the <code>BundleContext</code>
- * in which this activator is to execute will be in progress and the others
- * will automatically be put on hold.
- *
- * @param bundleContext the <code>BundleContext</code> in which the bundle
- * activation represented by this <code>BundleActivator</code>
- * executes
- */
- public void start(BundleContext bundleContext)
- {
- ProtocolProviderActivator.bundleContext = bundleContext;
-
- accountManager = new AccountManager(bundleContext);
- accountManagerServiceRegistration =
- bundleContext.registerService(AccountManager.class.getName(),
- accountManager, null);
- if(logger.isTraceEnabled())
- {
- logger.trace("ProtocolProviderActivator will create "
- + "SingleCallInProgressPolicy instance.");
- }
-
- singleCallInProgressPolicy =
- new SingleCallInProgressPolicy(bundleContext);
- }
-
- /**
- * Unregisters the <code>AccountManagerImpl</code> instance registered as an
- * <code>AccountManager</code> service in {@link #start(BundleContext)} and
- * stops the <code>SingleCallInProgressPolicy</code> started there as well.
- *
- * @param bundleContext the <code>BundleContext</code> in which the bundle
- * activation represented by this <code>BundleActivator</code>
- * executes
- */
- public void stop(BundleContext bundleContext)
- {
- if (accountManagerServiceRegistration != null)
- {
- accountManagerServiceRegistration.unregister();
- accountManagerServiceRegistration = null;
- accountManager = null;
- }
-
- if (singleCallInProgressPolicy != null)
- {
- singleCallInProgressPolicy.dispose();
- singleCallInProgressPolicy = null;
- }
-
- if (bundleContext.equals(ProtocolProviderActivator.bundleContext))
- ProtocolProviderActivator.bundleContext = null;
-
- configurationService = null;
- resourceService = null;
- }
-
- /**
- * Returns all protocol providers currently registered.
- * @return all protocol providers currently registered.
- */
- public static List<ProtocolProviderService>
- getProtocolProviders()
- {
- ServiceReference[] serRefs = null;
- try
- {
- // get all registered provider factories
- serRefs = bundleContext.getServiceReferences(
- ProtocolProviderService.class.getName(),
- null);
- }
- catch (InvalidSyntaxException e)
- {
- logger.error("ProtocolProviderActivator : " + e);
- }
-
- List<ProtocolProviderService>
- providersList = new ArrayList<ProtocolProviderService>();
-
- if (serRefs != null)
- {
- for (ServiceReference serRef : serRefs)
- {
- ProtocolProviderService pp
- = (ProtocolProviderService)bundleContext.getService(serRef);
-
- providersList.add(pp);
- }
- }
- return providersList;
- }
-
- /**
- * Get the <tt>AccountManager</tt> of the protocol.
- *
- * @return <tt>AccountManager</tt> of the protocol
- */
- public static AccountManager getAccountManager()
- {
- return accountManager;
- }
-
- /**
- * Returns OSGI bundle context.
- * @return OSGI bundle context.
- */
- public static BundleContext getBundleContext()
- {
- return bundleContext;
- }
-}
+package net.java.sip.communicator.service.protocol;
+
+import net.java.sip.communicator.service.calendar.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.configuration.*;
+import org.jitsi.service.resources.*;
+import org.osgi.framework.*;
+
+import java.util.*;
+
+/**
+ * Implements <code>BundleActivator</code> for the purposes of
+ * protocol.jar/protocol.provider.manifest.mf and in order to register and start
+ * services independent of the specifics of a particular protocol.
+ *
+ * @author Lubomir Marinov
+ * @author Yana Stamcheva
+ */
+public class ProtocolProviderActivator
+ implements BundleActivator
+{
+ /**
+ * The object used for logging.
+ */
+ private final static Logger logger
+ = Logger.getLogger(ProtocolProviderActivator.class);
+
+ /**
+ * The <code>ServiceRegistration</code> of the <code>AccountManager</code>
+ * implementation registered as a service by this activator and cached so
+ * that the service in question can be properly disposed of upon stopping
+ * this activator.
+ */
+ private ServiceRegistration accountManagerServiceRegistration;
+
+ /**
+ * The account manager.
+ */
+ private static AccountManager accountManager;
+
+ /**
+ * The <code>BundleContext</code> of the one and only
+ * <code>ProtocolProviderActivator</code> instance which is currently
+ * started.
+ */
+ private static BundleContext bundleContext;
+
+ /**
+ * The <code>ConfigurationService</code> used by the classes in the bundle
+ * represented by <code>ProtocolProviderActivator</code>.
+ */
+ private static ConfigurationService configurationService;
+
+ /**
+ * The resource service through which we obtain localized strings.
+ */
+ private static ResourceManagementService resourceService;
+
+ /**
+ * The calendar service instance.
+ */
+ private static CalendarService calendarService;
+
+ /**
+ * The <code>SingleCallInProgressPolicy</code> making sure that the
+ * <code>Call</code>s accessible in the <code>BundleContext</code> of this
+ * activator will obey to the rule that a new <code>Call</code> should put
+ * the other existing <code>Call</code>s on hold.
+ */
+ private SingleCallInProgressPolicy singleCallInProgressPolicy;
+
+ /**
+ * Gets the <code>ConfigurationService</code> to be used by the classes in
+ * the bundle represented by <code>ProtocolProviderActivator</code>.
+ *
+ * @return the <code>ConfigurationService</code> to be used by the classes
+ * in the bundle represented by
+ * <code>ProtocolProviderActivator</code>
+ */
+ public static ConfigurationService getConfigurationService()
+ {
+ if (configurationService == null)
+ {
+ configurationService
+ = (ConfigurationService)
+ bundleContext.getService(
+ bundleContext.getServiceReference(
+ ConfigurationService.class.getName()));
+ }
+ return configurationService;
+ }
+
+ /**
+ * Gets the <code>ResourceManagementService</code> to be used by the classes
+ * in the bundle represented by <code>ProtocolProviderActivator</code>.
+ *
+ * @return the <code>ResourceManagementService</code> to be used by the
+ * classes in the bundle represented by
+ * <code>ProtocolProviderActivator</code>
+ */
+ public static ResourceManagementService getResourceService()
+ {
+ if (resourceService == null)
+ {
+ resourceService
+ = (ResourceManagementService)
+ bundleContext.getService(
+ bundleContext.getServiceReference(
+ ResourceManagementService.class.getName()));
+ }
+ return resourceService;
+ }
+
+ /**
+ * Gets the <code>CalendarService</code> to be used by the classes
+ * in the bundle represented by <code>ProtocolProviderActivator</code>.
+ *
+ * @return the <code>CalendarService</code> to be used by the
+ * classes in the bundle represented by
+ * <code>ProtocolProviderActivator</code>
+ */
+ public static CalendarService getCalendarService()
+ {
+ if (calendarService == null)
+ {
+ ServiceReference serviceReference
+ = bundleContext.getServiceReference(
+ CalendarService.class.getName());
+ if(serviceReference == null)
+ return null;
+ calendarService
+ = (CalendarService)
+ bundleContext.getService(serviceReference);
+ }
+ return calendarService;
+ }
+
+ /**
+ * Returns a <tt>ProtocolProviderFactory</tt> for a given protocol
+ * provider.
+ * @param protocolName the name of the protocol, which factory we're
+ * looking for
+ * @return a <tt>ProtocolProviderFactory</tt> for a given protocol
+ * provider
+ */
+ public static ProtocolProviderFactory getProtocolProviderFactory(
+ String protocolName)
+ {
+ String osgiFilter
+ = "(" + ProtocolProviderFactory.PROTOCOL + "=" + protocolName + ")";
+ ProtocolProviderFactory protocolProviderFactory = null;
+
+ try
+ {
+ ServiceReference[] serRefs
+ = bundleContext.getServiceReferences(
+ ProtocolProviderFactory.class.getName(),
+ osgiFilter);
+
+ if ((serRefs != null) && (serRefs.length != 0))
+ {
+ protocolProviderFactory
+ = (ProtocolProviderFactory)
+ bundleContext.getService(serRefs[0]);
+ }
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("ProtocolProviderActivator : " + ex);
+ }
+
+ return protocolProviderFactory;
+ }
+
+ /**
+ * Registers a new <code>AccountManagerImpl</code> instance as an
+ * <code>AccountManager</code> service and starts a new
+ * <code>SingleCallInProgressPolicy</code> instance to ensure that only one
+ * of the <code>Call</code>s accessible in the <code>BundleContext</code>
+ * in which this activator is to execute will be in progress and the others
+ * will automatically be put on hold.
+ *
+ * @param bundleContext the <code>BundleContext</code> in which the bundle
+ * activation represented by this <code>BundleActivator</code>
+ * executes
+ */
+ public void start(BundleContext bundleContext)
+ {
+ ProtocolProviderActivator.bundleContext = bundleContext;
+
+ accountManager = new AccountManager(bundleContext);
+ accountManagerServiceRegistration =
+ bundleContext.registerService(AccountManager.class.getName(),
+ accountManager, null);
+ if(logger.isTraceEnabled())
+ {
+ logger.trace("ProtocolProviderActivator will create "
+ + "SingleCallInProgressPolicy instance.");
+ }
+
+ singleCallInProgressPolicy =
+ new SingleCallInProgressPolicy(bundleContext);
+ }
+
+ /**
+ * Unregisters the <code>AccountManagerImpl</code> instance registered as an
+ * <code>AccountManager</code> service in {@link #start(BundleContext)} and
+ * stops the <code>SingleCallInProgressPolicy</code> started there as well.
+ *
+ * @param bundleContext the <code>BundleContext</code> in which the bundle
+ * activation represented by this <code>BundleActivator</code>
+ * executes
+ */
+ public void stop(BundleContext bundleContext)
+ {
+ if (accountManagerServiceRegistration != null)
+ {
+ accountManagerServiceRegistration.unregister();
+ accountManagerServiceRegistration = null;
+ accountManager = null;
+ }
+
+ if (singleCallInProgressPolicy != null)
+ {
+ singleCallInProgressPolicy.dispose();
+ singleCallInProgressPolicy = null;
+ }
+
+ if (bundleContext.equals(ProtocolProviderActivator.bundleContext))
+ ProtocolProviderActivator.bundleContext = null;
+
+ configurationService = null;
+ resourceService = null;
+ }
+
+ /**
+ * Returns all protocol providers currently registered.
+ * @return all protocol providers currently registered.
+ */
+ public static List<ProtocolProviderService>
+ getProtocolProviders()
+ {
+ ServiceReference[] serRefs = null;
+ try
+ {
+ // get all registered provider factories
+ serRefs = bundleContext.getServiceReferences(
+ ProtocolProviderService.class.getName(),
+ null);
+ }
+ catch (InvalidSyntaxException e)
+ {
+ logger.error("ProtocolProviderActivator : " + e);
+ }
+
+ List<ProtocolProviderService>
+ providersList = new ArrayList<ProtocolProviderService>();
+
+ if (serRefs != null)
+ {
+ for (ServiceReference serRef : serRefs)
+ {
+ ProtocolProviderService pp
+ = (ProtocolProviderService)bundleContext.getService(serRef);
+
+ providersList.add(pp);
+ }
+ }
+ return providersList;
+ }
+
+ /**
+ * Get the <tt>AccountManager</tt> of the protocol.
+ *
+ * @return <tt>AccountManager</tt> of the protocol
+ */
+ public static AccountManager getAccountManager()
+ {
+ return accountManager;
+ }
+
+ /**
+ * Returns OSGI bundle context.
+ * @return OSGI bundle context.
+ */
+ public static BundleContext getBundleContext()
+ {
+ return bundleContext;
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java b/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java
index 7e8b9c8..561a092 100644
--- a/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java
+++ b/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,1302 +15,1311 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol;
-
-import java.lang.reflect.*;
-import java.util.*;
-
-import net.java.sip.communicator.service.credentialsstorage.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.configuration.*;
-import org.osgi.framework.*;
-
-/**
- * The ProtocolProviderFactory is what actually creates instances of a
- * ProtocolProviderService implementation. A provider factory would register,
- * persistently store, and remove when necessary, ProtocolProviders. The way
- * things are in the SIP Communicator, a user account is represented (in a 1:1
- * relationship) by an AccountID and a ProtocolProvider. In other words - one
- * would have as many protocol providers installed in a given moment as they
- * would user account registered through the various services.
- *
- * @author Emil Ivov
- * @author Lubomir Marinov
- */
-public abstract class ProtocolProviderFactory
-{
- /**
- * The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class
- * and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(ProtocolProviderFactory.class);
-
- /**
- * Then name of a property which represents a password.
- */
- public static final String PASSWORD = "PASSWORD";
-
- /**
- * The name of a property representing the name of the protocol for an
- * ProtocolProviderFactory.
- */
- public static final String PROTOCOL = "PROTOCOL_NAME";
-
- /**
- * The name of a property representing the path to protocol icons.
- */
- public static final String PROTOCOL_ICON_PATH = "PROTOCOL_ICON_PATH";
-
- /**
- * The name of a property representing the path to the account icon to
- * be used in the user interface, when the protocol provider service is not
- * available.
- */
- public static final String ACCOUNT_ICON_PATH = "ACCOUNT_ICON_PATH";
-
- /**
- * The name of a property which represents the AccountID of a
- * ProtocolProvider and that, together with a password is used to login
- * on the protocol network..
- */
- public static final String USER_ID = "USER_ID";
-
- /**
- * The name that should be displayed to others when we are calling or
- * writing them.
- */
- public static final String DISPLAY_NAME = "DISPLAY_NAME";
-
- /**
- * The name that should be displayed to the user on call via and chat via
- * lists.
- */
- public static final String ACCOUNT_DISPLAY_NAME = "ACCOUNT_DISPLAY_NAME";
-
- /**
- * The name of the property under which we store protocol AccountID-s.
- */
- public static final String ACCOUNT_UID = "ACCOUNT_UID";
-
- /**
- * The name of the property under which we store protocol the address of
- * a protocol centric entity (any protocol server).
- */
- public static final String SERVER_ADDRESS = "SERVER_ADDRESS";
-
- /**
- * The name of the property under which we store the number of the port
- * where the server stored against the SERVER_ADDRESS property is expecting
- * connections to be made via this protocol.
- */
- public static final String SERVER_PORT = "SERVER_PORT";
-
- /**
- * The name of the property under which we store the name of the transport
- * protocol that needs to be used to access the server.
- */
- public static final String SERVER_TRANSPORT = "SERVER_TRANSPORT";
-
- /**
- * The name of the property under which we store protocol the address of
- * a protocol proxy.
- */
- public static final String PROXY_ADDRESS = "PROXY_ADDRESS";
-
- /**
- * The name of the property under which we store the number of the port
- * where the proxy stored against the PROXY_ADDRESS property is expecting
- * connections to be made via this protocol.
- */
- public static final String PROXY_PORT = "PROXY_PORT";
-
- /**
- * The name of the property which defines whether proxy is auto configured
- * by the protocol by using known methods such as specific DNS queries.
- */
- public static final String PROXY_AUTO_CONFIG = "PROXY_AUTO_CONFIG";
-
- /**
- * The property indicating the preferred UDP and TCP
- * port to bind to for clear communications.
- */
- public static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME
- = "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT";
-
- /**
- * The property indicating the preferred TLS (TCP)
- * port to bind to for secure communications.
- */
- public static final String PREFERRED_SECURE_PORT_PROPERTY_NAME
- = "net.java.sip.communicator.SIP_PREFERRED_SECURE_PORT";
-
- /**
- * The name of the property under which we store the the type of the proxy
- * stored against the PROXY_ADDRESS property. Exact type values depend on
- * protocols and among them are socks4, socks5, http and possibly others.
- */
- public static final String PROXY_TYPE = "PROXY_TYPE";
-
- /**
- * The name of the property under which we store the the username for the
- * proxy stored against the PROXY_ADDRESS property.
- */
- public static final String PROXY_USERNAME = "PROXY_USERNAME";
-
- /**
- * The name of the property under which we store the the authorization name
- * for the proxy stored against the PROXY_ADDRESS property.
- */
- public static final String AUTHORIZATION_NAME = "AUTHORIZATION_NAME";
-
- /**
- * The name of the property under which we store the password for the proxy
- * stored against the PROXY_ADDRESS property.
- */
- public static final String PROXY_PASSWORD = "PROXY_PASSWORD";
-
- /**
- * The name of the property under which we store the name of the transport
- * protocol that needs to be used to access the proxy.
- */
- public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT";
-
- /**
- * The name of the property that indicates whether loose routing should be
- * forced for all traffic in an account, rather than routing through an
- * outbound proxy which is the default for Jitsi.
- */
- public static final String FORCE_PROXY_BYPASS = "FORCE_PROXY_BYPASS";
-
- /**
- * The name of the property that indicates whether the client must
- * be registered with a registrar when making outgoing calls.
- */
- public static final String MUST_REGISTER_TO_CALL = "MUST_REGISTER_TO_CALL";
-
- /**
- * The name of the property under which we store the user preference for a
- * transport protocol to use (i.e. tcp or udp).
- */
- public static final String PREFERRED_TRANSPORT = "PREFERRED_TRANSPORT";
-
- /**
- * The name of the property under which we store whether we generate
- * resource values or we just use the stored one.
- */
- public static final String AUTO_GENERATE_RESOURCE = "AUTO_GENERATE_RESOURCE";
-
- /**
- * The name of the property under which we store resources such as the
- * jabber resource property.
- */
- public static final String RESOURCE = "RESOURCE";
-
- /**
- * The name of the property under which we store resource priority.
- */
- public static final String RESOURCE_PRIORITY = "RESOURCE_PRIORITY";
-
- /**
- * The name of the property which defines that the call is encrypted by
- * default
- */
- public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION";
-
- /**
- * The name of the property that indicates the encryption protocols for this
- * account.
- */
- public static final String ENCRYPTION_PROTOCOL = "ENCRYPTION_PROTOCOL";
-
- /**
- * The name of the property that indicates the status (enabed or disabled)
- * encryption protocols for this account.
- */
- public static final String ENCRYPTION_PROTOCOL_STATUS
- = "ENCRYPTION_PROTOCOL_STATUS";
-
- /**
- * The name of the property which defines if to include the ZRTP attribute
- * to SIP/SDP
- */
- public static final String DEFAULT_SIPZRTP_ATTRIBUTE =
- "DEFAULT_SIPZRTP_ATTRIBUTE";
-
- /**
- * The name of the property which defines the ID of the client TLS
- * certificate configuration entry.
- */
- public static final String CLIENT_TLS_CERTIFICATE =
- "CLIENT_TLS_CERTIFICATE";
-
- /**
- * The name of the property under which we store the boolean value
- * indicating if the user name should be automatically changed if the
- * specified name already exists. This property is meant to be used by IRC
- * implementations.
- */
- public static final String AUTO_CHANGE_USER_NAME = "AUTO_CHANGE_USER_NAME";
-
- /**
- * The name of the property under which we store the boolean value
- * indicating if a password is required. Initially this property is meant to
- * be used by IRC implementations.
- */
- public static final String NO_PASSWORD_REQUIRED = "NO_PASSWORD_REQUIRED";
-
- /**
- * The name of the property under which we store if the presence is enabled.
- */
- public static final String IS_PRESENCE_ENABLED = "IS_PRESENCE_ENABLED";
-
- /**
- * The name of the property under which we store if the p2p mode for SIMPLE
- * should be forced.
- */
- public static final String FORCE_P2P_MODE = "FORCE_P2P_MODE";
-
- /**
- * The name of the property under which we store the offline contact polling
- * period for SIMPLE.
- */
- public static final String POLLING_PERIOD = "POLLING_PERIOD";
-
- /**
- * The name of the property under which we store the chosen default
- * subscription expiration value for SIMPLE.
- */
- public static final String SUBSCRIPTION_EXPIRATION
- = "SUBSCRIPTION_EXPIRATION";
-
- /**
- * Indicates if the server address has been validated.
- */
- public static final String SERVER_ADDRESS_VALIDATED
- = "SERVER_ADDRESS_VALIDATED";
-
- /**
- * Indicates if the server settings are over
- */
- public static final String IS_SERVER_OVERRIDDEN
- = "IS_SERVER_OVERRIDDEN";
- /**
- * Indicates if the proxy address has been validated.
- */
- public static final String PROXY_ADDRESS_VALIDATED
- = "PROXY_ADDRESS_VALIDATED";
-
- /**
- * Indicates the search strategy chosen for the DICT protocole.
- */
- public static final String STRATEGY = "STRATEGY";
-
- /**
- * Indicates a protocol that would not be shown in the user interface as an
- * account.
- */
- public static final String IS_PROTOCOL_HIDDEN = "IS_PROTOCOL_HIDDEN";
-
- /**
- * Indicates if the given account is the preferred account.
- */
- public static final String IS_PREFERRED_PROTOCOL = "IS_PREFERRED_PROTOCOL";
-
- /**
- * The name of the property that would indicate if a given account is
- * currently enabled or disabled.
- */
- public static final String IS_ACCOUNT_DISABLED = "IS_ACCOUNT_DISABLED";
-
- /**
- * The name of the property that would indicate if a given account
- * configuration form is currently hidden.
- */
- public static final String IS_ACCOUNT_CONFIG_HIDDEN = "IS_CONFIG_HIDDEN";
-
- /**
- * The name of the property that would indicate if a given account
- * status menu is currently hidden.
- */
- public static final String IS_ACCOUNT_STATUS_MENU_HIDDEN =
- "IS_STATUS_MENU_HIDDEN";
-
- /**
- * The name of the property that would indicate if a given account
- * configuration is read only.
- */
- public static final String IS_ACCOUNT_READ_ONLY = "IS_READ_ONLY";
-
- /**
- * The name of the property that would indicate if a given account
- * groups are readonly, values can be all or a comma separated
- * group names including root.
- */
- public static final String ACCOUNT_READ_ONLY_GROUPS = "READ_ONLY_GROUPS";
-
- /**
- * Indicates if ICE should be used.
- */
- public static final String IS_USE_ICE = "ICE_ENABLED";
-
- /**
- * Indicates if STUN server should be automatically discovered.
- */
- public static final String AUTO_DISCOVER_STUN = "AUTO_DISCOVER_STUN";
-
- /**
- * Indicates if default STUN server would be used if no other STUN/TURN
- * server are available.
- */
- public static final String USE_DEFAULT_STUN_SERVER
- = "USE_DEFAULT_STUN_SERVER";
-
- /**
- * The name of the boolean account property which indicates whether Jitsi
- * Videobridge is to be used, if available and supported, for conference
- * calls.
- */
- public static final String USE_JITSI_VIDEO_BRIDGE
- = "USE_JITSI_VIDEO_BRIDGE";
-
- /**
- * The property name prefix for all stun server properties. We generally use
- * this prefix in conjunction with an index which is how we store multiple
- * servers.
- */
- public static final String STUN_PREFIX = "STUN";
-
- /**
- * The base property name for address of additional STUN servers specified.
- */
- public static final String STUN_ADDRESS = "ADDRESS";
-
- /**
- * The base property name for port of additional STUN servers specified.
- */
- public static final String STUN_PORT = "PORT";
-
- /**
- * The base property name for username of additional STUN servers specified.
- */
- public static final String STUN_USERNAME = "USERNAME";
-
- /**
- * The base property name for password of additional STUN servers specified.
- */
- public static final String STUN_PASSWORD = "PASSWORD";
-
- /**
- * The base property name for the turn supported property of additional
- * STUN servers specified.
- */
- public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED";
-
- /**
- * Indicates if JingleNodes should be used with ICE.
- */
- public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED";
-
- /**
- * Indicates if JingleNodes should be used with ICE.
- */
- public static final String AUTO_DISCOVER_JINGLE_NODES
- = "AUTO_DISCOVER_JINGLE_NODES";
-
- /**
- * Indicates if JingleNodes should use buddies to search for nodes.
- */
- public static final String JINGLE_NODES_SEARCH_BUDDIES
- = "JINGLE_NODES_SEARCH_BUDDIES";
-
- /**
- * Indicates if UPnP should be used with ICE.
- */
- public static final String IS_USE_UPNP = "UPNP_ENABLED";
-
- /**
- * Indicates if we allow non-TLS connection.
- */
- public static final String IS_ALLOW_NON_SECURE = "ALLOW_NON_SECURE";
-
- /**
- * Enable notifications for new voicemail messages.
- */
- public static final String VOICEMAIL_ENABLED = "VOICEMAIL_ENABLED";
-
- /**
- * Address used to reach voicemail box, by services able to
- * subscribe for voicemail new messages notifications.
- */
- public static final String VOICEMAIL_URI = "VOICEMAIL_URI";
-
- /**
- * Address used to call to hear your messages stored on the server
- * for your voicemail.
- */
- public static final String VOICEMAIL_CHECK_URI = "VOICEMAIL_CHECK_URI";
-
- /**
- * Indicates if calling is disabled for a certain account.
- */
- public static final String IS_CALLING_DISABLED_FOR_ACCOUNT
- = "CALLING_DISABLED";
-
- /**
- * Indicates if desktop streaming/sharing is disabled for a certain account.
- */
- public static final String IS_DESKTOP_STREAMING_DISABLED
- = "DESKTOP_STREAMING_DISABLED";
-
- /**
- * Indicates if desktop remote control is disabled for a certain account.
- */
- public static final String IS_DESKTOP_REMOTE_CONTROL_DISABLED
- = "DESKTOP_REMOTE_CONTROL_DISABLED";
-
- /**
- * The sms default server address.
- */
- public static final String SMS_SERVER_ADDRESS = "SMS_SERVER_ADDRESS";
-
- /**
- * Keep-alive method used by the protocol.
- */
- public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD";
-
- /**
- * The interval for keep-alives if any.
- */
- public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";
-
- /**
- * The name of the property holding DTMF method.
- */
- public static final String DTMF_METHOD = "DTMF_METHOD";
-
- /**
- * The minimal DTMF tone duration.
- */
- public static final String DTMF_MINIMAL_TONE_DURATION
- = "DTMF_MINIMAL_TONE_DURATION";
-
- /**
- * Paranoia mode when turned on requires all calls to be secure and
- * indicated as such.
- */
- public static final String MODE_PARANOIA = "MODE_PARANOIA";
-
- /**
- * The name of the "override encodings" property
- */
- public static final String OVERRIDE_ENCODINGS = "OVERRIDE_ENCODINGS";
-
- /**
- * The prefix used to store account encoding properties
- */
- public static final String ENCODING_PROP_PREFIX = "Encodings";
-
- /**
- * An account property to provide a connected account to check for
- * its status. Used when the current provider need to reject calls
- * but is missing presence operation set and need to check other
- * provider for status.
- */
- public static final String CUSAX_PROVIDER_ACCOUNT_PROP
- = "cusax.XMPP_ACCOUNT_ID";
-
- /**
- * The <code>BundleContext</code> containing (or to contain) the service
- * registration of this factory.
- */
- private final BundleContext bundleContext;
-
- /**
- * The name of the protocol this factory registers its
- * <code>ProtocolProviderService</code>s with and to be placed in the
- * properties of the accounts created by this factory.
- */
- private final String protocolName;
-
- /**
- * The table that we store our accounts in.
- * <p>
- * TODO Synchronize the access to the field which may in turn be better
- * achieved by also hiding it from protected into private access.
- * </p>
- */
- protected final Map<AccountID, ServiceRegistration<ProtocolProviderService>>
- registeredAccounts
- = new HashMap<AccountID, ServiceRegistration<ProtocolProviderService>>();
-
- /**
- * The name of the property that indicates the AVP type.
- * <ul>
- * <li>{@link #SAVP_OFF}</li>
- * <li>{@link #SAVP_MANDATORY}</li>
- * <li>{@link #SAVP_OPTIONAL}</li>
- * </ul>
- */
- public static final String SAVP_OPTION = "SAVP_OPTION";
-
- /**
- * Always use RTP/AVP
- */
- public static final int SAVP_OFF = 0;
-
- /**
- * Always use RTP/SAVP
- */
- public static final int SAVP_MANDATORY = 1;
-
- /**
- * Sends two media description, with RTP/SAVP being first.
- */
- public static final int SAVP_OPTIONAL = 2;
-
- /**
- * The name of the property that defines the enabled SDES cipher suites.
- * Enabled suites are listed as CSV by their RFC name.
- */
- public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES";
-
- /**
- * The name of the property that defines the enabled/disabled state of
- * message carbons.
- */
- public static final String IS_CARBON_DISABLED = "CARBON_DISABLED";
-
- /**
- * Creates a new <tt>ProtocolProviderFactory</tt>.
- *
- * @param bundleContext the bundle context reference of the service
- * @param protocolName the name of the protocol
- */
- protected ProtocolProviderFactory(BundleContext bundleContext,
- String protocolName)
- {
- this.bundleContext = bundleContext;
- this.protocolName = protocolName;
- }
-
- /**
- * Gets the <code>BundleContext</code> containing (or to contain) the
- * service registration of this factory.
- *
- * @return the <code>BundleContext</code> containing (or to contain) the
- * service registration of this factory
- */
- public BundleContext getBundleContext()
- {
- return bundleContext;
- }
-
- /**
- * Initializes and creates an account corresponding to the specified
- * accountProperties and registers the resulting ProtocolProvider in the
- * <tt>context</tt> BundleContext parameter. Note that account
- * registration is persistent and accounts that are registered during
- * a particular sip-communicator session would be automatically reloaded
- * during all following sessions until they are removed through the
- * removeAccount method.
- *
- * @param userID the user identifier uniquely representing the newly
- * created account within the protocol namespace.
- * @param accountProperties a set of protocol (or implementation) specific
- * properties defining the new account.
- * @return the AccountID of the newly created account.
- * @throws java.lang.IllegalArgumentException if userID does not correspond
- * to an identifier in the context of the underlying protocol or if
- * accountProperties does not contain a complete set of account installation
- * properties.
- * @throws java.lang.IllegalStateException if the account has already been
- * installed.
- * @throws java.lang.NullPointerException if any of the arguments is null.
- */
- public abstract AccountID installAccount(String userID,
- Map<String, String> accountProperties)
- throws IllegalArgumentException,
- IllegalStateException,
- NullPointerException;
-
-
- /**
- * Modifies the account corresponding to the specified accountID. This
- * method is meant to be used to change properties of already existing
- * accounts. Note that if the given accountID doesn't correspond to any
- * registered account this method would do nothing.
- *
- * @param protocolProvider the protocol provider service corresponding to
- * the modified account.
- * @param accountProperties a set of protocol (or implementation) specific
- * properties defining the new account.
- *
- * @throws java.lang.NullPointerException if any of the arguments is null.
- */
- public abstract void modifyAccount(
- ProtocolProviderService protocolProvider,
- Map<String, String> accountProperties)
- throws NullPointerException;
-
- /**
- * Returns a copy of the list containing the <tt>AccountID</tt>s of all
- * accounts currently registered in this protocol provider.
- * @return a copy of the list containing the <tt>AccountID</tt>s of all
- * accounts currently registered in this protocol provider.
- */
- public ArrayList<AccountID> getRegisteredAccounts()
- {
- synchronized (registeredAccounts)
- {
- return new ArrayList<AccountID>(registeredAccounts.keySet());
- }
- }
-
- /**
- * Returns the ServiceReference for the protocol provider corresponding to
- * the specified accountID or null if the accountID is unknown.
- * @param accountID the accountID of the protocol provider we'd like to get
- * @return a ServiceReference object to the protocol provider with the
- * specified account id and null if the account id is unknown to the
- * provider factory.
- */
- public ServiceReference<ProtocolProviderService> getProviderForAccount(
- AccountID accountID)
- {
- ServiceRegistration<ProtocolProviderService> registration;
-
- synchronized (registeredAccounts)
- {
- registration = registeredAccounts.get(accountID);
- }
-
- try
- {
- if (registration != null)
- return registration.getReference();
- }
- catch (IllegalStateException ise)
- {
- synchronized (registeredAccounts)
- {
- registeredAccounts.remove(accountID);
- }
- }
-
- return null;
- }
-
- /**
- * Removes the specified account from the list of accounts that this
- * provider factory is handling. If the specified accountID is unknown to
- * the ProtocolProviderFactory, the call has no effect and false is
- * returned. This method is persistent in nature and once called the account
- * corresponding to the specified ID will not be loaded during future runs
- * of the project.
- *
- * @param accountID the ID of the account to remove.
- * @return true if an account with the specified ID existed and was removed
- * and false otherwise.
- */
- public boolean uninstallAccount(AccountID accountID)
- {
- // Unregister the protocol provider.
- ServiceReference<ProtocolProviderService> serRef
- = getProviderForAccount(accountID);
-
- boolean wasAccountExisting = false;
-
- // If the protocol provider service is registered, first unregister the
- // service.
- if (serRef != null)
- {
- BundleContext bundleContext = getBundleContext();
- ProtocolProviderService protocolProvider
- = bundleContext.getService(serRef);
-
- try
- {
- protocolProvider.unregister();
- }
- catch (OperationFailedException ex)
- {
- logger.error(
- "Failed to unregister protocol provider for account: "
- + accountID + " caused by: " + ex);
- }
- }
-
- ServiceRegistration<ProtocolProviderService> registration;
-
- synchronized (registeredAccounts)
- {
- registration = registeredAccounts.remove(accountID);
- }
-
- // first remove the stored account so when PP is unregistered we can
- // distinguish between deleted or just disabled account
- wasAccountExisting = removeStoredAccount(accountID);
-
- if (registration != null)
- {
- // Kill the service.
- registration.unregister();
- }
-
- return wasAccountExisting;
- }
-
- /**
- * The method stores the specified account in the configuration service
- * under the package name of the source factory. The restore and remove
- * account methods are to be used to obtain access to and control the stored
- * accounts.
- * <p>
- * In order to store all account properties, the method would create an
- * entry in the configuration service corresponding (beginning with) the
- * <tt>sourceFactory</tt>'s package name and add to it a unique identifier
- * (e.g. the current miliseconds.)
- * </p>
- *
- * @param accountID the AccountID corresponding to the account that we would
- * like to store.
- */
- protected void storeAccount(AccountID accountID)
- {
- this.storeAccount(accountID, true);
- }
-
- /**
- * The method stores the specified account in the configuration service
- * under the package name of the source factory. The restore and remove
- * account methods are to be used to obtain access to and control the stored
- * accounts.
- * <p>
- * In order to store all account properties, the method would create an
- * entry in the configuration service corresponding (beginning with) the
- * <tt>sourceFactory</tt>'s package name and add to it a unique identifier
- * (e.g. the current miliseconds.)
- * </p>
- *
- * @param accountID the AccountID corresponding to the account that we would
- * like to store.
- * @param isModification if <tt>false</tt> there must be no such already
- * loaded account, it <tt>true</tt> ist modification of an existing account.
- * Usually we use this method with <tt>false</tt> in method installAccount
- * and with <tt>true</tt> or the overridden method in method
- * modifyAccount.
- */
- protected void storeAccount(AccountID accountID, boolean isModification)
- {
- if(!isModification
- && getAccountManager().getStoredAccounts().contains(accountID))
- {
- throw new IllegalStateException(
- "An account for id " + accountID.getUserID()
- + " was already loaded!");
- }
-
- try
- {
- getAccountManager().storeAccount(this, accountID);
- }
- catch (OperationFailedException ofex)
- {
- throw new UndeclaredThrowableException(ofex);
- }
- }
-
- /**
- * Saves the password for the specified account after scrambling it a bit so
- * that it is not visible from first sight. (The method remains highly
- * insecure).
- *
- * @param accountID the AccountID for the account whose password we're
- * storing
- * @param password the password itself
- *
- * @throws IllegalArgumentException if no account corresponding to
- * <code>accountID</code> has been previously stored
- */
- public void storePassword(AccountID accountID, String password)
- throws IllegalArgumentException
- {
- try
- {
- storePassword(getBundleContext(), accountID, password);
- }
- catch (OperationFailedException ofex)
- {
- throw new UndeclaredThrowableException(ofex);
- }
- }
-
- /**
- * Saves the password for the specified account after scrambling it a bit
- * so that it is not visible from first sight (Method remains highly
- * insecure).
- * <p>
- * TODO Delegate the implementation to {@link AccountManager} because it
- * knows the format in which the password (among the other account
- * properties) is to be saved.
- * </p>
- *
- * @param bundleContext a currently valid bundle context.
- * @param accountID the <tt>AccountID</tt> of the account whose password is
- * to be stored
- * @param password the password to be stored
- *
- * @throws IllegalArgumentException if no account corresponding to
- * <tt>accountID</tt> has been previously stored.
- * @throws OperationFailedException if anything goes wrong while storing the
- * specified <tt>password</tt>
- */
- protected void storePassword(BundleContext bundleContext,
- AccountID accountID,
- String password)
- throws IllegalArgumentException,
- OperationFailedException
- {
- String accountPrefix
- = findAccountPrefix(
- bundleContext,
- accountID,
- getFactoryImplPackageName());
-
- if (accountPrefix == null)
- {
- throw
- new IllegalArgumentException(
- "No previous records found for account ID: "
- + accountID.getAccountUniqueID()
- + " in package"
- + getFactoryImplPackageName());
- }
-
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
-
- if (!credentialsStorage.storePassword(accountPrefix, password))
- {
- throw
- new OperationFailedException(
- "CredentialsStorageService failed to storePassword",
- OperationFailedException.GENERAL_ERROR);
- }
-
- // Update password property also in the AccountID
- // to prevent it from being removed during account reload
- // in some cases.
- accountID.setPassword(password);
-
- }
-
- /**
- * Returns the password last saved for the specified account.
- *
- * @param accountID the AccountID for the account whose password we're
- * looking for
- *
- * @return a String containing the password for the specified accountID
- */
- public String loadPassword(AccountID accountID)
- {
- return loadPassword(getBundleContext(), accountID);
- }
-
- /**
- * Returns the password last saved for the specified account.
- * <p>
- * TODO Delegate the implementation to {@link AccountManager} because it
- * knows the format in which the password (among the other account
- * properties) was saved.
- * </p>
- *
- * @param bundleContext a currently valid bundle context.
- * @param accountID the AccountID for the account whose password we're
- * looking for..
- *
- * @return a String containing the password for the specified accountID.
- */
- protected String loadPassword(BundleContext bundleContext,
- AccountID accountID)
- {
- String accountPrefix = findAccountPrefix(
- bundleContext, accountID, getFactoryImplPackageName());
-
- if (accountPrefix == null)
- return null;
-
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
-
- return credentialsStorage.loadPassword(accountPrefix);
- }
-
- /**
- * Initializes and creates an account corresponding to the specified
- * accountProperties and registers the resulting ProtocolProvider in the
- * <tt>context</tt> BundleContext parameter. This method has a persistent
- * effect. Once created the resulting account will remain installed until
- * removed through the uninstallAccount method.
- *
- * @param accountProperties a set of protocol (or implementation) specific
- * properties defining the new account.
- * @return the AccountID of the newly loaded account
- */
- public AccountID loadAccount(Map<String, String> accountProperties)
- {
- AccountID accountID = createAccount(accountProperties);
-
- loadAccount(accountID);
-
- return accountID;
- }
-
- /**
- * Creates a protocol provider for the given <tt>accountID</tt> and
- * registers it in the bundle context. This method has a persistent
- * effect. Once created the resulting account will remain installed until
- * removed through the uninstallAccount method.
- *
- * @param accountID the account identifier
- * @return <tt>true</tt> if the account with the given <tt>accountID</tt> is
- * successfully loaded, otherwise returns <tt>false</tt>
- */
- public boolean loadAccount(AccountID accountID)
- {
- // Need to obtain the original user id property, instead of calling
- // accountID.getUserID(), because this method could return a modified
- // version of the user id property.
- String userID
- = accountID.getAccountPropertyString(
- ProtocolProviderFactory.USER_ID);
-
- ProtocolProviderService service = createService(userID, accountID);
-
- Dictionary<String, String> properties = new Hashtable<String, String>();
- properties.put(PROTOCOL, protocolName);
- properties.put(USER_ID, userID);
-
- ServiceRegistration<ProtocolProviderService> serviceRegistration
- = bundleContext.registerService(
- ProtocolProviderService.class,
- service,
- properties);
-
- if (serviceRegistration == null)
- {
- return false;
- }
- else
- {
- synchronized (registeredAccounts)
- {
- registeredAccounts.put(accountID, serviceRegistration);
- }
- return true;
- }
- }
-
- /**
- * Unloads the account corresponding to the given <tt>accountID</tt>.
- * Unregisters the corresponding protocol provider, but keeps the account in
- * contrast to the uninstallAccount method.
- *
- * @param accountID the account identifier
- * @return true if an account with the specified ID existed and was unloaded
- * and false otherwise.
- */
- public boolean unloadAccount(AccountID accountID)
- {
- // Unregister the protocol provider.
- ServiceReference<ProtocolProviderService> serRef
- = getProviderForAccount(accountID);
-
- if (serRef == null)
- {
- return false;
- }
-
- BundleContext bundleContext = getBundleContext();
- ProtocolProviderService protocolProvider
- = bundleContext.getService(serRef);
-
- try
- {
- protocolProvider.unregister();
- }
- catch (OperationFailedException ex)
- {
- logger.error(
- "Failed to unregister protocol provider for account: "
- + accountID + " caused by: " + ex);
- }
-
- ServiceRegistration<ProtocolProviderService> registration;
-
- synchronized (registeredAccounts)
- {
- registration = registeredAccounts.remove(accountID);
- }
- if (registration == null)
- {
- return false;
- }
-
- // Kill the service.
- registration.unregister();
-
- return true;
- }
-
- /**
- * Initializes and creates an account corresponding to the specified
- * accountProperties.
- *
- * @param accountProperties a set of protocol (or implementation) specific
- * properties defining the new account.
- * @return the AccountID of the newly created account
- */
- public AccountID createAccount(Map<String, String> accountProperties)
- {
- BundleContext bundleContext = getBundleContext();
- if (bundleContext == null)
- throw new NullPointerException(
- "The specified BundleContext was null");
-
- if (accountProperties == null)
- throw new NullPointerException(
- "The specified property map was null");
-
- String userID = accountProperties.get(USER_ID);
- if (userID == null)
- throw new NullPointerException(
- "The account properties contained no user id.");
-
- String protocolName = getProtocolName();
- if (!accountProperties.containsKey(PROTOCOL))
- accountProperties.put(PROTOCOL, protocolName);
-
- return createAccountID(userID, accountProperties);
- }
-
- /**
- * Creates a new <code>AccountID</code> instance with a specific user ID to
- * represent a given set of account properties.
- * <p>
- * The method is a pure factory allowing implementers to specify the runtime
- * type of the created <code>AccountID</code> and customize the instance.
- * The returned <code>AccountID</code> will later be associated with a
- * <code>ProtocolProviderService</code> by the caller (e.g. using
- * {@link #createService(String, AccountID)}).
- * </p>
- *
- * @param userID the user ID of the new instance
- * @param accountProperties the set of properties to be represented by the
- * new instance
- * @return a new <code>AccountID</code> instance with the specified user ID
- * representing the given set of account properties
- */
- protected abstract AccountID createAccountID(
- String userID, Map<String, String> accountProperties);
-
- /**
- * Gets the name of the protocol this factory registers its
- * <code>ProtocolProviderService</code>s with and to be placed in the
- * properties of the accounts created by this factory.
- *
- * @return the name of the protocol this factory registers its
- * <code>ProtocolProviderService</code>s with and to be placed in
- * the properties of the accounts created by this factory
- */
- public String getProtocolName()
- {
- return protocolName;
- }
-
- /**
- * Initializes a new <code>ProtocolProviderService</code> instance with a
- * specific user ID to represent a specific <code>AccountID</code>.
- * <p>
- * The method is a pure factory allowing implementers to specify the runtime
- * type of the created <code>ProtocolProviderService</code> and customize
- * the instance. The caller will later register the returned service with
- * the <code>BundleContext</code> of this factory.
- * </p>
- *
- * @param userID the user ID to initialize the new instance with
- * @param accountID the <code>AccountID</code> to be represented by the new
- * instance
- * @return a new <code>ProtocolProviderService</code> instance with the
- * specific user ID representing the specified
- * <code>AccountID</code>
- */
- protected abstract ProtocolProviderService createService(String userID,
- AccountID accountID);
-
- /**
- * Removes the account with <tt>accountID</tt> from the set of accounts
- * that are persistently stored inside the configuration service.
- *
- * @param accountID the AccountID of the account to remove.
- *
- * @return true if an account has been removed and false otherwise.
- */
- protected boolean removeStoredAccount(AccountID accountID)
- {
- return getAccountManager().removeStoredAccount(this, accountID);
- }
-
- /**
- * Returns the prefix for all persistently stored properties of the account
- * with the specified id.
- * @param bundleContext a currently valid bundle context.
- * @param accountID the AccountID of the account whose properties we're
- * looking for.
- * @param sourcePackageName a String containing the package name of the
- * concrete factory class that extends us.
- * @return a String indicating the ConfigurationService property name
- * prefix under which all account properties are stored or null if no
- * account corresponding to the specified id was found.
- */
- public static String findAccountPrefix(BundleContext bundleContext,
- AccountID accountID,
- String sourcePackageName)
- {
- ServiceReference<ConfigurationService> confReference
- = bundleContext.getServiceReference(ConfigurationService.class);
- ConfigurationService configurationService
- = bundleContext.getService(confReference);
-
- //first retrieve all accounts that we've registered
- List<String> storedAccounts =
- configurationService.getPropertyNamesByPrefix(sourcePackageName,
- true);
-
- //find an account with the corresponding id.
- for (String accountRootPropertyName : storedAccounts)
- {
- //unregister the account in the configuration service.
- //all the properties must have been registered in the following
- //hierarchy:
- //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
- String accountUID = configurationService.getString(
- accountRootPropertyName //node id
- + "." + ACCOUNT_UID); // propname
-
- if (accountID.getAccountUniqueID().equals(accountUID))
- {
- return accountRootPropertyName;
- }
- }
- return null;
- }
-
- /**
- * Returns the name of the package that we're currently running in (i.e.
- * the name of the package containing the proto factory that extends us).
- *
- * @return a String containing the package name of the concrete factory
- * class that extends us.
- */
- private String getFactoryImplPackageName()
- {
- String className = getClass().getName();
-
- return className.substring(0, className.lastIndexOf('.'));
- }
-
- /**
- * Prepares the factory for bundle shutdown.
- */
- public void stop()
- {
- if (logger.isTraceEnabled())
- logger.trace("Preparing to stop all protocol providers of" + this);
-
- synchronized (registeredAccounts)
- {
- for (ServiceRegistration<ProtocolProviderService> reg
- : registeredAccounts.values())
- {
- stop(reg);
- reg.unregister();
- }
-
- registeredAccounts.clear();
- }
- }
-
- /**
- * Shuts down the <code>ProtocolProviderService</code> representing an
- * account registered with this factory.
- *
- * @param registeredAccount the <code>ServiceRegistration</code> of the
- * <code>ProtocolProviderService</code> representing an account
- * registered with this factory
- */
- protected void stop(
- ServiceRegistration<ProtocolProviderService> registeredAccount)
- {
- ProtocolProviderService protocolProviderService
- = getBundleContext().getService(registeredAccount.getReference());
-
- protocolProviderService.shutdown();
- }
-
- /**
- * Get the <tt>AccountManager</tt> of the protocol.
- *
- * @return <tt>AccountManager</tt> of the protocol
- */
- private AccountManager getAccountManager()
- {
- BundleContext bundleContext = getBundleContext();
- ServiceReference<AccountManager> serviceReference
- = bundleContext.getServiceReference(AccountManager.class);
-
- return bundleContext.getService(serviceReference);
- }
-
-
- /**
- * Finds registered <tt>ProtocolProviderFactory</tt> for given
- * <tt>protocolName</tt>.
- * @param bundleContext the OSGI bundle context that will be used.
- * @param protocolName the protocol name.
- * @return Registered <tt>ProtocolProviderFactory</tt> for given protocol
- * name or <tt>null</tt> if no provider was found.
- */
- static public ProtocolProviderFactory getProtocolProviderFactory(
- BundleContext bundleContext,
- String protocolName)
- {
- Collection<ServiceReference<ProtocolProviderFactory>> serRefs;
- String osgiFilter
- = "(" + ProtocolProviderFactory.PROTOCOL + "=" + protocolName + ")";
-
- try
- {
- serRefs
- = bundleContext.getServiceReferences(
- ProtocolProviderFactory.class,
- osgiFilter);
- }
- catch (InvalidSyntaxException ex)
- {
- serRefs = null;
- logger.error(ex);
- }
- if ((serRefs == null) || serRefs.isEmpty())
- return null;
- else
- return bundleContext.getService(serRefs.iterator().next());
- }
-}
+package net.java.sip.communicator.service.protocol;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import net.java.sip.communicator.service.credentialsstorage.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.configuration.*;
+import org.osgi.framework.*;
+
+/**
+ * The ProtocolProviderFactory is what actually creates instances of a
+ * ProtocolProviderService implementation. A provider factory would register,
+ * persistently store, and remove when necessary, ProtocolProviders. The way
+ * things are in the SIP Communicator, a user account is represented (in a 1:1
+ * relationship) by an AccountID and a ProtocolProvider. In other words - one
+ * would have as many protocol providers installed in a given moment as they
+ * would user account registered through the various services.
+ *
+ * @author Emil Ivov
+ * @author Lubomir Marinov
+ */
+public abstract class ProtocolProviderFactory
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class
+ * and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(ProtocolProviderFactory.class);
+
+ /**
+ * Then name of a property which represents a password.
+ */
+ public static final String PASSWORD = "PASSWORD";
+
+ /**
+ * The name of a property representing the name of the protocol for an
+ * ProtocolProviderFactory.
+ */
+ public static final String PROTOCOL = "PROTOCOL_NAME";
+
+ /**
+ * The name of a property representing the path to protocol icons.
+ */
+ public static final String PROTOCOL_ICON_PATH = "PROTOCOL_ICON_PATH";
+
+ /**
+ * The name of a property representing the path to the account icon to
+ * be used in the user interface, when the protocol provider service is not
+ * available.
+ */
+ public static final String ACCOUNT_ICON_PATH = "ACCOUNT_ICON_PATH";
+
+ /**
+ * The name of a property which represents the AccountID of a
+ * ProtocolProvider and that, together with a password is used to login
+ * on the protocol network..
+ */
+ public static final String USER_ID = "USER_ID";
+
+ /**
+ * The name that should be displayed to others when we are calling or
+ * writing them.
+ */
+ public static final String DISPLAY_NAME = "DISPLAY_NAME";
+
+ /**
+ * The name that should be displayed to the user on call via and chat via
+ * lists.
+ */
+ public static final String ACCOUNT_DISPLAY_NAME = "ACCOUNT_DISPLAY_NAME";
+
+ /**
+ * The name of the property under which we store protocol AccountID-s.
+ */
+ public static final String ACCOUNT_UID = "ACCOUNT_UID";
+
+ /**
+ * The name of the property under which we store protocol the address of
+ * a protocol centric entity (any protocol server).
+ */
+ public static final String SERVER_ADDRESS = "SERVER_ADDRESS";
+
+ /**
+ * The name of the property under which we store the number of the port
+ * where the server stored against the SERVER_ADDRESS property is expecting
+ * connections to be made via this protocol.
+ */
+ public static final String SERVER_PORT = "SERVER_PORT";
+
+ /**
+ * The name of the property under which we store the name of the transport
+ * protocol that needs to be used to access the server.
+ */
+ public static final String SERVER_TRANSPORT = "SERVER_TRANSPORT";
+
+ /**
+ * The name of the property under which we store protocol the address of
+ * a protocol proxy.
+ */
+ public static final String PROXY_ADDRESS = "PROXY_ADDRESS";
+
+ /**
+ * The name of the property under which we store the number of the port
+ * where the proxy stored against the PROXY_ADDRESS property is expecting
+ * connections to be made via this protocol.
+ */
+ public static final String PROXY_PORT = "PROXY_PORT";
+
+ /**
+ * The name of the property which defines whether proxy is auto configured
+ * by the protocol by using known methods such as specific DNS queries.
+ */
+ public static final String PROXY_AUTO_CONFIG = "PROXY_AUTO_CONFIG";
+
+ /**
+ * The property indicating the preferred UDP and TCP
+ * port to bind to for clear communications.
+ */
+ public static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME
+ = "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT";
+
+ /**
+ * The property indicating the preferred TLS (TCP)
+ * port to bind to for secure communications.
+ */
+ public static final String PREFERRED_SECURE_PORT_PROPERTY_NAME
+ = "net.java.sip.communicator.SIP_PREFERRED_SECURE_PORT";
+
+ /**
+ * The name of the property under which we store the the type of the proxy
+ * stored against the PROXY_ADDRESS property. Exact type values depend on
+ * protocols and among them are socks4, socks5, http and possibly others.
+ */
+ public static final String PROXY_TYPE = "PROXY_TYPE";
+
+ /**
+ * The name of the property under which we store the the username for the
+ * proxy stored against the PROXY_ADDRESS property.
+ */
+ public static final String PROXY_USERNAME = "PROXY_USERNAME";
+
+ /**
+ * The name of the property under which we store the the authorization name
+ * for the proxy stored against the PROXY_ADDRESS property.
+ */
+ public static final String AUTHORIZATION_NAME = "AUTHORIZATION_NAME";
+
+ /**
+ * The name of the property under which we store the password for the proxy
+ * stored against the PROXY_ADDRESS property.
+ */
+ public static final String PROXY_PASSWORD = "PROXY_PASSWORD";
+
+ /**
+ * The name of the property under which we store the name of the transport
+ * protocol that needs to be used to access the proxy.
+ */
+ public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT";
+
+ /**
+ * The name of the property that indicates whether loose routing should be
+ * forced for all traffic in an account, rather than routing through an
+ * outbound proxy which is the default for Jitsi.
+ */
+ public static final String FORCE_PROXY_BYPASS = "FORCE_PROXY_BYPASS";
+
+ /**
+ * The name of the property that indicates whether the client must
+ * be registered with a registrar when making outgoing calls.
+ */
+ public static final String MUST_REGISTER_TO_CALL = "MUST_REGISTER_TO_CALL";
+
+ /**
+ * The name of the property under which we store the user preference for a
+ * transport protocol to use (i.e. tcp or udp).
+ */
+ public static final String PREFERRED_TRANSPORT = "PREFERRED_TRANSPORT";
+
+ /**
+ * The name of the property under which we store whether we generate
+ * resource values or we just use the stored one.
+ */
+ public static final String AUTO_GENERATE_RESOURCE = "AUTO_GENERATE_RESOURCE";
+
+ /**
+ * The name of the property under which we store resources such as the
+ * jabber resource property.
+ */
+ public static final String RESOURCE = "RESOURCE";
+
+ /**
+ * The name of the property under which we store resource priority.
+ */
+ public static final String RESOURCE_PRIORITY = "RESOURCE_PRIORITY";
+
+ /**
+ * The name of the property which defines that the call is encrypted by
+ * default
+ */
+ public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION";
+
+ /**
+ * The name of the property that indicates the encryption protocols for this
+ * account.
+ */
+ public static final String ENCRYPTION_PROTOCOL = "ENCRYPTION_PROTOCOL";
+
+ /**
+ * The name of the property that indicates the status (enabed or disabled)
+ * encryption protocols for this account.
+ */
+ public static final String ENCRYPTION_PROTOCOL_STATUS
+ = "ENCRYPTION_PROTOCOL_STATUS";
+
+ /**
+ * The name of the property which defines if to include the ZRTP attribute
+ * to SIP/SDP
+ */
+ public static final String DEFAULT_SIPZRTP_ATTRIBUTE =
+ "DEFAULT_SIPZRTP_ATTRIBUTE";
+
+ /**
+ * The name of the property which defines the ID of the client TLS
+ * certificate configuration entry.
+ */
+ public static final String CLIENT_TLS_CERTIFICATE =
+ "CLIENT_TLS_CERTIFICATE";
+
+ /**
+ * The name of the property under which we store the boolean value
+ * indicating if the user name should be automatically changed if the
+ * specified name already exists. This property is meant to be used by IRC
+ * implementations.
+ */
+ public static final String AUTO_CHANGE_USER_NAME = "AUTO_CHANGE_USER_NAME";
+
+ /**
+ * The name of the property under which we store the boolean value
+ * indicating if a password is required. Initially this property is meant to
+ * be used by IRC implementations.
+ */
+ public static final String NO_PASSWORD_REQUIRED = "NO_PASSWORD_REQUIRED";
+
+ /**
+ * The name of the property under which we store if the presence is enabled.
+ */
+ public static final String IS_PRESENCE_ENABLED = "IS_PRESENCE_ENABLED";
+
+ /**
+ * The name of the property under which we store if the p2p mode for SIMPLE
+ * should be forced.
+ */
+ public static final String FORCE_P2P_MODE = "FORCE_P2P_MODE";
+
+ /**
+ * The name of the property under which we store the offline contact polling
+ * period for SIMPLE.
+ */
+ public static final String POLLING_PERIOD = "POLLING_PERIOD";
+
+ /**
+ * The name of the property under which we store the chosen default
+ * subscription expiration value for SIMPLE.
+ */
+ public static final String SUBSCRIPTION_EXPIRATION
+ = "SUBSCRIPTION_EXPIRATION";
+
+ /**
+ * Indicates if the server address has been validated.
+ */
+ public static final String SERVER_ADDRESS_VALIDATED
+ = "SERVER_ADDRESS_VALIDATED";
+
+ /**
+ * Indicates if the server settings are over
+ */
+ public static final String IS_SERVER_OVERRIDDEN
+ = "IS_SERVER_OVERRIDDEN";
+ /**
+ * Indicates if the proxy address has been validated.
+ */
+ public static final String PROXY_ADDRESS_VALIDATED
+ = "PROXY_ADDRESS_VALIDATED";
+
+ /**
+ * Indicates the search strategy chosen for the DICT protocole.
+ */
+ public static final String STRATEGY = "STRATEGY";
+
+ /**
+ * Indicates a protocol that would not be shown in the user interface as an
+ * account.
+ */
+ public static final String IS_PROTOCOL_HIDDEN = "IS_PROTOCOL_HIDDEN";
+
+ /**
+ * Indicates if the given account is the preferred account.
+ */
+ public static final String IS_PREFERRED_PROTOCOL = "IS_PREFERRED_PROTOCOL";
+
+ /**
+ * The name of the property that would indicate if a given account is
+ * currently enabled or disabled.
+ */
+ public static final String IS_ACCOUNT_DISABLED = "IS_ACCOUNT_DISABLED";
+
+ /**
+ * The name of the property that would indicate if a given account
+ * configuration form is currently hidden.
+ */
+ public static final String IS_ACCOUNT_CONFIG_HIDDEN = "IS_CONFIG_HIDDEN";
+
+ /**
+ * The name of the property that would indicate if a given account
+ * status menu is currently hidden.
+ */
+ public static final String IS_ACCOUNT_STATUS_MENU_HIDDEN =
+ "IS_STATUS_MENU_HIDDEN";
+
+ /**
+ * The name of the property that would indicate if a given account
+ * configuration is read only.
+ */
+ public static final String IS_ACCOUNT_READ_ONLY = "IS_READ_ONLY";
+
+ /**
+ * The name of the property that would indicate if a given account
+ * groups are readonly, values can be all or a comma separated
+ * group names including root.
+ */
+ public static final String ACCOUNT_READ_ONLY_GROUPS = "READ_ONLY_GROUPS";
+
+ /**
+ * Indicates if ICE should be used.
+ */
+ public static final String IS_USE_ICE = "ICE_ENABLED";
+
+ /**
+ * Indicates if STUN server should be automatically discovered.
+ */
+ public static final String AUTO_DISCOVER_STUN = "AUTO_DISCOVER_STUN";
+
+ /**
+ * Indicates if default STUN server would be used if no other STUN/TURN
+ * server are available.
+ */
+ public static final String USE_DEFAULT_STUN_SERVER
+ = "USE_DEFAULT_STUN_SERVER";
+
+ /**
+ * The name of the boolean account property which indicates whether Jitsi
+ * Videobridge is to be used, if available and supported, for conference
+ * calls.
+ */
+ public static final String USE_JITSI_VIDEO_BRIDGE
+ = "USE_JITSI_VIDEO_BRIDGE";
+
+ /**
+ * The name of the boolean account property which indicates whether Jitsi
+ * will use translator for media, instead of mixing, for conference
+ * calls.
+ * By default if supported mixing is used (audio mixed, video relayed).
+ */
+ public static final String USE_TRANSLATOR_IN_CONFERENCE
+ = "USE_TRANSLATOR_IN_CONFERENCE";
+
+ /**
+ * The property name prefix for all stun server properties. We generally use
+ * this prefix in conjunction with an index which is how we store multiple
+ * servers.
+ */
+ public static final String STUN_PREFIX = "STUN";
+
+ /**
+ * The base property name for address of additional STUN servers specified.
+ */
+ public static final String STUN_ADDRESS = "ADDRESS";
+
+ /**
+ * The base property name for port of additional STUN servers specified.
+ */
+ public static final String STUN_PORT = "PORT";
+
+ /**
+ * The base property name for username of additional STUN servers specified.
+ */
+ public static final String STUN_USERNAME = "USERNAME";
+
+ /**
+ * The base property name for password of additional STUN servers specified.
+ */
+ public static final String STUN_PASSWORD = "PASSWORD";
+
+ /**
+ * The base property name for the turn supported property of additional
+ * STUN servers specified.
+ */
+ public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED";
+
+ /**
+ * Indicates if JingleNodes should be used with ICE.
+ */
+ public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED";
+
+ /**
+ * Indicates if JingleNodes should be used with ICE.
+ */
+ public static final String AUTO_DISCOVER_JINGLE_NODES
+ = "AUTO_DISCOVER_JINGLE_NODES";
+
+ /**
+ * Indicates if JingleNodes should use buddies to search for nodes.
+ */
+ public static final String JINGLE_NODES_SEARCH_BUDDIES
+ = "JINGLE_NODES_SEARCH_BUDDIES";
+
+ /**
+ * Indicates if UPnP should be used with ICE.
+ */
+ public static final String IS_USE_UPNP = "UPNP_ENABLED";
+
+ /**
+ * Indicates if we allow non-TLS connection.
+ */
+ public static final String IS_ALLOW_NON_SECURE = "ALLOW_NON_SECURE";
+
+ /**
+ * Enable notifications for new voicemail messages.
+ */
+ public static final String VOICEMAIL_ENABLED = "VOICEMAIL_ENABLED";
+
+ /**
+ * Address used to reach voicemail box, by services able to
+ * subscribe for voicemail new messages notifications.
+ */
+ public static final String VOICEMAIL_URI = "VOICEMAIL_URI";
+
+ /**
+ * Address used to call to hear your messages stored on the server
+ * for your voicemail.
+ */
+ public static final String VOICEMAIL_CHECK_URI = "VOICEMAIL_CHECK_URI";
+
+ /**
+ * Indicates if calling is disabled for a certain account.
+ */
+ public static final String IS_CALLING_DISABLED_FOR_ACCOUNT
+ = "CALLING_DISABLED";
+
+ /**
+ * Indicates if desktop streaming/sharing is disabled for a certain account.
+ */
+ public static final String IS_DESKTOP_STREAMING_DISABLED
+ = "DESKTOP_STREAMING_DISABLED";
+
+ /**
+ * Indicates if desktop remote control is disabled for a certain account.
+ */
+ public static final String IS_DESKTOP_REMOTE_CONTROL_DISABLED
+ = "DESKTOP_REMOTE_CONTROL_DISABLED";
+
+ /**
+ * The sms default server address.
+ */
+ public static final String SMS_SERVER_ADDRESS = "SMS_SERVER_ADDRESS";
+
+ /**
+ * Keep-alive method used by the protocol.
+ */
+ public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD";
+
+ /**
+ * The interval for keep-alives if any.
+ */
+ public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";
+
+ /**
+ * The name of the property holding DTMF method.
+ */
+ public static final String DTMF_METHOD = "DTMF_METHOD";
+
+ /**
+ * The minimal DTMF tone duration.
+ */
+ public static final String DTMF_MINIMAL_TONE_DURATION
+ = "DTMF_MINIMAL_TONE_DURATION";
+
+ /**
+ * Paranoia mode when turned on requires all calls to be secure and
+ * indicated as such.
+ */
+ public static final String MODE_PARANOIA = "MODE_PARANOIA";
+
+ /**
+ * The name of the "override encodings" property
+ */
+ public static final String OVERRIDE_ENCODINGS = "OVERRIDE_ENCODINGS";
+
+ /**
+ * The prefix used to store account encoding properties
+ */
+ public static final String ENCODING_PROP_PREFIX = "Encodings";
+
+ /**
+ * An account property to provide a connected account to check for
+ * its status. Used when the current provider need to reject calls
+ * but is missing presence operation set and need to check other
+ * provider for status.
+ */
+ public static final String CUSAX_PROVIDER_ACCOUNT_PROP
+ = "cusax.XMPP_ACCOUNT_ID";
+
+ /**
+ * The <code>BundleContext</code> containing (or to contain) the service
+ * registration of this factory.
+ */
+ private final BundleContext bundleContext;
+
+ /**
+ * The name of the protocol this factory registers its
+ * <code>ProtocolProviderService</code>s with and to be placed in the
+ * properties of the accounts created by this factory.
+ */
+ private final String protocolName;
+
+ /**
+ * The table that we store our accounts in.
+ * <p>
+ * TODO Synchronize the access to the field which may in turn be better
+ * achieved by also hiding it from protected into private access.
+ * </p>
+ */
+ protected final Map<AccountID, ServiceRegistration<ProtocolProviderService>>
+ registeredAccounts
+ = new HashMap<AccountID, ServiceRegistration<ProtocolProviderService>>();
+
+ /**
+ * The name of the property that indicates the AVP type.
+ * <ul>
+ * <li>{@link #SAVP_OFF}</li>
+ * <li>{@link #SAVP_MANDATORY}</li>
+ * <li>{@link #SAVP_OPTIONAL}</li>
+ * </ul>
+ */
+ public static final String SAVP_OPTION = "SAVP_OPTION";
+
+ /**
+ * Always use RTP/AVP
+ */
+ public static final int SAVP_OFF = 0;
+
+ /**
+ * Always use RTP/SAVP
+ */
+ public static final int SAVP_MANDATORY = 1;
+
+ /**
+ * Sends two media description, with RTP/SAVP being first.
+ */
+ public static final int SAVP_OPTIONAL = 2;
+
+ /**
+ * The name of the property that defines the enabled SDES cipher suites.
+ * Enabled suites are listed as CSV by their RFC name.
+ */
+ public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES";
+
+ /**
+ * The name of the property that defines the enabled/disabled state of
+ * message carbons.
+ */
+ public static final String IS_CARBON_DISABLED = "CARBON_DISABLED";
+
+ /**
+ * Creates a new <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param bundleContext the bundle context reference of the service
+ * @param protocolName the name of the protocol
+ */
+ protected ProtocolProviderFactory(BundleContext bundleContext,
+ String protocolName)
+ {
+ this.bundleContext = bundleContext;
+ this.protocolName = protocolName;
+ }
+
+ /**
+ * Gets the <code>BundleContext</code> containing (or to contain) the
+ * service registration of this factory.
+ *
+ * @return the <code>BundleContext</code> containing (or to contain) the
+ * service registration of this factory
+ */
+ public BundleContext getBundleContext()
+ {
+ return bundleContext;
+ }
+
+ /**
+ * Initializes and creates an account corresponding to the specified
+ * accountProperties and registers the resulting ProtocolProvider in the
+ * <tt>context</tt> BundleContext parameter. Note that account
+ * registration is persistent and accounts that are registered during
+ * a particular sip-communicator session would be automatically reloaded
+ * during all following sessions until they are removed through the
+ * removeAccount method.
+ *
+ * @param userID the user identifier uniquely representing the newly
+ * created account within the protocol namespace.
+ * @param accountProperties a set of protocol (or implementation) specific
+ * properties defining the new account.
+ * @return the AccountID of the newly created account.
+ * @throws java.lang.IllegalArgumentException if userID does not correspond
+ * to an identifier in the context of the underlying protocol or if
+ * accountProperties does not contain a complete set of account installation
+ * properties.
+ * @throws java.lang.IllegalStateException if the account has already been
+ * installed.
+ * @throws java.lang.NullPointerException if any of the arguments is null.
+ */
+ public abstract AccountID installAccount(String userID,
+ Map<String, String> accountProperties)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ NullPointerException;
+
+
+ /**
+ * Modifies the account corresponding to the specified accountID. This
+ * method is meant to be used to change properties of already existing
+ * accounts. Note that if the given accountID doesn't correspond to any
+ * registered account this method would do nothing.
+ *
+ * @param protocolProvider the protocol provider service corresponding to
+ * the modified account.
+ * @param accountProperties a set of protocol (or implementation) specific
+ * properties defining the new account.
+ *
+ * @throws java.lang.NullPointerException if any of the arguments is null.
+ */
+ public abstract void modifyAccount(
+ ProtocolProviderService protocolProvider,
+ Map<String, String> accountProperties)
+ throws NullPointerException;
+
+ /**
+ * Returns a copy of the list containing the <tt>AccountID</tt>s of all
+ * accounts currently registered in this protocol provider.
+ * @return a copy of the list containing the <tt>AccountID</tt>s of all
+ * accounts currently registered in this protocol provider.
+ */
+ public ArrayList<AccountID> getRegisteredAccounts()
+ {
+ synchronized (registeredAccounts)
+ {
+ return new ArrayList<AccountID>(registeredAccounts.keySet());
+ }
+ }
+
+ /**
+ * Returns the ServiceReference for the protocol provider corresponding to
+ * the specified accountID or null if the accountID is unknown.
+ * @param accountID the accountID of the protocol provider we'd like to get
+ * @return a ServiceReference object to the protocol provider with the
+ * specified account id and null if the account id is unknown to the
+ * provider factory.
+ */
+ public ServiceReference<ProtocolProviderService> getProviderForAccount(
+ AccountID accountID)
+ {
+ ServiceRegistration<ProtocolProviderService> registration;
+
+ synchronized (registeredAccounts)
+ {
+ registration = registeredAccounts.get(accountID);
+ }
+
+ try
+ {
+ if (registration != null)
+ return registration.getReference();
+ }
+ catch (IllegalStateException ise)
+ {
+ synchronized (registeredAccounts)
+ {
+ registeredAccounts.remove(accountID);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Removes the specified account from the list of accounts that this
+ * provider factory is handling. If the specified accountID is unknown to
+ * the ProtocolProviderFactory, the call has no effect and false is
+ * returned. This method is persistent in nature and once called the account
+ * corresponding to the specified ID will not be loaded during future runs
+ * of the project.
+ *
+ * @param accountID the ID of the account to remove.
+ * @return true if an account with the specified ID existed and was removed
+ * and false otherwise.
+ */
+ public boolean uninstallAccount(AccountID accountID)
+ {
+ // Unregister the protocol provider.
+ ServiceReference<ProtocolProviderService> serRef
+ = getProviderForAccount(accountID);
+
+ boolean wasAccountExisting = false;
+
+ // If the protocol provider service is registered, first unregister the
+ // service.
+ if (serRef != null)
+ {
+ BundleContext bundleContext = getBundleContext();
+ ProtocolProviderService protocolProvider
+ = bundleContext.getService(serRef);
+
+ try
+ {
+ protocolProvider.unregister();
+ }
+ catch (OperationFailedException ex)
+ {
+ logger.error(
+ "Failed to unregister protocol provider for account: "
+ + accountID + " caused by: " + ex);
+ }
+ }
+
+ ServiceRegistration<ProtocolProviderService> registration;
+
+ synchronized (registeredAccounts)
+ {
+ registration = registeredAccounts.remove(accountID);
+ }
+
+ // first remove the stored account so when PP is unregistered we can
+ // distinguish between deleted or just disabled account
+ wasAccountExisting = removeStoredAccount(accountID);
+
+ if (registration != null)
+ {
+ // Kill the service.
+ registration.unregister();
+ }
+
+ return wasAccountExisting;
+ }
+
+ /**
+ * The method stores the specified account in the configuration service
+ * under the package name of the source factory. The restore and remove
+ * account methods are to be used to obtain access to and control the stored
+ * accounts.
+ * <p>
+ * In order to store all account properties, the method would create an
+ * entry in the configuration service corresponding (beginning with) the
+ * <tt>sourceFactory</tt>'s package name and add to it a unique identifier
+ * (e.g. the current miliseconds.)
+ * </p>
+ *
+ * @param accountID the AccountID corresponding to the account that we would
+ * like to store.
+ */
+ protected void storeAccount(AccountID accountID)
+ {
+ this.storeAccount(accountID, true);
+ }
+
+ /**
+ * The method stores the specified account in the configuration service
+ * under the package name of the source factory. The restore and remove
+ * account methods are to be used to obtain access to and control the stored
+ * accounts.
+ * <p>
+ * In order to store all account properties, the method would create an
+ * entry in the configuration service corresponding (beginning with) the
+ * <tt>sourceFactory</tt>'s package name and add to it a unique identifier
+ * (e.g. the current miliseconds.)
+ * </p>
+ *
+ * @param accountID the AccountID corresponding to the account that we would
+ * like to store.
+ * @param isModification if <tt>false</tt> there must be no such already
+ * loaded account, it <tt>true</tt> ist modification of an existing account.
+ * Usually we use this method with <tt>false</tt> in method installAccount
+ * and with <tt>true</tt> or the overridden method in method
+ * modifyAccount.
+ */
+ protected void storeAccount(AccountID accountID, boolean isModification)
+ {
+ if(!isModification
+ && getAccountManager().getStoredAccounts().contains(accountID))
+ {
+ throw new IllegalStateException(
+ "An account for id " + accountID.getUserID()
+ + " was already loaded!");
+ }
+
+ try
+ {
+ getAccountManager().storeAccount(this, accountID);
+ }
+ catch (OperationFailedException ofex)
+ {
+ throw new UndeclaredThrowableException(ofex);
+ }
+ }
+
+ /**
+ * Saves the password for the specified account after scrambling it a bit so
+ * that it is not visible from first sight. (The method remains highly
+ * insecure).
+ *
+ * @param accountID the AccountID for the account whose password we're
+ * storing
+ * @param password the password itself
+ *
+ * @throws IllegalArgumentException if no account corresponding to
+ * <code>accountID</code> has been previously stored
+ */
+ public void storePassword(AccountID accountID, String password)
+ throws IllegalArgumentException
+ {
+ try
+ {
+ storePassword(getBundleContext(), accountID, password);
+ }
+ catch (OperationFailedException ofex)
+ {
+ throw new UndeclaredThrowableException(ofex);
+ }
+ }
+
+ /**
+ * Saves the password for the specified account after scrambling it a bit
+ * so that it is not visible from first sight (Method remains highly
+ * insecure).
+ * <p>
+ * TODO Delegate the implementation to {@link AccountManager} because it
+ * knows the format in which the password (among the other account
+ * properties) is to be saved.
+ * </p>
+ *
+ * @param bundleContext a currently valid bundle context.
+ * @param accountID the <tt>AccountID</tt> of the account whose password is
+ * to be stored
+ * @param password the password to be stored
+ *
+ * @throws IllegalArgumentException if no account corresponding to
+ * <tt>accountID</tt> has been previously stored.
+ * @throws OperationFailedException if anything goes wrong while storing the
+ * specified <tt>password</tt>
+ */
+ protected void storePassword(BundleContext bundleContext,
+ AccountID accountID,
+ String password)
+ throws IllegalArgumentException,
+ OperationFailedException
+ {
+ String accountPrefix
+ = findAccountPrefix(
+ bundleContext,
+ accountID,
+ getFactoryImplPackageName());
+
+ if (accountPrefix == null)
+ {
+ throw
+ new IllegalArgumentException(
+ "No previous records found for account ID: "
+ + accountID.getAccountUniqueID()
+ + " in package"
+ + getFactoryImplPackageName());
+ }
+
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+
+ if (!credentialsStorage.storePassword(accountPrefix, password))
+ {
+ throw
+ new OperationFailedException(
+ "CredentialsStorageService failed to storePassword",
+ OperationFailedException.GENERAL_ERROR);
+ }
+
+ // Update password property also in the AccountID
+ // to prevent it from being removed during account reload
+ // in some cases.
+ accountID.setPassword(password);
+
+ }
+
+ /**
+ * Returns the password last saved for the specified account.
+ *
+ * @param accountID the AccountID for the account whose password we're
+ * looking for
+ *
+ * @return a String containing the password for the specified accountID
+ */
+ public String loadPassword(AccountID accountID)
+ {
+ return loadPassword(getBundleContext(), accountID);
+ }
+
+ /**
+ * Returns the password last saved for the specified account.
+ * <p>
+ * TODO Delegate the implementation to {@link AccountManager} because it
+ * knows the format in which the password (among the other account
+ * properties) was saved.
+ * </p>
+ *
+ * @param bundleContext a currently valid bundle context.
+ * @param accountID the AccountID for the account whose password we're
+ * looking for..
+ *
+ * @return a String containing the password for the specified accountID.
+ */
+ protected String loadPassword(BundleContext bundleContext,
+ AccountID accountID)
+ {
+ String accountPrefix = findAccountPrefix(
+ bundleContext, accountID, getFactoryImplPackageName());
+
+ if (accountPrefix == null)
+ return null;
+
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+
+ return credentialsStorage.loadPassword(accountPrefix);
+ }
+
+ /**
+ * Initializes and creates an account corresponding to the specified
+ * accountProperties and registers the resulting ProtocolProvider in the
+ * <tt>context</tt> BundleContext parameter. This method has a persistent
+ * effect. Once created the resulting account will remain installed until
+ * removed through the uninstallAccount method.
+ *
+ * @param accountProperties a set of protocol (or implementation) specific
+ * properties defining the new account.
+ * @return the AccountID of the newly loaded account
+ */
+ public AccountID loadAccount(Map<String, String> accountProperties)
+ {
+ AccountID accountID = createAccount(accountProperties);
+
+ loadAccount(accountID);
+
+ return accountID;
+ }
+
+ /**
+ * Creates a protocol provider for the given <tt>accountID</tt> and
+ * registers it in the bundle context. This method has a persistent
+ * effect. Once created the resulting account will remain installed until
+ * removed through the uninstallAccount method.
+ *
+ * @param accountID the account identifier
+ * @return <tt>true</tt> if the account with the given <tt>accountID</tt> is
+ * successfully loaded, otherwise returns <tt>false</tt>
+ */
+ public boolean loadAccount(AccountID accountID)
+ {
+ // Need to obtain the original user id property, instead of calling
+ // accountID.getUserID(), because this method could return a modified
+ // version of the user id property.
+ String userID
+ = accountID.getAccountPropertyString(
+ ProtocolProviderFactory.USER_ID);
+
+ ProtocolProviderService service = createService(userID, accountID);
+
+ Dictionary<String, String> properties = new Hashtable<String, String>();
+ properties.put(PROTOCOL, protocolName);
+ properties.put(USER_ID, userID);
+
+ ServiceRegistration<ProtocolProviderService> serviceRegistration
+ = bundleContext.registerService(
+ ProtocolProviderService.class,
+ service,
+ properties);
+
+ if (serviceRegistration == null)
+ {
+ return false;
+ }
+ else
+ {
+ synchronized (registeredAccounts)
+ {
+ registeredAccounts.put(accountID, serviceRegistration);
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Unloads the account corresponding to the given <tt>accountID</tt>.
+ * Unregisters the corresponding protocol provider, but keeps the account in
+ * contrast to the uninstallAccount method.
+ *
+ * @param accountID the account identifier
+ * @return true if an account with the specified ID existed and was unloaded
+ * and false otherwise.
+ */
+ public boolean unloadAccount(AccountID accountID)
+ {
+ // Unregister the protocol provider.
+ ServiceReference<ProtocolProviderService> serRef
+ = getProviderForAccount(accountID);
+
+ if (serRef == null)
+ {
+ return false;
+ }
+
+ BundleContext bundleContext = getBundleContext();
+ ProtocolProviderService protocolProvider
+ = bundleContext.getService(serRef);
+
+ try
+ {
+ protocolProvider.unregister();
+ }
+ catch (OperationFailedException ex)
+ {
+ logger.error(
+ "Failed to unregister protocol provider for account: "
+ + accountID + " caused by: " + ex);
+ }
+
+ ServiceRegistration<ProtocolProviderService> registration;
+
+ synchronized (registeredAccounts)
+ {
+ registration = registeredAccounts.remove(accountID);
+ }
+ if (registration == null)
+ {
+ return false;
+ }
+
+ // Kill the service.
+ registration.unregister();
+
+ return true;
+ }
+
+ /**
+ * Initializes and creates an account corresponding to the specified
+ * accountProperties.
+ *
+ * @param accountProperties a set of protocol (or implementation) specific
+ * properties defining the new account.
+ * @return the AccountID of the newly created account
+ */
+ public AccountID createAccount(Map<String, String> accountProperties)
+ {
+ BundleContext bundleContext = getBundleContext();
+ if (bundleContext == null)
+ throw new NullPointerException(
+ "The specified BundleContext was null");
+
+ if (accountProperties == null)
+ throw new NullPointerException(
+ "The specified property map was null");
+
+ String userID = accountProperties.get(USER_ID);
+ if (userID == null)
+ throw new NullPointerException(
+ "The account properties contained no user id.");
+
+ String protocolName = getProtocolName();
+ if (!accountProperties.containsKey(PROTOCOL))
+ accountProperties.put(PROTOCOL, protocolName);
+
+ return createAccountID(userID, accountProperties);
+ }
+
+ /**
+ * Creates a new <code>AccountID</code> instance with a specific user ID to
+ * represent a given set of account properties.
+ * <p>
+ * The method is a pure factory allowing implementers to specify the runtime
+ * type of the created <code>AccountID</code> and customize the instance.
+ * The returned <code>AccountID</code> will later be associated with a
+ * <code>ProtocolProviderService</code> by the caller (e.g. using
+ * {@link #createService(String, AccountID)}).
+ * </p>
+ *
+ * @param userID the user ID of the new instance
+ * @param accountProperties the set of properties to be represented by the
+ * new instance
+ * @return a new <code>AccountID</code> instance with the specified user ID
+ * representing the given set of account properties
+ */
+ protected abstract AccountID createAccountID(
+ String userID, Map<String, String> accountProperties);
+
+ /**
+ * Gets the name of the protocol this factory registers its
+ * <code>ProtocolProviderService</code>s with and to be placed in the
+ * properties of the accounts created by this factory.
+ *
+ * @return the name of the protocol this factory registers its
+ * <code>ProtocolProviderService</code>s with and to be placed in
+ * the properties of the accounts created by this factory
+ */
+ public String getProtocolName()
+ {
+ return protocolName;
+ }
+
+ /**
+ * Initializes a new <code>ProtocolProviderService</code> instance with a
+ * specific user ID to represent a specific <code>AccountID</code>.
+ * <p>
+ * The method is a pure factory allowing implementers to specify the runtime
+ * type of the created <code>ProtocolProviderService</code> and customize
+ * the instance. The caller will later register the returned service with
+ * the <code>BundleContext</code> of this factory.
+ * </p>
+ *
+ * @param userID the user ID to initialize the new instance with
+ * @param accountID the <code>AccountID</code> to be represented by the new
+ * instance
+ * @return a new <code>ProtocolProviderService</code> instance with the
+ * specific user ID representing the specified
+ * <code>AccountID</code>
+ */
+ protected abstract ProtocolProviderService createService(String userID,
+ AccountID accountID);
+
+ /**
+ * Removes the account with <tt>accountID</tt> from the set of accounts
+ * that are persistently stored inside the configuration service.
+ *
+ * @param accountID the AccountID of the account to remove.
+ *
+ * @return true if an account has been removed and false otherwise.
+ */
+ protected boolean removeStoredAccount(AccountID accountID)
+ {
+ return getAccountManager().removeStoredAccount(this, accountID);
+ }
+
+ /**
+ * Returns the prefix for all persistently stored properties of the account
+ * with the specified id.
+ * @param bundleContext a currently valid bundle context.
+ * @param accountID the AccountID of the account whose properties we're
+ * looking for.
+ * @param sourcePackageName a String containing the package name of the
+ * concrete factory class that extends us.
+ * @return a String indicating the ConfigurationService property name
+ * prefix under which all account properties are stored or null if no
+ * account corresponding to the specified id was found.
+ */
+ public static String findAccountPrefix(BundleContext bundleContext,
+ AccountID accountID,
+ String sourcePackageName)
+ {
+ ServiceReference<ConfigurationService> confReference
+ = bundleContext.getServiceReference(ConfigurationService.class);
+ ConfigurationService configurationService
+ = bundleContext.getService(confReference);
+
+ //first retrieve all accounts that we've registered
+ List<String> storedAccounts =
+ configurationService.getPropertyNamesByPrefix(sourcePackageName,
+ true);
+
+ //find an account with the corresponding id.
+ for (String accountRootPropertyName : storedAccounts)
+ {
+ //unregister the account in the configuration service.
+ //all the properties must have been registered in the following
+ //hierarchy:
+ //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
+ String accountUID = configurationService.getString(
+ accountRootPropertyName //node id
+ + "." + ACCOUNT_UID); // propname
+
+ if (accountID.getAccountUniqueID().equals(accountUID))
+ {
+ return accountRootPropertyName;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the name of the package that we're currently running in (i.e.
+ * the name of the package containing the proto factory that extends us).
+ *
+ * @return a String containing the package name of the concrete factory
+ * class that extends us.
+ */
+ private String getFactoryImplPackageName()
+ {
+ String className = getClass().getName();
+
+ return className.substring(0, className.lastIndexOf('.'));
+ }
+
+ /**
+ * Prepares the factory for bundle shutdown.
+ */
+ public void stop()
+ {
+ if (logger.isTraceEnabled())
+ logger.trace("Preparing to stop all protocol providers of" + this);
+
+ synchronized (registeredAccounts)
+ {
+ for (ServiceRegistration<ProtocolProviderService> reg
+ : registeredAccounts.values())
+ {
+ stop(reg);
+ reg.unregister();
+ }
+
+ registeredAccounts.clear();
+ }
+ }
+
+ /**
+ * Shuts down the <code>ProtocolProviderService</code> representing an
+ * account registered with this factory.
+ *
+ * @param registeredAccount the <code>ServiceRegistration</code> of the
+ * <code>ProtocolProviderService</code> representing an account
+ * registered with this factory
+ */
+ protected void stop(
+ ServiceRegistration<ProtocolProviderService> registeredAccount)
+ {
+ ProtocolProviderService protocolProviderService
+ = getBundleContext().getService(registeredAccount.getReference());
+
+ protocolProviderService.shutdown();
+ }
+
+ /**
+ * Get the <tt>AccountManager</tt> of the protocol.
+ *
+ * @return <tt>AccountManager</tt> of the protocol
+ */
+ private AccountManager getAccountManager()
+ {
+ BundleContext bundleContext = getBundleContext();
+ ServiceReference<AccountManager> serviceReference
+ = bundleContext.getServiceReference(AccountManager.class);
+
+ return bundleContext.getService(serviceReference);
+ }
+
+
+ /**
+ * Finds registered <tt>ProtocolProviderFactory</tt> for given
+ * <tt>protocolName</tt>.
+ * @param bundleContext the OSGI bundle context that will be used.
+ * @param protocolName the protocol name.
+ * @return Registered <tt>ProtocolProviderFactory</tt> for given protocol
+ * name or <tt>null</tt> if no provider was found.
+ */
+ static public ProtocolProviderFactory getProtocolProviderFactory(
+ BundleContext bundleContext,
+ String protocolName)
+ {
+ Collection<ServiceReference<ProtocolProviderFactory>> serRefs;
+ String osgiFilter
+ = "(" + ProtocolProviderFactory.PROTOCOL + "=" + protocolName + ")";
+
+ try
+ {
+ serRefs
+ = bundleContext.getServiceReferences(
+ ProtocolProviderFactory.class,
+ osgiFilter);
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ serRefs = null;
+ logger.error(ex);
+ }
+ if ((serRefs == null) || serRefs.isEmpty())
+ return null;
+ else
+ return bundleContext.getService(serRefs.iterator().next());
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java b/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java
index 0c85e98..ffc356c 100644
--- a/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java
+++ b/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java
@@ -211,13 +211,6 @@ public interface ProtocolProviderService
public void shutdown();
/**
- * A hashcode allowing usage of protocol providers as keys in Hashtables.
- * @return an int that may be used when storing protocol providers as
- * hashtable keys.
- */
- public int hashCode();
-
- /**
* 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.
@@ -225,6 +218,23 @@ public interface ProtocolProviderService
public AccountID getAccountID();
/**
+ * Validates the given protocol specific 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
+ */
+ public boolean validateContactAddress(String contactId,
+ List<String> result);
+
+ /**
* Indicate if the signaling transport of this protocol instance uses a
* secure (e.g. via TLS) connection.
*
diff --git a/src/net/java/sip/communicator/service/protocol/RegistrationState.java b/src/net/java/sip/communicator/service/protocol/RegistrationState.java
index 9bd3eb0..368e5b1 100644
--- a/src/net/java/sip/communicator/service/protocol/RegistrationState.java
+++ b/src/net/java/sip/communicator/service/protocol/RegistrationState.java
@@ -177,4 +177,10 @@ public class RegistrationState
&& obj != null
&& statusString.equals(((RegistrationState)obj).statusString);
}
+
+ @Override
+ public int hashCode()
+ {
+ return statusString.hashCode();
+ }
}
diff --git a/src/net/java/sip/communicator/service/protocol/ServerStoredDetails.java b/src/net/java/sip/communicator/service/protocol/ServerStoredDetails.java
index 23cb895..4d21864 100644
--- a/src/net/java/sip/communicator/service/protocol/ServerStoredDetails.java
+++ b/src/net/java/sip/communicator/service/protocol/ServerStoredDetails.java
@@ -156,6 +156,12 @@ public class ServerStoredDetails
else
return false;
}
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(detailDisplayName, value);
+ }
}
/**
diff --git a/src/net/java/sip/communicator/service/protocol/WhiteboardPoint.java b/src/net/java/sip/communicator/service/protocol/WhiteboardPoint.java
index 1774718..3e26e7a 100644
--- a/src/net/java/sip/communicator/service/protocol/WhiteboardPoint.java
+++ b/src/net/java/sip/communicator/service/protocol/WhiteboardPoint.java
@@ -17,6 +17,8 @@
*/
package net.java.sip.communicator.service.protocol;
+import java.util.Objects;
+
/**
* A point representing a location in {@code (x,y)} coordinate space,
* specified in integer precision.
@@ -126,6 +128,12 @@ public class WhiteboardPoint implements Cloneable
return false;
}
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(x, y);
+ }
+
/**
* Returns a string representation of this point and its location
* in the {@code (x,y)} coordinate space. This method is intended to be
diff --git a/src/net/java/sip/communicator/service/protocol/event/AccountManagerEvent.java b/src/net/java/sip/communicator/service/protocol/event/AccountManagerEvent.java
index db594bf..ecbd971 100644
--- a/src/net/java/sip/communicator/service/protocol/event/AccountManagerEvent.java
+++ b/src/net/java/sip/communicator/service/protocol/event/AccountManagerEvent.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,86 +15,86 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol.event;
-
-import java.util.*;
-
-import net.java.sip.communicator.service.protocol.*;
-
-/**
- * Represents a notifying event fired by a specific {@link AccountManager}.
- *
- * @author Lubomir Marinov
- */
-public class AccountManagerEvent
- extends EventObject
-{
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- /**
- * The type of event notifying that the loading of the stored accounts of a
- * specific <code>ProtocolProviderFactory</code> has finished.
- */
- public static final int STORED_ACCOUNTS_LOADED = 1;
-
- /**
- * The <code>ProtocolProviderFactory</code> being worked on at the time this
- * event has been fired.
- */
- private final ProtocolProviderFactory factory;
-
- /**
- * The (detail) type of this event which is one of
- * {@link #STORED_ACCOUNTS_LOADED}.
- */
- private final int type;
-
- /**
- * Initializes a new <code>AccountManagerEvent</code> instance fired by a
- * specific <code>AccountManager</code> in order to notify of an event of a
- * specific type occurring while working on a specific
- * <code>ProtocolProviderFactory</code>.
- *
- * @param accountManager the <code>AccountManager</code> issuing the
- * notification i.e. the source of the event
- * @param type the type of the event which is one of
- * {@link #STORED_ACCOUNTS_LOADED}
- * @param factory the <code>ProtocolProviderFactory</code> being worked on
- * at the time this event has been fired
- */
- public AccountManagerEvent(AccountManager accountManager, int type,
- ProtocolProviderFactory factory)
- {
- super(accountManager);
-
- this.type = type;
- this.factory = factory;
- }
-
- /**
- * Gets the <code>ProtocolProviderFactory</code> being worked on at the time
- * this event has been fired.
- *
- * @return the <code>ProtocolProviderFactory</code> being worked on at the
- * time this event has been fired
- */
- public ProtocolProviderFactory getFactory()
- {
- return factory;
- }
-
- /**
- * Gets the (detail) type of this event which is one of
- * <code>STORED_ACCOUNTS_LOADED</code>.
- *
- * @return the (detail) type of this event which is one of
- * <code>STORED_ACCOUNTS_LOADED</code>
- */
- public int getType()
- {
- return type;
- }
-}
+package net.java.sip.communicator.service.protocol.event;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * Represents a notifying event fired by a specific {@link AccountManager}.
+ *
+ * @author Lubomir Marinov
+ */
+public class AccountManagerEvent
+ extends EventObject
+{
+ /**
+ * Serial version UID.
+ */
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * The type of event notifying that the loading of the stored accounts of a
+ * specific <code>ProtocolProviderFactory</code> has finished.
+ */
+ public static final int STORED_ACCOUNTS_LOADED = 1;
+
+ /**
+ * The <code>ProtocolProviderFactory</code> being worked on at the time this
+ * event has been fired.
+ */
+ private final ProtocolProviderFactory factory;
+
+ /**
+ * The (detail) type of this event which is one of
+ * {@link #STORED_ACCOUNTS_LOADED}.
+ */
+ private final int type;
+
+ /**
+ * Initializes a new <code>AccountManagerEvent</code> instance fired by a
+ * specific <code>AccountManager</code> in order to notify of an event of a
+ * specific type occurring while working on a specific
+ * <code>ProtocolProviderFactory</code>.
+ *
+ * @param accountManager the <code>AccountManager</code> issuing the
+ * notification i.e. the source of the event
+ * @param type the type of the event which is one of
+ * {@link #STORED_ACCOUNTS_LOADED}
+ * @param factory the <code>ProtocolProviderFactory</code> being worked on
+ * at the time this event has been fired
+ */
+ public AccountManagerEvent(AccountManager accountManager, int type,
+ ProtocolProviderFactory factory)
+ {
+ super(accountManager);
+
+ this.type = type;
+ this.factory = factory;
+ }
+
+ /**
+ * Gets the <code>ProtocolProviderFactory</code> being worked on at the time
+ * this event has been fired.
+ *
+ * @return the <code>ProtocolProviderFactory</code> being worked on at the
+ * time this event has been fired
+ */
+ public ProtocolProviderFactory getFactory()
+ {
+ return factory;
+ }
+
+ /**
+ * Gets the (detail) type of this event which is one of
+ * <code>STORED_ACCOUNTS_LOADED</code>.
+ *
+ * @return the (detail) type of this event which is one of
+ * <code>STORED_ACCOUNTS_LOADED</code>
+ */
+ public int getType()
+ {
+ return type;
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/event/AccountManagerListener.java b/src/net/java/sip/communicator/service/protocol/event/AccountManagerListener.java
index 607caea..6bcf4e0 100644
--- a/src/net/java/sip/communicator/service/protocol/event/AccountManagerListener.java
+++ b/src/net/java/sip/communicator/service/protocol/event/AccountManagerListener.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.service.protocol.event;
-
-import java.util.*;
-
-import net.java.sip.communicator.service.protocol.*;
-
-/**
- * Represents a listener receiving notifications from {@link AccountManager}.
- *
- * @author Lubomir Marinov
- */
-public interface AccountManagerListener
- extends EventListener
-{
-
- /**
- * Notifies this listener about an event fired by a specific
- * <code>AccountManager</code>.
- *
- * @param event the <code>AccountManagerEvent</code> describing the
- * <code>AccountManager</code> firing the notification and the
- * other details of the specific notification.
- */
- void handleAccountManagerEvent(AccountManagerEvent event);
-}
+package net.java.sip.communicator.service.protocol.event;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * Represents a listener receiving notifications from {@link AccountManager}.
+ *
+ * @author Lubomir Marinov
+ */
+public interface AccountManagerListener
+ extends EventListener
+{
+
+ /**
+ * Notifies this listener about an event fired by a specific
+ * <code>AccountManager</code>.
+ *
+ * @param event the <code>AccountManagerEvent</code> describing the
+ * <code>AccountManager</code> firing the notification and the
+ * other details of the specific notification.
+ */
+ void handleAccountManagerEvent(AccountManagerEvent event);
+}
diff --git a/src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityMessageEvent.java b/src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityMessageEvent.java
index bcbb66a..92a3f31 100644
--- a/src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityMessageEvent.java
+++ b/src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityMessageEvent.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.service.protocol.event;
diff --git a/src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityStatusEvent.java b/src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityStatusEvent.java
index 6720f4b..931998b 100644
--- a/src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityStatusEvent.java
+++ b/src/net/java/sip/communicator/service/protocol/event/CallPeerSecurityStatusEvent.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.service.protocol.event;
diff --git a/src/net/java/sip/communicator/service/protocol/jabber/AbstractSmackInteroperabilityLayer.java b/src/net/java/sip/communicator/service/protocol/jabber/AbstractSmackInteroperabilityLayer.java
new file mode 100644
index 0000000..277da84
--- /dev/null
+++ b/src/net/java/sip/communicator/service/protocol/jabber/AbstractSmackInteroperabilityLayer.java
@@ -0,0 +1,141 @@
+/*
+ * 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.service.protocol.jabber;
+
+import net.java.sip.communicator.util.*;
+import org.jivesoftware.smack.provider.*;
+
+
+/**
+ *
+ * This class abstracts interactions within Smack XMPP library (mostly with
+ * its <tt>ProviderManager</tt> class).
+ * This exists because <tt>JingleIQProvider</tt> and <tt>ColibriIQProvider</tt>
+ * are in need to be used with Smack v4, where <tt>ProviderManager</tt>
+ * has a different interface.
+ *
+ * @author Maksym Kulish
+ */
+abstract public class AbstractSmackInteroperabilityLayer {
+
+ /**
+ * The <tt>Logger</tt> used by the
+ * <tt>AbstractSmackInteroperabilityLayer</tt> class for
+ * reporting on improper implementations instantiation
+ */
+ private static final Logger logger = Logger.getLogger(
+ AbstractSmackInteroperabilityLayer.class);
+
+ /**
+ * The implementation class to be used within its Jitsi application
+ */
+ private static Class<AbstractSmackInteroperabilityLayer>
+ implementationClass;
+
+ /**
+ * The instance of Smack interoperability layer implementation class
+ */
+ private static AbstractSmackInteroperabilityLayer
+ interopLayerInstance;
+
+ /**
+ * Get the instance of Smack interoperability layer implementation class
+ *
+ * @return Smack interoperation layer implementation class
+ */
+ public static AbstractSmackInteroperabilityLayer getInstance()
+ {
+ if (interopLayerInstance == null)
+ {
+ try
+ {
+ interopLayerInstance =
+ implementationClass.newInstance();
+ }
+ catch (IllegalAccessException e)
+ {
+ // Never thrown within proper implementation
+ logger.fatal("Your AbstractSmackInteroperabilityLayer " +
+ "implementation " +
+ "cannot be accessed properly. " +
+ "Please fix the implementation");
+ }
+ catch (InstantiationException e)
+ {
+ // Never thrown within proper implementation
+ logger.fatal("Your AbstractSmackInteroperabilityLayer " +
+ "implementation " +
+ "cannot be instantiated properly. " +
+ "Please fix the implementation");
+ }
+ }
+ return interopLayerInstance;
+ }
+
+ /**
+ * Set the Smack interoperation layer
+ * implementation class to be used within this Jitsi application
+ * @param implementationClass Smack interoperation layer
+ * implementation class
+ */
+ public static void setImplementationClass(
+ Class implementationClass)
+ {
+ AbstractSmackInteroperabilityLayer.implementationClass
+ = implementationClass;
+ }
+
+
+ /**
+ * 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
+ */
+ abstract public void addExtensionProvider(
+ String elementName, String namespace, Object 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
+ */
+ abstract public void addIQProvider(
+ String elementName, String namespace, Object 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
+ */
+ abstract public PacketExtensionProvider getExtensionProvider(
+ String elementName, String namespace);
+
+
+}
diff --git a/src/net/java/sip/communicator/service/protocol/jabber/JabberAccountID.java b/src/net/java/sip/communicator/service/protocol/jabber/JabberAccountID.java
index 81473c2..dce28d6 100644
--- a/src/net/java/sip/communicator/service/protocol/jabber/JabberAccountID.java
+++ b/src/net/java/sip/communicator/service/protocol/jabber/JabberAccountID.java
@@ -44,6 +44,12 @@ public class JabberAccountID
public static final String ANONYMOUS_AUTH = "ANONYMOUS_AUTH";
/**
+ * Configures the URL which is to be used with BOSH transport. If the value
+ * is <tt>null</tt> or empty then the TCP transport will be used instead.
+ */
+ public static final String BOSH_URL = "BOSH_URL";
+
+ /**
* Account suffix for Google service.
*/
public static final String GOOGLE_USER_SUFFIX = "gmail.com";
@@ -119,6 +125,29 @@ public class JabberAccountID
}
/**
+ * Returns the BOSH URL which should be used to connect to the XMPP server.
+ * If the value is set then BOSH transport instead of TCP will be used.
+ *
+ * @return a <tt>String</tt> with the URL which should be used for BOSH
+ * transport or <tt>null</tt> if disabled.
+ */
+ public String getBoshUrl()
+ {
+ return getAccountPropertyString(BOSH_URL);
+ }
+
+ /**
+ * Sets new URL which should be used for the BOSH transport.
+ *
+ * @param boshPath a <tt>String</tt> with the new BOSH URL or <tt>null</tt>
+ * to disable BOSH.
+ */
+ public void setBoshUrl(String boshPath)
+ {
+ putAccountProperty(BOSH_URL, boshPath);
+ }
+
+ /**
* Returns the override phone suffix.
*
* @return the phone suffix
diff --git a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java
index b0d62ed..ead01d1 100644
--- a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java
+++ b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java
@@ -87,6 +87,16 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
public static final String VIDEO_REMOTE_SSRC = "VIDEO_REMOTE_SSRC";
/**
+ * The initial content of a hole punch packet. It has some fields pre-set.
+ * Like rtp verion, sequence number and timestamp.
+ */
+ private static final byte[] HOLE_PUNCH_PACKET =
+ {
+ (byte)0x80, 0x00, 0x02, (byte)0x9E, 0x00, 0x09,
+ (byte)0xD0, (byte)0x80, 0x00, 0x00, 0x00, (byte)0x00,
+ };
+
+ /**
* List of advertised encryption methods. Indicated before establishing the
* call.
*/
@@ -374,7 +384,7 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
if (call == null)
return;
- for (MediaType mediaType : MediaType.values())
+ for (MediaType mediaType : new MediaType[] {MediaType.AUDIO, MediaType.VIDEO} )
{
MediaStream stream = getStream(mediaType);
@@ -622,10 +632,8 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
return audioDirectionUserPreference;
case VIDEO:
return videoDirectionUserPreference;
- case DATA:
- return MediaDirection.INACTIVE;
default:
- throw new IllegalArgumentException("mediaType");
+ return MediaDirection.INACTIVE;
}
}
@@ -689,7 +697,7 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
return
(transportManager == null)
- ? null
+ ? 0
: transportManager.getHarvestingTime(harvesterName);
}
@@ -942,7 +950,9 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
/**
* Returns the number of harvesting for this agent.
*
- * @return The number of harvesting for this agent.
+ * @return The number of harvesting for this agent. 0 if this harvester
+ * does not exists, if the ICE agent is null, or
+ * if the agent has never harvested with this harvester.
*/
public int getNbHarvesting()
{
@@ -950,7 +960,7 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
return
(transportManager == null)
- ? null
+ ? 0
: transportManager.getNbHarvesting();
}
@@ -961,7 +971,8 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
* @param harvesterName The class name if the harvester.
*
* @return The number of harvesting time for the harvester given in
- * parameter.
+ * parameter. 0 if this harvester does not exists, if the ICE agent is null, or if the
+ * agent has never harvested with this harvester.
*/
public int getNbHarvesting(String harvesterName)
{
@@ -969,7 +980,7 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
return
(transportManager == null)
- ? null
+ ? 0
: transportManager.getNbHarvesting(harvesterName);
}
@@ -1036,17 +1047,10 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
{
case AUDIO:
return audioStream;
- case DATA:
- /*
- * DATA is a valid MediaType value and CallPeerMediaHandler does not
- * utilize it at this time so no IllegalArgumentException is thrown
- * and null is returned (as documented).
- */
- return null;
case VIDEO:
return videoStream;
default:
- throw new IllegalArgumentException("mediaType");
+ return null;
}
}
@@ -1054,7 +1058,7 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
* 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.
+ * the ICE agent is null, or if the agent has never harvested.
*/
public long getTotalHarvestingTime()
{
@@ -1062,7 +1066,7 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
return
(transportManager == null)
- ? null
+ ? 0
: transportManager.getTotalHarvestingTime();
}
@@ -1581,11 +1585,21 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
* to open port on NAT or RTP proxy if any. In order to be really efficient,
* this method should be called after we send our offer or answer.
*
- * @param target <tt>MediaStreamTarget</tt>
+ * @param stream <tt>MediaStream</tt> non-null stream
+ * @param mediaType <tt>MediaType</tt>
*/
- protected void sendHolePunchPacket(MediaStreamTarget target)
+ protected void sendHolePunchPacket(MediaStream stream, MediaType mediaType)
{
- getTransportManager().sendHolePunchPacket(target, MediaType.VIDEO);
+ // send as a hole punch packet a constructed rtp packet
+ // has the correct payload type and ssrc
+ RawPacket packet = new RawPacket(
+ HOLE_PUNCH_PACKET, 0, RawPacket.FIXED_HEADER_SIZE);
+ packet.setPayloadType(
+ dynamicPayloadTypes.getPayloadType(stream.getFormat()));
+ packet.setSSRC((int)stream.getLocalSourceID());
+
+ getTransportManager().sendHolePunchPacket(
+ stream.getTarget(), mediaType, packet);
}
/**
@@ -1942,6 +1956,8 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
stream.getTarget(),
MediaType.AUDIO);
stream.start();
+
+ sendHolePunchPacket(stream, MediaType.AUDIO);
}
stream = getStream(MediaType.VIDEO);
@@ -1970,7 +1986,7 @@ public abstract class CallPeerMediaHandler<T extends MediaAwareCallPeer<?,?,?>>
* send the hole-punch packet anyway to let the remote video
* reach this local peer.
*/
- sendHolePunchPacket(stream.getTarget());
+ sendHolePunchPacket(stream, MediaType.VIDEO);
}
}
}
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java
index f9b6c2f..2bf802b 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java
@@ -906,7 +906,7 @@ public abstract class MediaAwareCall<
@Override
protected CallConference createConference()
{
- return new MediaAwareCallConference();
+ return new MediaAwareCallConference(false, this.useTranslator);
}
/**
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java
index 7a58e4a..81e917c 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,623 +15,667 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol.media;
-
-import java.beans.*;
-import java.lang.ref.*;
-import java.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jitsi.service.neomedia.device.*;
-import org.jitsi.util.*;
-import org.jitsi.util.event.*;
-
-import net.java.sip.communicator.service.protocol.*;
-
-/**
- * Extends <tt>CallConference</tt> to represent the media-specific information
- * associated with the telephony conference-related state of a
- * <tt>MediaAwareCall</tt>.
- *
- * @author Lyubomir Marinov
- */
-public class MediaAwareCallConference
- extends CallConference
-{
- /**
- * The <tt>PropertyChangeListener</tt> which will listen to the
- * <tt>MediaService</tt> about <tt>PropertyChangeEvent</tt>s.
- */
- private static WeakPropertyChangeListener
- mediaServicePropertyChangeListener;
-
- /**
- * The <tt>MediaDevice</tt>s indexed by <tt>MediaType</tt> ordinal which are
- * to be used by this telephony conference for media capture and/or
- * playback. If the <tt>MediaDevice</tt> for a specific <tt>MediaType</tt>
- * is <tt>null</tt>,
- * {@link MediaService#getDefaultDevice(MediaType, MediaUseCase)} is called.
- */
- private final MediaDevice[] devices;
-
- /**
- * The <tt>MediaDevice</tt>s which implement media mixing on the respective
- * <tt>MediaDevice</tt> in {@link #devices} for the purposes of this
- * telephony conference.
- */
- private final MediaDevice[] mixers;
-
- /**
- * The <tt>VolumeControl</tt> implementation which is to control the volume
- * (level) of the audio played back the telephony conference represented by
- * this instance.
- */
- private final VolumeControl outputVolumeControl
- = new BasicVolumeControl(
- VolumeControl.PLAYBACK_VOLUME_LEVEL_PROPERTY_NAME);
-
- /**
- * The <tt>PropertyChangeListener</tt> which listens to sources of
- * <tt>PropertyChangeEvent</tt>s on behalf of this instance.
- */
- private final PropertyChangeListener propertyChangeListener
- = new PropertyChangeListener()
- {
- @Override
- public void propertyChange(PropertyChangeEvent ev)
- {
- MediaAwareCallConference.this.propertyChange(ev);
- }
- };
-
- /**
- * The <tt>RTPTranslator</tt> which forwards video RTP and RTCP traffic
- * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
- * this telephony conference when the local peer is acting as a conference
- * focus.
- */
- private RTPTranslator videoRTPTranslator;
-
- /**
- * Initializes a new <tt>MediaAwareCallConference</tt> instance.
- */
- public MediaAwareCallConference()
- {
- this(false);
- }
-
- /**
- * Initializes a new <tt>MediaAwareCallConference</tt> instance which is to
- * optionally utilize the Jitsi Videobridge server-side telephony
- * conferencing technology.
- *
- * @param jitsiVideobridge <tt>true</tt> if the telephony conference
- * represented by the new instance is to utilize the Jitsi Videobridge
- * server-side telephony conferencing technology; otherwise, <tt>false</tt>
- */
- public MediaAwareCallConference(boolean jitsiVideobridge)
- {
- super(jitsiVideobridge);
-
- int mediaTypeCount = MediaType.values().length;
-
- devices = new MediaDevice[mediaTypeCount];
- mixers = new MediaDevice[mediaTypeCount];
-
- /*
- * Listen to the MediaService in order to reflect changes in the user's
- * selection with respect to the default media device.
- */
- addMediaServicePropertyChangeListener(propertyChangeListener);
- }
-
- /**
- * Adds a specific <tt>PropertyChangeListener</tt> to be notified about
- * <tt>PropertyChangeEvent</tt>s fired by the current <tt>MediaService</tt>
- * implementation. The implementation adds a <tt>WeakReference</tt> to the
- * specified <tt>listener</tt> because <tt>MediaAwareCallConference</tt>
- * is unable to determine when the <tt>PropertyChangeListener</tt> is to be
- * removed.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to add
- */
- private static synchronized void addMediaServicePropertyChangeListener(
- PropertyChangeListener listener)
- {
- if (mediaServicePropertyChangeListener == null)
- {
- final MediaService mediaService
- = ProtocolMediaActivator.getMediaService();
-
- if (mediaService != null)
- {
- mediaServicePropertyChangeListener
- = new WeakPropertyChangeListener()
- {
- @Override
- protected void addThisToNotifier()
- {
- mediaService.addPropertyChangeListener(this);
- }
-
- @Override
- protected void removeThisFromNotifier()
- {
- mediaService.removePropertyChangeListener(this);
- }
- };
- }
- }
- if (mediaServicePropertyChangeListener != null)
- {
- mediaServicePropertyChangeListener.addPropertyChangeListener(
- listener);
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * If this telephony conference switches from being a conference focus to
- * not being such, disposes of the mixers used by this instance when it was
- * a conference focus
- */
- @Override
- protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
- {
- /*
- * If this telephony conference switches from being a conference
- * focus to not being one, dispose of the mixers used when it was a
- * conference focus.
- */
- if (oldValue && !newValue)
- {
- Arrays.fill(mixers, null);
-
- /* Disposing the video translator is not needed when the conference
- changes as we have video and we will want to continue with
- the video
- Removed when chasing a bug where video call becomes conference
- call and then back again video call and the video from the
- conference focus side is not transmitted.
- if (videoRTPTranslator != null)
- {
- videoRTPTranslator.dispose();
- videoRTPTranslator = null;
- }
- */
- }
-
- super.conferenceFocusChanged(oldValue, newValue);
- }
-
- /**
- * {@inheritDoc}
- *
- * Disposes of <tt>this.videoRTPTranslator</tt> if the removed <tt>Call</tt>
- * was the last <tt>Call</tt> in this <tt>CallConference</tt>.
- *
- * @param call the <tt>Call</tt> which has been removed from the list of
- * <tt>Call</tt>s participating in this telephony conference.
- */
- @Override
- protected void callRemoved(Call call)
- {
- super.callRemoved(call);
-
- if (getCallCount() == 0 && (videoRTPTranslator != null))
- {
- videoRTPTranslator.dispose();
- videoRTPTranslator = null;
- }
- }
-
- /**
- * Gets a <tt>MediaDevice</tt> which is capable of capture and/or playback
- * of media of the specified <tt>MediaType</tt> and is the default choice of
- * the user with respect to such a <tt>MediaDevice</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> in which the retrieved
- * <tt>MediaDevice</tt> is to capture and/or play back media
- * @param useCase the <tt>MediaUseCase</tt> associated with the intended
- * utilization of the <tt>MediaDevice</tt> to be retrieved
- * @return a <tt>MediaDevice</tt> which is capable of capture and/or
- * playback of media of the specified <tt>mediaType</tt> and is the default
- * choice of the user with respect to such a <tt>MediaDevice</tt>
- */
- public MediaDevice getDefaultDevice(
- MediaType mediaType,
- MediaUseCase useCase)
- {
- int mediaTypeIndex = mediaType.ordinal();
- MediaDevice device = devices[mediaTypeIndex];
- MediaService mediaService = ProtocolMediaActivator.getMediaService();
-
- if (device == null)
- device = mediaService.getDefaultDevice(mediaType, useCase);
-
- /*
- * Make sure that the device is capable of mixing in order to support
- * conferencing and call recording.
- */
- if (device != null)
- {
- MediaDevice mixer = mixers[mediaTypeIndex];
-
- if (mixer == null)
- {
- switch (mediaType)
- {
- case AUDIO:
- /*
- * TODO AudioMixer leads to very poor audio quality on
- * Android so do not use it unless it is really really
- * necessary.
- */
- if ((!OSUtils.IS_ANDROID || isConferenceFocus())
- /*
- * We can use the AudioMixer only if the device is
- * able to capture (because the AudioMixer will push
- * when the capture device pushes).
- */
- && device.getDirection().allowsSending())
- {
- mixer = mediaService.createMixer(device);
- }
- break;
-
- case VIDEO:
- if (isConferenceFocus())
- mixer = mediaService.createMixer(device);
- break;
- }
-
- mixers[mediaTypeIndex] = mixer;
- }
-
- if (mixer != null)
- device = mixer;
- }
-
- return device;
- }
-
- /**
- * Gets the <tt>VolumeControl</tt> which controls the volume (level) of the
- * audio played back in the telephony conference represented by this
- * instance.
- *
- * @return the <tt>VolumeControl</tt> which controls the volume (level) of
- * the audio played back in the telephony conference represented by this
- * instance
- */
- public VolumeControl getOutputVolumeControl()
- {
- return outputVolumeControl;
- }
-
- /**
- * Gets the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
- * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
- * this telephony conference when the local peer is acting as a conference
- * focus.
- *
- * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
- * RTP and RTCP traffic is to be forwarded between
- * @return the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
- * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
- * this telephony conference when the local peer is acting as a conference
- * focus
- */
- public RTPTranslator getRTPTranslator(MediaType mediaType)
- {
- RTPTranslator rtpTranslator = null;
-
- /*
- * XXX A mixer is created for audio even when the local peer is not a
- * conference focus in order to enable additional functionality.
- * Similarly, the videoRTPTranslator is created even when the local peer
- * is not a conference focus in order to enable the local peer to turn
- * into a conference focus at a later time. More specifically,
- * MediaStreamImpl is unable to accommodate an RTPTranslator after it
- * has created its RTPManager. Yet again like the audio mixer, we'd
- * better not try to use it on Android at this time because of
- * performance issues that might arise.
- */
- if (MediaType.VIDEO.equals(mediaType)
- && (!OSUtils.IS_ANDROID || isConferenceFocus()))
- {
- if (videoRTPTranslator == null)
- {
- videoRTPTranslator
- = ProtocolMediaActivator
- .getMediaService()
- .createRTPTranslator();
- }
- rtpTranslator = videoRTPTranslator;
- }
- return rtpTranslator;
- }
-
- /**
- * Notifies this <tt>MediaAwareCallConference</tt> about changes in the
- * values of the properties of sources of <tt>PropertyChangeEvent</tt>s. For
- * example, this instance listens to changes of the value of
- * {@link MediaService#DEFAULT_DEVICE} which represents the user's choice
- * with respect to the default audio device.
- *
- * @param ev a <tt>PropertyChangeEvent</tt> which specifies the name of the
- * property which had its value changed and the old and new values of that
- * property
- */
- private void propertyChange(PropertyChangeEvent ev)
- {
- String propertyName = ev.getPropertyName();
-
- if (MediaService.DEFAULT_DEVICE.equals(propertyName))
- {
- Object source = ev.getSource();
-
- if (source instanceof MediaService)
- {
- /*
- * XXX We only support changing the default audio device at the
- * time of this writing.
- */
- int mediaTypeIndex = MediaType.AUDIO.ordinal();
- MediaDevice mixer = mixers[mediaTypeIndex];
- MediaDevice oldValue
- = (mixer instanceof MediaDeviceWrapper)
- ? ((MediaDeviceWrapper) mixer).getWrappedDevice()
- : null;
- MediaDevice newValue = devices[mediaTypeIndex];
-
- if (newValue == null)
- {
- newValue
- = ProtocolMediaActivator
- .getMediaService()
- .getDefaultDevice(
- MediaType.AUDIO,
- MediaUseCase.ANY);
- }
-
- /*
- * XXX If MediaService#getDefaultDevice(MediaType, MediaUseCase)
- * above returns null and its earlier return value was not null,
- * we will not notify of an actual change in the value of the
- * user's choice with respect to the default audio device.
- */
- if (oldValue != newValue)
- {
- mixers[mediaTypeIndex] = null;
- firePropertyChange(
- MediaAwareCall.DEFAULT_DEVICE,
- oldValue, newValue);
- }
- }
- }
- }
-
- /**
- * Sets the <tt>MediaDevice</tt> to be used by this telephony conference for
- * capture and/or playback of media of a specific <tt>MediaType</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> of the media which is to be
- * captured and/or played back by the specified <tt>device</tt>
- * @param device the <tt>MediaDevice</tt> to be used by this telephony
- * conference for capture and/or playback of media of the specified
- * <tt>mediaType</tt>
- */
- void setDevice(MediaType mediaType, MediaDevice device)
- {
- int mediaTypeIndex = mediaType.ordinal();
- MediaDevice oldValue = devices[mediaTypeIndex];
-
- /*
- * XXX While we know the old and the new master/wrapped devices, we
- * are not sure whether the mixer has been used. Anyway, we have to
- * report different values in order to have PropertyChangeSupport
- * really fire an event.
- */
- MediaDevice mixer = mixers[mediaTypeIndex];
-
- if (mixer instanceof MediaDeviceWrapper)
- oldValue = ((MediaDeviceWrapper) mixer).getWrappedDevice();
-
- MediaDevice newValue = devices[mediaTypeIndex] = device;
-
- if (oldValue != newValue)
- {
- mixers[mediaTypeIndex] = null;
- firePropertyChange(
- MediaAwareCall.DEFAULT_DEVICE,
- oldValue, newValue);
- }
- }
-
- /**
- * Implements a <tt>PropertyChangeListener</tt> which weakly references and
- * delegates to specific <tt>PropertyChangeListener</tt>s and automatically
- * adds itself to and removes itself from a specific
- * <tt>PropertyChangeNotifier</tt> depending on whether there are
- * <tt>PropertyChangeListener</tt>s to delegate to. Thus enables listening
- * to a <tt>PropertyChangeNotifier</tt> by invoking
- * {@link PropertyChangeNotifier#addPropertyChangeListener(
- * PropertyChangeListener)} without
- * {@link PropertyChangeNotifier#removePropertyChangeListener(
- * PropertyChangeListener)}.
- */
- private static class WeakPropertyChangeListener
- implements PropertyChangeListener
- {
- /**
- * The indicator which determines whether this
- * <tt>PropertyChangeListener</tt> has been added to {@link #notifier}.
- */
- private boolean added = false;
-
- /**
- * The list of <tt>PropertyChangeListener</tt>s which are to be notified
- * about <tt>PropertyChangeEvent</tt>s fired by {@link #notifier}.
- */
- private final List<WeakReference<PropertyChangeListener>> listeners
- = new LinkedList<WeakReference<PropertyChangeListener>>();
-
- /**
- * The <tt>PropertyChangeNotifier</tt> this instance is to listen to
- * about <tt>PropertyChangeEvent</tt>s which are to be forwarded to
- * {@link #listeners}.
- */
- private final PropertyChangeNotifier notifier;
-
- /**
- * Initializes a new <tt>WeakPropertyChangeListener</tt> instance.
- */
- protected WeakPropertyChangeListener()
- {
- this(null);
- }
-
- /**
- * Initializes a new <tt>WeakPropertyChangeListener</tt> instance which
- * is to listen to a specific <tt>PropertyChangeNotifier</tt>.
- *
- * @param notifier the <tt>PropertyChangeNotifier</tt> the new instance
- * is to listen to
- */
- public WeakPropertyChangeListener(PropertyChangeNotifier notifier)
- {
- this.notifier = notifier;
- }
-
- /**
- * Adds a specific <tt>PropertyChangeListener</tt> to the list of
- * <tt>PropertyChangeListener</tt>s to be notified about
- * <tt>PropertyChangeEvent</tt>s fired by the
- * <tt>PropertyChangeNotifier</tt> associated with this instance.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to add
- */
- public synchronized void addPropertyChangeListener(
- PropertyChangeListener listener)
- {
- Iterator<WeakReference<PropertyChangeListener>> i
- = listeners.iterator();
- boolean add = true;
-
- while (i.hasNext())
- {
- PropertyChangeListener l = i.next().get();
-
- if (l == null)
- i.remove();
- else if (l.equals(listener))
- add = false;
- }
- if (add
- && listeners.add(
- new WeakReference<PropertyChangeListener>(listener))
- && !this.added)
- {
- addThisToNotifier();
- this.added = true;
- }
- }
-
- /**
- * Adds this as a <tt>PropertyChangeListener</tt> to {@link #notifier}.
- */
- protected void addThisToNotifier()
- {
- if (notifier != null)
- notifier.addPropertyChangeListener(this);
- }
-
- /**
- * {@inheritDoc}
- *
- * Notifies this instance about a <tt>PropertyChangeEvent</tt> fired by
- * {@link #notifier}.
- */
- @Override
- public void propertyChange(PropertyChangeEvent ev)
- {
- PropertyChangeListener[] ls;
- int n;
-
- synchronized (this)
- {
- Iterator<WeakReference<PropertyChangeListener>> i
- = listeners.iterator();
-
- ls = new PropertyChangeListener[listeners.size()];
- n = 0;
- while (i.hasNext())
- {
- PropertyChangeListener l = i.next().get();
-
- if (l == null)
- i.remove();
- else
- ls[n++] = l;
- }
- if ((n == 0) && this.added)
- {
- removeThisFromNotifier();
- this.added = false;
- }
- }
-
- if (n != 0)
- {
- for (PropertyChangeListener l : ls)
- {
- if (l == null)
- break;
- else
- l.propertyChange(ev);
- }
- }
- }
-
- /**
- * Removes a specific <tt>PropertyChangeListener</tt> from the list of
- * <tt>PropertyChangeListener</tt>s to be notified about
- * <tt>PropertyChangeEvent</tt>s fired by the
- * <tt>PropertyChangeNotifier</tt> associated with this instance.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to remove
- */
- @SuppressWarnings("unused")
- public synchronized void removePropertyChangeListener(
- PropertyChangeListener listener)
- {
- Iterator<WeakReference<PropertyChangeListener>> i
- = listeners.iterator();
-
- while (i.hasNext())
- {
- PropertyChangeListener l = i.next().get();
-
- if ((l == null) || l.equals(listener))
- i.remove();
- }
- if (this.added && (listeners.size() == 0))
- {
- removeThisFromNotifier();
- this.added = false;
- }
- }
-
- /**
- * Removes this as a <tt>PropertyChangeListener</tt> from
- * {@link #notifier}.
- */
- protected void removeThisFromNotifier()
- {
- if (notifier != null)
- notifier.removePropertyChangeListener(this);
- }
- }
-}
+package net.java.sip.communicator.service.protocol.media;
+
+import java.beans.*;
+import java.lang.ref.*;
+import java.util.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jitsi.service.neomedia.device.*;
+import org.jitsi.util.*;
+import org.jitsi.util.event.*;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * Extends <tt>CallConference</tt> to represent the media-specific information
+ * associated with the telephony conference-related state of a
+ * <tt>MediaAwareCall</tt>.
+ *
+ * @author Lyubomir Marinov
+ */
+public class MediaAwareCallConference
+ extends CallConference
+{
+ /**
+ * The <tt>PropertyChangeListener</tt> which will listen to the
+ * <tt>MediaService</tt> about <tt>PropertyChangeEvent</tt>s.
+ */
+ private static WeakPropertyChangeListener
+ mediaServicePropertyChangeListener;
+
+ /**
+ * The <tt>MediaDevice</tt>s indexed by <tt>MediaType</tt> ordinal which are
+ * to be used by this telephony conference for media capture and/or
+ * playback. If the <tt>MediaDevice</tt> for a specific <tt>MediaType</tt>
+ * is <tt>null</tt>,
+ * {@link MediaService#getDefaultDevice(MediaType, MediaUseCase)} is called.
+ */
+ private final MediaDevice[] devices;
+
+ /**
+ * The <tt>MediaDevice</tt>s which implement media mixing on the respective
+ * <tt>MediaDevice</tt> in {@link #devices} for the purposes of this
+ * telephony conference.
+ */
+ private final MediaDevice[] mixers;
+
+ /**
+ * The <tt>VolumeControl</tt> implementation which is to control the volume
+ * (level) of the audio played back the telephony conference represented by
+ * this instance.
+ */
+ private final VolumeControl outputVolumeControl
+ = new BasicVolumeControl(
+ VolumeControl.PLAYBACK_VOLUME_LEVEL_PROPERTY_NAME);
+
+ /**
+ * The <tt>PropertyChangeListener</tt> which listens to sources of
+ * <tt>PropertyChangeEvent</tt>s on behalf of this instance.
+ */
+ private final PropertyChangeListener propertyChangeListener
+ = new PropertyChangeListener()
+ {
+ @Override
+ public void propertyChange(PropertyChangeEvent ev)
+ {
+ MediaAwareCallConference.this.propertyChange(ev);
+ }
+ };
+
+ /**
+ * The <tt>RTPTranslator</tt> which forwards video RTP and RTCP traffic
+ * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
+ * this telephony conference when the local peer is acting as a conference
+ * focus.
+ */
+ private RTPTranslator videoRTPTranslator;
+
+ /**
+ * The <tt>RTPTranslator</tt> which forwards autio RTP and RTCP traffic
+ * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
+ * this telephony conference when the local peer is acting as a conference
+ * focus.
+ */
+ private RTPTranslator audioRTPTranslator;
+
+ /**
+ * The indicator which determines whether the telephony conference
+ * represented by this instance is mixing or relaying.
+ * By default what can be mixed is mixed (audio) and rest is relayed.
+ */
+ private boolean translator = false;
+
+ /**
+ * Initializes a new <tt>MediaAwareCallConference</tt> instance.
+ */
+ public MediaAwareCallConference()
+ {
+ this(false);
+ }
+
+ /**
+ * Initializes a new <tt>MediaAwareCallConference</tt> instance which is to
+ * optionally utilize the Jitsi Videobridge server-side telephony
+ * conferencing technology.
+ *
+ * @param jitsiVideobridge <tt>true</tt> if the telephony conference
+ * represented by the new instance is to utilize the Jitsi Videobridge
+ * server-side telephony conferencing technology; otherwise, <tt>false</tt>
+ */
+ public MediaAwareCallConference(boolean jitsiVideobridge)
+ {
+ this(jitsiVideobridge, false);
+ }
+
+ /**
+ * Initializes a new <tt>MediaAwareCallConference</tt> instance which is to
+ * optionally utilize the Jitsi Videobridge server-side telephony
+ * conferencing technology.
+ *
+ * @param jitsiVideobridge <tt>true</tt> if the telephony conference
+ * represented by the new instance is to utilize the Jitsi Videobridge
+ * server-side telephony conferencing technology; otherwise, <tt>false</tt>
+ */
+ public MediaAwareCallConference(boolean jitsiVideobridge,
+ boolean translator)
+ {
+ super(jitsiVideobridge);
+
+ this.translator = translator;
+
+ int mediaTypeCount = MediaType.values().length;
+
+ devices = new MediaDevice[mediaTypeCount];
+ mixers = new MediaDevice[mediaTypeCount];
+
+ /*
+ * Listen to the MediaService in order to reflect changes in the user's
+ * selection with respect to the default media device.
+ */
+ addMediaServicePropertyChangeListener(propertyChangeListener);
+ }
+
+ /**
+ * Adds a specific <tt>PropertyChangeListener</tt> to be notified about
+ * <tt>PropertyChangeEvent</tt>s fired by the current <tt>MediaService</tt>
+ * implementation. The implementation adds a <tt>WeakReference</tt> to the
+ * specified <tt>listener</tt> because <tt>MediaAwareCallConference</tt>
+ * is unable to determine when the <tt>PropertyChangeListener</tt> is to be
+ * removed.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to add
+ */
+ private static synchronized void addMediaServicePropertyChangeListener(
+ PropertyChangeListener listener)
+ {
+ if (mediaServicePropertyChangeListener == null)
+ {
+ final MediaService mediaService
+ = ProtocolMediaActivator.getMediaService();
+
+ if (mediaService != null)
+ {
+ mediaServicePropertyChangeListener
+ = new WeakPropertyChangeListener()
+ {
+ @Override
+ protected void addThisToNotifier()
+ {
+ mediaService.addPropertyChangeListener(this);
+ }
+
+ @Override
+ protected void removeThisFromNotifier()
+ {
+ mediaService.removePropertyChangeListener(this);
+ }
+ };
+ }
+ }
+ if (mediaServicePropertyChangeListener != null)
+ {
+ mediaServicePropertyChangeListener.addPropertyChangeListener(
+ listener);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * If this telephony conference switches from being a conference focus to
+ * not being such, disposes of the mixers used by this instance when it was
+ * a conference focus
+ */
+ @Override
+ protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
+ {
+ /*
+ * If this telephony conference switches from being a conference
+ * focus to not being one, dispose of the mixers used when it was a
+ * conference focus.
+ */
+ if (oldValue && !newValue)
+ {
+ Arrays.fill(mixers, null);
+
+ /* Disposing the video translator is not needed when the conference
+ changes as we have video and we will want to continue with
+ the video
+ Removed when chasing a bug where video call becomes conference
+ call and then back again video call and the video from the
+ conference focus side is not transmitted.
+ if (videoRTPTranslator != null)
+ {
+ videoRTPTranslator.dispose();
+ videoRTPTranslator = null;
+ }
+ */
+ }
+
+ super.conferenceFocusChanged(oldValue, newValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Disposes of <tt>this.videoRTPTranslator</tt> if the removed <tt>Call</tt>
+ * was the last <tt>Call</tt> in this <tt>CallConference</tt>.
+ *
+ * @param call the <tt>Call</tt> which has been removed from the list of
+ * <tt>Call</tt>s participating in this telephony conference.
+ */
+ @Override
+ protected void callRemoved(Call call)
+ {
+ super.callRemoved(call);
+
+ if (getCallCount() == 0 && (videoRTPTranslator != null))
+ {
+ videoRTPTranslator.dispose();
+ videoRTPTranslator = null;
+ }
+ }
+
+ /**
+ * Gets a <tt>MediaDevice</tt> which is capable of capture and/or playback
+ * of media of the specified <tt>MediaType</tt> and is the default choice of
+ * the user with respect to such a <tt>MediaDevice</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> in which the retrieved
+ * <tt>MediaDevice</tt> is to capture and/or play back media
+ * @param useCase the <tt>MediaUseCase</tt> associated with the intended
+ * utilization of the <tt>MediaDevice</tt> to be retrieved
+ * @return a <tt>MediaDevice</tt> which is capable of capture and/or
+ * playback of media of the specified <tt>mediaType</tt> and is the default
+ * choice of the user with respect to such a <tt>MediaDevice</tt>
+ */
+ public MediaDevice getDefaultDevice(
+ MediaType mediaType,
+ MediaUseCase useCase)
+ {
+ int mediaTypeIndex = mediaType.ordinal();
+ MediaDevice device = devices[mediaTypeIndex];
+ MediaService mediaService = ProtocolMediaActivator.getMediaService();
+
+ if (device == null)
+ device = mediaService.getDefaultDevice(mediaType, useCase);
+
+ /*
+ * Make sure that the device is capable of mixing in order to support
+ * conferencing and call recording.
+ */
+ if (device != null)
+ {
+ MediaDevice mixer = mixers[mediaTypeIndex];
+
+ if (mixer == null)
+ {
+ switch (mediaType)
+ {
+ case AUDIO:
+ /*
+ * TODO AudioMixer leads to very poor audio quality on
+ * Android so do not use it unless it is really really
+ * necessary.
+ */
+ if ((!OSUtils.IS_ANDROID || isConferenceFocus())
+ && !this.translator
+ /*
+ * We can use the AudioMixer only if the device is
+ * able to capture (because the AudioMixer will push
+ * when the capture device pushes).
+ */
+ && device.getDirection().allowsSending())
+ {
+ mixer = mediaService.createMixer(device);
+ }
+ break;
+
+ case VIDEO:
+ if (isConferenceFocus())
+ mixer = mediaService.createMixer(device);
+ break;
+ }
+
+ mixers[mediaTypeIndex] = mixer;
+ }
+
+ if (mixer != null)
+ device = mixer;
+ }
+
+ return device;
+ }
+
+ /**
+ * Gets the <tt>VolumeControl</tt> which controls the volume (level) of the
+ * audio played back in the telephony conference represented by this
+ * instance.
+ *
+ * @return the <tt>VolumeControl</tt> which controls the volume (level) of
+ * the audio played back in the telephony conference represented by this
+ * instance
+ */
+ public VolumeControl getOutputVolumeControl()
+ {
+ return outputVolumeControl;
+ }
+
+ /**
+ * Gets the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
+ * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
+ * this telephony conference when the local peer is acting as a conference
+ * focus.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
+ * RTP and RTCP traffic is to be forwarded between
+ * @return the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
+ * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
+ * this telephony conference when the local peer is acting as a conference
+ * focus
+ */
+ public RTPTranslator getRTPTranslator(MediaType mediaType)
+ {
+ /*
+ * XXX A mixer is created for audio even when the local peer is not a
+ * conference focus in order to enable additional functionality.
+ * Similarly, the videoRTPTranslator is created even when the local peer
+ * is not a conference focus in order to enable the local peer to turn
+ * into a conference focus at a later time. More specifically,
+ * MediaStreamImpl is unable to accommodate an RTPTranslator after it
+ * has created its RTPManager. Yet again like the audio mixer, we'd
+ * better not try to use it on Android at this time because of
+ * performance issues that might arise.
+ */
+ if (MediaType.VIDEO.equals(mediaType)
+ && (!OSUtils.IS_ANDROID || isConferenceFocus()))
+ {
+ if (videoRTPTranslator == null)
+ {
+ videoRTPTranslator
+ = ProtocolMediaActivator
+ .getMediaService()
+ .createRTPTranslator();
+ }
+ return videoRTPTranslator;
+ }
+
+ if (this.translator)
+ {
+ if(audioRTPTranslator == null)
+ {
+ audioRTPTranslator
+ = ProtocolMediaActivator
+ .getMediaService()
+ .createRTPTranslator();
+ }
+ return audioRTPTranslator;
+ }
+
+ return null;
+ }
+
+ /**
+ * Notifies this <tt>MediaAwareCallConference</tt> about changes in the
+ * values of the properties of sources of <tt>PropertyChangeEvent</tt>s. For
+ * example, this instance listens to changes of the value of
+ * {@link MediaService#DEFAULT_DEVICE} which represents the user's choice
+ * with respect to the default audio device.
+ *
+ * @param ev a <tt>PropertyChangeEvent</tt> which specifies the name of the
+ * property which had its value changed and the old and new values of that
+ * property
+ */
+ private void propertyChange(PropertyChangeEvent ev)
+ {
+ String propertyName = ev.getPropertyName();
+
+ if (MediaService.DEFAULT_DEVICE.equals(propertyName))
+ {
+ Object source = ev.getSource();
+
+ if (source instanceof MediaService)
+ {
+ /*
+ * XXX We only support changing the default audio device at the
+ * time of this writing.
+ */
+ int mediaTypeIndex = MediaType.AUDIO.ordinal();
+ MediaDevice mixer = mixers[mediaTypeIndex];
+ MediaDevice oldValue
+ = (mixer instanceof MediaDeviceWrapper)
+ ? ((MediaDeviceWrapper) mixer).getWrappedDevice()
+ : null;
+ MediaDevice newValue = devices[mediaTypeIndex];
+
+ if (newValue == null)
+ {
+ newValue
+ = ProtocolMediaActivator
+ .getMediaService()
+ .getDefaultDevice(
+ MediaType.AUDIO,
+ MediaUseCase.ANY);
+ }
+
+ /*
+ * XXX If MediaService#getDefaultDevice(MediaType, MediaUseCase)
+ * above returns null and its earlier return value was not null,
+ * we will not notify of an actual change in the value of the
+ * user's choice with respect to the default audio device.
+ */
+ if (oldValue != newValue)
+ {
+ mixers[mediaTypeIndex] = null;
+ firePropertyChange(
+ MediaAwareCall.DEFAULT_DEVICE,
+ oldValue, newValue);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the <tt>MediaDevice</tt> to be used by this telephony conference for
+ * capture and/or playback of media of a specific <tt>MediaType</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the media which is to be
+ * captured and/or played back by the specified <tt>device</tt>
+ * @param device the <tt>MediaDevice</tt> to be used by this telephony
+ * conference for capture and/or playback of media of the specified
+ * <tt>mediaType</tt>
+ */
+ void setDevice(MediaType mediaType, MediaDevice device)
+ {
+ int mediaTypeIndex = mediaType.ordinal();
+ MediaDevice oldValue = devices[mediaTypeIndex];
+
+ /*
+ * XXX While we know the old and the new master/wrapped devices, we
+ * are not sure whether the mixer has been used. Anyway, we have to
+ * report different values in order to have PropertyChangeSupport
+ * really fire an event.
+ */
+ MediaDevice mixer = mixers[mediaTypeIndex];
+
+ if (mixer instanceof MediaDeviceWrapper)
+ oldValue = ((MediaDeviceWrapper) mixer).getWrappedDevice();
+
+ MediaDevice newValue = devices[mediaTypeIndex] = device;
+
+ if (oldValue != newValue)
+ {
+ mixers[mediaTypeIndex] = null;
+ firePropertyChange(
+ MediaAwareCall.DEFAULT_DEVICE,
+ oldValue, newValue);
+ }
+ }
+
+ /**
+ * Implements a <tt>PropertyChangeListener</tt> which weakly references and
+ * delegates to specific <tt>PropertyChangeListener</tt>s and automatically
+ * adds itself to and removes itself from a specific
+ * <tt>PropertyChangeNotifier</tt> depending on whether there are
+ * <tt>PropertyChangeListener</tt>s to delegate to. Thus enables listening
+ * to a <tt>PropertyChangeNotifier</tt> by invoking
+ * {@link PropertyChangeNotifier#addPropertyChangeListener(
+ * PropertyChangeListener)} without
+ * {@link PropertyChangeNotifier#removePropertyChangeListener(
+ * PropertyChangeListener)}.
+ */
+ private static class WeakPropertyChangeListener
+ implements PropertyChangeListener
+ {
+ /**
+ * The indicator which determines whether this
+ * <tt>PropertyChangeListener</tt> has been added to {@link #notifier}.
+ */
+ private boolean added = false;
+
+ /**
+ * The list of <tt>PropertyChangeListener</tt>s which are to be notified
+ * about <tt>PropertyChangeEvent</tt>s fired by {@link #notifier}.
+ */
+ private final List<WeakReference<PropertyChangeListener>> listeners
+ = new LinkedList<WeakReference<PropertyChangeListener>>();
+
+ /**
+ * The <tt>PropertyChangeNotifier</tt> this instance is to listen to
+ * about <tt>PropertyChangeEvent</tt>s which are to be forwarded to
+ * {@link #listeners}.
+ */
+ private final PropertyChangeNotifier notifier;
+
+ /**
+ * Initializes a new <tt>WeakPropertyChangeListener</tt> instance.
+ */
+ protected WeakPropertyChangeListener()
+ {
+ this(null);
+ }
+
+ /**
+ * Initializes a new <tt>WeakPropertyChangeListener</tt> instance which
+ * is to listen to a specific <tt>PropertyChangeNotifier</tt>.
+ *
+ * @param notifier the <tt>PropertyChangeNotifier</tt> the new instance
+ * is to listen to
+ */
+ public WeakPropertyChangeListener(PropertyChangeNotifier notifier)
+ {
+ this.notifier = notifier;
+ }
+
+ /**
+ * Adds a specific <tt>PropertyChangeListener</tt> to the list of
+ * <tt>PropertyChangeListener</tt>s to be notified about
+ * <tt>PropertyChangeEvent</tt>s fired by the
+ * <tt>PropertyChangeNotifier</tt> associated with this instance.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to add
+ */
+ public synchronized void addPropertyChangeListener(
+ PropertyChangeListener listener)
+ {
+ Iterator<WeakReference<PropertyChangeListener>> i
+ = listeners.iterator();
+ boolean add = true;
+
+ while (i.hasNext())
+ {
+ PropertyChangeListener l = i.next().get();
+
+ if (l == null)
+ i.remove();
+ else if (l.equals(listener))
+ add = false;
+ }
+ if (add
+ && listeners.add(
+ new WeakReference<PropertyChangeListener>(listener))
+ && !this.added)
+ {
+ addThisToNotifier();
+ this.added = true;
+ }
+ }
+
+ /**
+ * Adds this as a <tt>PropertyChangeListener</tt> to {@link #notifier}.
+ */
+ protected void addThisToNotifier()
+ {
+ if (notifier != null)
+ notifier.addPropertyChangeListener(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Notifies this instance about a <tt>PropertyChangeEvent</tt> fired by
+ * {@link #notifier}.
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent ev)
+ {
+ PropertyChangeListener[] ls;
+ int n;
+
+ synchronized (this)
+ {
+ Iterator<WeakReference<PropertyChangeListener>> i
+ = listeners.iterator();
+
+ ls = new PropertyChangeListener[listeners.size()];
+ n = 0;
+ while (i.hasNext())
+ {
+ PropertyChangeListener l = i.next().get();
+
+ if (l == null)
+ i.remove();
+ else
+ ls[n++] = l;
+ }
+ if ((n == 0) && this.added)
+ {
+ removeThisFromNotifier();
+ this.added = false;
+ }
+ }
+
+ if (n != 0)
+ {
+ for (PropertyChangeListener l : ls)
+ {
+ if (l == null)
+ break;
+ else
+ l.propertyChange(ev);
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>PropertyChangeListener</tt> from the list of
+ * <tt>PropertyChangeListener</tt>s to be notified about
+ * <tt>PropertyChangeEvent</tt>s fired by the
+ * <tt>PropertyChangeNotifier</tt> associated with this instance.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to remove
+ */
+ @SuppressWarnings("unused")
+ public synchronized void removePropertyChangeListener(
+ PropertyChangeListener listener)
+ {
+ Iterator<WeakReference<PropertyChangeListener>> i
+ = listeners.iterator();
+
+ while (i.hasNext())
+ {
+ PropertyChangeListener l = i.next().get();
+
+ if ((l == null) || l.equals(listener))
+ i.remove();
+ }
+ if (this.added && (listeners.size() == 0))
+ {
+ removeThisFromNotifier();
+ this.added = false;
+ }
+ }
+
+ /**
+ * Removes this as a <tt>PropertyChangeListener</tt> from
+ * {@link #notifier}.
+ */
+ protected void removeThisFromNotifier()
+ {
+ if (notifier != null)
+ notifier.removePropertyChangeListener(this);
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
index 4e15038..23e42d4 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,1247 +15,1247 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol.media;
-
-import java.beans.*;
-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.jitsi.service.neomedia.*;
-import org.jitsi.service.neomedia.event.*;
-
-/**
- * A utility class implementing media control code shared between current
- * telephony implementations. This class is only meant for use by protocol
- * implementations and should/could not be accessed by bundles that are simply
- * using the telephony functionalities.
- *
- * @param <T> the peer extension class like for example <tt>CallSipImpl</tt>
- * or <tt>CallJabberImpl</tt>
- * @param <U> the media handler extension class like for example
- * <tt>CallPeerMediaHandlerSipImpl</tt> or
- * <tt>CallPeerMediaHandlerJabberImpl</tt>
- * @param <V> the provider extension class like for example
- * <tt>ProtocolProviderServiceSipImpl</tt> or
- * <tt>ProtocolProviderServiceJabberImpl</tt>
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Boris Grozev
- */
-public abstract class MediaAwareCallPeer
- <T extends MediaAwareCall<?, ?, V>,
- U extends CallPeerMediaHandler<?>,
- V extends ProtocolProviderService>
- extends AbstractCallPeer<T, V>
- implements SrtpListener,
- CallPeerConferenceListener,
- CsrcAudioLevelListener,
- SimpleAudioLevelListener
-{
- /**
- * The <tt>Logger</tt> used by the <tt>MediaAwareCallPeer</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(MediaAwareCallPeer.class);
-
- /**
- * The call this peer belongs to.
- */
- private T call;
-
- /**
- * The listeners registered for level changes in the audio of participants
- * that this peer might be mixing and that we are not directly communicating
- * with.
- */
- private final List<ConferenceMembersSoundLevelListener>
- conferenceMembersSoundLevelListeners
- = new ArrayList<ConferenceMembersSoundLevelListener>();
-
- /**
- * A byte array containing the image/photo representing the call peer.
- */
- private byte[] image;
-
- /**
- * The media handler class handles all media management for a single
- * <tt>CallPeer</tt>. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>always
- * corresponds to exactly one instance of <tt>CallPeerMediaHandler</tt> and
- * both classes are only separated for reasons of readability.
- */
- private U mediaHandler;
-
- /**
- * The <tt>PropertyChangeListener</tt> which listens to
- * {@link CallPeerMediaHandler} for changes in the values of its properties.
- */
- private PropertyChangeListener mediaHandlerPropertyChangeListener;
-
- /**
- * A string uniquely identifying the peer.
- */
- private String peerID;
-
- /**
- * The protocol provider that this peer belongs to.
- */
- private final V protocolProvider;
-
- /**
- * The list of <tt>SoundLevelListener</tt>s interested in level changes in
- * the audio we are getting from the remote peer.
- * <p>
- * It is implemented as a copy-on-write storage because the number of
- * additions and removals of <tt>SoundLevelListener</tt>s is expected to be
- * far smaller than the number of audio level changes. The access to it is
- * to be synchronized using {@link #streamSoundLevelListenersSyncRoot}.
- * </p>
- */
- private List<SoundLevelListener> streamSoundLevelListeners;
-
- /**
- * The <tt>Object</tt> to synchronize the access to
- * {@link #streamSoundLevelListeners}.
- */
- private final Object streamSoundLevelListenersSyncRoot = new Object();
-
- /**
- * The <tt>List</tt> of <tt>PropertyChangeListener</tt>s listening to this
- * <tt>CallPeer</tt> for changes in the values of its properties related to
- * video.
- */
- private final List<PropertyChangeListener> videoPropertyChangeListeners
- = new LinkedList<PropertyChangeListener>();
-
- /**
- * Represents the last Conference Information (RFC4575) document sent to
- * this <tt>CallPeer</tt>. This is always a document with state "full", even
- * if the last document actually sent was a "partial"
- */
- private ConferenceInfoDocument lastConferenceInfoSent = null;
-
- /**
- * The time (as obtained by <tt>System.currentTimeMillis()</tt>) at which
- * a Conference Information (RFC4575) document was last sent to this
- * <tt>CallPeer</tt>.
- */
- private long lastConferenceInfoSentTimestamp = -1;
-
- /**
- * The last Conference Information (RFC4575) document sent to us by this
- * <tt>CallPeer</tt>. This is always a document with state "full", which is
- * only gets updated by "partial" or "deleted" documents.
- */
- private ConferenceInfoDocument lastConferenceInfoReceived = null;
-
- /**
- * Whether a conference-info document has been scheduled to be sent to this
- * <tt>CallPeer</tt>
- */
- private boolean confInfoScheduled = false;
-
- /**
- * Synchronization object for confInfoScheduled
- */
- private final Object confInfoScheduledSyncRoot = new Object();
-
- /**
- * Creates a new call peer with address <tt>peerAddress</tt>.
- *
- * @param owningCall the call that contains this call peer.
- */
- public MediaAwareCallPeer(T owningCall)
- {
- this.call = owningCall;
- this.protocolProvider = owningCall.getProtocolProvider();
-
- // create the uid
- this.peerID
- = String.valueOf(System.currentTimeMillis())
- + String.valueOf(hashCode());
-
- // we listen for events when the call will become focus or not
- // of a conference so we will add or remove our sound level listeners
- super.addCallPeerConferenceListener(this);
- }
-
- /**
- * Adds a specific <tt>ConferenceMembersSoundLevelListener</tt> to the list
- * of listeners interested in and notified about changes in conference
- * members sound level.
- *
- * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to add
- * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
- */
- public void addConferenceMembersSoundLevelListener(
- ConferenceMembersSoundLevelListener listener)
- {
- /*
- * XXX The uses of the method at the time of this writing rely on being
- * able to add a null listener so make it a no-op here.
- */
- if (listener == null)
- return;
-
- synchronized (conferenceMembersSoundLevelListeners)
- {
- if (conferenceMembersSoundLevelListeners.size() == 0)
- {
- // if this is the first listener that's being registered with
- // us, we also need to register ourselves as a CSRC audio level
- // listener with the media handler.
- getMediaHandler().setCsrcAudioLevelListener(this);
- }
- conferenceMembersSoundLevelListeners.add(listener);
- }
- }
-
- /**
- * Adds a specific <tt>SoundLevelListener</tt> to the list of listeners
- * interested in and notified about changes in the sound level of the audio
- * sent by the remote party. When the first listener is being registered
- * the method also registers its single listener with the media handler so
- * that it would receive level change events and delegate them to the
- * listeners that have registered with us.
- *
- * @param listener the <tt>SoundLevelListener</tt> to add
- */
- public void addStreamSoundLevelListener(SoundLevelListener listener)
- {
- synchronized (streamSoundLevelListenersSyncRoot)
- {
- if ((streamSoundLevelListeners == null)
- || streamSoundLevelListeners.isEmpty())
- {
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- if (isJitsiVideobridge())
- {
- /*
- * When the local user/peer has organized a telephony
- * conference utilizing the Jitsi Videobridge server-side
- * technology, the server will calculate the audio levels
- * and not the client.
- */
- mediaHandler.setCsrcAudioLevelListener(this);
- }
- else
- {
- /*
- * If this is the first listener that's being registered
- * with us, we also need to register ourselves as an audio
- * level listener with the media handler. We do this so that
- * audio levels would only be calculated if anyone is
- * interested in receiving them.
- */
- mediaHandler.setStreamAudioLevelListener(this);
- }
- }
-
- /*
- * Implement streamAudioLevelListeners as a copy-on-write storage so
- * that iterators over it can iterate without
- * ConcurrentModificationExceptions.
- */
- streamSoundLevelListeners
- = (streamSoundLevelListeners == null)
- ? new ArrayList<SoundLevelListener>()
- : new ArrayList<SoundLevelListener>(
- streamSoundLevelListeners);
- streamSoundLevelListeners.add(listener);
- }
- }
-
- /**
- * Adds a specific <tt>PropertyChangeListener</tt> to the list of
- * listeners which get notified when the properties (e.g.
- * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
- * their values.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to be notified
- * when the properties associated with the specified <tt>Call</tt> change
- * their values
- */
- public void addVideoPropertyChangeListener(PropertyChangeListener listener)
- {
- if (listener == null)
- throw new NullPointerException("listener");
-
- synchronized (videoPropertyChangeListeners)
- {
- /*
- * The video is part of the media-related functionality and thus it
- * is the responsibility of mediaHandler. So listen to mediaHandler
- * for video-related property changes and re-fire them as
- * originating from this instance.
- */
- if (!videoPropertyChangeListeners.contains(listener)
- && videoPropertyChangeListeners.add(listener)
- && (mediaHandlerPropertyChangeListener == null))
- {
- mediaHandlerPropertyChangeListener
- = new PropertyChangeListener()
- {
- public void propertyChange(PropertyChangeEvent event)
- {
- Iterable<PropertyChangeListener> listeners;
-
- synchronized (videoPropertyChangeListeners)
- {
- listeners
- = new LinkedList<PropertyChangeListener>(
- videoPropertyChangeListeners);
- }
-
- PropertyChangeEvent thisEvent
- = new PropertyChangeEvent(
- this,
- event.getPropertyName(),
- event.getOldValue(),
- event.getNewValue());
-
- for (PropertyChangeListener listener : listeners)
- listener.propertyChange(thisEvent);
- }
- };
- getMediaHandler()
- .addPropertyChangeListener(
- mediaHandlerPropertyChangeListener);
- }
- }
- }
-
- /**
- * Notified by its very majesty the media service about changes in the audio
- * level of the stream coming from this peer, the method generates the
- * corresponding events and delivers them to the listeners that have
- * registered here.
- *
- * @param newLevel the new audio level of the audio stream received from the
- * remote peer
- */
- public void audioLevelChanged(int newLevel)
- {
- /*
- * If we're in a conference in which this CallPeer is the focus and
- * we're the only member in it besides the focus, we will not receive
- * audio levels in the RTP and our media will instead measure the audio
- * levels of the received media. In order to make the UI oblivious of
- * the difference, we have to translate the event to the appropriate
- * type of listener.
- *
- * We may end up in a conference call with 0 members if the server
- * for some reason doesn't support sip conference (our subscribes
- * doesn't go to the focus of the conference) and so we must
- * pass the sound levels measured on the stream so we can see
- * the stream activity of the call.
- */
- int conferenceMemberCount = getConferenceMemberCount();
-
- if ((conferenceMemberCount > 0) && (conferenceMemberCount < 3))
- {
- long audioRemoteSSRC
- = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
-
- if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
- {
- audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel });
- return;
- }
- }
-
- fireStreamSoundLevelChanged(newLevel);
- }
-
-
- /**
- * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[])}.
- * Delivers the received audio levels to the
- * {@link ConferenceMembersSoundLevelListener}s registered with this
- * <tt>MediaAwareCallPeer</tt>..
- *
- * @param audioLevels the levels that we need to dispatch to all registered
- * <tt>ConferenceMemberSoundLevelListeners</tt>.
- */
- public void audioLevelsReceived(long[] audioLevels)
- {
- /*
- * When the local user/peer has organized a telephony conference
- * utilizing the Jitsi Videobridge server-side technology, the server
- * will calculate the audio levels and not the client.
- */
- if (isJitsiVideobridge())
- {
- long audioRemoteSSRC
- = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
-
- if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
- {
- for (int i = 0; i < audioLevels.length; i += 2)
- {
- if (audioLevels[i] == audioRemoteSSRC)
- {
- fireStreamSoundLevelChanged((int) audioLevels[i + 1]);
- break;
- }
- }
- }
- }
-
- if (getConferenceMemberCount() == 0)
- return;
-
- Map<ConferenceMember, Integer> levelsMap
- = new HashMap<ConferenceMember, Integer>();
-
- for (int i = 0; i < audioLevels.length; i += 2)
- {
- ConferenceMember mmbr = findConferenceMember(audioLevels[i]);
-
- if (mmbr != null)
- levelsMap.put(mmbr, (int) audioLevels[i + 1]);
- }
-
- synchronized (conferenceMembersSoundLevelListeners)
- {
- int conferenceMemberSoundLevelListenerCount
- = conferenceMembersSoundLevelListeners.size();
-
- if (conferenceMemberSoundLevelListenerCount > 0)
- {
- ConferenceMembersSoundLevelEvent ev
- = new ConferenceMembersSoundLevelEvent(this, levelsMap);
-
- for (int i = 0;
- i < conferenceMemberSoundLevelListenerCount;
- i++)
- {
- conferenceMembersSoundLevelListeners
- .get(i)
- .soundLevelChanged(ev);
- }
- }
- }
- }
-
- /**
- * Does nothing.
- * @param evt the event.
- */
- public void callPeerAdded(CallPeerEvent evt) {}
-
- /**
- * Does nothing.
- * @param evt the event.
- */
- public void callPeerRemoved(CallPeerEvent evt) {}
-
- /**
- * Dummy implementation of {@link CallPeerConferenceListener
- * #conferenceFocusChanged(CallPeerConferenceEvent)}.
- *
- * @param evt ignored
- */
- public void conferenceFocusChanged(CallPeerConferenceEvent evt)
- {
- }
-
- /**
- * Called when this peer becomes a mixer. The method add removes this class
- * as the stream audio level listener for the media coming from this peer
- * because the levels it delivers no longer represent the level of a
- * particular member. The method also adds this class as a member (CSRC)
- * audio level listener.
- *
- * @param conferenceEvent the event containing information (that we don't
- * really use) on the newly add member.
- */
- public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent)
- {
- if (getConferenceMemberCount() > 2)
- {
- /*
- * This peer is now a conference focus with more than three
- * participants. It means that this peer is mixing and sending us
- * audio for at least two separate participants. We therefore need
- * to switch from stream to CSRC level listening.
- */
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- mediaHandler.setStreamAudioLevelListener(null);
- mediaHandler.setCsrcAudioLevelListener(this);
- }
- }
-
- /**
- * Dummy implementation of {@link CallPeerConferenceListener
- * #conferenceMemberErrorReceived(CallPeerConferenceEvent)}.
- *
- * @param ev the event
- */
- public void conferenceMemberErrorReceived(CallPeerConferenceEvent ev) {};
-
- /**
- * Called when this peer stops being a mixer. The method add removes this
- * class as the stream audio level listener for the media coming from this
- * peer because the levels it delivers no longer represent the level of a
- * particular member. The method also adds this class as a member (CSRC)
- * audio level listener.
- *
- * @param conferenceEvent the event containing information (that we don't
- * really use) on the freshly removed member.
- */
- public void conferenceMemberRemoved(CallPeerConferenceEvent conferenceEvent)
- {
- if (getConferenceMemberCount() < 3)
- {
- /*
- * This call peer is no longer mixing audio from multiple sources
- * since there's only us and her in the call. We therefore need to
- * switch from CSRC to stream level listening.
- */
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- mediaHandler.setStreamAudioLevelListener(this);
- mediaHandler.setCsrcAudioLevelListener(null);
- }
- }
-
- /**
- * Invokes {@link SoundLevelListener#soundLevelChanged(Object, int) on
- * the <tt>SoundLevelListener</tt>s interested in the changes of the audio
- * stream received from the remote peer i.e. in
- * {@link #streamSoundLevelListeners}.
- *
- * @param newLevel the new value of the sound level to notify
- * <tt>streamSoundLevelListeners</tt> about
- */
- private void fireStreamSoundLevelChanged(int newLevel)
- {
- List<SoundLevelListener> streamSoundLevelListeners;
-
- synchronized (streamSoundLevelListenersSyncRoot)
- {
- /*
- * Since the streamAudioLevelListeners field of this
- * MediaAwareCallPeer is implemented as a copy-on-write storage,
- * just get a reference to it and it should be safe to iterate over it
- * without ConcurrentModificationExceptions.
- */
- streamSoundLevelListeners = this.streamSoundLevelListeners;
- }
-
- if (streamSoundLevelListeners != null)
- {
- /*
- * Iterate over streamAudioLevelListeners using an index rather than
- * an Iterator in order to try to reduce the number of allocations
- * (as the number of audio level changes is expected to be very
- * large).
- */
- int streamSoundLevelListenerCount
- = streamSoundLevelListeners.size();
-
- for(int i = 0; i < streamSoundLevelListenerCount; i++)
- {
- streamSoundLevelListeners.get(i).soundLevelChanged(
- this,
- newLevel);
- }
- }
- }
-
- /**
- * Returns a reference to the call that this peer belongs to. Calls
- * are created by underlying telephony protocol implementations.
- *
- * @return a reference to the call containing this peer.
- */
- @Override
- public T getCall()
- {
- return call;
- }
-
- /**
- * The method returns an image representation of the call peer if one is
- * available.
- *
- * @return byte[] a byte array containing the image or null if no image is
- * available.
- */
- public byte[] getImage()
- {
- return image;
- }
-
- /**
- * Returns a reference to the <tt>CallPeerMediaHandler</tt> used by this
- * peer. The media handler class handles all media management for a single
- * <tt>CallPeer</tt>. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
- * always corresponds to exactly one instance of
- * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
- * reasons of readability.
- *
- * @return a reference to the <tt>CallPeerMediaHandler</tt> instance that
- * this peer uses for media related tips and tricks.
- */
- public U getMediaHandler()
- {
- return mediaHandler;
- }
-
- /**
- * Returns a unique identifier representing this peer.
- *
- * @return an identifier representing this call peer.
- */
- public String getPeerID()
- {
- return peerID;
- }
-
- /**
- * Returns the protocol provider that this peer belongs to.
- *
- * @return a reference to the <tt>ProtocolProviderService</tt> that this
- * peer belongs to.
- */
- @Override
- public V getProtocolProvider()
- {
- return protocolProvider;
- }
-
- /**
- * Determines whether this <tt>CallPeer</tt> is participating in a telephony
- * conference organized by the local user/peer utilizing the Jitsi
- * Videobridge server-side technology.
- *
- * @return <tt>true</tt> if this <tt>CallPeer</tt> is participating in a
- * telephony conference organized by the local user/peer utilizing the Jitsi
- * Videobridge server-side technology; otherwise, <tt>false</tt>
- */
- public final boolean isJitsiVideobridge()
- {
- Call call = getCall();
-
- if (call != null)
- {
- CallConference conference = call.getConference();
-
- if (conference != null)
- return conference.isJitsiVideobridge();
- }
- return false;
- }
-
- /**
- * Determines whether we are currently streaming video toward whoever this
- * <tt>MediaAwareCallPeer</tt> represents.
- *
- * @return <tt>true</tt> if we are currently streaming video toward this
- * <tt>CallPeer</tt> and <tt>false</tt> otherwise.
- */
- public boolean isLocalVideoStreaming()
- {
- return getMediaHandler().isLocalVideoTransmissionEnabled();
- }
-
- /**
- * Determines whether the audio stream (if any) being sent to this
- * peer is mute.
- *
- * @return <tt>true</tt> if an audio stream is being sent to this
- * peer and it is currently mute; <tt>false</tt>, otherwise
- */
- @Override
- public boolean isMute()
- {
- return getMediaHandler().isMute();
- }
-
- /**
- * Logs <tt>message</tt> and <tt>cause</tt> and sets this <tt>peer</tt>'s
- * state to <tt>CallPeerState.FAILED</tt>
- *
- * @param message a message to log and display to the user.
- * @param throwable the exception that cause the error we are logging
- */
- public void logAndFail(String message, Throwable throwable)
- {
- logger.error(message, throwable);
- setState(CallPeerState.FAILED, message);
- }
-
- /**
- * Updates the state of this <tt>CallPeer</tt> to match the locally-on-hold
- * status of our media handler.
- */
- public void reevalLocalHoldStatus()
- {
- CallPeerState state = getState();
- boolean locallyOnHold = getMediaHandler().isLocallyOnHold();
-
- if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
- {
- if (!locallyOnHold)
- setState(CallPeerState.CONNECTED);
- }
- else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
- {
- if (!locallyOnHold)
- setState(CallPeerState.ON_HOLD_REMOTELY);
- }
- else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
- {
- if (locallyOnHold)
- setState(CallPeerState.ON_HOLD_MUTUALLY);
- }
- else if (locallyOnHold)
- {
- setState(CallPeerState.ON_HOLD_LOCALLY);
- }
- }
-
- /**
- * Updates the state of this <tt>CallPeer</tt> to match the remotely-on-hold
- * status of our media handler.
- */
- public void reevalRemoteHoldStatus()
- {
- boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold();
-
- CallPeerState state = getState();
- if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
- {
- if (remotelyOnHold)
- setState(CallPeerState.ON_HOLD_MUTUALLY);
- }
- else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
- {
- if (!remotelyOnHold)
- setState(CallPeerState.ON_HOLD_LOCALLY);
- }
- else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
- {
- if (!remotelyOnHold)
- setState(CallPeerState.CONNECTED);
- }
- else if (remotelyOnHold)
- {
- setState(CallPeerState.ON_HOLD_REMOTELY);
- }
- }
-
- /**
- * Removes a specific <tt>ConferenceMembersSoundLevelListener</tt> of the
- * list of listeners interested in and notified about changes in conference
- * members sound level.
- *
- * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to
- * remove
- */
- public void removeConferenceMembersSoundLevelListener(
- ConferenceMembersSoundLevelListener listener)
- {
- synchronized (conferenceMembersSoundLevelListeners)
- {
- if (conferenceMembersSoundLevelListeners.remove(listener)
- && (conferenceMembersSoundLevelListeners.size() == 0))
- {
- // if this was the last listener then we also remove ourselves
- // as a CSRC audio level listener from the handler so that we
- // don't have to create new events and maps for something no one
- // is interested in.
- getMediaHandler().setCsrcAudioLevelListener(null);
- }
- }
- }
-
- /**
- * Removes a specific <tt>SoundLevelListener</tt> of the list of
- * listeners interested in and notified about changes in stream sound level
- * related information.
- *
- * @param listener the <tt>SoundLevelListener</tt> to remove
- */
- public void removeStreamSoundLevelListener(SoundLevelListener listener)
- {
- synchronized (streamSoundLevelListenersSyncRoot)
- {
- /*
- * Implement streamAudioLevelListeners as a copy-on-write storage so
- * that iterators over it can iterate over it without
- * ConcurrentModificationExceptions.
- */
- if (streamSoundLevelListeners != null)
- {
- streamSoundLevelListeners
- = new ArrayList<SoundLevelListener>(
- streamSoundLevelListeners);
- if (streamSoundLevelListeners.remove(listener)
- && streamSoundLevelListeners.isEmpty())
- streamSoundLevelListeners = null;
- }
-
- if ((streamSoundLevelListeners == null)
- || streamSoundLevelListeners.isEmpty())
- {
- // if this was the last listener then we also need to remove
- // ourselves as an audio level so that audio levels would only
- // be calculated if anyone is interested in receiving them.
- getMediaHandler().setStreamAudioLevelListener(null);
- }
- }
- }
-
- /**
- * Removes a specific <tt>PropertyChangeListener</tt> from the list of
- * listeners which get notified when the properties (e.g.
- * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
- * their values.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to no longer be
- * notified when the properties associated with the specified <tt>Call</tt>
- * change their values
- */
- public void removeVideoPropertyChangeListener(
- PropertyChangeListener listener)
- {
- if (listener != null)
- synchronized (videoPropertyChangeListeners)
- {
- /*
- * The video is part of the media-related functionality and thus
- * it is the responsibility of mediaHandler. So we're listening
- * to mediaHandler for video-related property changes and w're
- * re-firing them as originating from this instance. Make sure
- * that we're not listening to mediaHandler if noone is
- * interested in video-related property changes originating from
- * this instance.
- */
- if (videoPropertyChangeListeners.remove(listener)
- && videoPropertyChangeListeners.isEmpty()
- && (mediaHandlerPropertyChangeListener != null))
- {
-// getMediaHandler()
-// .removePropertyChangeListener(
-// mediaHandlerPropertyChangeListener);
- mediaHandlerPropertyChangeListener = null;
- }
- }
- }
-
- /**
- * Sets the security message associated with a failure/warning or
- * information coming from the encryption protocol.
- *
- * @param messageType the type of the message.
- * @param i18nMessage the message
- * @param severity severity level
- */
- public void securityMessageReceived(
- String messageType,
- String i18nMessage,
- int severity)
- {
- fireCallPeerSecurityMessageEvent(messageType,
- i18nMessage,
- severity);
- }
-
- /**
- * Indicates that the other party has timeouted replying to our
- * offer to secure the connection.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- * @param sender the security controller that caused the event
- */
- public void securityNegotiationStarted(
- MediaType mediaType,
- SrtpControl sender)
- {
- fireCallPeerSecurityNegotiationStartedEvent(
- new CallPeerSecurityNegotiationStartedEvent(
- this,
- toSessionType(mediaType),
- sender));
- }
-
- /**
- * Indicates that the other party has timeouted replying to our
- * offer to secure the connection.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- */
- public void securityTimeout(MediaType mediaType)
- {
- fireCallPeerSecurityTimeoutEvent(
- new CallPeerSecurityTimeoutEvent(
- this,
- toSessionType(mediaType)));
- }
-
- /**
- * Sets the security status to OFF for this call peer.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- */
- public void securityTurnedOff(MediaType mediaType)
- {
- // If this event has been triggered because of a call end event and the
- // call is already ended we don't need to alert the user for
- // security off.
- if((call != null) && !call.getCallState().equals(CallState.CALL_ENDED))
- {
- fireCallPeerSecurityOffEvent(
- new CallPeerSecurityOffEvent(
- this,
- toSessionType(mediaType)));
- }
- }
-
- /**
- * Sets the security status to ON for this call peer.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- * @param cipher the cipher
- * @param sender the security controller that caused the event
- */
- public void securityTurnedOn(
- MediaType mediaType,
- String cipher,
- SrtpControl sender)
- {
- getMediaHandler().startSrtpMultistream(sender);
- fireCallPeerSecurityOnEvent(
- new CallPeerSecurityOnEvent(
- this,
- toSessionType(mediaType),
- cipher,
- sender));
- }
-
- /**
- * Sets the call containing this peer.
- *
- * @param call the call that this call peer is participating in.
- */
- public void setCall(T call)
- {
- this.call = call;
- }
-
- /**
- * Sets the byte array containing an image representation (photo or picture)
- * of the call peer.
- *
- * @param image a byte array containing the image
- */
- public void setImage(byte[] image)
- {
- byte[] oldImage = getImage();
- this.image = image;
-
- //Fire the Event
- fireCallPeerChangeEvent(
- CallPeerChangeEvent.CALL_PEER_IMAGE_CHANGE,
- oldImage,
- image);
- }
-
- /**
- * Modifies the local media setup to reflect the requested setting for the
- * streaming of the local video and then re-invites the peer represented by
- * this class using a corresponding SDP description..
- *
- * @param allowed <tt>true</tt> if local video transmission is allowed and
- * <tt>false</tt> otherwise.
- *
- * @throws OperationFailedException if video initialization fails.
- */
- public void setLocalVideoAllowed(boolean allowed)
- throws OperationFailedException
- {
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed)
- {
- // Modify the local media setup to reflect the requested setting for
- // the streaming of the local video.
- mediaHandler.setLocalVideoTransmissionEnabled(allowed);
- }
- }
-
- /**
- * Sets a reference to the <tt>CallPeerMediaHandler</tt> used by this
- * peer. The media handler class handles all media management for a single
- * <tt>CallPeer</tt>. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
- * always corresponds to exactly one instance of
- * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
- * reasons of readability.
- *
- * @param mediaHandler a reference to the <tt>CallPeerMediaHandler</tt>
- * instance that this peer uses for media related tips and tricks.
- */
- protected void setMediaHandler(U mediaHandler)
- {
- this.mediaHandler = mediaHandler;
- }
-
- /**
- * Sets the mute property for this call peer.
- *
- * @param newMuteValue the new value of the mute property for this call peer
- */
- @Override
- public void setMute(boolean newMuteValue)
- {
- getMediaHandler().setMute(newMuteValue);
- super.setMute(newMuteValue);
- }
-
- /**
- * Sets the String that serves as a unique identifier of this
- * CallPeer.
- * @param peerID the ID of this call peer.
- */
- public void setPeerID(String peerID)
- {
- this.peerID = peerID;
- }
-
- /**
- * Overrides the parent set state method in order to make sure that we
- * close our media handler whenever we enter a disconnected state.
- *
- * @param newState the <tt>CallPeerState</tt> that we are about to enter and
- * that we pass to our predecessor.
- * @param reason a reason phrase explaining the state (e.g. if newState
- * indicates a failure) and that we pass to our predecessor.
- * @param reasonCode the code for the reason of the state change.
- */
- @Override
- public void setState(CallPeerState newState, String reason, int reasonCode)
- {
- // synchronized to mediaHandler if there are currently jobs of
- // initializing, configuring and starting streams (method processAnswer
- // of CallPeerMediaHandler) we won't set and fire the current state
- // to Disconnected. Before closing the mediaHandler is setting the state
- // in order to deliver states as quick as possible.
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- synchronized(mediaHandler)
- {
- try
- {
- super.setState(newState, reason, reasonCode);
- }
- finally
- {
- // make sure whatever happens to close the media
- if (CallPeerState.DISCONNECTED.equals(newState)
- || CallPeerState.FAILED.equals(newState))
- mediaHandler.close();
- }
- }
- }
-
- /**
- * Returns the last <tt>ConferenceInfoDocument</tt> sent by us to this
- * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
- * @return the last <tt>ConferenceInfoDocument</tt> sent by us to this
- * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
- */
- public ConferenceInfoDocument getLastConferenceInfoSent()
- {
- return lastConferenceInfoSent;
- }
-
- /**
- * Sets the last <tt>ConferenceInfoDocument</tt> sent by us to this
- * <tt>CallPeer</tt>.
- * @param confInfo the document to set.
- */
- public void setLastConferenceInfoSent(ConferenceInfoDocument confInfo)
- {
- lastConferenceInfoSent = confInfo;
- }
-
- /**
- * Gets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
- * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
- * <tt>CallPeer</tt>.
- * @return the time (as obtained by <tt>System.currentTimeMillis()</tt>)
- * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
- * <tt>CallPeer</tt>.
- */
- public long getLastConferenceInfoSentTimestamp()
- {
- return lastConferenceInfoSentTimestamp;
- }
-
- /**
- * Sets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
- * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
- * <tt>CallPeer</tt>.
- * @param newTimestamp the time to set
- */
- public void setLastConferenceInfoSentTimestamp(long newTimestamp)
- {
- lastConferenceInfoSentTimestamp = newTimestamp;
- }
-
- /**
- * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- */
- public ConferenceInfoDocument getLastConferenceInfoReceived()
- {
- return lastConferenceInfoReceived;
- }
-
- /**
- * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- */
- public void setLastConferenceInfoReceived(ConferenceInfoDocument confInfo)
- {
- lastConferenceInfoReceived = confInfo;
- }
-
- /**
- * Gets the <tt>version</tt> of the last <tt>ConferenceInfoDocument</tt>
- * sent to us by this <tt>CallPeer</tt>, or -1 if we haven't (yet) received
- * a <tt>ConferenceInformationDocument</tt> from this <tt>CallPeer</tt>.
- * @return
- */
- public int getLastConferenceInfoReceivedVersion()
- {
- return (lastConferenceInfoReceived == null)
- ? -1
- : lastConferenceInfoReceived.getVersion();
- }
-
- /**
- * Gets the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
- * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
- * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
- * element corresponding to this <tt>CallPeer</tt>)
- *
- * @return the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
- * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
- * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
- * element corresponding to this <tt>CallPeer</tt>)
- */
- public abstract String getEntity();
-
- /**
- * Check whether a conference-info document is scheduled to be sent to
- * this <tt>CallPeer</tt> (i.e. there is a thread which will eventually
- * (after sleeping a certain amount of time) trigger a document to be sent)
- * @return <tt>true</tt> if there is a conference-info document scheduled
- * to be sent to this <tt>CallPeer</tt> and <tt>false</tt> otherwise.
- */
- public boolean isConfInfoScheduled()
- {
- synchronized (confInfoScheduledSyncRoot)
- {
- return confInfoScheduled;
- }
- }
-
- /**
- * Sets the property which indicates whether a conference-info document
- * is scheduled to be sent to this <tt>CallPeer</tt>.
- * @param confInfoScheduled
- */
- public void setConfInfoScheduled(boolean confInfoScheduled)
- {
- synchronized (confInfoScheduledSyncRoot)
- {
- this.confInfoScheduled = confInfoScheduled;
- }
- }
-
- /**
- * Returns the direction of the session for media of type <tt>mediaType</tt>
- * that we have with this <tt>CallPeer</tt>. This is the direction of the
- * session negotiated in the signaling protocol, and it may or may not
- * coincide with the direction of the media stream.
- * For example, if we are the focus of a videobridge conference and another
- * peer is sending video to us, we have a <tt>RECVONLY</tt> video stream,
- * but <tt>SENDONLY</tt> or <tt>SENDRECV</tt> (Jingle) sessions with the
- * rest of the conference members.
- * Should always return non-null.
- *
- * @param mediaType the <tt>MediaType</tt> to use
- * @return Returns the direction of the session for media of type
- * <tt>mediaType</tt> that we have with this <tt>CallPeer</tt>.
- */
- public abstract MediaDirection getDirection(MediaType mediaType);
-
- /**
- * {@inheritDoc}
- *
- * When a <tt>ConferenceMember</tt> is removed from a conference with a
- * Jitsi-videobridge, an RTCP BYE packet is not always sent. Therefore,
- * if the <tt>ConferenceMember</tt> had an associated video SSRC, the stream
- * isn't be removed until it times out, leaving a blank video container in
- * the interface for a few seconds.
- * TODO: This works around the problem by removing the
- * <tt>ConferenceMember</tt>'s <tt>ReceiveStream</tt> when the
- * <tt>ConferenceMember</tt> is removed. The proper solution is to ensure
- * that RTCP BYEs are sent whenever necessary, and when it is deployed this
- * code should be removed.
- *
- * @param conferenceMember a <tt>ConferenceMember</tt> to be removed from
- * the list of <tt>ConferenceMember</tt> reported by this peer. If the
- * specified <tt>ConferenceMember</tt> is no contained in the list, no event
- */
- @Override
- public void removeConferenceMember(ConferenceMember conferenceMember)
- {
- MediaStream videoStream = getMediaHandler().getStream(MediaType.VIDEO);
- if (videoStream != null)
- videoStream.removeReceiveStreamForSsrc(
- conferenceMember.getVideoSsrc());
-
- super.removeConferenceMember(conferenceMember);
- }
-
- /**
- * Converts a specific <tt>MediaType</tt> into a <tt>sessionType</tt> value
- * in the terms of the <tt>CallPeerSecurityStatusEvent</tt> class.
- *
- * @param mediaType the <tt>MediaType</tt> to be converted
- * @return the <tt>sessionType</tt> value in the terms of the
- * <tt>CallPeerSecurityStatusEvent</tt> class that is equivalent to the
- * specified <tt>mediaType</tt>
- */
- private static int toSessionType(MediaType mediaType)
- {
- switch (mediaType)
- {
- case AUDIO:
- return CallPeerSecurityStatusEvent.AUDIO_SESSION;
- case VIDEO:
- return CallPeerSecurityStatusEvent.VIDEO_SESSION;
- default:
- throw new IllegalArgumentException("mediaType");
- }
- }
-}
+package net.java.sip.communicator.service.protocol.media;
+
+import java.beans.*;
+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.jitsi.service.neomedia.*;
+import org.jitsi.service.neomedia.event.*;
+
+/**
+ * A utility class implementing media control code shared between current
+ * telephony implementations. This class is only meant for use by protocol
+ * implementations and should/could not be accessed by bundles that are simply
+ * using the telephony functionalities.
+ *
+ * @param <T> the peer extension class like for example <tt>CallSipImpl</tt>
+ * or <tt>CallJabberImpl</tt>
+ * @param <U> the media handler extension class like for example
+ * <tt>CallPeerMediaHandlerSipImpl</tt> or
+ * <tt>CallPeerMediaHandlerJabberImpl</tt>
+ * @param <V> the provider extension class like for example
+ * <tt>ProtocolProviderServiceSipImpl</tt> or
+ * <tt>ProtocolProviderServiceJabberImpl</tt>
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Boris Grozev
+ */
+public abstract class MediaAwareCallPeer
+ <T extends MediaAwareCall<?, ?, V>,
+ U extends CallPeerMediaHandler<?>,
+ V extends ProtocolProviderService>
+ extends AbstractCallPeer<T, V>
+ implements SrtpListener,
+ CallPeerConferenceListener,
+ CsrcAudioLevelListener,
+ SimpleAudioLevelListener
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>MediaAwareCallPeer</tt> class and its
+ * instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(MediaAwareCallPeer.class);
+
+ /**
+ * The call this peer belongs to.
+ */
+ private T call;
+
+ /**
+ * The listeners registered for level changes in the audio of participants
+ * that this peer might be mixing and that we are not directly communicating
+ * with.
+ */
+ private final List<ConferenceMembersSoundLevelListener>
+ conferenceMembersSoundLevelListeners
+ = new ArrayList<ConferenceMembersSoundLevelListener>();
+
+ /**
+ * A byte array containing the image/photo representing the call peer.
+ */
+ private byte[] image;
+
+ /**
+ * The media handler class handles all media management for a single
+ * <tt>CallPeer</tt>. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>always
+ * corresponds to exactly one instance of <tt>CallPeerMediaHandler</tt> and
+ * both classes are only separated for reasons of readability.
+ */
+ private U mediaHandler;
+
+ /**
+ * The <tt>PropertyChangeListener</tt> which listens to
+ * {@link CallPeerMediaHandler} for changes in the values of its properties.
+ */
+ private PropertyChangeListener mediaHandlerPropertyChangeListener;
+
+ /**
+ * A string uniquely identifying the peer.
+ */
+ private String peerID;
+
+ /**
+ * The protocol provider that this peer belongs to.
+ */
+ private final V protocolProvider;
+
+ /**
+ * The list of <tt>SoundLevelListener</tt>s interested in level changes in
+ * the audio we are getting from the remote peer.
+ * <p>
+ * It is implemented as a copy-on-write storage because the number of
+ * additions and removals of <tt>SoundLevelListener</tt>s is expected to be
+ * far smaller than the number of audio level changes. The access to it is
+ * to be synchronized using {@link #streamSoundLevelListenersSyncRoot}.
+ * </p>
+ */
+ private List<SoundLevelListener> streamSoundLevelListeners;
+
+ /**
+ * The <tt>Object</tt> to synchronize the access to
+ * {@link #streamSoundLevelListeners}.
+ */
+ private final Object streamSoundLevelListenersSyncRoot = new Object();
+
+ /**
+ * The <tt>List</tt> of <tt>PropertyChangeListener</tt>s listening to this
+ * <tt>CallPeer</tt> for changes in the values of its properties related to
+ * video.
+ */
+ private final List<PropertyChangeListener> videoPropertyChangeListeners
+ = new LinkedList<PropertyChangeListener>();
+
+ /**
+ * Represents the last Conference Information (RFC4575) document sent to
+ * this <tt>CallPeer</tt>. This is always a document with state "full", even
+ * if the last document actually sent was a "partial"
+ */
+ private ConferenceInfoDocument lastConferenceInfoSent = null;
+
+ /**
+ * The time (as obtained by <tt>System.currentTimeMillis()</tt>) at which
+ * a Conference Information (RFC4575) document was last sent to this
+ * <tt>CallPeer</tt>.
+ */
+ private long lastConferenceInfoSentTimestamp = -1;
+
+ /**
+ * The last Conference Information (RFC4575) document sent to us by this
+ * <tt>CallPeer</tt>. This is always a document with state "full", which is
+ * only gets updated by "partial" or "deleted" documents.
+ */
+ private ConferenceInfoDocument lastConferenceInfoReceived = null;
+
+ /**
+ * Whether a conference-info document has been scheduled to be sent to this
+ * <tt>CallPeer</tt>
+ */
+ private boolean confInfoScheduled = false;
+
+ /**
+ * Synchronization object for confInfoScheduled
+ */
+ private final Object confInfoScheduledSyncRoot = new Object();
+
+ /**
+ * Creates a new call peer with address <tt>peerAddress</tt>.
+ *
+ * @param owningCall the call that contains this call peer.
+ */
+ public MediaAwareCallPeer(T owningCall)
+ {
+ this.call = owningCall;
+ this.protocolProvider = owningCall.getProtocolProvider();
+
+ // create the uid
+ this.peerID
+ = String.valueOf(System.currentTimeMillis())
+ + String.valueOf(hashCode());
+
+ // we listen for events when the call will become focus or not
+ // of a conference so we will add or remove our sound level listeners
+ super.addCallPeerConferenceListener(this);
+ }
+
+ /**
+ * Adds a specific <tt>ConferenceMembersSoundLevelListener</tt> to the list
+ * of listeners interested in and notified about changes in conference
+ * members sound level.
+ *
+ * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to add
+ * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
+ */
+ public void addConferenceMembersSoundLevelListener(
+ ConferenceMembersSoundLevelListener listener)
+ {
+ /*
+ * XXX The uses of the method at the time of this writing rely on being
+ * able to add a null listener so make it a no-op here.
+ */
+ if (listener == null)
+ return;
+
+ synchronized (conferenceMembersSoundLevelListeners)
+ {
+ if (conferenceMembersSoundLevelListeners.size() == 0)
+ {
+ // if this is the first listener that's being registered with
+ // us, we also need to register ourselves as a CSRC audio level
+ // listener with the media handler.
+ getMediaHandler().setCsrcAudioLevelListener(this);
+ }
+ conferenceMembersSoundLevelListeners.add(listener);
+ }
+ }
+
+ /**
+ * Adds a specific <tt>SoundLevelListener</tt> to the list of listeners
+ * interested in and notified about changes in the sound level of the audio
+ * sent by the remote party. When the first listener is being registered
+ * the method also registers its single listener with the media handler so
+ * that it would receive level change events and delegate them to the
+ * listeners that have registered with us.
+ *
+ * @param listener the <tt>SoundLevelListener</tt> to add
+ */
+ public void addStreamSoundLevelListener(SoundLevelListener listener)
+ {
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ if ((streamSoundLevelListeners == null)
+ || streamSoundLevelListeners.isEmpty())
+ {
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ if (isJitsiVideobridge())
+ {
+ /*
+ * When the local user/peer has organized a telephony
+ * conference utilizing the Jitsi Videobridge server-side
+ * technology, the server will calculate the audio levels
+ * and not the client.
+ */
+ mediaHandler.setCsrcAudioLevelListener(this);
+ }
+ else
+ {
+ /*
+ * If this is the first listener that's being registered
+ * with us, we also need to register ourselves as an audio
+ * level listener with the media handler. We do this so that
+ * audio levels would only be calculated if anyone is
+ * interested in receiving them.
+ */
+ mediaHandler.setStreamAudioLevelListener(this);
+ }
+ }
+
+ /*
+ * Implement streamAudioLevelListeners as a copy-on-write storage so
+ * that iterators over it can iterate without
+ * ConcurrentModificationExceptions.
+ */
+ streamSoundLevelListeners
+ = (streamSoundLevelListeners == null)
+ ? new ArrayList<SoundLevelListener>()
+ : new ArrayList<SoundLevelListener>(
+ streamSoundLevelListeners);
+ streamSoundLevelListeners.add(listener);
+ }
+ }
+
+ /**
+ * Adds a specific <tt>PropertyChangeListener</tt> to the list of
+ * listeners which get notified when the properties (e.g.
+ * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
+ * their values.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to be notified
+ * when the properties associated with the specified <tt>Call</tt> change
+ * their values
+ */
+ public void addVideoPropertyChangeListener(PropertyChangeListener listener)
+ {
+ if (listener == null)
+ throw new NullPointerException("listener");
+
+ synchronized (videoPropertyChangeListeners)
+ {
+ /*
+ * The video is part of the media-related functionality and thus it
+ * is the responsibility of mediaHandler. So listen to mediaHandler
+ * for video-related property changes and re-fire them as
+ * originating from this instance.
+ */
+ if (!videoPropertyChangeListeners.contains(listener)
+ && videoPropertyChangeListeners.add(listener)
+ && (mediaHandlerPropertyChangeListener == null))
+ {
+ mediaHandlerPropertyChangeListener
+ = new PropertyChangeListener()
+ {
+ public void propertyChange(PropertyChangeEvent event)
+ {
+ Iterable<PropertyChangeListener> listeners;
+
+ synchronized (videoPropertyChangeListeners)
+ {
+ listeners
+ = new LinkedList<PropertyChangeListener>(
+ videoPropertyChangeListeners);
+ }
+
+ PropertyChangeEvent thisEvent
+ = new PropertyChangeEvent(
+ this,
+ event.getPropertyName(),
+ event.getOldValue(),
+ event.getNewValue());
+
+ for (PropertyChangeListener listener : listeners)
+ listener.propertyChange(thisEvent);
+ }
+ };
+ getMediaHandler()
+ .addPropertyChangeListener(
+ mediaHandlerPropertyChangeListener);
+ }
+ }
+ }
+
+ /**
+ * Notified by its very majesty the media service about changes in the audio
+ * level of the stream coming from this peer, the method generates the
+ * corresponding events and delivers them to the listeners that have
+ * registered here.
+ *
+ * @param newLevel the new audio level of the audio stream received from the
+ * remote peer
+ */
+ public void audioLevelChanged(int newLevel)
+ {
+ /*
+ * If we're in a conference in which this CallPeer is the focus and
+ * we're the only member in it besides the focus, we will not receive
+ * audio levels in the RTP and our media will instead measure the audio
+ * levels of the received media. In order to make the UI oblivious of
+ * the difference, we have to translate the event to the appropriate
+ * type of listener.
+ *
+ * We may end up in a conference call with 0 members if the server
+ * for some reason doesn't support sip conference (our subscribes
+ * doesn't go to the focus of the conference) and so we must
+ * pass the sound levels measured on the stream so we can see
+ * the stream activity of the call.
+ */
+ int conferenceMemberCount = getConferenceMemberCount();
+
+ if ((conferenceMemberCount > 0) && (conferenceMemberCount < 3))
+ {
+ long audioRemoteSSRC
+ = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
+
+ if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
+ {
+ audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel });
+ return;
+ }
+ }
+
+ fireStreamSoundLevelChanged(newLevel);
+ }
+
+
+ /**
+ * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[])}.
+ * Delivers the received audio levels to the
+ * {@link ConferenceMembersSoundLevelListener}s registered with this
+ * <tt>MediaAwareCallPeer</tt>..
+ *
+ * @param audioLevels the levels that we need to dispatch to all registered
+ * <tt>ConferenceMemberSoundLevelListeners</tt>.
+ */
+ public void audioLevelsReceived(long[] audioLevels)
+ {
+ /*
+ * When the local user/peer has organized a telephony conference
+ * utilizing the Jitsi Videobridge server-side technology, the server
+ * will calculate the audio levels and not the client.
+ */
+ if (isJitsiVideobridge())
+ {
+ long audioRemoteSSRC
+ = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
+
+ if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
+ {
+ for (int i = 0; i < audioLevels.length; i += 2)
+ {
+ if (audioLevels[i] == audioRemoteSSRC)
+ {
+ fireStreamSoundLevelChanged((int) audioLevels[i + 1]);
+ break;
+ }
+ }
+ }
+ }
+
+ if (getConferenceMemberCount() == 0)
+ return;
+
+ Map<ConferenceMember, Integer> levelsMap
+ = new HashMap<ConferenceMember, Integer>();
+
+ for (int i = 0; i < audioLevels.length; i += 2)
+ {
+ ConferenceMember mmbr = findConferenceMember(audioLevels[i]);
+
+ if (mmbr != null)
+ levelsMap.put(mmbr, (int) audioLevels[i + 1]);
+ }
+
+ synchronized (conferenceMembersSoundLevelListeners)
+ {
+ int conferenceMemberSoundLevelListenerCount
+ = conferenceMembersSoundLevelListeners.size();
+
+ if (conferenceMemberSoundLevelListenerCount > 0)
+ {
+ ConferenceMembersSoundLevelEvent ev
+ = new ConferenceMembersSoundLevelEvent(this, levelsMap);
+
+ for (int i = 0;
+ i < conferenceMemberSoundLevelListenerCount;
+ i++)
+ {
+ conferenceMembersSoundLevelListeners
+ .get(i)
+ .soundLevelChanged(ev);
+ }
+ }
+ }
+ }
+
+ /**
+ * Does nothing.
+ * @param evt the event.
+ */
+ public void callPeerAdded(CallPeerEvent evt) {}
+
+ /**
+ * Does nothing.
+ * @param evt the event.
+ */
+ public void callPeerRemoved(CallPeerEvent evt) {}
+
+ /**
+ * Dummy implementation of {@link CallPeerConferenceListener
+ * #conferenceFocusChanged(CallPeerConferenceEvent)}.
+ *
+ * @param evt ignored
+ */
+ public void conferenceFocusChanged(CallPeerConferenceEvent evt)
+ {
+ }
+
+ /**
+ * Called when this peer becomes a mixer. The method add removes this class
+ * as the stream audio level listener for the media coming from this peer
+ * because the levels it delivers no longer represent the level of a
+ * particular member. The method also adds this class as a member (CSRC)
+ * audio level listener.
+ *
+ * @param conferenceEvent the event containing information (that we don't
+ * really use) on the newly add member.
+ */
+ public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent)
+ {
+ if (getConferenceMemberCount() > 2)
+ {
+ /*
+ * This peer is now a conference focus with more than three
+ * participants. It means that this peer is mixing and sending us
+ * audio for at least two separate participants. We therefore need
+ * to switch from stream to CSRC level listening.
+ */
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ mediaHandler.setStreamAudioLevelListener(null);
+ mediaHandler.setCsrcAudioLevelListener(this);
+ }
+ }
+
+ /**
+ * Dummy implementation of {@link CallPeerConferenceListener
+ * #conferenceMemberErrorReceived(CallPeerConferenceEvent)}.
+ *
+ * @param ev the event
+ */
+ public void conferenceMemberErrorReceived(CallPeerConferenceEvent ev) {};
+
+ /**
+ * Called when this peer stops being a mixer. The method add removes this
+ * class as the stream audio level listener for the media coming from this
+ * peer because the levels it delivers no longer represent the level of a
+ * particular member. The method also adds this class as a member (CSRC)
+ * audio level listener.
+ *
+ * @param conferenceEvent the event containing information (that we don't
+ * really use) on the freshly removed member.
+ */
+ public void conferenceMemberRemoved(CallPeerConferenceEvent conferenceEvent)
+ {
+ if (getConferenceMemberCount() < 3)
+ {
+ /*
+ * This call peer is no longer mixing audio from multiple sources
+ * since there's only us and her in the call. We therefore need to
+ * switch from CSRC to stream level listening.
+ */
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ mediaHandler.setStreamAudioLevelListener(this);
+ mediaHandler.setCsrcAudioLevelListener(null);
+ }
+ }
+
+ /**
+ * Invokes {@link SoundLevelListener#soundLevelChanged(Object, int) on
+ * the <tt>SoundLevelListener</tt>s interested in the changes of the audio
+ * stream received from the remote peer i.e. in
+ * {@link #streamSoundLevelListeners}.
+ *
+ * @param newLevel the new value of the sound level to notify
+ * <tt>streamSoundLevelListeners</tt> about
+ */
+ private void fireStreamSoundLevelChanged(int newLevel)
+ {
+ List<SoundLevelListener> streamSoundLevelListeners;
+
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ /*
+ * Since the streamAudioLevelListeners field of this
+ * MediaAwareCallPeer is implemented as a copy-on-write storage,
+ * just get a reference to it and it should be safe to iterate over it
+ * without ConcurrentModificationExceptions.
+ */
+ streamSoundLevelListeners = this.streamSoundLevelListeners;
+ }
+
+ if (streamSoundLevelListeners != null)
+ {
+ /*
+ * Iterate over streamAudioLevelListeners using an index rather than
+ * an Iterator in order to try to reduce the number of allocations
+ * (as the number of audio level changes is expected to be very
+ * large).
+ */
+ int streamSoundLevelListenerCount
+ = streamSoundLevelListeners.size();
+
+ for(int i = 0; i < streamSoundLevelListenerCount; i++)
+ {
+ streamSoundLevelListeners.get(i).soundLevelChanged(
+ this,
+ newLevel);
+ }
+ }
+ }
+
+ /**
+ * Returns a reference to the call that this peer belongs to. Calls
+ * are created by underlying telephony protocol implementations.
+ *
+ * @return a reference to the call containing this peer.
+ */
+ @Override
+ public T getCall()
+ {
+ return call;
+ }
+
+ /**
+ * The method returns an image representation of the call peer if one is
+ * available.
+ *
+ * @return byte[] a byte array containing the image or null if no image is
+ * available.
+ */
+ public byte[] getImage()
+ {
+ return image;
+ }
+
+ /**
+ * Returns a reference to the <tt>CallPeerMediaHandler</tt> used by this
+ * peer. The media handler class handles all media management for a single
+ * <tt>CallPeer</tt>. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
+ * always corresponds to exactly one instance of
+ * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
+ * reasons of readability.
+ *
+ * @return a reference to the <tt>CallPeerMediaHandler</tt> instance that
+ * this peer uses for media related tips and tricks.
+ */
+ public U getMediaHandler()
+ {
+ return mediaHandler;
+ }
+
+ /**
+ * Returns a unique identifier representing this peer.
+ *
+ * @return an identifier representing this call peer.
+ */
+ public String getPeerID()
+ {
+ return peerID;
+ }
+
+ /**
+ * Returns the protocol provider that this peer belongs to.
+ *
+ * @return a reference to the <tt>ProtocolProviderService</tt> that this
+ * peer belongs to.
+ */
+ @Override
+ public V getProtocolProvider()
+ {
+ return protocolProvider;
+ }
+
+ /**
+ * Determines whether this <tt>CallPeer</tt> is participating in a telephony
+ * conference organized by the local user/peer utilizing the Jitsi
+ * Videobridge server-side technology.
+ *
+ * @return <tt>true</tt> if this <tt>CallPeer</tt> is participating in a
+ * telephony conference organized by the local user/peer utilizing the Jitsi
+ * Videobridge server-side technology; otherwise, <tt>false</tt>
+ */
+ public final boolean isJitsiVideobridge()
+ {
+ Call call = getCall();
+
+ if (call != null)
+ {
+ CallConference conference = call.getConference();
+
+ if (conference != null)
+ return conference.isJitsiVideobridge();
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether we are currently streaming video toward whoever this
+ * <tt>MediaAwareCallPeer</tt> represents.
+ *
+ * @return <tt>true</tt> if we are currently streaming video toward this
+ * <tt>CallPeer</tt> and <tt>false</tt> otherwise.
+ */
+ public boolean isLocalVideoStreaming()
+ {
+ return getMediaHandler().isLocalVideoTransmissionEnabled();
+ }
+
+ /**
+ * Determines whether the audio stream (if any) being sent to this
+ * peer is mute.
+ *
+ * @return <tt>true</tt> if an audio stream is being sent to this
+ * peer and it is currently mute; <tt>false</tt>, otherwise
+ */
+ @Override
+ public boolean isMute()
+ {
+ return getMediaHandler().isMute();
+ }
+
+ /**
+ * Logs <tt>message</tt> and <tt>cause</tt> and sets this <tt>peer</tt>'s
+ * state to <tt>CallPeerState.FAILED</tt>
+ *
+ * @param message a message to log and display to the user.
+ * @param throwable the exception that cause the error we are logging
+ */
+ public void logAndFail(String message, Throwable throwable)
+ {
+ logger.error(message, throwable);
+ setState(CallPeerState.FAILED, message);
+ }
+
+ /**
+ * Updates the state of this <tt>CallPeer</tt> to match the locally-on-hold
+ * status of our media handler.
+ */
+ public void reevalLocalHoldStatus()
+ {
+ CallPeerState state = getState();
+ boolean locallyOnHold = getMediaHandler().isLocallyOnHold();
+
+ if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
+ {
+ if (!locallyOnHold)
+ setState(CallPeerState.CONNECTED);
+ }
+ else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
+ {
+ if (!locallyOnHold)
+ setState(CallPeerState.ON_HOLD_REMOTELY);
+ }
+ else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
+ {
+ if (locallyOnHold)
+ setState(CallPeerState.ON_HOLD_MUTUALLY);
+ }
+ else if (locallyOnHold)
+ {
+ setState(CallPeerState.ON_HOLD_LOCALLY);
+ }
+ }
+
+ /**
+ * Updates the state of this <tt>CallPeer</tt> to match the remotely-on-hold
+ * status of our media handler.
+ */
+ public void reevalRemoteHoldStatus()
+ {
+ boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold();
+
+ CallPeerState state = getState();
+ if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
+ {
+ if (remotelyOnHold)
+ setState(CallPeerState.ON_HOLD_MUTUALLY);
+ }
+ else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
+ {
+ if (!remotelyOnHold)
+ setState(CallPeerState.ON_HOLD_LOCALLY);
+ }
+ else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
+ {
+ if (!remotelyOnHold)
+ setState(CallPeerState.CONNECTED);
+ }
+ else if (remotelyOnHold)
+ {
+ setState(CallPeerState.ON_HOLD_REMOTELY);
+ }
+ }
+
+ /**
+ * Removes a specific <tt>ConferenceMembersSoundLevelListener</tt> of the
+ * list of listeners interested in and notified about changes in conference
+ * members sound level.
+ *
+ * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to
+ * remove
+ */
+ public void removeConferenceMembersSoundLevelListener(
+ ConferenceMembersSoundLevelListener listener)
+ {
+ synchronized (conferenceMembersSoundLevelListeners)
+ {
+ if (conferenceMembersSoundLevelListeners.remove(listener)
+ && (conferenceMembersSoundLevelListeners.size() == 0))
+ {
+ // if this was the last listener then we also remove ourselves
+ // as a CSRC audio level listener from the handler so that we
+ // don't have to create new events and maps for something no one
+ // is interested in.
+ getMediaHandler().setCsrcAudioLevelListener(null);
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>SoundLevelListener</tt> of the list of
+ * listeners interested in and notified about changes in stream sound level
+ * related information.
+ *
+ * @param listener the <tt>SoundLevelListener</tt> to remove
+ */
+ public void removeStreamSoundLevelListener(SoundLevelListener listener)
+ {
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ /*
+ * Implement streamAudioLevelListeners as a copy-on-write storage so
+ * that iterators over it can iterate over it without
+ * ConcurrentModificationExceptions.
+ */
+ if (streamSoundLevelListeners != null)
+ {
+ streamSoundLevelListeners
+ = new ArrayList<SoundLevelListener>(
+ streamSoundLevelListeners);
+ if (streamSoundLevelListeners.remove(listener)
+ && streamSoundLevelListeners.isEmpty())
+ streamSoundLevelListeners = null;
+ }
+
+ if ((streamSoundLevelListeners == null)
+ || streamSoundLevelListeners.isEmpty())
+ {
+ // if this was the last listener then we also need to remove
+ // ourselves as an audio level so that audio levels would only
+ // be calculated if anyone is interested in receiving them.
+ getMediaHandler().setStreamAudioLevelListener(null);
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>PropertyChangeListener</tt> from the list of
+ * listeners which get notified when the properties (e.g.
+ * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
+ * their values.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to no longer be
+ * notified when the properties associated with the specified <tt>Call</tt>
+ * change their values
+ */
+ public void removeVideoPropertyChangeListener(
+ PropertyChangeListener listener)
+ {
+ if (listener != null)
+ synchronized (videoPropertyChangeListeners)
+ {
+ /*
+ * The video is part of the media-related functionality and thus
+ * it is the responsibility of mediaHandler. So we're listening
+ * to mediaHandler for video-related property changes and w're
+ * re-firing them as originating from this instance. Make sure
+ * that we're not listening to mediaHandler if noone is
+ * interested in video-related property changes originating from
+ * this instance.
+ */
+ if (videoPropertyChangeListeners.remove(listener)
+ && videoPropertyChangeListeners.isEmpty()
+ && (mediaHandlerPropertyChangeListener != null))
+ {
+// getMediaHandler()
+// .removePropertyChangeListener(
+// mediaHandlerPropertyChangeListener);
+ mediaHandlerPropertyChangeListener = null;
+ }
+ }
+ }
+
+ /**
+ * Sets the security message associated with a failure/warning or
+ * information coming from the encryption protocol.
+ *
+ * @param messageType the type of the message.
+ * @param i18nMessage the message
+ * @param severity severity level
+ */
+ public void securityMessageReceived(
+ String messageType,
+ String i18nMessage,
+ int severity)
+ {
+ fireCallPeerSecurityMessageEvent(messageType,
+ i18nMessage,
+ severity);
+ }
+
+ /**
+ * Indicates that the other party has timeouted replying to our
+ * offer to secure the connection.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ * @param sender the security controller that caused the event
+ */
+ public void securityNegotiationStarted(
+ MediaType mediaType,
+ SrtpControl sender)
+ {
+ fireCallPeerSecurityNegotiationStartedEvent(
+ new CallPeerSecurityNegotiationStartedEvent(
+ this,
+ toSessionType(mediaType),
+ sender));
+ }
+
+ /**
+ * Indicates that the other party has timeouted replying to our
+ * offer to secure the connection.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ */
+ public void securityTimeout(MediaType mediaType)
+ {
+ fireCallPeerSecurityTimeoutEvent(
+ new CallPeerSecurityTimeoutEvent(
+ this,
+ toSessionType(mediaType)));
+ }
+
+ /**
+ * Sets the security status to OFF for this call peer.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ */
+ public void securityTurnedOff(MediaType mediaType)
+ {
+ // If this event has been triggered because of a call end event and the
+ // call is already ended we don't need to alert the user for
+ // security off.
+ if((call != null) && !call.getCallState().equals(CallState.CALL_ENDED))
+ {
+ fireCallPeerSecurityOffEvent(
+ new CallPeerSecurityOffEvent(
+ this,
+ toSessionType(mediaType)));
+ }
+ }
+
+ /**
+ * Sets the security status to ON for this call peer.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ * @param cipher the cipher
+ * @param sender the security controller that caused the event
+ */
+ public void securityTurnedOn(
+ MediaType mediaType,
+ String cipher,
+ SrtpControl sender)
+ {
+ getMediaHandler().startSrtpMultistream(sender);
+ fireCallPeerSecurityOnEvent(
+ new CallPeerSecurityOnEvent(
+ this,
+ toSessionType(mediaType),
+ cipher,
+ sender));
+ }
+
+ /**
+ * Sets the call containing this peer.
+ *
+ * @param call the call that this call peer is participating in.
+ */
+ public void setCall(T call)
+ {
+ this.call = call;
+ }
+
+ /**
+ * Sets the byte array containing an image representation (photo or picture)
+ * of the call peer.
+ *
+ * @param image a byte array containing the image
+ */
+ public void setImage(byte[] image)
+ {
+ byte[] oldImage = getImage();
+ this.image = image;
+
+ //Fire the Event
+ fireCallPeerChangeEvent(
+ CallPeerChangeEvent.CALL_PEER_IMAGE_CHANGE,
+ oldImage,
+ image);
+ }
+
+ /**
+ * Modifies the local media setup to reflect the requested setting for the
+ * streaming of the local video and then re-invites the peer represented by
+ * this class using a corresponding SDP description..
+ *
+ * @param allowed <tt>true</tt> if local video transmission is allowed and
+ * <tt>false</tt> otherwise.
+ *
+ * @throws OperationFailedException if video initialization fails.
+ */
+ public void setLocalVideoAllowed(boolean allowed)
+ throws OperationFailedException
+ {
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed)
+ {
+ // Modify the local media setup to reflect the requested setting for
+ // the streaming of the local video.
+ mediaHandler.setLocalVideoTransmissionEnabled(allowed);
+ }
+ }
+
+ /**
+ * Sets a reference to the <tt>CallPeerMediaHandler</tt> used by this
+ * peer. The media handler class handles all media management for a single
+ * <tt>CallPeer</tt>. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
+ * always corresponds to exactly one instance of
+ * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
+ * reasons of readability.
+ *
+ * @param mediaHandler a reference to the <tt>CallPeerMediaHandler</tt>
+ * instance that this peer uses for media related tips and tricks.
+ */
+ protected void setMediaHandler(U mediaHandler)
+ {
+ this.mediaHandler = mediaHandler;
+ }
+
+ /**
+ * Sets the mute property for this call peer.
+ *
+ * @param newMuteValue the new value of the mute property for this call peer
+ */
+ @Override
+ public void setMute(boolean newMuteValue)
+ {
+ getMediaHandler().setMute(newMuteValue);
+ super.setMute(newMuteValue);
+ }
+
+ /**
+ * Sets the String that serves as a unique identifier of this
+ * CallPeer.
+ * @param peerID the ID of this call peer.
+ */
+ public void setPeerID(String peerID)
+ {
+ this.peerID = peerID;
+ }
+
+ /**
+ * Overrides the parent set state method in order to make sure that we
+ * close our media handler whenever we enter a disconnected state.
+ *
+ * @param newState the <tt>CallPeerState</tt> that we are about to enter and
+ * that we pass to our predecessor.
+ * @param reason a reason phrase explaining the state (e.g. if newState
+ * indicates a failure) and that we pass to our predecessor.
+ * @param reasonCode the code for the reason of the state change.
+ */
+ @Override
+ public void setState(CallPeerState newState, String reason, int reasonCode)
+ {
+ // synchronized to mediaHandler if there are currently jobs of
+ // initializing, configuring and starting streams (method processAnswer
+ // of CallPeerMediaHandler) we won't set and fire the current state
+ // to Disconnected. Before closing the mediaHandler is setting the state
+ // in order to deliver states as quick as possible.
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ synchronized(mediaHandler)
+ {
+ try
+ {
+ super.setState(newState, reason, reasonCode);
+ }
+ finally
+ {
+ // make sure whatever happens to close the media
+ if (CallPeerState.DISCONNECTED.equals(newState)
+ || CallPeerState.FAILED.equals(newState))
+ mediaHandler.close();
+ }
+ }
+ }
+
+ /**
+ * Returns the last <tt>ConferenceInfoDocument</tt> sent by us to this
+ * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
+ * @return the last <tt>ConferenceInfoDocument</tt> sent by us to this
+ * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
+ */
+ public ConferenceInfoDocument getLastConferenceInfoSent()
+ {
+ return lastConferenceInfoSent;
+ }
+
+ /**
+ * Sets the last <tt>ConferenceInfoDocument</tt> sent by us to this
+ * <tt>CallPeer</tt>.
+ * @param confInfo the document to set.
+ */
+ public void setLastConferenceInfoSent(ConferenceInfoDocument confInfo)
+ {
+ lastConferenceInfoSent = confInfo;
+ }
+
+ /**
+ * Gets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
+ * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
+ * <tt>CallPeer</tt>.
+ * @return the time (as obtained by <tt>System.currentTimeMillis()</tt>)
+ * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
+ * <tt>CallPeer</tt>.
+ */
+ public long getLastConferenceInfoSentTimestamp()
+ {
+ return lastConferenceInfoSentTimestamp;
+ }
+
+ /**
+ * Sets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
+ * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
+ * <tt>CallPeer</tt>.
+ * @param newTimestamp the time to set
+ */
+ public void setLastConferenceInfoSentTimestamp(long newTimestamp)
+ {
+ lastConferenceInfoSentTimestamp = newTimestamp;
+ }
+
+ /**
+ * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ */
+ public ConferenceInfoDocument getLastConferenceInfoReceived()
+ {
+ return lastConferenceInfoReceived;
+ }
+
+ /**
+ * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ */
+ public void setLastConferenceInfoReceived(ConferenceInfoDocument confInfo)
+ {
+ lastConferenceInfoReceived = confInfo;
+ }
+
+ /**
+ * Gets the <tt>version</tt> of the last <tt>ConferenceInfoDocument</tt>
+ * sent to us by this <tt>CallPeer</tt>, or -1 if we haven't (yet) received
+ * a <tt>ConferenceInformationDocument</tt> from this <tt>CallPeer</tt>.
+ * @return
+ */
+ public int getLastConferenceInfoReceivedVersion()
+ {
+ return (lastConferenceInfoReceived == null)
+ ? -1
+ : lastConferenceInfoReceived.getVersion();
+ }
+
+ /**
+ * Gets the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
+ * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
+ * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
+ * element corresponding to this <tt>CallPeer</tt>)
+ *
+ * @return the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
+ * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
+ * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
+ * element corresponding to this <tt>CallPeer</tt>)
+ */
+ public abstract String getEntity();
+
+ /**
+ * Check whether a conference-info document is scheduled to be sent to
+ * this <tt>CallPeer</tt> (i.e. there is a thread which will eventually
+ * (after sleeping a certain amount of time) trigger a document to be sent)
+ * @return <tt>true</tt> if there is a conference-info document scheduled
+ * to be sent to this <tt>CallPeer</tt> and <tt>false</tt> otherwise.
+ */
+ public boolean isConfInfoScheduled()
+ {
+ synchronized (confInfoScheduledSyncRoot)
+ {
+ return confInfoScheduled;
+ }
+ }
+
+ /**
+ * Sets the property which indicates whether a conference-info document
+ * is scheduled to be sent to this <tt>CallPeer</tt>.
+ * @param confInfoScheduled
+ */
+ public void setConfInfoScheduled(boolean confInfoScheduled)
+ {
+ synchronized (confInfoScheduledSyncRoot)
+ {
+ this.confInfoScheduled = confInfoScheduled;
+ }
+ }
+
+ /**
+ * Returns the direction of the session for media of type <tt>mediaType</tt>
+ * that we have with this <tt>CallPeer</tt>. This is the direction of the
+ * session negotiated in the signaling protocol, and it may or may not
+ * coincide with the direction of the media stream.
+ * For example, if we are the focus of a videobridge conference and another
+ * peer is sending video to us, we have a <tt>RECVONLY</tt> video stream,
+ * but <tt>SENDONLY</tt> or <tt>SENDRECV</tt> (Jingle) sessions with the
+ * rest of the conference members.
+ * Should always return non-null.
+ *
+ * @param mediaType the <tt>MediaType</tt> to use
+ * @return Returns the direction of the session for media of type
+ * <tt>mediaType</tt> that we have with this <tt>CallPeer</tt>.
+ */
+ public abstract MediaDirection getDirection(MediaType mediaType);
+
+ /**
+ * {@inheritDoc}
+ *
+ * When a <tt>ConferenceMember</tt> is removed from a conference with a
+ * Jitsi-videobridge, an RTCP BYE packet is not always sent. Therefore,
+ * if the <tt>ConferenceMember</tt> had an associated video SSRC, the stream
+ * isn't be removed until it times out, leaving a blank video container in
+ * the interface for a few seconds.
+ * TODO: This works around the problem by removing the
+ * <tt>ConferenceMember</tt>'s <tt>ReceiveStream</tt> when the
+ * <tt>ConferenceMember</tt> is removed. The proper solution is to ensure
+ * that RTCP BYEs are sent whenever necessary, and when it is deployed this
+ * code should be removed.
+ *
+ * @param conferenceMember a <tt>ConferenceMember</tt> to be removed from
+ * the list of <tt>ConferenceMember</tt> reported by this peer. If the
+ * specified <tt>ConferenceMember</tt> is no contained in the list, no event
+ */
+ @Override
+ public void removeConferenceMember(ConferenceMember conferenceMember)
+ {
+ MediaStream videoStream = getMediaHandler().getStream(MediaType.VIDEO);
+ if (videoStream != null)
+ videoStream.removeReceiveStreamForSsrc(
+ conferenceMember.getVideoSsrc());
+
+ super.removeConferenceMember(conferenceMember);
+ }
+
+ /**
+ * Converts a specific <tt>MediaType</tt> into a <tt>sessionType</tt> value
+ * in the terms of the <tt>CallPeerSecurityStatusEvent</tt> class.
+ *
+ * @param mediaType the <tt>MediaType</tt> to be converted
+ * @return the <tt>sessionType</tt> value in the terms of the
+ * <tt>CallPeerSecurityStatusEvent</tt> class that is equivalent to the
+ * specified <tt>mediaType</tt>
+ */
+ private static int toSessionType(MediaType mediaType)
+ {
+ switch (mediaType)
+ {
+ case AUDIO:
+ return CallPeerSecurityStatusEvent.AUDIO_SESSION;
+ case VIDEO:
+ return CallPeerSecurityStatusEvent.VIDEO_SESSION;
+ default:
+ throw new IllegalArgumentException("mediaType");
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java
index 973ebcb..051a302 100644
--- a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java
+++ b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,870 +15,890 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.service.protocol.media;
-
-import java.net.*;
-
-import net.java.sip.communicator.service.netaddr.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-import org.ice4j.ice.*;
-import org.jitsi.service.configuration.*;
-import org.jitsi.service.neomedia.*;
-
-/**
- * <tt>TransportManager</tt>s are responsible for allocating ports, gathering
- * local candidates and managing ICE whenever we are using it.
- *
- * @param <U> the peer extension class like for example <tt>CallPeerSipImpl</tt>
- * or <tt>CallPeerJabberImpl</tt>
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Sebastien Vincent
- */
-public abstract class TransportManager<U extends MediaAwareCallPeer<?, ?, ?>>
-{
- /**
- * The <tt>Logger</tt> used by the <tt>TransportManager</tt>
- * class and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(TransportManager.class);
-
- /**
- * The port tracker that we should use when binding generic media streams.
- * <p>
- * Initialized by {@link #initializePortNumbers()}.
- * </p>
- */
- private static final PortTracker defaultPortTracker
- = new PortTracker(5000, 6000);
-
- /**
- * The port tracker that we should use when binding video media streams.
- * <p>
- * Potentially initialized by {@link #initializePortNumbers()} if the
- * necessary properties are set.
- * </p>
- */
- private static PortTracker videoPortTracker;
-
- /**
- * The port tracker that we should use when binding data channels.
- * <p>
- * Potentially initialized by {@link #initializePortNumbers()} if the
- * necessary properties are set.
- * </p>
- */
- private static PortTracker dataPortTracker;
-
- /**
- * The port tracker that we should use when binding data media streams.
- * <p>
- * Potentially initialized by {@link #initializePortNumbers()} if the
- * necessary properties are set.
- * </p>
- */
- private static PortTracker audioPortTracker;
-
- /**
- * RTP audio DSCP configuration property name.
- */
- private static final String RTP_AUDIO_DSCP_PROPERTY =
- "net.java.sip.communicator.impl.protocol.RTP_AUDIO_DSCP";
-
- /**
- * RTP video DSCP configuration property name.
- */
- private static final String RTP_VIDEO_DSCP_PROPERTY =
- "net.java.sip.communicator.impl.protocol.RTP_VIDEO_DSCP";
-
- /**
- * Number of empty UDP packets to send for NAT hole punching.
- */
- private static final String HOLE_PUNCH_PKT_COUNT_PROPERTY =
- "net.java.sip.communicator.impl.protocol.HOLE_PUNCH_PKT_COUNT";
-
- /**
- * Number of empty UDP packets to send for NAT hole punching.
- */
- private static final int DEFAULT_HOLE_PUNCH_PKT_COUNT = 3;
-
- /**
- * The {@link MediaAwareCallPeer} whose traffic we will be taking care of.
- */
- private U callPeer;
-
- /**
- * The RTP/RTCP socket couples that this <tt>TransportManager</tt> uses to
- * send and receive media flows through indexed by <tt>MediaType</tt>
- * (ordinal).
- */
- private final StreamConnector[] streamConnectors
- = new StreamConnector[MediaType.values().length];
-
- /**
- * Creates a new instance of this transport manager, binding it to the
- * specified peer.
- *
- * @param callPeer the {@link MediaAwareCallPeer} whose traffic we will be
- * taking care of.
- */
- protected TransportManager(U callPeer)
- {
- this.callPeer = callPeer;
- }
-
- /**
- * Returns the <tt>StreamConnector</tt> instance that this media handler
- * should use for streams of the specified <tt>mediaType</tt>. The method
- * would also create a new <tt>StreamConnector</tt> if no connector has
- * been initialized for this <tt>mediaType</tt> yet or in case one
- * of its underlying sockets has been closed.
- *
- * @param mediaType the <tt>MediaType</tt> that we'd like to create a
- * connector for.
- * @return this media handler's <tt>StreamConnector</tt> for the specified
- * <tt>mediaType</tt>.
- *
- * @throws OperationFailedException in case we failed to initialize our
- * connector.
- */
- public StreamConnector getStreamConnector(MediaType mediaType)
- throws OperationFailedException
- {
- int streamConnectorIndex = mediaType.ordinal();
- StreamConnector streamConnector
- = streamConnectors[streamConnectorIndex];
-
- if((streamConnector == null)
- || (streamConnector.getProtocol() == StreamConnector.Protocol.UDP))
- {
- DatagramSocket controlSocket;
-
- if((streamConnector == null)
- || streamConnector.getDataSocket().isClosed()
- || (((controlSocket = streamConnector.getControlSocket())
- != null)
- && controlSocket.isClosed()))
- {
- streamConnectors[streamConnectorIndex]
- = streamConnector
- = createStreamConnector(mediaType);
- }
- }
- else if(streamConnector.getProtocol() == StreamConnector.Protocol.TCP)
- {
- Socket controlTCPSocket;
-
- if(streamConnector.getDataTCPSocket().isClosed()
- || (((controlTCPSocket = streamConnector.getControlTCPSocket())
- != null)
- && controlTCPSocket.isClosed()))
- {
- streamConnectors[streamConnectorIndex]
- = streamConnector
- = createStreamConnector(mediaType);
- }
- }
- return streamConnector;
- }
-
- /**
- * Closes the existing <tt>StreamConnector</tt>, if any, associated with a
- * specific <tt>MediaType</tt> and removes its reference from this
- * <tt>TransportManager</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> associated with the
- * <tt>StreamConnector</tt> to close
- */
- public void closeStreamConnector(MediaType mediaType)
- {
- int index = mediaType.ordinal();
- StreamConnector streamConnector = streamConnectors[index];
-
- if (streamConnector != null)
- {
- closeStreamConnector(mediaType, streamConnector);
- streamConnectors[index] = null;
- }
- }
-
- /**
- * 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. Allows extenders to
- * override and perform additional customizations to the closing of the
- * specified <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
- * @see #closeStreamConnector(MediaType)
- */
- protected void closeStreamConnector(
- MediaType mediaType,
- StreamConnector streamConnector)
- {
- /*
- * XXX The connected owns the sockets so it is important that it
- * decides whether to close them i.e. this TransportManager is not
- * allowed to explicitly close the sockets by itself.
- */
- streamConnector.close();
- }
-
- /**
- * 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 createStreamConnector(MediaType mediaType)
- throws OperationFailedException
- {
- NetworkAddressManagerService nam
- = ProtocolMediaActivator.getNetworkAddressManagerService();
- InetAddress intendedDestination = getIntendedDestination(getCallPeer());
- InetAddress localHostForPeer = nam.getLocalHost(intendedDestination);
-
- PortTracker portTracker = getPortTracker(mediaType);
-
- //create the RTP socket.
- DatagramSocket rtpSocket = null;
- try
- {
- rtpSocket = nam.createDatagramSocket(
- localHostForPeer, portTracker.getPort(),
- portTracker.getMinPort(), portTracker.getMaxPort());
- }
- catch (Exception exc)
- {
- throw new OperationFailedException(
- "Failed to allocate the network ports necessary for the call.",
- OperationFailedException.INTERNAL_ERROR, exc);
- }
-
- //make sure that next time we don't try to bind on occupied ports
- //also, refuse validation in case someone set the tracker range to 1
- portTracker.setNextPort( rtpSocket.getLocalPort() + 1, false);
-
- //create the RTCP socket, preferably on the port following our RTP one.
- DatagramSocket rtcpSocket = null;
- try
- {
- rtcpSocket = nam.createDatagramSocket(
- localHostForPeer, portTracker.getPort(),
- portTracker.getMinPort(), portTracker.getMaxPort());
- }
- catch (Exception exc)
- {
- throw new OperationFailedException(
- "Failed to allocate the network ports necessary for the call.",
- OperationFailedException.INTERNAL_ERROR,
- exc);
- }
-
- //make sure that next time we don't try to bind on occupied ports
- portTracker.setNextPort( rtcpSocket.getLocalPort() + 1);
-
- return new DefaultStreamConnector(rtpSocket, rtcpSocket);
- }
-
- /**
- * Tries to set the ranges of the <tt>PortTracker</tt>s (e.g. default,
- * audio, video, data channel) to the values specified in the
- * <tt>ConfigurationService</tt>.
- */
- protected synchronized static void initializePortNumbers()
- {
- //try the default tracker first
- ConfigurationService cfg
- = ProtocolMediaActivator.getConfigurationService();
- String minPort, maxPort;
-
- minPort
- = cfg.getString(
- OperationSetBasicTelephony
- .MIN_MEDIA_PORT_NUMBER_PROPERTY_NAME);
- if (minPort != null)
- {
- maxPort
- = cfg.getString(
- OperationSetBasicTelephony
- .MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME);
- if (maxPort != null)
- {
- //Try the specified range; otherwise, leave the tracker as it
- //is: [5000, 6000].
- defaultPortTracker.tryRange(minPort, maxPort);
- }
- }
-
- //try the VIDEO tracker
- minPort
- = cfg.getString(
- OperationSetBasicTelephony
- .MIN_VIDEO_PORT_NUMBER_PROPERTY_NAME);
- if (minPort != null)
- {
- maxPort
- = cfg.getString(
- OperationSetBasicTelephony
- .MAX_VIDEO_PORT_NUMBER_PROPERTY_NAME);
- if (maxPort != null)
- {
- //Try the specified range; otherwise, leave the tracker to null.
- if (videoPortTracker == null)
- {
- videoPortTracker
- = PortTracker.createTracker(minPort, maxPort);
- }
- else
- {
- videoPortTracker.tryRange(minPort, maxPort);
- }
- }
- }
-
- //try the AUDIO tracker
- minPort
- = cfg.getString(
- OperationSetBasicTelephony
- .MIN_AUDIO_PORT_NUMBER_PROPERTY_NAME);
- if (minPort != null)
- {
- maxPort
- = cfg.getString(
- OperationSetBasicTelephony
- .MAX_AUDIO_PORT_NUMBER_PROPERTY_NAME);
- if (maxPort != null)
- {
- //Try the specified range; otherwise, leave the tracker to null.
- if (audioPortTracker == null)
- {
- audioPortTracker
- = PortTracker.createTracker(minPort, maxPort);
- }
- else
- {
- audioPortTracker.tryRange(minPort, maxPort);
- }
- }
- }
-
- //try the DATA CHANNEL tracker
- minPort
- = cfg.getString(
- OperationSetBasicTelephony
- .MIN_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME);
- if (minPort != null)
- {
- maxPort
- = cfg.getString(
- OperationSetBasicTelephony
- .MAX_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME);
- if (maxPort != null)
- {
- //Try the specified range; otherwise, leave the tracker to null.
- if (dataPortTracker == null)
- {
- dataPortTracker
- = PortTracker.createTracker(minPort, maxPort);
- }
- else
- {
- dataPortTracker.tryRange(minPort, maxPort);
- }
- }
- }
- }
-
- /**
- * Returns the <tt>InetAddress</tt> that we are using in one of our
- * <tt>StreamConnector</tt>s or, in case we don't have any connectors yet
- * the address returned by the our network address manager as the best local
- * address to use when contacting the <tt>CallPeer</tt> associated with this
- * <tt>MediaHandler</tt>. This method is primarily meant for use with the
- * o= and c= fields of a newly created session description. The point is
- * that we create our <tt>StreamConnector</tt>s when constructing the media
- * descriptions so we already have a specific local address assigned to them
- * at the time we get ready to create the c= and o= fields. It is therefore
- * better to try and return one of these addresses before trying the net
- * address manager again and running the slight risk of getting a different
- * address.
- *
- * @return an <tt>InetAddress</tt> that we use in one of the
- * <tt>StreamConnector</tt>s in this class.
- */
- public InetAddress getLastUsedLocalHost()
- {
- for (MediaType mediaType : MediaType.values())
- {
- StreamConnector streamConnector
- = streamConnectors[mediaType.ordinal()];
-
- if (streamConnector != null)
- return streamConnector.getDataSocket().getLocalAddress();
- }
-
- NetworkAddressManagerService nam
- = ProtocolMediaActivator.getNetworkAddressManagerService();
- InetAddress intendedDestination = getIntendedDestination(getCallPeer());
-
- return nam.getLocalHost(intendedDestination);
- }
-
- /**
- * Sends empty UDP packets to target destination data/control ports in order
- * to open ports on NATs or and help RTP proxies latch onto our RTP ports.
- *
- * @param target <tt>MediaStreamTarget</tt>
- * @param type the {@link MediaType} of the connector we'd like to send the
- * hole punching packet through.
- */
- public void sendHolePunchPacket(MediaStreamTarget target, MediaType type)
- {
- logger.info("Send NAT hole punch packets");
-
- //check how many hole punch packets we would be supposed to send:
- int packetCount
- = ProtocolMediaActivator.getConfigurationService().getInt(
- HOLE_PUNCH_PKT_COUNT_PROPERTY,
- DEFAULT_HOLE_PUNCH_PKT_COUNT);
-
- if (packetCount < 0)
- packetCount = DEFAULT_HOLE_PUNCH_PKT_COUNT;
- if (packetCount == 0)
- return;
-
- try
- {
- StreamConnector connector = getStreamConnector(type);
-
- if(connector.getProtocol() == StreamConnector.Protocol.TCP)
- return;
-
- byte[] buf = new byte[0];
-
- synchronized(connector)
- {
- //we may want to send more than one packet in case they get lost
- for(int i=0; i < packetCount; i++)
- {
- DatagramSocket socket;
-
- // data/RTP
- if((socket = connector.getDataSocket()) != null)
- {
- InetSocketAddress dataAddress = target.getDataAddress();
-
- socket.send(
- new DatagramPacket(
- buf,
- buf.length,
- dataAddress.getAddress(),
- dataAddress.getPort()));
- }
-
- // control/RTCP
- if((socket = connector.getControlSocket()) != null)
- {
- InetSocketAddress controlAddress
- = target.getControlAddress();
-
- socket.send(
- new DatagramPacket(
- buf,
- buf.length,
- controlAddress.getAddress(),
- controlAddress.getPort()));
- }
- }
- }
- }
- catch(Exception e)
- {
- logger.error("Error cannot send to remote peer", e);
- }
- }
-
- /**
- * Set traffic class (QoS) for the RTP socket.
- *
- * @param target <tt>MediaStreamTarget</tt>
- * @param type the {@link MediaType} of the connector we'd like to set
- * traffic class
- */
- protected void setTrafficClass(MediaStreamTarget target, MediaType type)
- {
- // get traffic class value for RTP audio/video
- int trafficClass = getDSCP(type);
-
- if(trafficClass <= 0)
- return;
-
- if (logger.isInfoEnabled())
- logger.info(
- "Set traffic class for " + type + " to " + trafficClass);
- try
- {
- StreamConnector connector = getStreamConnector(type);
-
- synchronized(connector)
- {
- if(connector.getProtocol() == StreamConnector.Protocol.TCP)
- {
- connector.getDataTCPSocket().setTrafficClass(trafficClass);
-
- Socket controlTCPSocket = connector.getControlTCPSocket();
-
- if (controlTCPSocket != null)
- controlTCPSocket.setTrafficClass(trafficClass);
- }
- else
- {
- /* data port (RTP) */
- connector.getDataSocket().setTrafficClass(trafficClass);
-
- /* control port (RTCP) */
- DatagramSocket controlSocket = connector.getControlSocket();
-
- if (controlSocket != null)
- controlSocket.setTrafficClass(trafficClass);
- }
- }
- }
- catch(Exception e)
- {
- logger.error(
- "Failed to set traffic class for " + type + " to "
- + trafficClass,
- e);
- }
- }
-
- /**
- * Gets the SIP traffic class associated with a specific <tt>MediaType</tt>
- * from the configuration.
- *
- * @param type the <tt>MediaType</tt> to get the associated SIP traffic
- * class of
- * @return the SIP traffic class associated with the specified
- * <tt>MediaType</tt> or <tt>0</tt> if not configured
- */
- private int getDSCP(MediaType type)
- {
- String dscpPropertyName;
-
- switch (type)
- {
- case AUDIO:
- dscpPropertyName = RTP_AUDIO_DSCP_PROPERTY;
- break;
- case VIDEO:
- dscpPropertyName = RTP_VIDEO_DSCP_PROPERTY;
- break;
- default:
- dscpPropertyName = null;
- break;
- }
-
- return
- (dscpPropertyName == null)
- ? 0
- : (ProtocolMediaActivator.getConfigurationService().getInt(
- dscpPropertyName,
- 0)
- << 2);
- }
-
- /**
- * Returns the <tt>InetAddress</tt> that is most likely 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
- */
- protected abstract InetAddress getIntendedDestination(U peer);
-
- /**
- * Returns the {@link MediaAwareCallPeer} that this transport manager is
- * serving.
- *
- * @return the {@link MediaAwareCallPeer} that this transport manager is
- * serving.
- */
- public U getCallPeer()
- {
- return callPeer;
- }
-
- /**
- * Returns the port tracker that we are supposed to use when binding ports
- * for the specified {@link MediaType}.
- *
- * @param mediaType the media type that we want to obtain the port tracker
- * for. Use <tt>null</tt> to obtain the default port tracker.
- *
- * @return the port tracker that we are supposed to use when binding ports
- * for the specified {@link MediaType}.
- */
- protected static PortTracker getPortTracker(MediaType mediaType)
- {
- //make sure our port numbers reflect the configuration service settings
- initializePortNumbers();
-
- if (mediaType != null)
- {
- switch (mediaType)
- {
- case AUDIO:
- if (audioPortTracker != null)
- return audioPortTracker;
- else
- break;
- case VIDEO:
- if (videoPortTracker != null)
- return videoPortTracker;
- else
- break;
- case DATA:
- if (dataPortTracker != null)
- return dataPortTracker;
- else
- break;
- }
- }
-
- return defaultPortTracker;
- }
-
- /**
- * Returns the port tracker that we are supposed to use when binding ports
- * for the {@link MediaType} indicated by the string param. If we do not
- * recognize the string as a valid media type, we simply return the default
- * port tracker.
- *
- * @param mediaTypeStr the name of the media type that we want to obtain a
- * port tracker for.
- *
- * @return the port tracker that we are supposed to use when binding ports
- * for the {@link MediaType} with the specified name or the default tracker
- * in case the name doesn't ring a bell.
- */
- protected static PortTracker getPortTracker(String mediaTypeStr)
- {
- try
- {
- return getPortTracker(MediaType.parseString(mediaTypeStr));
- }
- catch (Exception e)
- {
- logger.info(
- "Returning default port tracker for unrecognized media type: "
- + mediaTypeStr);
-
- return defaultPortTracker;
- }
- }
-
- /**
- * 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.
- */
- public abstract String getICECandidateExtendedType(String streamName);
-
- /**
- * Returns the current state of ICE processing.
- *
- * @return the current state of ICE processing if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- public abstract String getICEState();
-
- /**
- * 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.
- */
- public abstract InetSocketAddress getICELocalHostAddress(String streamName);
-
- /**
- * 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.
- */
- public abstract InetSocketAddress getICERemoteHostAddress(
- String streamName);
-
- /**
- * 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.
- */
- public abstract InetSocketAddress getICELocalReflexiveAddress(
- String streamName);
-
- /**
- * 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.
- */
- public abstract InetSocketAddress getICERemoteReflexiveAddress(
- String streamName);
-
- /**
- * 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.
- */
- public abstract InetSocketAddress getICELocalRelayedAddress(
- String streamName);
-
- /**
- * 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.
- */
- public abstract InetSocketAddress getICERemoteRelayedAddress(
- String streamName);
-
- /**
- * 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.
- */
- public abstract long getTotalHarvestingTime();
-
- /**
- * 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.
- */
- public abstract long getHarvestingTime(String harvesterName);
-
- /**
- * Returns the number of harvesting for this agent.
- *
- * @return The number of harvesting for this agent.
- */
- public abstract int getNbHarvesting();
-
- /**
- * 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.
- */
- public abstract int getNbHarvesting(String harvesterName);
-
- /**
- * Returns the ICE candidate extended type selected by the given agent.
- *
- * @param iceAgent The ICE agent managing the ICE offer/answer exchange,
- * collecting and selecting the candidate.
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return The ICE candidate extended type selected by the given agent. null
- * if the iceAgent is null or if there is no candidate selected or
- * available.
- */
- public static String getICECandidateExtendedType(
- Agent iceAgent,
- String streamName)
- {
- if(iceAgent != null)
- {
- LocalCandidate localCandidate
- = iceAgent.getSelectedLocalCandidate(streamName);
-
- if(localCandidate != null)
- return localCandidate.getExtendedType().toString();
- }
- return null;
- }
-
-
- /**
- * Creates the ICE agent that we would be using in this transport manager
- * for all negotiation.
- *
- * @return the ICE agent to use for all the ICE negotiation that this
- * transport manager would be going through
- */
- protected Agent createIceAgent()
- {
- //work in progress
- return null;
- }
-
- /**
- * Creates an {@link IceMediaStream} with the specified <tt>media</tt>
- * name.
- *
- * @param media the name of the stream we'd like to create.
- * @param agent the ICE {@link Agent} that we will be appending the stream
- * to.
- *
- * @return the newly created {@link IceMediaStream}
- *
- * @throws OperationFailedException if binding on the specified media stream
- * fails for some reason.
- */
- protected IceMediaStream createIceStream(String media, Agent agent)
- throws OperationFailedException
- {
- return null;
- }
-}
+package net.java.sip.communicator.service.protocol.media;
+
+import java.net.*;
+
+import net.java.sip.communicator.service.netaddr.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+
+import org.ice4j.ice.*;
+import org.jitsi.service.configuration.*;
+import org.jitsi.service.neomedia.*;
+
+/**
+ * <tt>TransportManager</tt>s are responsible for allocating ports, gathering
+ * local candidates and managing ICE whenever we are using it.
+ *
+ * @param <U> the peer extension class like for example <tt>CallPeerSipImpl</tt>
+ * or <tt>CallPeerJabberImpl</tt>
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Sebastien Vincent
+ */
+public abstract class TransportManager<U extends MediaAwareCallPeer<?, ?, ?>>
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>TransportManager</tt>
+ * class and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(TransportManager.class);
+
+ /**
+ * The port tracker that we should use when binding generic media streams.
+ * <p>
+ * Initialized by {@link #initializePortNumbers()}.
+ * </p>
+ */
+ private static final PortTracker defaultPortTracker
+ = new PortTracker(5000, 6000);
+
+ /**
+ * The port tracker that we should use when binding video media streams.
+ * <p>
+ * Potentially initialized by {@link #initializePortNumbers()} if the
+ * necessary properties are set.
+ * </p>
+ */
+ private static PortTracker videoPortTracker;
+
+ /**
+ * The port tracker that we should use when binding data channels.
+ * <p>
+ * Potentially initialized by {@link #initializePortNumbers()} if the
+ * necessary properties are set.
+ * </p>
+ */
+ private static PortTracker dataPortTracker;
+
+ /**
+ * The port tracker that we should use when binding data media streams.
+ * <p>
+ * Potentially initialized by {@link #initializePortNumbers()} if the
+ * necessary properties are set.
+ * </p>
+ */
+ private static PortTracker audioPortTracker;
+
+ /**
+ * RTP audio DSCP configuration property name.
+ */
+ private static final String RTP_AUDIO_DSCP_PROPERTY =
+ "net.java.sip.communicator.impl.protocol.RTP_AUDIO_DSCP";
+
+ /**
+ * RTP video DSCP configuration property name.
+ */
+ private static final String RTP_VIDEO_DSCP_PROPERTY =
+ "net.java.sip.communicator.impl.protocol.RTP_VIDEO_DSCP";
+
+ /**
+ * Number of empty UDP packets to send for NAT hole punching.
+ */
+ private static final String HOLE_PUNCH_PKT_COUNT_PROPERTY =
+ "net.java.sip.communicator.impl.protocol.HOLE_PUNCH_PKT_COUNT";
+
+ /**
+ * Number of empty UDP packets to send for NAT hole punching.
+ */
+ private static final int DEFAULT_HOLE_PUNCH_PKT_COUNT = 3;
+
+ /**
+ * Returns the port tracker that we are supposed to use when binding ports
+ * for the specified {@link MediaType}.
+ *
+ * @param mediaType the media type that we want to obtain the port tracker
+ * for. Use <tt>null</tt> to obtain the default port tracker.
+ *
+ * @return the port tracker that we are supposed to use when binding ports
+ * for the specified {@link MediaType}.
+ */
+ protected static PortTracker getPortTracker(MediaType mediaType)
+ {
+ //make sure our port numbers reflect the configuration service settings
+ initializePortNumbers();
+
+ if (mediaType != null)
+ {
+ switch (mediaType)
+ {
+ case AUDIO:
+ if (audioPortTracker != null)
+ return audioPortTracker;
+ else
+ break;
+ case VIDEO:
+ if (videoPortTracker != null)
+ return videoPortTracker;
+ else
+ break;
+ case DATA:
+ if (dataPortTracker != null)
+ return dataPortTracker;
+ else
+ break;
+ }
+ }
+
+ return defaultPortTracker;
+ }
+
+ /**
+ * Returns the port tracker that we are supposed to use when binding ports
+ * for the {@link MediaType} indicated by the string param. If we do not
+ * recognize the string as a valid media type, we simply return the default
+ * port tracker.
+ *
+ * @param mediaTypeStr the name of the media type that we want to obtain a
+ * port tracker for.
+ *
+ * @return the port tracker that we are supposed to use when binding ports
+ * for the {@link MediaType} with the specified name or the default tracker
+ * in case the name doesn't ring a bell.
+ */
+ protected static PortTracker getPortTracker(String mediaTypeStr)
+ {
+ try
+ {
+ return getPortTracker(MediaType.parseString(mediaTypeStr));
+ }
+ catch (Exception e)
+ {
+ logger.info(
+ "Returning default port tracker for unrecognized media type: "
+ + mediaTypeStr);
+
+ return defaultPortTracker;
+ }
+ }
+
+
+ /**
+ * The {@link MediaAwareCallPeer} whose traffic we will be taking care of.
+ */
+ private U callPeer;
+
+ /**
+ * The RTP/RTCP socket couples that this <tt>TransportManager</tt> uses to
+ * send and receive media flows through indexed by <tt>MediaType</tt>
+ * (ordinal).
+ */
+ private final StreamConnector[] streamConnectors
+ = new StreamConnector[MediaType.values().length];
+
+ /**
+ * Creates a new instance of this transport manager, binding it to the
+ * specified peer.
+ *
+ * @param callPeer the {@link MediaAwareCallPeer} whose traffic we will be
+ * taking care of.
+ */
+ protected TransportManager(U callPeer)
+ {
+ this.callPeer = callPeer;
+ }
+
+ /**
+ * Returns the <tt>StreamConnector</tt> instance that this media handler
+ * should use for streams of the specified <tt>mediaType</tt>. The method
+ * would also create a new <tt>StreamConnector</tt> if no connector has
+ * been initialized for this <tt>mediaType</tt> yet or in case one
+ * of its underlying sockets has been closed.
+ *
+ * @param mediaType the <tt>MediaType</tt> that we'd like to create a
+ * connector for.
+ * @return this media handler's <tt>StreamConnector</tt> for the specified
+ * <tt>mediaType</tt>.
+ *
+ * @throws OperationFailedException in case we failed to initialize our
+ * connector.
+ */
+ public StreamConnector getStreamConnector(MediaType mediaType)
+ throws OperationFailedException
+ {
+ int streamConnectorIndex = mediaType.ordinal();
+ StreamConnector streamConnector
+ = streamConnectors[streamConnectorIndex];
+
+ if((streamConnector == null)
+ || (streamConnector.getProtocol() == StreamConnector.Protocol.UDP))
+ {
+ DatagramSocket controlSocket;
+
+ if((streamConnector == null)
+ || streamConnector.getDataSocket().isClosed()
+ || (((controlSocket = streamConnector.getControlSocket())
+ != null)
+ && controlSocket.isClosed()))
+ {
+ streamConnectors[streamConnectorIndex]
+ = streamConnector
+ = createStreamConnector(mediaType);
+ }
+ }
+ else if(streamConnector.getProtocol() == StreamConnector.Protocol.TCP)
+ {
+ Socket controlTCPSocket;
+
+ if(streamConnector.getDataTCPSocket().isClosed()
+ || (((controlTCPSocket = streamConnector.getControlTCPSocket())
+ != null)
+ && controlTCPSocket.isClosed()))
+ {
+ streamConnectors[streamConnectorIndex]
+ = streamConnector
+ = createStreamConnector(mediaType);
+ }
+ }
+ return streamConnector;
+ }
+
+ /**
+ * Closes the existing <tt>StreamConnector</tt>, if any, associated with a
+ * specific <tt>MediaType</tt> and removes its reference from this
+ * <tt>TransportManager</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> associated with the
+ * <tt>StreamConnector</tt> to close
+ */
+ public void closeStreamConnector(MediaType mediaType)
+ {
+ int index = mediaType.ordinal();
+ StreamConnector streamConnector = streamConnectors[index];
+
+ if (streamConnector != null)
+ {
+ closeStreamConnector(mediaType, streamConnector);
+ streamConnectors[index] = null;
+ }
+ }
+
+ /**
+ * 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. Allows extenders to
+ * override and perform additional customizations to the closing of the
+ * specified <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
+ * @see #closeStreamConnector(MediaType)
+ */
+ protected void closeStreamConnector(
+ MediaType mediaType,
+ StreamConnector streamConnector)
+ {
+ /*
+ * XXX The connected owns the sockets so it is important that it
+ * decides whether to close them i.e. this TransportManager is not
+ * allowed to explicitly close the sockets by itself.
+ */
+ streamConnector.close();
+ }
+
+ /**
+ * 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 createStreamConnector(MediaType mediaType)
+ throws OperationFailedException
+ {
+ NetworkAddressManagerService nam
+ = ProtocolMediaActivator.getNetworkAddressManagerService();
+ InetAddress intendedDestination = getIntendedDestination(getCallPeer());
+ InetAddress localHostForPeer = nam.getLocalHost(intendedDestination);
+
+ PortTracker portTracker = getPortTracker(mediaType);
+
+ //create the RTP socket.
+ DatagramSocket rtpSocket = null;
+ try
+ {
+ rtpSocket = nam.createDatagramSocket(
+ localHostForPeer, portTracker.getPort(),
+ portTracker.getMinPort(), portTracker.getMaxPort());
+ }
+ catch (Exception exc)
+ {
+ throw new OperationFailedException(
+ "Failed to allocate the network ports necessary for the call.",
+ OperationFailedException.INTERNAL_ERROR, exc);
+ }
+
+ //make sure that next time we don't try to bind on occupied ports
+ //also, refuse validation in case someone set the tracker range to 1
+ portTracker.setNextPort( rtpSocket.getLocalPort() + 1, false);
+
+ //create the RTCP socket, preferably on the port following our RTP one.
+ DatagramSocket rtcpSocket = null;
+ try
+ {
+ rtcpSocket = nam.createDatagramSocket(
+ localHostForPeer, portTracker.getPort(),
+ portTracker.getMinPort(), portTracker.getMaxPort());
+ }
+ catch (Exception exc)
+ {
+ throw new OperationFailedException(
+ "Failed to allocate the network ports necessary for the call.",
+ OperationFailedException.INTERNAL_ERROR,
+ exc);
+ }
+
+ //make sure that next time we don't try to bind on occupied ports
+ portTracker.setNextPort( rtcpSocket.getLocalPort() + 1);
+
+ return new DefaultStreamConnector(rtpSocket, rtcpSocket);
+ }
+
+ /**
+ * Tries to set the ranges of the <tt>PortTracker</tt>s (e.g. default,
+ * audio, video, data channel) to the values specified in the
+ * <tt>ConfigurationService</tt>.
+ */
+ protected synchronized static void initializePortNumbers()
+ {
+ //try the default tracker first
+ ConfigurationService cfg
+ = ProtocolMediaActivator.getConfigurationService();
+ String minPort, maxPort;
+
+ minPort
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MIN_MEDIA_PORT_NUMBER_PROPERTY_NAME);
+ if (minPort != null)
+ {
+ maxPort
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME);
+ if (maxPort != null)
+ {
+ //Try the specified range; otherwise, leave the tracker as it
+ //is: [5000, 6000].
+ defaultPortTracker.tryRange(minPort, maxPort);
+ }
+ }
+
+ //try the VIDEO tracker
+ minPort
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MIN_VIDEO_PORT_NUMBER_PROPERTY_NAME);
+ if (minPort != null)
+ {
+ maxPort
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MAX_VIDEO_PORT_NUMBER_PROPERTY_NAME);
+ if (maxPort != null)
+ {
+ //Try the specified range; otherwise, leave the tracker to null.
+ if (videoPortTracker == null)
+ {
+ videoPortTracker
+ = PortTracker.createTracker(minPort, maxPort);
+ }
+ else
+ {
+ videoPortTracker.tryRange(minPort, maxPort);
+ }
+ }
+ }
+
+ //try the AUDIO tracker
+ minPort
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MIN_AUDIO_PORT_NUMBER_PROPERTY_NAME);
+ if (minPort != null)
+ {
+ maxPort
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MAX_AUDIO_PORT_NUMBER_PROPERTY_NAME);
+ if (maxPort != null)
+ {
+ //Try the specified range; otherwise, leave the tracker to null.
+ if (audioPortTracker == null)
+ {
+ audioPortTracker
+ = PortTracker.createTracker(minPort, maxPort);
+ }
+ else
+ {
+ audioPortTracker.tryRange(minPort, maxPort);
+ }
+ }
+ }
+
+ //try the DATA CHANNEL tracker
+ minPort
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MIN_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME);
+ if (minPort != null)
+ {
+ maxPort
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MAX_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME);
+ if (maxPort != null)
+ {
+ //Try the specified range; otherwise, leave the tracker to null.
+ if (dataPortTracker == null)
+ {
+ dataPortTracker
+ = PortTracker.createTracker(minPort, maxPort);
+ }
+ else
+ {
+ dataPortTracker.tryRange(minPort, maxPort);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the <tt>InetAddress</tt> that we are using in one of our
+ * <tt>StreamConnector</tt>s or, in case we don't have any connectors yet
+ * the address returned by the our network address manager as the best local
+ * address to use when contacting the <tt>CallPeer</tt> associated with this
+ * <tt>MediaHandler</tt>. This method is primarily meant for use with the
+ * o= and c= fields of a newly created session description. The point is
+ * that we create our <tt>StreamConnector</tt>s when constructing the media
+ * descriptions so we already have a specific local address assigned to them
+ * at the time we get ready to create the c= and o= fields. It is therefore
+ * better to try and return one of these addresses before trying the net
+ * address manager again and running the slight risk of getting a different
+ * address.
+ *
+ * @return an <tt>InetAddress</tt> that we use in one of the
+ * <tt>StreamConnector</tt>s in this class.
+ */
+ public InetAddress getLastUsedLocalHost()
+ {
+ for (MediaType mediaType : MediaType.values())
+ {
+ StreamConnector streamConnector
+ = streamConnectors[mediaType.ordinal()];
+
+ if (streamConnector != null)
+ return streamConnector.getDataSocket().getLocalAddress();
+ }
+
+ NetworkAddressManagerService nam
+ = ProtocolMediaActivator.getNetworkAddressManagerService();
+ InetAddress intendedDestination = getIntendedDestination(getCallPeer());
+
+ return nam.getLocalHost(intendedDestination);
+ }
+
+ /**
+ * Sends empty UDP packets to target destination data/control ports in order
+ * to open ports on NATs or and help RTP proxies latch onto our RTP ports.
+ *
+ * @param target <tt>MediaStreamTarget</tt>
+ * @param type the {@link MediaType} of the connector we'd like to send the
+ * hole punching packet through.
+ */
+ public void sendHolePunchPacket(MediaStreamTarget target, MediaType type)
+ {
+ this.sendHolePunchPacket(target, type, null);
+ }
+
+ /**
+ * Sends empty UDP packets to target destination data/control ports in order
+ * to open ports on NATs or and help RTP proxies latch onto our RTP ports.
+ *
+ * @param target <tt>MediaStreamTarget</tt>
+ * @param type the {@link MediaType} of the connector we'd like to send the
+ * hole punching packet through.
+ * @param packet (optional) use a pre-generated packet that will be sent
+ */
+ public void sendHolePunchPacket(
+ MediaStreamTarget target, MediaType type, RawPacket packet)
+ {
+ logger.info("Send NAT hole punch packets");
+
+ //check how many hole punch packets we would be supposed to send:
+ int packetCount
+ = ProtocolMediaActivator.getConfigurationService().getInt(
+ HOLE_PUNCH_PKT_COUNT_PROPERTY,
+ DEFAULT_HOLE_PUNCH_PKT_COUNT);
+
+ if (packetCount < 0)
+ packetCount = DEFAULT_HOLE_PUNCH_PKT_COUNT;
+ if (packetCount == 0)
+ return;
+
+ try
+ {
+ final StreamConnector connector = getStreamConnector(type);
+
+ if(connector.getProtocol() == StreamConnector.Protocol.TCP)
+ return;
+
+ byte[] buf;
+ if (packet != null)
+ buf = packet.getBuffer();
+ else
+ buf = new byte[0];
+
+ synchronized(connector)
+ {
+ //we may want to send more than one packet in case they get lost
+ for(int i=0; i < packetCount; i++)
+ {
+ DatagramSocket socket;
+
+ // data/RTP
+ if((socket = connector.getDataSocket()) != null)
+ {
+ InetSocketAddress dataAddress = target.getDataAddress();
+
+ socket.send(
+ new DatagramPacket(
+ buf,
+ buf.length,
+ dataAddress.getAddress(),
+ dataAddress.getPort()));
+ }
+
+ // control/RTCP
+ if((socket = connector.getControlSocket()) != null)
+ {
+ InetSocketAddress controlAddress
+ = target.getControlAddress();
+
+ socket.send(
+ new DatagramPacket(
+ buf,
+ buf.length,
+ controlAddress.getAddress(),
+ controlAddress.getPort()));
+ }
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ logger.error("Error cannot send to remote peer", e);
+ }
+ }
+
+ /**
+ * Set traffic class (QoS) for the RTP socket.
+ *
+ * @param target <tt>MediaStreamTarget</tt>
+ * @param type the {@link MediaType} of the connector we'd like to set
+ * traffic class
+ */
+ protected void setTrafficClass(MediaStreamTarget target, MediaType type)
+ {
+ // get traffic class value for RTP audio/video
+ int trafficClass = getDSCP(type);
+
+ if(trafficClass <= 0)
+ return;
+
+ if (logger.isInfoEnabled())
+ logger.info(
+ "Set traffic class for " + type + " to " + trafficClass);
+ try
+ {
+ final StreamConnector connector = getStreamConnector(type);
+
+ synchronized(connector)
+ {
+ if(connector.getProtocol() == StreamConnector.Protocol.TCP)
+ {
+ connector.getDataTCPSocket().setTrafficClass(trafficClass);
+
+ Socket controlTCPSocket = connector.getControlTCPSocket();
+
+ if (controlTCPSocket != null)
+ controlTCPSocket.setTrafficClass(trafficClass);
+ }
+ else
+ {
+ /* data port (RTP) */
+ connector.getDataSocket().setTrafficClass(trafficClass);
+
+ /* control port (RTCP) */
+ DatagramSocket controlSocket = connector.getControlSocket();
+
+ if (controlSocket != null)
+ controlSocket.setTrafficClass(trafficClass);
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ logger.error(
+ "Failed to set traffic class for " + type + " to "
+ + trafficClass,
+ e);
+ }
+ }
+
+ /**
+ * Gets the SIP traffic class associated with a specific <tt>MediaType</tt>
+ * from the configuration.
+ *
+ * @param type the <tt>MediaType</tt> to get the associated SIP traffic
+ * class of
+ * @return the SIP traffic class associated with the specified
+ * <tt>MediaType</tt> or <tt>0</tt> if not configured
+ */
+ private int getDSCP(MediaType type)
+ {
+ String dscpPropertyName;
+
+ switch (type)
+ {
+ case AUDIO:
+ dscpPropertyName = RTP_AUDIO_DSCP_PROPERTY;
+ break;
+ case VIDEO:
+ dscpPropertyName = RTP_VIDEO_DSCP_PROPERTY;
+ break;
+ default:
+ dscpPropertyName = null;
+ break;
+ }
+
+ return
+ (dscpPropertyName == null)
+ ? 0
+ : (ProtocolMediaActivator.getConfigurationService().getInt(
+ dscpPropertyName,
+ 0)
+ << 2);
+ }
+
+ /**
+ * Returns the <tt>InetAddress</tt> that is most likely 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
+ */
+ protected abstract InetAddress getIntendedDestination(U peer);
+
+ /**
+ * Returns the {@link MediaAwareCallPeer} that this transport manager is
+ * serving.
+ *
+ * @return the {@link MediaAwareCallPeer} that this transport manager is
+ * serving.
+ */
+ public U getCallPeer()
+ {
+ return callPeer;
+ }
+
+ /**
+ * 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.
+ */
+ public abstract String getICECandidateExtendedType(String streamName);
+
+ /**
+ * Returns the current state of ICE processing.
+ *
+ * @return the current state of ICE processing if this transport
+ * manager is using ICE. Otherwise, returns null.
+ */
+ public abstract String getICEState();
+
+ /**
+ * 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.
+ */
+ public abstract InetSocketAddress getICELocalHostAddress(String streamName);
+
+ /**
+ * 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.
+ */
+ public abstract InetSocketAddress getICERemoteHostAddress(
+ String streamName);
+
+ /**
+ * 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.
+ */
+ public abstract InetSocketAddress getICELocalReflexiveAddress(
+ String streamName);
+
+ /**
+ * 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.
+ */
+ public abstract InetSocketAddress getICERemoteReflexiveAddress(
+ String streamName);
+
+ /**
+ * 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.
+ */
+ public abstract InetSocketAddress getICELocalRelayedAddress(
+ String streamName);
+
+ /**
+ * 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.
+ */
+ public abstract InetSocketAddress getICERemoteRelayedAddress(
+ String streamName);
+
+ /**
+ * 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.
+ */
+ public abstract long getTotalHarvestingTime();
+
+ /**
+ * 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.
+ */
+ public abstract long getHarvestingTime(String harvesterName);
+
+ /**
+ * Returns the number of harvesting for this agent.
+ *
+ * @return The number of harvesting for this agent.
+ */
+ public abstract int getNbHarvesting();
+
+ /**
+ * 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.
+ */
+ public abstract int getNbHarvesting(String harvesterName);
+
+ /**
+ * Returns the ICE candidate extended type selected by the given agent.
+ *
+ * @param iceAgent The ICE agent managing the ICE offer/answer exchange,
+ * collecting and selecting the candidate.
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return The ICE candidate extended type selected by the given agent. null
+ * if the iceAgent is null or if there is no candidate selected or
+ * available.
+ */
+ public static String getICECandidateExtendedType(
+ Agent iceAgent,
+ String streamName)
+ {
+ if(iceAgent != null)
+ {
+ LocalCandidate localCandidate
+ = iceAgent.getSelectedLocalCandidate(streamName);
+
+ if(localCandidate != null)
+ return localCandidate.getExtendedType().toString();
+ }
+ return null;
+ }
+
+
+ /**
+ * Creates the ICE agent that we would be using in this transport manager
+ * for all negotiation.
+ *
+ * @return the ICE agent to use for all the ICE negotiation that this
+ * transport manager would be going through
+ */
+ protected Agent createIceAgent()
+ {
+ //work in progress
+ return null;
+ }
+
+ /**
+ * Creates an {@link IceMediaStream} with the specified <tt>media</tt>
+ * name.
+ *
+ * @param media the name of the stream we'd like to create.
+ * @param agent the ICE {@link Agent} that we will be appending the stream
+ * to.
+ *
+ * @return the newly created {@link IceMediaStream}
+ *
+ * @throws OperationFailedException if binding on the specified media stream
+ * fails for some reason.
+ */
+ protected IceMediaStream createIceStream(String media, Agent agent)
+ throws OperationFailedException
+ {
+ return null;
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/media/protocol.media.manifest.mf b/src/net/java/sip/communicator/service/protocol/media/protocol.media.manifest.mf
index 4436e93..7bac7aa 100644
--- a/src/net/java/sip/communicator/service/protocol/media/protocol.media.manifest.mf
+++ b/src/net/java/sip/communicator/service/protocol/media/protocol.media.manifest.mf
@@ -15,7 +15,6 @@ Import-Package: javax.xml.parsers,
org.ice4j,
org.ice4j.ice,
org.ice4j.ice.harvest,
- org.ice4j.ice.sdp,
org.ice4j.security,
org.ice4j.socket,
org.ice4j.stack,
diff --git a/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf b/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf
index 271af97..233eb84 100644
--- a/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf
+++ b/src/net/java/sip/communicator/service/protocol/protocol.provider.manifest.mf
@@ -17,12 +17,10 @@ Import-Package: net.java.sip.communicator.service.credentialsstorage,
org.jitsi.util.event,
org.osgi.framework
Export-Package: net.java.sip.communicator.service.protocol,
- net.java.sip.communicator.service.protocol.aimconstants,
net.java.sip.communicator.service.protocol.event,
net.java.sip.communicator.service.protocol.globalstatus,
net.java.sip.communicator.service.protocol.icqconstants,
net.java.sip.communicator.service.protocol.jabber,
net.java.sip.communicator.service.protocol.jabberconstants,
net.java.sip.communicator.service.protocol.sip,
- net.java.sip.communicator.service.protocol.whiteboardobjects,
- net.java.sip.communicator.service.protocol.yahooconstants
+ net.java.sip.communicator.service.protocol.whiteboardobjects
diff --git a/src/net/java/sip/communicator/service/protocol/sip/SIPAccountRegistration.java b/src/net/java/sip/communicator/service/protocol/sip/SIPAccountRegistration.java
index 9719b8c..5355855 100644
--- a/src/net/java/sip/communicator/service/protocol/sip/SIPAccountRegistration.java
+++ b/src/net/java/sip/communicator/service/protocol/sip/SIPAccountRegistration.java
@@ -24,7 +24,7 @@ import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.neomedia.*;
-
+import org.jitsi.util.StringUtils;
import org.osgi.framework.*;
/**
@@ -257,23 +257,37 @@ public class SIPAccountRegistration
encodingsRegistration.storeProperties(this.accountProperties);
- if (isModification)
+ if(isMessageWaitingIndicationsEnabled())
{
- if (isMessageWaitingIndicationsEnabled())
+ if(!StringUtils.isNullOrEmpty(getVoicemailURI(), true))
+ accountProperties.put(
+ ProtocolProviderFactory.VOICEMAIL_URI,
+ getVoicemailURI());
+ else if(isModification)
+ accountProperties.put(ProtocolProviderFactory.VOICEMAIL_URI, "");
+
+ if(!StringUtils.isNullOrEmpty(
+ getVoicemailCheckURI(), true))
+ accountProperties.put(
+ ProtocolProviderFactory.VOICEMAIL_CHECK_URI,
+ getVoicemailCheckURI());
+ else if(isModification)
+ accountProperties.put(
+ ProtocolProviderFactory.VOICEMAIL_CHECK_URI, "");
+
+ if(isModification)
{
- setVoicemailURI("");
- setVoicemailCheckURI("");
// remove the property as true is by default,
// and null removes property
- removeAccountProperty(
- ProtocolProviderFactory.VOICEMAIL_ENABLED);
- } else
- {
- accountProperties.put(
- ProtocolProviderFactory.VOICEMAIL_ENABLED,
- Boolean.FALSE.toString());
+ accountProperties.put(ProtocolProviderFactory.VOICEMAIL_ENABLED,
+ null);
}
}
+ else if(isModification)
+ {
+ accountProperties.put(ProtocolProviderFactory.VOICEMAIL_ENABLED,
+ Boolean.FALSE.toString());
+ }
super.storeProperties(
protocolIconPath, accountIconPath, accountProperties);
diff --git a/src/net/java/sip/communicator/service/protocol/yahooconstants/YahooStatusEnum.java b/src/net/java/sip/communicator/service/protocol/yahooconstants/YahooStatusEnum.java
deleted file mode 100644
index d21ec033..0000000
--- a/src/net/java/sip/communicator/service/protocol/yahooconstants/YahooStatusEnum.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.service.protocol.yahooconstants;
-
-import java.io.*;
-import java.util.*;
-
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-/**
- * An enumeration containing all status instances that MUST be supported by
- * an implementation of the yahoo protocol. Implementations may
- * support other forms of PresenceStatus but they MUST ALL support those
- * enumerated here.
- * <p>
- * For testing purposes, this class also provides a <tt>List</tt> containing
- * all of the status fields.
- *
- * @author Damian Minkov
- */
-public class YahooStatusEnum
- extends PresenceStatus
-{
- /**
- * The <tt>Logger</tt> used by the <tt>YahooStatusEnum</tt> class and its
- * instances for logging output.
- */
- private static Logger logger = Logger.getLogger(YahooStatusEnum.class);
-
- /**
- * The Online status. Indicate that the user is able and willing to
- * communicate.
- */
- public static final YahooStatusEnum AVAILABLE
- = new YahooStatusEnum(65, "Available",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-online.png"));
-
- /**
- * 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 YahooStatusEnum BE_RIGHT_BACK
- = new YahooStatusEnum(48, "Be Right Back",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-away.png"));
-
- /**
- * The Idle status. Indicates that the user is not using the messanger.
- */
- public static final YahooStatusEnum IDLE
- = new YahooStatusEnum(46, "Idle",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-idle.png"));
-
- /**
- * 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 YahooStatusEnum INVISIBLE
- = new YahooStatusEnum(45, "Invisible",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-invisible.png"));
-
- /**
- * The STEPPED_OUT status. Indicates that the user has connectivity but might
- * not be able to immediately act upon initiation of communication.
- */
- public static final YahooStatusEnum STEPPED_OUT
- = new YahooStatusEnum(40, "Stepped out",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-away.png"));
-
- /**
- * The Out to lunch status. Indicates that the user is eating.
- */
- public static final YahooStatusEnum OUT_TO_LUNCH
- = new YahooStatusEnum(39, "Out to lunch",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-lunch.png"));
-
- /**
- * The Not at home status. Indicates that the user is not at home.
- */
- public static final YahooStatusEnum NOT_AT_HOME
- = new YahooStatusEnum(38, "Not at home",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-na.png"));
-
- /**
- * The Not at desk status. Indicates that the user is not at his desk, but
- * somewhere in the office.
- */
- public static final YahooStatusEnum NOT_AT_DESK
- = new YahooStatusEnum(36, "Not at desk",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-na.png"));
-
- /**
- * The Not in office status. Indicates that the user is out of the office.
- */
- public static final YahooStatusEnum NOT_IN_OFFICE
- = new YahooStatusEnum(34, "Not in office",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-na.png"));
-
- /**
- * The On vacation status. Indicates that the user is somewhere on the
- * beach or skiing.
- */
- public static final YahooStatusEnum ON_VACATION
- = new YahooStatusEnum(33, "On vacation",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-vacation.png"));
-
- /**
- * The On the phone status. Indicates that the user is talking to the phone.
- */
- public static final YahooStatusEnum ON_THE_PHONE
- = new YahooStatusEnum(31, "On the phone",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-phone.png"));
-
- /**
- * The DND status. Indicates that the user has connectivity but prefers
- * not to be contacted.
- */
- public static final YahooStatusEnum BUSY
- = new YahooStatusEnum(30, "Busy",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-busy.png"));
-
- /**
- * The Offline status. Indicates the user does not seem to be connected
- * to the network or at least does not want us to know she is
- */
- public static final YahooStatusEnum OFFLINE
- = new YahooStatusEnum(0, "Offline",
- loadIcon("resources/images/protocol/yahoo/yahoo16x16-offline.png"));
-
- /**
- * The minimal set of states that any implementation must support.
- */
- public static final ArrayList<YahooStatusEnum> yahooStatusSet
- = new ArrayList<YahooStatusEnum>();
- static{
- yahooStatusSet.add(AVAILABLE);
- yahooStatusSet.add(BE_RIGHT_BACK);
- yahooStatusSet.add(BUSY);
- yahooStatusSet.add(IDLE);
- yahooStatusSet.add(INVISIBLE);
- yahooStatusSet.add(NOT_AT_DESK);
- yahooStatusSet.add(NOT_AT_HOME);
- yahooStatusSet.add(NOT_IN_OFFICE);
- yahooStatusSet.add(OFFLINE);
- yahooStatusSet.add(ON_THE_PHONE);
- yahooStatusSet.add(ON_VACATION);
- yahooStatusSet.add(OUT_TO_LUNCH);
- yahooStatusSet.add(STEPPED_OUT);
- }
-
- /**
- * Creates a status with the specified connectivity coeff, name and icon.
- * @param status the connectivity coefficient for the specified status
- * @param statusName String
- * @param statusIcon the icon associated with this status
- */
- protected YahooStatusEnum(int status, String statusName, byte[] statusIcon)
- {
- super(status, statusName, statusIcon);
- }
-
- /**
- * Loads an image from a given image path.
- * @param imagePath The identifier of the image.
- * @return The image for the given identifier.
- */
- public static byte[] loadIcon(String imagePath) {
- InputStream is = YahooStatusEnum.class.getClassLoader()
- .getResourceAsStream(imagePath);
-
- if(is == null)
- return null;
-
- byte[] icon = null;
- try {
- icon = new byte[is.available()];
- is.read(icon);
- } catch (IOException exc) {
- logger.error("Failed to load icon: " + imagePath, exc);
- }
- return icon;
- }
-}