diff --git a/lib/installer-exclude/fmj.jar b/lib/installer-exclude/fmj.jar index 699a8930d..a4133b8f0 100644 Binary files a/lib/installer-exclude/fmj.jar and b/lib/installer-exclude/fmj.jar differ diff --git a/lib/installer-exclude/libjitsi.jar b/lib/installer-exclude/libjitsi.jar index 9a9aa1106..48d4ab582 100644 Binary files a/lib/installer-exclude/libjitsi.jar and b/lib/installer-exclude/libjitsi.jar differ diff --git a/resources/install/README.Debian b/resources/install/README.Debian index ecd925b20..f75ea2891 100644 --- a/resources/install/README.Debian +++ b/resources/install/README.Debian @@ -2,6 +2,7 @@ To create debian source package you need some other projects sources that jitsi depends on. In the same folder where jitsi is checked out do: git clone https://github.com/jitsi/otr4j.git git clone https://github.com/jitsi/libsrc.git +svn checkout http://irc-api.googlecode.com/svn/trunk/ irc-api And then in jitsi do: ant deb-src -Dlabel=4444 This will create orig.tar.gz debian sources that can be used to debuild debian package. \ No newline at end of file diff --git a/resources/install/build.xml b/resources/install/build.xml index 57b8e11cc..9e9cacd82 100644 --- a/resources/install/build.xml +++ b/resources/install/build.xml @@ -2458,9 +2458,6 @@ and libsrc --> - - @@ -2524,6 +2521,14 @@ + + + + + + + + @@ -2658,26 +2663,7 @@ - - - - - - - - - - - - - @@ -2898,7 +2884,8 @@ - @@ -2908,6 +2895,68 @@ overwrite="true" link="${debian.src.dir}/lib/installer-exclude"/> + + + + + + + + + + + + + + + + + + + + + reference:file:/usr/share/jitsi-common/ui-service.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/install/debian/control-src.tmpl b/resources/install/debian/control-src.tmpl index 4963ab4db..cd1b674e2 100644 --- a/resources/install/debian/control-src.tmpl +++ b/resources/install/debian/control-src.tmpl @@ -48,7 +48,9 @@ Build-Depends: debhelper (>= 9), javahelper, glassfish-activation, glassfish-mail, libbcpkix-java, - libjcalendar-java + libjcalendar-java, + libphonenumber6-java, + libslf4j-java Standards-Version: 3.9.5 Package: _PACKAGE_NAME_ @@ -80,7 +82,9 @@ Depends: ${misc:Depends}, libguava-java, libhsqldb-java, libjson-simple-java (>= 1.1.1), - libjcalendar-java + libjcalendar-java, + libphonenumber6-java, + libslf4j-java Recommends: ${java:Recommends} Description: VoIP and Instant Messaging client _APP_NAME_ is an application that allows you to do audio/video diff --git a/resources/install/debian/rules.tmpl b/resources/install/debian/rules.tmpl index a960b7d37..eeec3b6fe 100755 --- a/resources/install/debian/rules.tmpl +++ b/resources/install/debian/rules.tmpl @@ -21,7 +21,7 @@ override_dh_install-indep: # make and install the debian specific bundles # use the prebuild and installed bundles to extract/modify and use the # exising debian java packages - $(ANT) -file build.xml -Ddebian.bundles.dest=debian/$(PACKAGE_NAME)/usr/share/$(PACKAGE_NAME)/sc-bundles -Ddebian.bundles.common.dest=debian/$(PACKAGE_NAME)-common/usr/share/$(PACKAGE_NAME)-common deb-bundle-jna deb-bundle-util deb-bundle-sysactivitynotifications deb-bundle-swing-ui deb-bundle-json deb-bundle-smacklib deb-bundle-jmdnslib deb-bundle-desktoputil deb-bundle-bouncycastle deb-bundle-plugin-accountinfo deb-bundle-commons-lang deb-bundle-hsqldb deb-libjitsi-deps deb-bundle-httpmime deb-bundle-common + $(ANT) -file build.xml -Ddebian.bundles.dest=debian/$(PACKAGE_NAME)/usr/share/$(PACKAGE_NAME)/sc-bundles -Ddebian.bundles.common.dest=debian/$(PACKAGE_NAME)-common/usr/share/$(PACKAGE_NAME)-common deb-bundle-jna deb-bundle-util deb-bundle-sysactivitynotifications deb-bundle-swing-ui deb-bundle-json deb-bundle-smacklib deb-bundle-jmdnslib deb-bundle-desktoputil deb-bundle-bouncycastle deb-bundle-plugin-accountinfo deb-bundle-commons-lang deb-bundle-hsqldb deb-libjitsi-deps deb-bundle-httpmime deb-bundle-common deb-bundle-slf4j override_dh_install-arch: ifeq ($(DEB_HOST_ARCH),amd64) diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomAutoOpenConfigDialog.java b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomAutoOpenConfigDialog.java index 410ce7b15..88cb77d11 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomAutoOpenConfigDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomAutoOpenConfigDialog.java @@ -228,7 +228,7 @@ private void refreshValue() propertyListener); if(value == null) - value = MUCService.OPEN_ON_IMPORTANT_MESSAGE; + value = MUCService.DEFAULT_AUTO_OPEN_BEHAVIOUR; if(value.equals(MUCService.OPEN_ON_ACTIVITY)) { diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java index a7643eb22..b4f2abd7b 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java @@ -24,9 +24,8 @@ import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.Logger; -import org.jitsi.util.*; - import org.jdesktop.swingworker.SwingWorker; +import org.jitsi.util.*; import org.osgi.framework.*; /** @@ -214,7 +213,7 @@ public void messageReceived(ChatRoomMessageReceivedEvent evt) sourceChatRoom.getParentProvider(), sourceChatRoom.getIdentifier()); if(autoOpenConfig == null) - autoOpenConfig = MUCService.OPEN_ON_IMPORTANT_MESSAGE; + autoOpenConfig = MUCService.DEFAULT_AUTO_OPEN_BEHAVIOUR; if(autoOpenConfig.equals(MUCService.OPEN_ON_ACTIVITY) || (autoOpenConfig.equals(MUCService.OPEN_ON_MESSAGE) @@ -307,7 +306,7 @@ else if(o instanceof ChatRoomMessageReceivedEvent) null); if(createWindow) - chatWindowManager.openChat(chatPanel, true); + chatWindowManager.openChat(chatPanel, false); } /** diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java index d622ba4f3..06d5705ed 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java @@ -89,11 +89,11 @@ private void initItems() .getPreferredContactDetail(OperationSetBasicTelephony.class); // Call menu. - if (cDetail != null - && sourceContact.getContactSource().getType() - != ContactSourceService.RECENT_MESSAGES_TYPE) + if (cDetail != null) { - add(initCallMenu()); + Component c = initCallMenu(); + if(c != null) + add(c); } // Only create the menu if the add contact functionality is enabled. @@ -181,6 +181,10 @@ else if (providersCount > 1) contains(OperationSetBasicTelephony.class)); callContactMenu.add(callContactItem); } + + if(callContactMenu.getMenuComponentCount() == 0) + return null; + return callContactMenu; } diff --git a/src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java b/src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java index c6ed3cc6b..5ff97c385 100644 --- a/src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java +++ b/src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java @@ -1139,6 +1139,8 @@ private void loadRecentMessages() messageSourceServiceReg = bundleContext.registerService( ContactSourceService.class.getName(), messageSourceService, null); + MessageHistoryActivator.getContactListService() + .addMetaContactListListener(this.messageSourceService); } /** @@ -1148,6 +1150,9 @@ private void stopRecentMessages() { if(messageSourceServiceReg != null) { + MessageHistoryActivator.getContactListService() + .removeMetaContactListListener(this.messageSourceService); + messageSourceServiceReg.unregister(); messageSourceServiceReg = null; diff --git a/src/net/java/sip/communicator/impl/msghistory/MessageSourceContact.java b/src/net/java/sip/communicator/impl/msghistory/MessageSourceContact.java index dcb43a1f9..9031cbdff 100644 --- a/src/net/java/sip/communicator/impl/msghistory/MessageSourceContact.java +++ b/src/net/java/sip/communicator/impl/msghistory/MessageSourceContact.java @@ -6,6 +6,7 @@ */ package net.java.sip.communicator.impl.msghistory; +import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.contactsource.*; import net.java.sip.communicator.service.msghistory.*; import net.java.sip.communicator.service.muc.*; @@ -175,7 +176,7 @@ void update(EventObject source) this.contact = e.getDestinationContact(); this.address = contact.getAddress(); - this.displayName = contact.getDisplayName(); + this.updateDisplayName(); this.ppService = contact.getProtocolProvider(); this.image = contact.getImage(); this.status = contact.getPresenceStatus(); @@ -189,7 +190,7 @@ else if(source instanceof MessageReceivedEvent) this.contact = e.getSourceContact(); this.address = contact.getAddress(); - this.displayName = contact.getDisplayName(); + this.updateDisplayName(); this.ppService = contact.getProtocolProvider(); this.image = contact.getImage(); this.status = contact.getPresenceStatus(); @@ -279,6 +280,9 @@ else if(source instanceof ChatRoomMessageDeliveredEvent */ void initDetails(boolean isChatRoom, Contact contact) { + if(!isChatRoom && contact != null) + this.updateDisplayName(); + ContactDetail contactDetail = new ContactDetail( this.address, @@ -334,6 +338,7 @@ void initDetails(boolean isChatRoom, Contact contact) contactDetail.setSupportedOpSets(supportedOpSets); } + contactDetails.clear(); contactDetails.add(contactDetail); } @@ -506,6 +511,24 @@ public void setDisplayName(String displayName) this.displayName = displayName; } + /** + * Updates display name if contact is not null. + */ + private void updateDisplayName() + { + if(this.contact == null) + return; + + MetaContact metaContact + = MessageHistoryActivator.getContactListService() + .findMetaContactByContact(contact); + + if(metaContact == null) + return; + + this.displayName = metaContact.getDisplayName(); + } + /** * Compares two MessageSourceContacts. * @param o the object to compare with diff --git a/src/net/java/sip/communicator/impl/msghistory/MessageSourceContactQuery.java b/src/net/java/sip/communicator/impl/msghistory/MessageSourceContactQuery.java new file mode 100644 index 000000000..6e0d6049a --- /dev/null +++ b/src/net/java/sip/communicator/impl/msghistory/MessageSourceContactQuery.java @@ -0,0 +1,241 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.msghistory; + +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.protocol.*; + +import java.util.*; +import java.util.regex.*; + +/** + * The query which creates source contacts and uses the values stored in + * MessageSourceService. + * + * @author Damian Minkov + */ +public class MessageSourceContactQuery + extends AsyncContactQuery +{ + /** + * Constructs. + * + * @param messageSourceService + */ + MessageSourceContactQuery(MessageSourceService messageSourceService) + { + super(messageSourceService, + Pattern.compile("", + Pattern.CASE_INSENSITIVE | Pattern.LITERAL), + false); + } + + /** + * Creates MessageSourceContact for all currently cached + * recent messages in the MessageSourceService. + */ + @Override + public void run() + { + getContactSource().updateRecentMessages(); + } + + /** + * Updates capabilities from EventObject for the found + * MessageSourceContact equals to the Object supplied. + * Note that Object may not be MessageSourceContact, but its + * equals method can return true for message source contact instances. + * @param srcObj used to search for MessageSourceContact + * @param eventObj the values used for the update + */ + public void updateCapabilities(Object srcObj, EventObject eventObj) + { + for(SourceContact msc : getQueryResults()) + { + if(srcObj.equals(msc) + && msc instanceof MessageSourceContact) + { + ((MessageSourceContact)msc).initDetails(eventObj); + + break; + } + } + } + + /** + * Updates capabilities from Contact for the found + * MessageSourceContact equals to the Object supplied. + * Note that Object may not be MessageSourceContact, but its + * equals method can return true for message source contact instances. + * @param srcObj used to search for MessageSourceContact + * @param contact the values used for the update + */ + public void updateCapabilities(Object srcObj, Contact contact) + { + for(SourceContact msc : getQueryResults()) + { + if(srcObj.equals(msc) + && msc instanceof MessageSourceContact) + { + ((MessageSourceContact)msc).initDetails(false, contact); + + break; + } + } + } + + /** + * Notifies the ContactQueryListeners registered with this + * ContactQuery that a SourceContact has been + * changed. + * Note that Object may not be MessageSourceContact, but its + * equals method can return true for message source contact instances. + * + * @param srcObj the Object representing a recent message + * which has been changed and corresponding SourceContact + * which the registered ContactQueryListeners are to be + * notified about + */ + public void updateContact(Object srcObj, EventObject eventObject) + { + for(SourceContact msc : getQueryResults()) + { + if(srcObj.equals(msc) + && msc instanceof MessageSourceContact) + { + ((MessageSourceContact)msc).update(eventObject); + + super.fireContactChanged(msc); + + break; + } + } + } + + /** + * Notifies the ContactQueryListeners registered with this + * ContactQuery that a SourceContact has been + * changed. + * Note that Object may not be MessageSourceContact, but its + * equals method can return true for message source contact instances. + * + * @param srcObj the Object representing a recent message + * which has been changed and corresponding SourceContact + * which the registered ContactQueryListeners are to be + * notified about + */ + public void fireContactChanged(Object srcObj) + { + for(SourceContact msc : getQueryResults()) + { + if(srcObj.equals(msc) + && msc instanceof MessageSourceContact) + { + super.fireContactChanged(msc); + + break; + } + } + } + + /** + * Notifies the ContactQueryListeners registered with this + * ContactQuery that a SourceContact has been + * changed. + * Note that Object may not be MessageSourceContact, but its + * equals method can return true for message source contact instances. + * + * @param srcObj the Object representing a recent message + * which has been changed and corresponding SourceContact + * which the registered ContactQueryListeners are to be + * notified about + */ + public void updateContactStatus(Object srcObj, + PresenceStatus status) + { + for(SourceContact msc : getQueryResults()) + { + if(srcObj.equals(msc) + && msc instanceof MessageSourceContact) + { + ((MessageSourceContact)msc).setStatus(status); + + super.fireContactChanged(msc); + + break; + } + } + } + + /** + * Notifies the ContactQueryListeners registered with this + * ContactQuery that a SourceContact has been + * changed. + * Note that Object may not be MessageSourceContact, but its + * equals method can return true for message source contact instances. + * + * @param srcObj the Object representing a recent message + * which has been changed and corresponding SourceContact + * which the registered ContactQueryListeners are to be + * notified about + */ + public void updateContactDisplayName(Object srcObj, + String newName) + { + for(SourceContact msc : getQueryResults()) + { + if(srcObj.equals(msc) + && msc instanceof MessageSourceContact) + { + ((MessageSourceContact)msc).setDisplayName(newName); + + super.fireContactChanged(msc); + + break; + } + } + } + + /** + * Notifies the ContactQueryListeners registered with this + * ContactQuery that a SourceContact has been + * removed. + * Note that Object may not be MessageSourceContact, but its + * equals method can return true for message source contact instances. + * + * @param srcObj representing the message and its corresponding + * SourceContact which has been removed and which the registered + * ContactQueryListeners are to be notified about + */ + public void fireContactRemoved(Object srcObj) + { + for(SourceContact msc : getQueryResults()) + { + if(srcObj.equals(msc)) + { + super.fireContactRemoved(msc); + + break; + } + } + } + + /** + * Adds a specific SourceContact to the list of + * SourceContacts to be returned by this ContactQuery in + * response to {@link #getQueryResults()}. + * + * @param sourceContact the SourceContact to be added to the + * queryResults of this ContactQuery + * @return true if the queryResults of this + * ContactQuery has changed in response to the call + */ + public boolean addQueryResult(SourceContact sourceContact) + { + return super.addQueryResult(sourceContact, false); + } +} diff --git a/src/net/java/sip/communicator/impl/msghistory/MessageSourceService.java b/src/net/java/sip/communicator/impl/msghistory/MessageSourceService.java index 8e0cb70e9..15d104bd1 100644 --- a/src/net/java/sip/communicator/impl/msghistory/MessageSourceService.java +++ b/src/net/java/sip/communicator/impl/msghistory/MessageSourceService.java @@ -13,6 +13,7 @@ import java.util.regex.*; import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.contactlist.event.*; import net.java.sip.communicator.service.contactsource.*; import net.java.sip.communicator.service.history.*; import net.java.sip.communicator.service.history.records.*; @@ -32,6 +33,7 @@ * @author Damian Minkov */ public class MessageSourceService + extends MetaContactListAdapter implements ContactSourceService, ContactPresenceStatusListener, ContactCapabilitiesListener, @@ -126,8 +128,8 @@ public class MessageSourceService /** * List of recent messages. */ - private List recentMessages - = new LinkedList(); + private final List recentMessages + = new LinkedList(); /** * Date of the oldest shown message. @@ -137,7 +139,7 @@ public class MessageSourceService /** * The last query created. */ - private MessageHistoryContactQuery recentQuery = null; + private MessageSourceContactQuery recentQuery = null; /** * The message subtype if any. @@ -223,19 +225,54 @@ public int getIndex() public ContactQuery createContactQuery(String queryString) { recentQuery = - (MessageHistoryContactQuery)createContactQuery( + (MessageSourceContactQuery)createContactQuery( queryString, numberOfMessages); return recentQuery; } + /** + * Updates the contact sources in the recent query if any. + * Done here in order to sync with recentMessages instance, and to + * check for already existing instances of contact sources. + * Normally called from the query. + */ + public void updateRecentMessages() + { + if(recentQuery == null) + return; + + synchronized(recentMessages) + { + List currentContactsInQuery + = recentQuery.getQueryResults(); + + for(ComparableEvtObj evtObj : recentMessages) + { + // the contains will use the correct equals method of + // the object evtObj + if(!currentContactsInQuery.contains(evtObj)) + { + MessageSourceContact newSourceContact = + new MessageSourceContact( + evtObj.getEventObject(), + MessageSourceService.this); + newSourceContact.initDetails(evtObj.getEventObject()); + + recentQuery.addQueryResult(newSourceContact); + } + } + } + } + /** * Searches for entries in cached recent messages in history. * - * @param provider - * @return + * @param provider the provider which contact messages we will search + * @param isStatusChanged is the search because of status changed + * @return entries in cached recent messages in history. */ - private List getSourceContacts( + private List getCachedRecentMessages( ProtocolProviderService provider, boolean isStatusChanged) { String providerID = provider.getAccountID().getAccountUniqueID(); @@ -244,8 +281,8 @@ private List getSourceContacts( recentMessages.size() < numberOfMessages ? null : oldestRecentMessage ); - List sourceContactsToAdd - = new ArrayList(); + List cachedRecentMessages + = new ArrayList(); for(String contactID : recentMessagesContactIDs) { @@ -256,10 +293,10 @@ private List getSourceContacts( contactID, isSMSEnabled); - processEventObjects(res, sourceContactsToAdd, isStatusChanged); + processEventObjects(res, cachedRecentMessages, isStatusChanged); } - return sourceContactsToAdd; + return cachedRecentMessages; } /** @@ -273,46 +310,46 @@ private List getSourceContacts( * If nothing found a new contact is created. * * @param res list of event - * @param sourceContactsToAdd list of newly created source contacts + * @param cachedRecentMessages list of newly created source contacts * or already existed but updated with corresponding event object * @param isStatusChanged whether provider status changed * and we are processing */ private void processEventObjects( Collection res, - List sourceContactsToAdd, + List cachedRecentMessages, boolean isStatusChanged) { for(EventObject obj : res) { - MessageSourceContact msc = - findMessageSourceContact(obj, recentMessages); + ComparableEvtObj oldMsg = findRecentMessage(obj, recentMessages); - if(msc != null) + if(oldMsg != null) { - msc.update(obj);// update + oldMsg.update(obj);// update - if(isStatusChanged) - msc.initDetails(obj);// update capabilities + if(isStatusChanged && recentQuery != null) + recentQuery.updateCapabilities(oldMsg, obj); - // we still add it to sourceContactsToAdd + // we still add it to cachedRecentMessages // later we will find it is duplicate and will fire // update event - if(!sourceContactsToAdd.contains(msc)) - sourceContactsToAdd.add(msc); + if(!cachedRecentMessages.contains(oldMsg)) + cachedRecentMessages.add(oldMsg); continue; } - msc = findMessageSourceContact(obj, sourceContactsToAdd); + oldMsg = findRecentMessage(obj, cachedRecentMessages); - if(msc == null) + if(oldMsg == null) { - msc = new MessageSourceContact( - obj, MessageSourceService.this); - if(isStatusChanged) - msc.initDetails(obj); - sourceContactsToAdd.add(msc); + oldMsg = new ComparableEvtObj(obj); + + if(isStatusChanged && recentQuery != null) + recentQuery.updateCapabilities(oldMsg, obj); + + cachedRecentMessages.add(oldMsg); } } } @@ -327,64 +364,52 @@ boolean isSMSEnabled() } /** - * Add the source contacts, newly added will fire new, + * Add the ComparableEvtObj, newly added will fire new, * for existing fire update and when trimming the list to desired length * fire remove for those that were removed * @param contactsToAdd */ - private void addNewRecentMessages(List contactsToAdd) + private void addNewRecentMessages( + List contactsToAdd) { // now find object to fire new, and object to fire remove // let us find duplicates and fire update - List duplicates - = new ArrayList(); - for(MessageSourceContact msc : recentMessages) + List duplicates = new ArrayList(); + for(ComparableEvtObj msgToAdd : contactsToAdd) { - for(MessageSourceContact mscToAdd : contactsToAdd) + if(recentMessages.contains(msgToAdd)) { - if(mscToAdd.equals(msc)) - { - duplicates.add(msc); + duplicates.add(msgToAdd); - // update currently used instance - //msc.update(mscToAdd); - // it was already updated - - // save update - updateRecentMessageToHistory(msc); - } + // save update + updateRecentMessageToHistory(msgToAdd); } } + recentMessages.removeAll(duplicates); - if(!duplicates.isEmpty()) - { - contactsToAdd.removeAll(duplicates); + // now contacts to add has no duplicates, add them all + boolean changed = recentMessages.addAll(contactsToAdd); + if(changed) + { Collections.sort(recentMessages); if(recentQuery != null) { - for(MessageSourceContact msc : duplicates) - recentQuery.fireContactChanged(msc); + for(ComparableEvtObj obj : duplicates) + recentQuery.updateContact(obj, obj.getEventObject()); } - - return; } - // now contacts to add has no duplicates, add them all - recentMessages.addAll(contactsToAdd); - - Collections.sort(recentMessages); - if(!recentMessages.isEmpty()) oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); // trim - List removedItems = null; + List removedItems = null; if(recentMessages.size() > numberOfMessages) { - removedItems = new ArrayList( + removedItems = new ArrayList( recentMessages.subList(numberOfMessages, recentMessages.size())); recentMessages.removeAll(removedItems); @@ -396,7 +421,7 @@ private void addNewRecentMessages(List contactsToAdd) // and now are removed after trim if(removedItems != null) { - for(MessageSourceContact msc : removedItems) + for(ComparableEvtObj msc : removedItems) { if(!contactsToAdd.contains(msc)) recentQuery.fireContactRemoved(msc); @@ -404,12 +429,28 @@ private void addNewRecentMessages(List contactsToAdd) } // fire new for all that were added, and not removed after trim - for(MessageSourceContact msc : contactsToAdd) + for(ComparableEvtObj msc : contactsToAdd) { - if(removedItems == null + if((removedItems == null || !removedItems.contains(msc)) - recentQuery.fireContactReceived(msc); + && !duplicates.contains(msc)) + { + MessageSourceContact newSourceContact = + new MessageSourceContact( + msc.getEventObject(), + MessageSourceService.this); + newSourceContact.initDetails(msc.getEventObject()); + + recentQuery.addQueryResult(newSourceContact); + } } + + // if recent messages were changed, indexes have change lets + // fire event for the last element which will reorder the whole + // group if needed. + if(changed) + recentQuery.fireContactChanged( + recentMessages.get(recentMessages.size() - 1)); } } @@ -446,10 +487,10 @@ private void handleProviderAddedInSeparateThread( synchronized(recentMessages) { - List sourceContactsToAdd - = getSourceContacts(provider, isStatusChanged); + List cachedRecentMessages + = getCachedRecentMessages(provider, isStatusChanged); - if(sourceContactsToAdd.isEmpty()) + if(cachedRecentMessages.isEmpty()) { // maybe there is no cached history for this // let's check @@ -461,33 +502,32 @@ private void handleProviderAddedInSeparateThread( null, isSMSEnabled); - List newMsc - = new ArrayList(); + List newMsc + = new ArrayList(); processEventObjects(res, newMsc, isStatusChanged); addNewRecentMessages(newMsc); - for(MessageSourceContact msc : newMsc) + for(ComparableEvtObj msc : newMsc) { saveRecentMessageToHistory(msc); } - } else - addNewRecentMessages(sourceContactsToAdd); + addNewRecentMessages(cachedRecentMessages); } } /** * Tries to match the event object to already existing - * MessageSourceContact in the supplied list. + * ComparableEvtObj in the supplied list. * @param obj the object that we will try to match. * @param list the list we will search in. - * @return the found source contact + * @return the found ComparableEvtObj */ - private static MessageSourceContact findMessageSourceContact( - EventObject obj, List list) + private static ComparableEvtObj findRecentMessage( + EventObject obj, List list) { Contact contact = null; ChatRoom chatRoom = null; @@ -509,13 +549,13 @@ else if(obj instanceof ChatRoomMessageReceivedEvent) chatRoom = ((ChatRoomMessageReceivedEvent)obj).getSourceChatRoom(); } - for(MessageSourceContact msc : list) + for(ComparableEvtObj evt : list) { if((contact != null - && contact.equals(msc.getContact())) + && contact.equals(evt.getContact())) || (chatRoom != null - && chatRoom.equals(msc.getRoom()))) - return msc; + && chatRoom.equals(evt.getRoom()))) + return evt; } return null; @@ -534,9 +574,9 @@ void handleProviderRemoved(ProtocolProviderService provider) { if(provider != null) { - List removedItems - = new ArrayList(); - for(MessageSourceContact msc : recentMessages) + List removedItems + = new ArrayList(); + for(ComparableEvtObj msc : recentMessages) { if(msc.getProtocolProviderService().equals(provider)) removedItems.add(msc); @@ -552,7 +592,7 @@ void handleProviderRemoved(ProtocolProviderService provider) if(recentQuery != null) { - for(MessageSourceContact msc : removedItems) + for(ComparableEvtObj msc : removedItems) { recentQuery.fireContactRemoved(msc); } @@ -561,12 +601,12 @@ void handleProviderRemoved(ProtocolProviderService provider) // lets do the same as we enable provider // for all registered providers and finally fire events - List contactsToAdd - = new ArrayList(); + List contactsToAdd + = new ArrayList(); for (ProtocolProviderService pps : messageHistoryService.getCurrentlyAvailableProviders()) { - contactsToAdd.addAll(getSourceContacts(pps, true)); + contactsToAdd.addAll(getCachedRecentMessages(pps, true)); } addNewRecentMessages(contactsToAdd); @@ -715,7 +755,11 @@ int getIndex(MessageSourceContact messageSourceContact) { synchronized(recentMessages) { - return recentMessages.indexOf(messageSourceContact); + for (int i = 0; i < recentMessages.size(); i++) + if(recentMessages.get(i).equals(messageSourceContact)) + return i; + + return -1; } } @@ -731,7 +775,8 @@ public ContactQuery createContactQuery(String queryString, int contactCount) if(!StringUtils.isNullOrEmpty(queryString)) return null; - recentQuery = new MessageHistoryContactQuery(numberOfMessages); + recentQuery = new MessageSourceContactQuery( + MessageSourceService.this); return recentQuery; } @@ -741,20 +786,20 @@ public ContactQuery createContactQuery(String queryString, int contactCount) * @param evt the ContactPresenceStatusChangeEvent describing the status */ @Override - public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt) + public void contactPresenceStatusChanged( + ContactPresenceStatusChangeEvent evt) { if(recentQuery == null) return; synchronized(recentMessages) { - for(MessageSourceContact msgSC : recentMessages) + for(ComparableEvtObj msg : recentMessages) { - if(msgSC.getContact() != null - && msgSC.getContact().equals(evt.getSourceContact())) + if(msg.getContact() != null + && msg.getContact().equals(evt.getSourceContact())) { - msgSC.setStatus(evt.getNewStatus()); - recentQuery.fireContactChanged(msgSC); + recentQuery.updateContactStatus(msg, evt.getNewStatus()); } } } @@ -780,11 +825,11 @@ public void localUserPresenceChanged( if(recentQuery == null) return; - MessageSourceContact srcContact = null; + ComparableEvtObj srcContact = null; synchronized(recentMessages) { - for(MessageSourceContact msg : recentMessages) + for(ComparableEvtObj msg : recentMessages) { if(msg.getRoom() != null && msg.getRoom().equals(evt.getChatRoom())) @@ -803,8 +848,9 @@ public void localUserPresenceChanged( if (LocalUserChatRoomPresenceChangeEvent .LOCAL_USER_JOINED.equals(eventType)) { - srcContact.setStatus(ChatRoomPresenceStatus.CHAT_ROOM_ONLINE); - recentQuery.fireContactChanged(srcContact); + recentQuery.updateContactStatus( + srcContact, + ChatRoomPresenceStatus.CHAT_ROOM_ONLINE); } else if ((LocalUserChatRoomPresenceChangeEvent .LOCAL_USER_LEFT.equals(eventType) @@ -814,8 +860,9 @@ else if ((LocalUserChatRoomPresenceChangeEvent .LOCAL_USER_DROPPED.equals(eventType)) ) { - srcContact.setStatus(ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE); - recentQuery.fireContactChanged(srcContact); + recentQuery.updateContactStatus( + srcContact, + ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE); } } @@ -833,8 +880,8 @@ private void handle(EventObject obj, // check if provider - contact exist update message content synchronized(recentMessages) { - MessageSourceContact existingMsc = null; - for(MessageSourceContact msc : recentMessages) + ComparableEvtObj existingMsc = null; + for(ComparableEvtObj msc : recentMessages) { if(msc.getProtocolProviderService().equals(provider) && msc.getContactAddress().equals(id)) @@ -865,17 +912,18 @@ private void handle(EventObject obj, new MessageSourceContact(obj, MessageSourceService.this); newSourceContact.initDetails(obj); // we have already checked for duplicate - recentMessages.add(newSourceContact); + ComparableEvtObj newMsg = new ComparableEvtObj(obj); + recentMessages.add(newMsg); Collections.sort(recentMessages); oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); // trim - List removedItems = null; + List removedItems = null; if(recentMessages.size() > numberOfMessages) { - removedItems = new ArrayList( + removedItems = new ArrayList( recentMessages.subList( numberOfMessages, recentMessages.size())); @@ -883,7 +931,7 @@ private void handle(EventObject obj, } // save - saveRecentMessageToHistory(newSourceContact); + saveRecentMessageToHistory(newMsg); // no query nothing to fire if(recentQuery == null) @@ -892,20 +940,20 @@ private void handle(EventObject obj, // now fire if(removedItems != null) { - for(MessageSourceContact msc : removedItems) + for(ComparableEvtObj msc : removedItems) { recentQuery.fireContactRemoved(msc); } } - recentQuery.fireContactReceived(newSourceContact); + recentQuery.addQueryResult(newSourceContact); } } /** * Adds recent message in history. */ - private void saveRecentMessageToHistory(MessageSourceContact msc) + private void saveRecentMessageToHistory(ComparableEvtObj msc) { synchronized(historyID) { @@ -942,7 +990,7 @@ private void saveRecentMessageToHistory(MessageSourceContact msc) /** * Updates recent message in history. */ - private void updateRecentMessageToHistory(final MessageSourceContact msc) + private void updateRecentMessageToHistory(final ComparableEvtObj msg) { synchronized(historyID) { @@ -972,13 +1020,15 @@ public boolean isMatching() { boolean providerFound = false; boolean contactFound = false; - for(int i = 0; i < hr.getPropertyNames().length; i++) + for(int i = 0; + i < hr.getPropertyNames().length; + i++) { String propName = hr.getPropertyNames()[i]; if(propName.equals(STRUCTURE_NAMES[0])) { - if(msc.getProtocolProviderService() + if(msg.getProtocolProviderService() .getAccountID().getAccountUniqueID() .equals(hr.getPropertyValues()[i])) { @@ -987,7 +1037,7 @@ public boolean isMatching() } else if(propName.equals(STRUCTURE_NAMES[1])) { - if(msc.getContactAddress() + if(msg.getContactAddress() .equals(hr.getPropertyValues()[i])) { contactFound = true; @@ -1016,18 +1066,18 @@ public Map getUpdateChanges() { map.put( propName, - msc.getProtocolProviderService() + msg.getProtocolProviderService() .getAccountID() .getAccountUniqueID()); } else if(propName.equals(STRUCTURE_NAMES[1])) { - map.put(propName, msc.getContactAddress()); + map.put(propName, msg.getContactAddress()); } else if(propName.equals(STRUCTURE_NAMES[2])) { map.put(propName, - sdf.format(msc.getTimestamp())); + sdf.format(msg.getTimestamp())); } else if(propName.equals(STRUCTURE_NAMES[3])) map.put(propName, RECENT_MSGS_VER); @@ -1175,19 +1225,36 @@ public void contactModified(ContactPropertyChangeEvent evt) if(contact == null) return; - for(MessageSourceContact msc : recentMessages) + for(ComparableEvtObj msc : recentMessages) { if(contact.equals(msc.getContact())) { - msc.setDisplayName(contact.getDisplayName()); - if(recentQuery != null) - recentQuery.fireContactChanged(msc); + recentQuery.updateContactDisplayName( + msc, + contact.getDisplayName()); return; } } + } + /** + * Indicates that a MetaContact has been modified. + * @param evt the MetaContactListEvent containing the corresponding contact + */ + public void metaContactRenamed(MetaContactRenamedEvent evt) + { + for(ComparableEvtObj msc : recentMessages) + { + if(evt.getSourceMetaContact().containsContact(msc.getContact())) + { + if(recentQuery != null) + recentQuery.updateContactDisplayName( + msc, + evt.getNewDisplayName()); + } + } } @Override @@ -1198,14 +1265,12 @@ public void supportedOperationSetsChanged(ContactCapabilitiesEvent event) if(contact == null) return; - for(MessageSourceContact msc : recentMessages) + for(ComparableEvtObj msc : recentMessages) { if(contact.equals(msc.getContact())) { - msc.initDetails(false, contact); - if(recentQuery != null) - recentQuery.fireContactChanged(msc); + recentQuery.updateCapabilities(msc, contact); return; } @@ -1219,19 +1284,19 @@ public void supportedOperationSetsChanged(ContactCapabilitiesEvent event) public void eraseLocallyStoredHistory() throws IOException { + List toRemove = null; synchronized(recentMessages) { - List toRemove - = new ArrayList(recentMessages); + toRemove = new ArrayList(recentMessages); recentMessages.clear(); + } - if(recentQuery != null) + if(recentQuery != null) + { + for(ComparableEvtObj msc : toRemove) { - for(MessageSourceContact msc : toRemove) - { - recentQuery.fireContactRemoved(msc); - } + recentQuery.fireContactRemoved(msc); } } } @@ -1243,10 +1308,10 @@ public void eraseLocallyStoredHistory() public void eraseLocallyStoredHistory(MetaContact contact) throws IOException { + List toRemove = null; synchronized(recentMessages) { - List toRemove - = new ArrayList(); + toRemove = new ArrayList(); Iterator iter = contact.getContacts(); while(iter.hasNext()) { @@ -1254,7 +1319,7 @@ public void eraseLocallyStoredHistory(MetaContact contact) String id = item.getAddress(); ProtocolProviderService provider = item.getProtocolProvider(); - for(MessageSourceContact msc : recentMessages) + for(ComparableEvtObj msc : recentMessages) { if(msc.getProtocolProviderService().equals(provider) && msc.getContactAddress().equals(id)) @@ -1265,16 +1330,14 @@ public void eraseLocallyStoredHistory(MetaContact contact) } recentMessages.removeAll(toRemove); - - if(recentQuery != null) + } + if(recentQuery != null) + { + for(ComparableEvtObj msc : toRemove) { - for(MessageSourceContact msc : toRemove) - { - recentQuery.fireContactRemoved(msc); - } + recentQuery.fireContactRemoved(msc); } } - } /** @@ -1283,10 +1346,10 @@ public void eraseLocallyStoredHistory(MetaContact contact) */ public void eraseLocallyStoredHistory(ChatRoom room) { + ComparableEvtObj toRemove = null; synchronized(recentMessages) { - MessageSourceContact toRemove = null; - for(MessageSourceContact msg : recentMessages) + for(ComparableEvtObj msg : recentMessages) { if(msg.getRoom() != null && msg.getRoom().equals(room)) @@ -1300,93 +1363,236 @@ public void eraseLocallyStoredHistory(ChatRoom room) return; recentMessages.remove(toRemove); - - if(recentQuery != null) - recentQuery.fireContactRemoved(toRemove); } + + if(recentQuery != null) + recentQuery.fireContactRemoved(toRemove); } /** - * The contact query implementation. + * Object used to cache recent messages. */ - private class MessageHistoryContactQuery - extends AsyncContactQuery + private class ComparableEvtObj + implements Comparable { - MessageHistoryContactQuery(int contactCount) + private EventObject eventObject; + + /** + * The protocol provider. + */ + private ProtocolProviderService ppService = null; + + /** + * The address. + */ + private String address = null; + + /** + * The timestamp. + */ + private Date timestamp = null; + + /** + * The contact instance. + */ + private Contact contact = null; + + /** + * The room instance. + */ + private ChatRoom room = null; + + /** + * Constructs. + * @param source used to extract initial values. + */ + ComparableEvtObj(EventObject source) { - super(MessageSourceService.this, - Pattern.compile("", - Pattern.CASE_INSENSITIVE | Pattern.LITERAL), - false); + update(source); } - @Override - public void run() + /** + * Extract values from EventObject. + * @param source + */ + public void update(EventObject source) { - synchronized(recentMessages) + this.eventObject = source; + + if(source instanceof MessageDeliveredEvent) { - for(MessageSourceContact rm : recentMessages) - { - addQueryResult(rm); - } + MessageDeliveredEvent e = (MessageDeliveredEvent)source; + + this.contact = e.getDestinationContact(); + + this.address = contact.getAddress(); + this.ppService = contact.getProtocolProvider(); + this.timestamp = e.getTimestamp(); + } + else if(source instanceof MessageReceivedEvent) + { + MessageReceivedEvent e = (MessageReceivedEvent)source; + + this.contact = e.getSourceContact(); + + this.address = contact.getAddress(); + this.ppService = contact.getProtocolProvider(); + this.timestamp = e.getTimestamp(); } + else if(source instanceof ChatRoomMessageDeliveredEvent) + { + ChatRoomMessageDeliveredEvent e + = (ChatRoomMessageDeliveredEvent)source; + + this.room = e.getSourceChatRoom(); + + this.address = room.getIdentifier(); + this.ppService = room.getParentProvider(); + this.timestamp = e.getTimestamp(); + } + else if(source instanceof ChatRoomMessageReceivedEvent) + { + ChatRoomMessageReceivedEvent e + = (ChatRoomMessageReceivedEvent)source; + + this.room = e.getSourceChatRoom(); + + this.address = room.getIdentifier(); + this.ppService = room.getParentProvider(); + this.timestamp = e.getTimestamp(); + } + } + + @Override + public String toString() + { + return "ComparableEvtObj{" + + "address='" + address + '\'' + + ", ppService=" + ppService + + '}'; + } + + /** + * The timestamp of the message. + * @return the timestamp of the message. + */ + public Date getTimestamp() + { + return timestamp; + } + + /** + * The contact. + * @return the contact. + */ + public Contact getContact() + { + return contact; + } + + /** + * The room. + * @return the room. + */ + public ChatRoom getRoom() + { + return room; + } + + /** + * The protocol provider. + * @return the protocol provider. + */ + public ProtocolProviderService getProtocolProviderService() + { + return ppService; } /** - * Notifies the ContactQueryListeners registered with this - * ContactQuery that a new SourceContact has been - * received. - * - * @param contact the SourceContact which has been received and - * which the registered ContactQueryListeners are to be notified - * about + * The address. + * @return the address. */ - public void fireContactReceived(SourceContact contact) + public String getContactAddress() { - fireContactReceived(contact, false); + if(this.address != null) + return this.address; + + return null; } /** - * Notifies the ContactQueryListeners registered with this - * ContactQuery that a SourceContact has been - * changed. - * - * @param contact the SourceContact which has been changed and - * which the registered ContactQueryListeners are to be notified - * about + * The event object. + * @return the event object. */ - public void fireContactChanged(SourceContact contact) + public EventObject getEventObject() { - super.fireContactChanged(contact); + return eventObject; } /** - * Notifies the ContactQueryListeners registered with this - * ContactQuery that a SourceContact has been - * removed. - * - * @param contact the SourceContact which has been removed and - * which the registered ContactQueryListeners are to be notified - * about + * Compares two ComparableEvtObj. + * @param o the object to compare with + * @return 0, less than zero, greater than zero, if equals, + * less or greater. */ - public void fireContactRemoved(SourceContact contact) + @Override + public int compareTo(ComparableEvtObj o) { - super.fireContactRemoved(contact); + if(o == null + || o.getTimestamp() == null) + return 1; + + return o.getTimestamp() + .compareTo(getTimestamp()); } /** - * Adds a specific SourceContact to the list of - * SourceContacts to be returned by this ContactQuery in - * response to {@link #getQueryResults()}. - * - * @param sourceContact the SourceContact to be added to the - * queryResults of this ContactQuery - * @return true if the queryResults of this - * ContactQuery has changed in response to the call + * Checks if equals, and if this event object is used to create + * a MessageSourceContact, if the supplied Object is instance + * of MessageSourceContact. + * @param o the object to check. + * @return true if equals. */ - public boolean addQueryResult(SourceContact sourceContact) + @Override + public boolean equals(Object o) + { + if(this == o) + return true; + if(o == null + || (!(o instanceof MessageSourceContact) + && getClass() != o.getClass())) + return false; + + if(o instanceof ComparableEvtObj) + { + ComparableEvtObj that = (ComparableEvtObj) o; + + if(!address.equals(that.address)) + return false; + if(!ppService.equals(that.ppService)) + return false; + } + else if(o instanceof MessageSourceContact) + { + MessageSourceContact that = (MessageSourceContact)o; + + if(!address.equals(that.getContactAddress())) + return false; + if(!ppService.equals(that.getProtocolProviderService())) + return false; + } + else + return false; + + return true; + } + + @Override + public int hashCode() { - return super.addQueryResult(sourceContact); + int result = address.hashCode(); + result = 31 * result + ppService.hashCode(); + return result; } } } diff --git a/src/net/java/sip/communicator/impl/msghistory/msghistory.manifest.mf b/src/net/java/sip/communicator/impl/msghistory/msghistory.manifest.mf index 9748e9272..93a89c932 100644 --- a/src/net/java/sip/communicator/impl/msghistory/msghistory.manifest.mf +++ b/src/net/java/sip/communicator/impl/msghistory/msghistory.manifest.mf @@ -12,6 +12,7 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.history.event, net.java.sip.communicator.service.msghistory, net.java.sip.communicator.service.contactlist, + net.java.sip.communicator.service.contactlist.event, net.java.sip.communicator.service.contactsource, net.java.sip.communicator.service.history.records, net.java.sip.communicator.service.muc, diff --git a/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java b/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java index 1dac54447..97a2ca15a 100644 --- a/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java +++ b/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java @@ -8,8 +8,6 @@ import java.util.*; -import org.jitsi.service.resources.*; - import net.java.sip.communicator.plugin.desktoputil.chat.*; import net.java.sip.communicator.service.contactsource.*; import net.java.sip.communicator.service.customcontactactions.*; @@ -17,6 +15,8 @@ import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; +import org.jitsi.service.resources.*; + /** * Implements CustomContactActionsService for MUC contact source. * @@ -643,7 +643,7 @@ public String getText(SourceContact actionSource) ((ChatRoomSourceContact)actionSource).getProvider(), ((ChatRoomSourceContact)actionSource).getChatRoomID()); if(openAutomaticallyValue == null) - openAutomaticallyValue = MUCService.OPEN_ON_IMPORTANT_MESSAGE; + openAutomaticallyValue = MUCService.DEFAULT_AUTO_OPEN_BEHAVIOUR; String openAutomaticallyKey = MUCService.autoOpenConfigValuesTexts .get(openAutomaticallyValue); return "" + text + "...
(" diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallJabberGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallJabberGTalkImpl.java index 8155cf455..cd314cee6 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallJabberGTalkImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallJabberGTalkImpl.java @@ -85,9 +85,12 @@ public abstract void modifyVideoContent() */ public T getPeer(String sid) { + if (sid == null) + return null; + for(T peer : getCallPeerList()) { - if (peer.getSID() != null && peer.getSID().equals(sid)) + if (sid.equals(peer.getSID())) return peer; } return null; @@ -118,9 +121,12 @@ public boolean containsSID(String sid) */ public T getPeerBySessInitPacketID(String id) { + if (id == null) + return null; + for(T peer : getCallPeerList()) { - if (peer.getSessInitID().equals(id)) + if (id.equals(peer.getSessInitID())) return peer; } return null; diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java index eb6449cf2..8577d4bac 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java @@ -19,6 +19,7 @@ * * @author Lyubomir Marinov * @author Boris Grozev + * @author George Politis */ public class ColibriConferenceIQ extends IQ @@ -49,17 +50,22 @@ public class ColibriConferenceIQ */ public static final int[] NO_SSRCS = new int[0]; + /** + * The list of {@link ChannelBundle}s included into this conference + * IQ. + */ + private final List channelBundles + = new LinkedList(); + /** * The list of {@link Content}s included into this conference IQ. */ private final List contents = new LinkedList(); /** - * The list of {@link ChannelBundle}s included into this conference - * IQ. + * The list of Endpoints included into this conference IQ. */ - private final List channelBundles - = new LinkedList(); + private final List endpoints = new LinkedList(); /** * The ID of the conference represented by this IQ. @@ -69,14 +75,9 @@ public class ColibriConferenceIQ /** * Media recording. */ - public Recording recording = null; - - private RTCPTerminationStrategy rtcpTerminationStrategy = null; + private Recording recording; - /** - * The list of Endpoints included into this conference IQ. - */ - private final List endpoints = new LinkedList(); + private RTCPTerminationStrategy rtcpTerminationStrategy; /** Initializes a new ColibriConferenceIQ instance. */ public ColibriConferenceIQ() @@ -84,19 +85,19 @@ public ColibriConferenceIQ() } /** - * Initializes a new {@link Content} instance with a specific name and adds - * it to the list of Content instances included into this - * conference IQ. - * - * @param contentName the name which which the new Content instance - * is to be initialized - * @return true if the list of Content instances included - * into this conference IQ has been modified as a result of the - * method call; otherwise, false + * Adds a specific {@link Content} instance to the list of Content + * instances included into this conference IQ. + * @param channelBundle the ChannelBundle to add. */ - public boolean addContent(String contentName) + public boolean addChannelBundle(ChannelBundle channelBundle) { - return addContent(new Content(contentName)); + if (channelBundle == null) + throw new NullPointerException("channelBundle"); + + return + channelBundles.contains(channelBundles) + ? false + : channelBundles.add(channelBundle); } /** @@ -120,19 +121,40 @@ public boolean addContent(Content content) } /** - * Adds a specific {@link Content} instance to the list of Content - * instances included into this conference IQ. - * @param the ChannelBundle to add. + * Initializes a new {@link Content} instance with a specific name and adds + * it to the list of Content instances included into this + * conference IQ. + * + * @param contentName the name which which the new Content instance + * is to be initialized + * @return true if the list of Content instances included + * into this conference IQ has been modified as a result of the + * method call; otherwise, false */ - public boolean addChannelBundle(ChannelBundle channelBundle) + public boolean addContent(String contentName) { - if (channelBundle == null) - throw new NullPointerException("channelBundle"); + return addContent(new Content(contentName)); + } - return channelBundles.contains(channelBundles) - ? false - : channelBundles.add(channelBundle); + /** + * Add an Endpoint to this ColibriConferenceIQ. + * @param endpoint the Endpoint to add. + */ + public void addEndpoint(Endpoint endpoint) + { + endpoints.add(endpoint); + } + /** + * Returns a list of the ChannelBundles included into this + * conference IQ. + * + * @return an unmodifiable List of the ChannelBundles + * included into this conference IQ. + */ + public List getChannelBundles() + { + return Collections.unmodifiableList(channelBundles); } /** @@ -157,10 +179,11 @@ public String getChildElementXML() List contents = getContents(); List channelBundles = getChannelBundles(); - boolean hasChildren = recording != null - || rtcpTerminationStrategy != null - || contents.size() > 0 - || channelBundles.size() > 0; + boolean hasChildren + = (recording != null) + || (rtcpTerminationStrategy != null) + || (contents.size() > 0) + || (channelBundles.size() > 0); if (!hasChildren) { @@ -175,7 +198,6 @@ public String getChildElementXML() channelBundle.toXML(xml); if (recording != null) recording.toXML(xml); - if (rtcpTerminationStrategy != null) rtcpTerminationStrategy.toXML(xml); @@ -197,8 +219,10 @@ public String getChildElementXML() public Content getContent(String contentName) { for (Content content : getContents()) + { if (contentName.equals(content.getName())) return content; + } return null; } @@ -215,15 +239,14 @@ public List getContents() } /** - * Returns a list of the ChannelBundles included into this - * conference IQ. - * - * @return an unmodifiable List of the ChannelBundles - * included into this conference IQ. + * Returns the list of Endpoints included in this + * ColibriConferenceIQ. + * @return the list of Endpoints included in this + * ColibriConferenceIQ. */ - public List getChannelBundles() + public List getEndpoints() { - return Collections.unmodifiableList(channelBundles); + return Collections.unmodifiableList(endpoints); } /** @@ -236,24 +259,6 @@ public String getID() return id; } - /** - * Gets the value of the recording field. - * @return the value of the recording field. - */ - public Recording getRecording() - { - return recording; - } - - /** - * Sets the recording field. - * @param recording the value to set. - */ - public void setRecording(Recording recording) - { - this.recording = recording; - } - /** * Returns a Content from the list of Contents of this * conference IQ which has a specific name. If no such @@ -274,10 +279,23 @@ public Content getOrCreateContent(String contentName) content = new Content(contentName); addContent(content); } - return content; } + /** + * Gets the value of the recording field. + * @return the value of the recording field. + */ + public Recording getRecording() + { + return recording; + } + + public RTCPTerminationStrategy getRTCPTerminationStrategy() + { + return rtcpTerminationStrategy; + } + /** * Removes a specific {@link Content} instance from the list of * Content instances included into this conference IQ. @@ -303,1044 +321,1141 @@ public void setID(String id) this.id = id; } - public RTCPTerminationStrategy getRTCPTerminationStrategy() + /** + * Sets the recording field. + * @param recording the value to set. + */ + public void setRecording(Recording recording) { - return rtcpTerminationStrategy; + this.recording = recording; } - public void setRTCPTerminationStrategy(RTCPTerminationStrategy rtcpTerminationStrategy) + public void setRTCPTerminationStrategy( + RTCPTerminationStrategy rtcpTerminationStrategy) { this.rtcpTerminationStrategy = rtcpTerminationStrategy; } /** - * Returns the list of Endpoints included in this - * ColibriConferenceIQ. - * @return the list of Endpoints included in this - * ColibriConferenceIQ. + * Represents a channel included into a content of a Jitsi + * Videobridge conference IQ. */ - public List getEndpoints() + public static class Channel + extends ChannelCommon { - return Collections.unmodifiableList(endpoints); - } + /** + * The name of the XML attribute of a channel which represents + * its direction. + */ + public static final String DIRECTION_ATTR_NAME = "direction"; - /** - * Add an Endpoint to this ColibriConferenceIQ. - * @param endpoint the Endpoint to add. - */ - public void addEndpoint(Endpoint endpoint) - { - endpoints.add(endpoint); - } - /** - * Class contains common code for both Channel and - * SctpConnection IQ classes. - * - * @author Pawel Domas - */ - public static abstract class ChannelCommon - { /** - * The XML name of the endpoint attribute which specifies the - * optional identifier of the endpoint of the conference participant - * associated with a channel. The value of the - * endpoint attribute is an opaque String from the - * point of view of Jitsi Videobridge. + * The XML element name of a channel of a content of a + * Jitsi Videobridge conference IQ. */ - public static final String ENDPOINT_ATTR_NAME = "endpoint"; + public static final String ELEMENT_NAME = "channel"; /** - * The XML name of the expire attribute of a channel - * of a content of a conference IQ which represents - * the value of the expire property of + * The XML name of the host attribute of a channel of + * a content of a conference IQ which represents the + * value of the host property of * ColibriConferenceIQ.Channel. + * + * @deprecated The attribute is supported for the purposes of + * compatibility with legacy versions of Jitsi and Jitsi Videobridge. */ - public static final String EXPIRE_ATTR_NAME = "expire"; + @Deprecated + public static final String HOST_ATTR_NAME = "host"; /** - * The name of the "channel-bundle-id" attribute. + * The XML name of the last-n attribute of a video + * channel which specifies the maximum number of video RTP + * streams to be sent from Jitsi Videobridge to the endpoint associated + * with the video channel. The value of the last-n + * attribute is a positive number. */ - public static final String CHANNEL_BUNDLE_ID_ATTR_NAME - = "channel-bundle-id"; + public static final String LAST_N_ATTR_NAME = "last-n"; /** - * The value of the expire property of - * ColibriConferenceIQ.Channel which indicates that no actual - * value has been specified for the property in question. + * The XML name of the receive-simulcast-layer attribute of a + * video Channel which specifies the target quality of the + * simulcast substreams to be sent from Jitsi Videobridge to the + * endpoint associated with the video Channel. The value of the + * receive-simulcast-layer attribute is an unsigned integer. + * Typically used for debugging purposes. */ - public static final int EXPIRE_NOT_SPECIFIED = -1; + public static final String RECEIVING_SIMULCAST_LAYER + = "receive-simulcast-layer"; /** - * The XML name of the initiator attribute of a - * channel of a content of a conference IQ - * which represents the value of the initiator property of + * The XML name of the rtcpport attribute of a channel + * of a content of a conference IQ which represents + * the value of the rtcpPort property of * ColibriConferenceIQ.Channel. + * + * @deprecated The attribute is supported for the purposes of + * compatibility with legacy versions of Jitsi and Jitsi Videobridge. */ - public static final String INITIATOR_ATTR_NAME = "initiator"; + @Deprecated + public static final String RTCP_PORT_ATTR_NAME = "rtcpport"; + + public static final String RTP_LEVEL_RELAY_TYPE_ATTR_NAME + = "rtp-level-relay-type"; /** - * The identifier of the endpoint of the conference participant - * associated with this Channel. + * The XML name of the rtpport attribute of a channel + * of a content of a conference IQ which represents + * the value of the rtpPort property of + * ColibriConferenceIQ.Channel. + * + * @deprecated The attribute is supported for the purposes of + * compatibility with legacy versions of Jitsi and Jitsi Videobridge. */ - private String endpoint; + @Deprecated + public static final String RTP_PORT_ATTR_NAME = "rtpport"; /** - * The number of seconds of inactivity after which the channel - * represented by this instance expires. + * The name of the XML element which is a child of the <channel> + * element and which identifies/specifies an (RTP) SSRC which has been + * seen/received on the respective Channel. */ - private int expire = EXPIRE_NOT_SPECIFIED; + public static final String SSRC_ELEMENT_NAME = "ssrc"; /** - * The indicator which determines whether the conference focus is the - * initiator/offerer (as opposed to the responder/answerer) of the media - * negotiation associated with this instance. + * The direction of the channel represented by this instance. */ - private Boolean initiator; - - private IceUdpTransportPacketExtension transport; + private MediaDirection direction; /** - * XML element name. + * The host of the channel represented by this instance. + * + * @deprecated The field is supported for the purposes of compatibility + * with legacy versions of Jitsi and Jitsi Videobridge. */ - private String elementName; + @Deprecated + private String host; /** - * The channel-bundle-id attribute of this CommonChannel. + * The maximum number of video RTP streams to be sent from Jitsi + * Videobridge to the endpoint associated with this video + * Channel. */ - private String channelBundleId = null; + private Integer lastN; /** - * Initializes this class with given XML elementName. - * @param elementName XML element name to be used for producing XML - * representation of derived IQ class. + * The payload-type elements defined by XEP-0167: Jingle RTP + * Sessions associated with this channel. */ - protected ChannelCommon(String elementName) - { - this.elementName = elementName; - } + private final List payloadTypes + = new ArrayList(); /** - * Gets the identifier of the endpoint of the conference participant - * associated with this Channel. + * The target quality of the simulcast substreams to be sent from Jitsi + * Videobridge to the endpoint associated with this video + * Channel. + */ + private Integer receivingSimulcastLayer; + + /** + * The RTCP port of the channel represented by this instance. * - * @return the identifier of the endpoint of the conference participant - * associated with this Channel + * @deprecated The field is supported for the purposes of compatibility + * with legacy versions of Jitsi and Jitsi Videobridge. */ - public String getEndpoint() - { - return endpoint; - } + @Deprecated + private int rtcpPort; /** - * Gets the number of seconds of inactivity after which the - * channel represented by this instance expires. + * The type of RTP-level relay (in the terms specified by RFC 3550 + * "RTP: A Transport Protocol for Real-Time Applications" in + * section 2.3 "Mixers and Translators") used for this + * Channel. + */ + private RTPLevelRelayType rtpLevelRelayType; + + /** + * The RTP port of the channel represented by this instance. * - * @return the number of seconds of inactivity after which the - * channel represented by this instance expires + * @deprecated The field is supported for the purposes of compatibility + * with legacy versions of Jitsi and Jitsi Videobridge. */ - public int getExpire() - { - return expire; - } + @Deprecated + private int rtpPort; - public IceUdpTransportPacketExtension getTransport() + /** + * The SourceGroupPacketExtensions of this channel. + */ + private List sourceGroups; + + /** + * The SourcePacketExtensions of this channel. + */ + private final List sources + = new LinkedList(); + + /** + * The list of (RTP) SSRCs which have been seen/received on this + * Channel by now. These may exclude SSRCs which are no longer + * active. Set by the Jitsi Videobridge server, not its clients. + */ + private int[] ssrcs = NO_SSRCS; + + /** Initializes a new Channel instance. */ + public Channel() { - return transport; + super(Channel.ELEMENT_NAME); } /** - * Gets the indicator which determines whether the conference focus is - * the initiator/offerer (as opposed to the responder/answerer) of the - * media negotiation associated with this instance. + * Adds a payload-type element defined by XEP-0167: Jingle RTP + * Sessions to this channel. * - * @return {@link Boolean#TRUE} if the conference focus is the - * initiator/offerer of the media negotiation associated with this - * instance, {@link Boolean#FALSE} if the conference focus is the - * responder/answerer or null if the initiator state - * is unspecified + * @param payloadType the payload-type element to be added to + * this channel + * @return true if the list of payload-type elements + * associated with this channel has been modified as part of + * the method call; otherwise, false + * @throws NullPointerException if the specified payloadType is + * null */ - public Boolean isInitiator() + public boolean addPayloadType(PayloadTypePacketExtension payloadType) { - return initiator; + if (payloadType == null) + throw new NullPointerException("payloadType"); + + // Make sure that the COLIBRI namespace is used. + payloadType.setNamespace(null); + for (ParameterPacketExtension p : payloadType.getParameters()) + p.setNamespace(null); + + return + payloadTypes.contains(payloadType) + ? false + : payloadTypes.add(payloadType); } /** - * Get the channel-bundle-id attribute of this CommonChannel. - * @return the channel-bundle-id attribute of this - * CommonChannel. + * Adds a SourcePacketExtension to the list of sources of this + * channel. + * + * @param source the SourcePacketExtension to add to the list + * of sources of this channel + * @return true if the list of sources of this channel changed + * as a result of the execution of the method; otherwise, false */ - public String getChannelBundleId() + public synchronized boolean addSource(SourcePacketExtension source) { - return channelBundleId; + if (source == null) + throw new NullPointerException("source"); + + return sources.contains(source) ? false : sources.add(source); } /** - * Sets the identifier of the endpoint of the conference participant - * associated with this Channel. + * Adds a SourceGroupPacketExtension to the list of source + * groups of this channel. * - * @param endpoint the identifier of the endpoint of the conference - * participant associated with this Channel + * @param sourceGroup the SourcePacketExtension to add to the + * list of sources of this channel + * + * @return true if the list of sources of this channel changed + * as a result of the execution of the method; otherwise, false */ - public void setEndpoint(String endpoint) + public synchronized boolean addSourceGroup( + SourceGroupPacketExtension sourceGroup) { - this.endpoint = endpoint; + if (sourceGroup == null) + throw new NullPointerException("sourceGroup"); + + if (sourceGroups == null) + sourceGroups = new LinkedList(); + + return + sourceGroups.contains(sourceGroup) + ? false + : sourceGroups.add(sourceGroup); } /** - * Sets the number of seconds of inactivity after which the - * channel represented by this instance expires. + * Adds a specific (RTP) SSRC to the list of SSRCs seen/received on this + * Channel. Invoked by the Jitsi Videobridge server, not its + * clients. * - * @param expire the number of seconds of activity after which the - * channel represented by this instance expires - * @throws IllegalArgumentException if the value of the specified - * expire is other than {@link #EXPIRE_NOT_SPECIFIED} and - * negative + * @param ssrc the (RTP) SSRC to be added to the list of SSRCs + * seen/received on this Channel + * @return true if the list of SSRCs seen/received on this + * Channel has been modified as part of the method call; + * otherwise, false */ - public void setExpire(int expire) + public synchronized boolean addSSRC(int ssrc) { - if ((expire != EXPIRE_NOT_SPECIFIED) && (expire < 0)) - throw new IllegalArgumentException("expire"); + // contains + for (int i = 0; i < ssrcs.length; i++) + { + if (ssrcs[i] == ssrc) + return false; + } - this.expire = expire; + // add + int[] newSSRCs = new int[ssrcs.length + 1]; + + System.arraycopy(ssrcs, 0, newSSRCs, 0, ssrcs.length); + newSSRCs[ssrcs.length] = ssrc; + ssrcs = newSSRCs; + return true; } /** - * Sets the indicator which determines whether the conference focus is - * the initiator/offerer (as opposed to the responder/answerer) of the - * media negotiation associated with this instance. + * Gets the direction of this Channel. * - * @param initiator {@link Boolean#TRUE} if the conference focus is the - * initiator/offerer of the media negotiation associated with this - * instance, {@link Boolean#FALSE} if the conference focus is the - * responder/answerer or null if the initiator state - * is to be unspecified + * @return the direction of this Channel. */ - public void setInitiator(Boolean initiator) + public MediaDirection getDirection() { - this.initiator = initiator; + return (direction == null) ? MediaDirection.SENDRECV : direction; } - public void setTransport(IceUdpTransportPacketExtension transport) + /** + * Gets the IP address (as a String value) of the host on which + * the channel represented by this instance has been allocated. + * + * @return a String value which represents the IP address of + * the host on which the channel represented by this instance + * has been allocated + * + * @deprecated The method is supported for the purposes of compatibility + * with legacy versions of Jitsi and Jitsi Videobridge. + */ + @Deprecated + public String getHost() { - this.transport = transport; + return host; } /** - * Sets the channel-bundle-id attribute of this CommonChannel. - * @param channelBundleId the value to set. + * Gets the maximum number of video RTP streams to be sent from Jitsi + * Videobridge to the endpoint associated with this video + * Channel. + * + * @return the maximum number of video RTP streams to be sent from Jitsi + * Videobridge to the endpoint associated with this video + * Channel */ - public void setChannelBundleId(String channelBundleId) + public Integer getLastN() { - this.channelBundleId = channelBundleId; + return lastN; } /** - * Derived class implements this method in order to print additional - * attributes to main XML element. - * @param xml StringBuilder to which the XML - * String representation of this Channel - * is to be appended + * Gets a list of payload-type elements defined by XEP-0167: + * Jingle RTP Sessions added to this channel. + * + * @return an unmodifiable List of payload-type + * elements defined by XEP-0167: Jingle RTP Sessions added to this + * channel */ - protected abstract void printAttributes(StringBuilder xml); + public List getPayloadTypes() + { + return Collections.unmodifiableList(payloadTypes); + } /** - * Indicates whether there are some contents that should be printed as - * child elements of this IQ. If true is returned - * {@link #printContent(StringBuilder)} method will be called when - * XML representation of this IQ is being constructed. - * @return true if there are content to be printed as child - * elements of this IQ or false otherwise. + * Gets the target quality of the simulcast substreams to be sent from + * Jitsi Videobridge to the endpoint associated with this video + * Channel. + * + * @return the target quality of the simulcast substreams to be sent + * from Jitsi Videobridge to the endpoint associated with this video + * Channel. */ - protected abstract boolean hasContent(); + public Integer getReceivingSimulcastLayer() + { + return receivingSimulcastLayer; + } /** - * Implement in order to print content child elements of this IQ using - * given StringBuilder. Called during construction of XML - * representation if {@link #hasContent()} returns true. + * Gets the port which has been allocated to this channel for + * the purposes of transmitting RTCP packets. * - * @param xml the StringBuilder to which the XML - * String representation of this Channel - * is to be appended. + * @return the port which has been allocated to this channel + * for the purposes of transmitting RTCP packets + * + * @deprecated The method is supported for the purposes of compatibility + * with legacy versions of Jitsi and Jitsi Videobridge. */ - protected abstract void printContent(StringBuilder xml); + @Deprecated + public int getRTCPPort() + { + return rtcpPort; + } /** - * Appends the XML String representation of this - * Channel to a specific StringBuilder. - * - * @param xml the StringBuilder to which the XML - * String representation of this Channel is to be - * appended + * Gets the type of RTP-level relay (in the terms specified by RFC 3550 + * "RTP: A Transport Protocol for Real-Time Applications" in + * section 2.3 "Mixers and Translators") used for this + * Channel. + * + * @return the type of RTP-level relay used for this Channel */ - public void toXML(StringBuilder xml) + public RTPLevelRelayType getRTPLevelRelayType() { - xml.append('<').append(elementName); + return rtpLevelRelayType; + } - // endpoint - String endpoint = getEndpoint(); + /** + * Gets the port which has been allocated to this channel for + * the purposes of transmitting RTP packets. + * + * @return the port which has been allocated to this channel + * for the purposes of transmitting RTP packets + * + * @deprecated The method is supported for the purposes of compatibility + * with legacy versions of Jitsi and Jitsi Videobridge. + */ + @Deprecated + public int getRTPPort() + { + return rtpPort; + } - if (endpoint != null) - { - xml.append(' ').append(ENDPOINT_ATTR_NAME).append("='") - .append(endpoint).append('\''); - } + /** + * Gets the list of SourceGroupPacketExtensionss which + * represent the source groups of this channel. + * + * @return a List of SourceGroupPacketExtensions which + * represent the source groups of this channel + */ + public synchronized List getSourceGroups() + { + return + (sourceGroups == null) + ? null + : new ArrayList(sourceGroups); + } - // expire - int expire = getExpire(); + /** + * Gets the list of SourcePacketExtensionss which represent the + * sources of this channel. + * + * @return a List of SourcePacketExtensions which + * represent the sources of this channel + */ + public synchronized List getSources() + { + return new ArrayList(sources); + } - if (expire >= 0) - { - xml.append(' ').append(EXPIRE_ATTR_NAME).append("='") - .append(expire).append('\''); - } + /** + * Gets (a copy of) the list of (RTP) SSRCs seen/received on this + * Channel. + * + * @return an array of ints which represents (a copy of) the + * list of (RTP) SSRCs seen/received on this Channel + */ + public synchronized int[] getSSRCs() + { + return (ssrcs.length == 0) ? NO_SSRCS : ssrcs.clone(); + } - // initiator - Boolean initiator = isInitiator(); + @Override + protected boolean hasContent() + { + List payloadTypes = getPayloadTypes(); - if (initiator != null) + if (!payloadTypes.isEmpty()) + return true; + + List sources = getSources(); + if (!sources.isEmpty()) + return true; + + int[] ssrcs = getSSRCs(); + + return (ssrcs.length != 0); + } + + @Override + protected void printAttributes(StringBuilder xml) + { + // direction + MediaDirection direction = getDirection(); + + if ((direction != null) && (direction != MediaDirection.SENDRECV)) { - xml.append(' ').append(INITIATOR_ATTR_NAME).append("='") - .append(initiator).append('\''); + xml.append(' ').append(DIRECTION_ATTR_NAME).append("='") + .append(direction.toString()).append('\''); } - String channelBundleId = getChannelBundleId(); - if (channelBundleId != null) + // host + String host = getHost(); + + if (host != null) { - xml.append(' ').append(CHANNEL_BUNDLE_ID_ATTR_NAME) - .append("='").append(channelBundleId).append('\''); + xml.append(' ').append(HOST_ATTR_NAME).append("='").append(host) + .append('\''); } - // Print derived class attributes - printAttributes(xml); + // lastN + Integer lastN = getLastN(); - IceUdpTransportPacketExtension transport = getTransport(); - boolean hasTransport = (transport != null); - if (hasTransport || hasContent()) + if (lastN != null) { - xml.append('>'); - if(hasContent()) - printContent(xml); - if (hasTransport) - xml.append(transport.toXML()); - xml.append("'); + xml.append(' ').append(LAST_N_ATTR_NAME).append("='") + .append(lastN).append('\''); } - else + + // rtcpPort + int rtcpPort = getRTCPPort(); + + if (rtcpPort > 0) { - xml.append(" />"); + xml.append(' ').append(RTCP_PORT_ATTR_NAME).append("='") + .append(rtcpPort).append('\''); } - } - } - - public static class RTCPTerminationStrategy - { - public static final String ELEMENT_NAME = "rtcp-termination-strategy"; - public static final String NAME_ATTR_NAME = "name"; + // rtpLevelRelayType + RTPLevelRelayType rtpLevelRelayType = getRTPLevelRelayType(); - private String name; + if (rtpLevelRelayType != null) + { + xml.append(' ').append(RTP_LEVEL_RELAY_TYPE_ATTR_NAME) + .append("='").append(rtpLevelRelayType).append('\''); + } - public void setName(String name) - { - this.name = name; - } + // rtpPort + int rtpPort = getRTPPort(); - public String getName() - { - return name; + if (rtpPort > 0) + { + xml.append(' ').append(RTP_PORT_ATTR_NAME).append("='") + .append(rtpPort).append('\''); + } } - public void toXML(StringBuilder xml) + @Override + protected void printContent(StringBuilder xml) { - xml.append('<').append(ELEMENT_NAME); - xml.append(' ').append(NAME_ATTR_NAME).append("='") - .append(name).append('\''); - xml.append("/>"); - } - } + List payloadTypes = getPayloadTypes(); + List sources = getSources(); + List souceGroups = getSourceGroups(); + int[] ssrcs = getSSRCs(); - /** - * Represents a "channel-bundle" element. - */ - public static class ChannelBundle - { - /** - * The name of the "channel-bundle" element. - */ - public static final String ELEMENT_NAME = "channel-bundle"; + for (PayloadTypePacketExtension payloadType : payloadTypes) + xml.append(payloadType.toXML()); - /** - * The name of the "id" attribute. - */ - public static final String ID_ATTR_NAME = "id"; + for (SourcePacketExtension source : sources) + xml.append(source.toXML()); - /** - * The ID of this ChannelBundle. - */ - private String id; + if (souceGroups != null && souceGroups.size() != 0) + for (SourceGroupPacketExtension sourceGroup : souceGroups) + xml.append(sourceGroup.toXML()); - /** - * The transport element of this ChannelBundle. - */ - private IceUdpTransportPacketExtension transport; + for (int i = 0; i < ssrcs.length; i++) + { + xml.append('<').append(SSRC_ELEMENT_NAME).append('>') + .append(Long.toString(ssrcs[i] & 0xFFFFFFFFL)) + .append("'); + } + } /** - * Initializes a new ChannelBundle with the given ID. - * @param id the ID. + * Removes a payload-type element defined by XEP-0167: Jingle + * RTP Sessions from this channel. + * + * @param payloadType the payload-type element to be removed + * from this channel + * @return true if the list of payload-type elements + * associated with this channel has been modified as part of + * the method call; otherwise, false */ - public ChannelBundle(String id) + public boolean removePayloadType(PayloadTypePacketExtension payloadType) { - this.id = id; + return payloadTypes.remove(payloadType); } /** - * Returns the transport element of this ChannelBundle. - * @return the transport element of this ChannelBundle. + * Removes a SourcePacketExtension from the list of sources of + * this channel. + * + * @param source the SourcePacketExtension to remove from the + * list of sources of this channel + * @return true if the list of sources of this channel changed + * as a result of the execution of the method; otherwise, false */ - public IceUdpTransportPacketExtension getTransport() + public synchronized boolean removeSource(SourcePacketExtension source) { - return transport; + return sources.remove(source); } /** - * Sets the transport element of this ChannelBundle. - * @param transport the transport to set. + * Removes a specific (RTP) SSRC from the list of SSRCs seen/received on + * this Channel. Invoked by the Jitsi Videobridge server, not + * its clients. + * + * @param ssrc the (RTP) SSRC to be removed from the list of SSRCs + * seen/received on this Channel + * @return true if the list of SSRCs seen/received on this + * Channel has been modified as part of the method call; + * otherwise, false */ - public void setTransport(IceUdpTransportPacketExtension transport) + public synchronized boolean removeSSRC(int ssrc) { - this.transport = transport; + if (ssrcs.length == 1) + { + if (ssrcs[0] == ssrc) + { + ssrcs = NO_SSRCS; + return true; + } + else + return false; + } + else + { + for (int i = 0; i < ssrcs.length; i++) + { + if (ssrcs[i] == ssrc) + { + int[] newSSRCs = new int[ssrcs.length - 1]; + + if (i != 0) + System.arraycopy(ssrcs, 0, newSSRCs, 0, i); + if (i != newSSRCs.length) + { + System.arraycopy( + ssrcs, i + 1, + newSSRCs, i, + newSSRCs.length - i); + } + ssrcs = newSSRCs; + return true; + } + } + return false; + } } /** - * Returns the ID of this ChannelBundle. - * @return the ID of this ChannelBundle. + * Sets the direction of this Channel + * + * @param direction the MediaDirection to set the + * direction of this Channel to. */ - public String getId() + public void setDirection(MediaDirection direction) { - return id; + this.direction = direction; } /** - * Sets the ID of this ChannelBundle. - * @param id the ID to set. + * Sets the IP address (as a String value) of the host on which + * the channel represented by this instance has been allocated. + * + * @param host a String value which represents the IP address + * of the host on which the channel represented by this + * instance has been allocated + * + * @deprecated The method is supported for the purposes of compatibility + * with legacy versions of Jitsi and Jitsi Videobridge. */ - public void setId(String id) + @Deprecated + public void setHost(String host) { - this.id = id; + this.host = host; } /** - * Appends an XML representation of this ChannelBundle to - * xml. - * @param xml the StringBuilder to append to. + * Sets the maximum number of video RTP streams to be sent from Jitsi + * Videobridge to the endpoint associated with this video + * Channel. + * + * @param lastN the maximum number of video RTP streams to be sent from + * Jitsi Videobridge to the endpoint associated with this video + * Channel */ - public void toXML(StringBuilder xml) + public void setLastN(Integer lastN) { - xml.append('<').append(ELEMENT_NAME).append(' ') - .append(ID_ATTR_NAME).append("='").append(id).append('\''); - - if (transport != null) - { - xml.append('>'); - xml.append(transport.toXML()); - xml.append("'); - } - else - { - xml.append(" />"); - } + this.lastN = lastN; } - } - /** - * Represents a channel included into a content of a Jitsi - * Videobridge conference IQ. - */ - public static class Channel - extends ChannelCommon - { /** - * The name of the XML attribute of a channel which represents - * its direction. - */ - public static final String DIRECTION_ATTR_NAME = "direction"; - - /** - * The XML element name of a channel of a content of a - * Jitsi Videobridge conference IQ. + * Sets the target quality of the simulcast substreams to be sent from + * Jitsi Videobridge to the endpoint associated with this video + * Channel. + * + * @param simulcastLayer the target quality of the simulcast substreams + * to be sent from Jitsi Videobridge to the endpoint associated with + * this video Channel. */ - public static final String ELEMENT_NAME = "channel"; + public void setReceivingSimulcastLayer(Integer simulcastLayer) + { + this.receivingSimulcastLayer = simulcastLayer; + } /** - * The XML name of the host attribute of a channel of - * a content of a conference IQ which represents the - * value of the host property of - * ColibriConferenceIQ.Channel. + * Sets the port which has been allocated to this channel for + * the purposes of transmitting RTCP packets. * - * @deprecated The attribute is supported for the purposes of - * compatibility with legacy versions of Jitsi and Jitsi Videobridge. + * @param rtcpPort the port which has been allocated to this + * channel for the purposes of transmitting RTCP packets + * + * @deprecated The method is supported for the purposes of compatibility + * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated - public static final String HOST_ATTR_NAME = "host"; - - /** - * The XML name of the id attribute of a channel of a - * content of a conference IQ which represents the - * value of the id property of - * ColibriConferenceIQ.Channel. - */ - public static final String ID_ATTR_NAME = "id"; + public void setRTCPPort(int rtcpPort) + { + this.rtcpPort = rtcpPort; + } /** - * The XML name of the last-n attribute of a video - * channel which specifies the maximum number of video RTP - * streams to be sent from Jitsi Videobridge to the endpoint associated - * with the video channel. The value of the last-n - * attribute is a positive number. + * Sets the type of RTP-level relay (in the terms specified by RFC 3550 + * "RTP: A Transport Protocol for Real-Time Applications" in + * section 2.3 "Mixers and Translators") used for this + * Channel. + * + * @param rtpLevelRelayType the type of RTP-level relay used for + * this Channel */ - public static final String LAST_N_ATTR_NAME = "last-n"; + public void setRTPLevelRelayType(RTPLevelRelayType rtpLevelRelayType) + { + this.rtpLevelRelayType = rtpLevelRelayType; + } /** - * The XML name of the rtcpport attribute of a channel - * of a content of a conference IQ which represents - * the value of the rtcpPort property of - * ColibriConferenceIQ.Channel. + * Sets the type of RTP-level relay (in the terms specified by RFC 3550 + * "RTP: A Transport Protocol for Real-Time Applications" in + * section 2.3 "Mixers and Translators") used for this + * Channel. * - * @deprecated The attribute is supported for the purposes of - * compatibility with legacy versions of Jitsi and Jitsi Videobridge. + * @param s the type of RTP-level relay used for this Channel */ - @Deprecated - public static final String RTCP_PORT_ATTR_NAME = "rtcpport"; - - public static final String RTP_LEVEL_RELAY_TYPE_ATTR_NAME - = "rtp-level-relay-type"; + public void setRTPLevelRelayType(String s) + { + setRTPLevelRelayType(RTPLevelRelayType.parseRTPLevelRelayType(s)); + } /** - * The XML name of the rtpport attribute of a channel - * of a content of a conference IQ which represents - * the value of the rtpPort property of - * ColibriConferenceIQ.Channel. + * Sets the port which has been allocated to this channel for + * the purposes of transmitting RTP packets. * - * @deprecated The attribute is supported for the purposes of - * compatibility with legacy versions of Jitsi and Jitsi Videobridge. + * @param rtpPort the port which has been allocated to this + * channel for the purposes of transmitting RTP packets + * + * @deprecated The method is supported for the purposes of compatibility + * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated - public static final String RTP_PORT_ATTR_NAME = "rtpport"; + public void setRTPPort(int rtpPort) + { + this.rtpPort = rtpPort; + } /** - * The name of the XML element which is a child of the <channel> - * element and which identifies/specifies an (RTP) SSRC which has been - * seen/received on the respective Channel. + * Sets the list of (RTP) SSRCs seen/received on this Channel. + * + * @param ssrcs the list of (RTP) SSRCs to be set as seen/received on + * this Channel */ - public static final String SSRC_ELEMENT_NAME = "ssrc"; + public void setSSRCs(int[] ssrcs) + { + /* + * TODO Make sure that the SSRCs set on this instance do not contain + * duplicates. + */ + this.ssrcs + = ((ssrcs == null) || (ssrcs.length == 0)) + ? NO_SSRCS + : ssrcs.clone(); + } + } + /** + * Represents a "channel-bundle" element. + */ + public static class ChannelBundle + { /** - * The direction of the channel represented by this instance. + * The name of the "channel-bundle" element. */ - private MediaDirection direction; + public static final String ELEMENT_NAME = "channel-bundle"; /** - * The host of the channel represented by this instance. - * - * @deprecated The field is supported for the purposes of compatibility - * with legacy versions of Jitsi and Jitsi Videobridge. + * The name of the "id" attribute. */ - @Deprecated - private String host; + public static final String ID_ATTR_NAME = "id"; /** - * The ID of the channel represented by this instance. + * The ID of this ChannelBundle. */ private String id; /** - * The maximum number of video RTP streams to be sent from Jitsi - * Videobridge to the endpoint associated with this video - * Channel. - */ - private Integer lastN; - - /** - * The payload-type elements defined by XEP-0167: Jingle RTP - * Sessions associated with this channel. + * The transport element of this ChannelBundle. */ - private final List payloadTypes - = new ArrayList(); + private IceUdpTransportPacketExtension transport; /** - * The RTCP port of the channel represented by this instance. - * - * @deprecated The field is supported for the purposes of compatibility - * with legacy versions of Jitsi and Jitsi Videobridge. + * Initializes a new ChannelBundle with the given ID. + * @param id the ID. */ - @Deprecated - private int rtcpPort; + public ChannelBundle(String id) + { + this.id = id; + } /** - * The type of RTP-level relay (in the terms specified by RFC 3550 - * "RTP: A Transport Protocol for Real-Time Applications" in - * section 2.3 "Mixers and Translators") used for this - * Channel. + * Returns the ID of this ChannelBundle. + * @return the ID of this ChannelBundle. */ - private RTPLevelRelayType rtpLevelRelayType; + public String getId() + { + return id; + } /** - * The RTP port of the channel represented by this instance. - * - * @deprecated The field is supported for the purposes of compatibility - * with legacy versions of Jitsi and Jitsi Videobridge. + * Returns the transport element of this ChannelBundle. + * @return the transport element of this ChannelBundle. */ - @Deprecated - private int rtpPort; + public IceUdpTransportPacketExtension getTransport() + { + return transport; + } /** - * The SourcePacketExtensions of this channel. + * Sets the ID of this ChannelBundle. + * @param id the ID to set. */ - private final List sources - = new LinkedList(); + public void setId(String id) + { + this.id = id; + } /** - * The list of (RTP) SSRCs which have been seen/received on this - * Channel by now. These may exclude SSRCs which are no longer - * active. Set by the Jitsi Videobridge server, not its clients. + * Sets the transport element of this ChannelBundle. + * @param transport the transport to set. */ - private int[] ssrcs = NO_SSRCS; - - /** Initializes a new Channel instance. */ - public Channel() + public void setTransport(IceUdpTransportPacketExtension transport) { - super(Channel.ELEMENT_NAME); + this.transport = transport; } /** - * Adds a payload-type element defined by XEP-0167: Jingle RTP - * Sessions to this channel. - * - * @param payloadType the payload-type element to be added to - * this channel - * @return true if the list of payload-type elements - * associated with this channel has been modified as part of - * the method call; otherwise, false - * @throws NullPointerException if the specified payloadType is - * null + * Appends an XML representation of this ChannelBundle to + * xml. + * @param xml the StringBuilder to append to. */ - public boolean addPayloadType(PayloadTypePacketExtension payloadType) + public void toXML(StringBuilder xml) { - if (payloadType == null) - throw new NullPointerException("payloadType"); - - // Make sure that the COLIBRI namespace is used. - payloadType.setNamespace(null); - for (ParameterPacketExtension p : payloadType.getParameters()) - p.setNamespace(null); + xml.append('<').append(ELEMENT_NAME).append(' ') + .append(ID_ATTR_NAME).append("='").append(id).append('\''); - return - payloadTypes.contains(payloadType) - ? false - : payloadTypes.add(payloadType); + if (transport != null) + { + xml.append('>'); + xml.append(transport.toXML()); + xml.append("'); + } + else + { + xml.append(" />"); + } } + } + /** + * Class contains common code for both Channel and + * SctpConnection IQ classes. + * + * @author Pawel Domas + */ + public static abstract class ChannelCommon + { /** - * Adds a SourcePacketExtension to the list of sources of this - * channel. - * - * @param source the SourcePacketExtension to add to the list - * of sources of this channel - * @return true if the list of sources of this channel changed - * as a result of the execution of the method; otherwise, false + * The name of the "channel-bundle-id" attribute. */ - public synchronized boolean addSource(SourcePacketExtension source) - { - if (source == null) - throw new NullPointerException("source"); + public static final String CHANNEL_BUNDLE_ID_ATTR_NAME + = "channel-bundle-id"; - return sources.contains(source) ? false : sources.add(source); - } + /** + * The XML name of the endpoint attribute which specifies the + * optional identifier of the endpoint of the conference participant + * associated with a channel. The value of the + * endpoint attribute is an opaque String from the + * point of view of Jitsi Videobridge. + */ + public static final String ENDPOINT_ATTR_NAME = "endpoint"; /** - * Adds a specific (RTP) SSRC to the list of SSRCs seen/received on this - * Channel. Invoked by the Jitsi Videobridge server, not its - * clients. - * - * @param ssrc the (RTP) SSRC to be added to the list of SSRCs - * seen/received on this Channel - * @return true if the list of SSRCs seen/received on this - * Channel has been modified as part of the method call; - * otherwise, false + * The XML name of the expire attribute of a channel + * of a content of a conference IQ which represents + * the value of the expire property of + * ColibriConferenceIQ.Channel. */ - public synchronized boolean addSSRC(int ssrc) - { - // contains - for (int i = 0; i < ssrcs.length; i++) - if (ssrcs[i] == ssrc) - return false; + public static final String EXPIRE_ATTR_NAME = "expire"; - // add - int[] newSSRCs = new int[ssrcs.length + 1]; + /** + * The value of the expire property of + * ColibriConferenceIQ.Channel which indicates that no actual + * value has been specified for the property in question. + */ + public static final int EXPIRE_NOT_SPECIFIED = -1; - System.arraycopy(ssrcs, 0, newSSRCs, 0, ssrcs.length); - newSSRCs[ssrcs.length] = ssrc; - ssrcs = newSSRCs; - return true; - } + /** + * The XML name of the id attribute of a channel of a + * content of a conference IQ which represents the + * value of the id property of + * ColibriConferenceIQ.Channel. + */ + public static final String ID_ATTR_NAME = "id"; /** - * Gets the direction of this Channel. - * - * @return the direction of this Channel. + * The XML name of the initiator attribute of a + * channel of a content of a conference IQ + * which represents the value of the initiator property of + * ColibriConferenceIQ.Channel. */ - public MediaDirection getDirection() - { - return (direction == null) ? MediaDirection.SENDRECV : direction; - } + public static final String INITIATOR_ATTR_NAME = "initiator"; /** - * Gets the IP address (as a String value) of the host on which - * the channel represented by this instance has been allocated. - * - * @return a String value which represents the IP address of - * the host on which the channel represented by this instance - * has been allocated - * - * @deprecated The method is supported for the purposes of compatibility - * with legacy versions of Jitsi and Jitsi Videobridge. + * The channel-bundle-id attribute of this CommonChannel. */ - @Deprecated - public String getHost() - { - return host; - } + private String channelBundleId = null; + + /** + * XML element name. + */ + private String elementName; /** - * Gets the ID of the channel represented by this instance. - * - * @return the ID of the channel represented by this instance + * The identifier of the endpoint of the conference participant + * associated with this Channel. */ - public String getID() - { - return id; - } + private String endpoint; /** - * Gets the maximum number of video RTP streams to be sent from Jitsi - * Videobridge to the endpoint associated with this video - * Channel. - * - * @return the maximum number of video RTP streams to be sent from Jitsi - * Videobridge to the endpoint associated with this video - * Channel + * The number of seconds of inactivity after which the channel + * represented by this instance expires. */ - public Integer getLastN() - { - return lastN; - } + private int expire = EXPIRE_NOT_SPECIFIED; /** - * Gets a list of payload-type elements defined by XEP-0167: - * Jingle RTP Sessions added to this channel. - * - * @return an unmodifiable List of payload-type - * elements defined by XEP-0167: Jingle RTP Sessions added to this - * channel + * The ID of the channel represented by this instance. */ - public List getPayloadTypes() - { - return Collections.unmodifiableList(payloadTypes); - } + private String id; /** - * Gets the port which has been allocated to this channel for - * the purposes of transmitting RTCP packets. - * - * @return the port which has been allocated to this channel - * for the purposes of transmitting RTCP packets - * - * @deprecated The method is supported for the purposes of compatibility - * with legacy versions of Jitsi and Jitsi Videobridge. + * The indicator which determines whether the conference focus is the + * initiator/offerer (as opposed to the responder/answerer) of the media + * negotiation associated with this instance. */ - @Deprecated - public int getRTCPPort() - { - return rtcpPort; - } + private Boolean initiator; + + private IceUdpTransportPacketExtension transport; /** - * Gets the type of RTP-level relay (in the terms specified by RFC 3550 - * "RTP: A Transport Protocol for Real-Time Applications" in - * section 2.3 "Mixers and Translators") used for this - * Channel. - * - * @return the type of RTP-level relay used for this Channel + * Initializes this class with given XML elementName. + * @param elementName XML element name to be used for producing XML + * representation of derived IQ class. */ - public RTPLevelRelayType getRTPLevelRelayType() + protected ChannelCommon(String elementName) { - return rtpLevelRelayType; + this.elementName = elementName; } /** - * Gets the port which has been allocated to this channel for - * the purposes of transmitting RTP packets. - * - * @return the port which has been allocated to this channel - * for the purposes of transmitting RTP packets - * - * @deprecated The method is supported for the purposes of compatibility - * with legacy versions of Jitsi and Jitsi Videobridge. + * Get the channel-bundle-id attribute of this CommonChannel. + * @return the channel-bundle-id attribute of this + * CommonChannel. */ - @Deprecated - public int getRTPPort() + public String getChannelBundleId() { - return rtpPort; + return channelBundleId; } /** - * Gets the list of SourcePacketExtensionss which represent the - * sources of this channel. + * Gets the identifier of the endpoint of the conference participant + * associated with this Channel. * - * @return a List of SourcePacketExtensions which - * represent the sources of this channel + * @return the identifier of the endpoint of the conference participant + * associated with this Channel */ - public synchronized List getSources() + public String getEndpoint() { - return new ArrayList(sources); + return endpoint; } /** - * Gets (a copy of) the list of (RTP) SSRCs seen/received on this - * Channel. + * Gets the number of seconds of inactivity after which the + * channel represented by this instance expires. * - * @return an array of ints which represents (a copy of) the - * list of (RTP) SSRCs seen/received on this Channel + * @return the number of seconds of inactivity after which the + * channel represented by this instance expires */ - public synchronized int[] getSSRCs() + public int getExpire() { - return (ssrcs.length == 0) ? NO_SSRCS : ssrcs.clone(); + return expire; } /** - * Removes a payload-type element defined by XEP-0167: Jingle - * RTP Sessions from this channel. + * Gets the ID of the channel represented by this instance. * - * @param payloadType the payload-type element to be removed - * from this channel - * @return true if the list of payload-type elements - * associated with this channel has been modified as part of - * the method call; otherwise, false + * @return the ID of the channel represented by this instance */ - public boolean removePayloadType(PayloadTypePacketExtension payloadType) + public String getID() { - return payloadTypes.remove(payloadType); + return id; } - /** - * Removes a SourcePacketExtension from the list of sources of - * this channel. - * - * @param source the SourcePacketExtension to remove from the - * list of sources of this channel - * @return true if the list of sources of this channel changed - * as a result of the execution of the method; otherwise, false - */ - public synchronized boolean removeSource(SourcePacketExtension source) + public IceUdpTransportPacketExtension getTransport() { - return sources.remove(source); + return transport; } /** - * Removes a specific (RTP) SSRC from the list of SSRCs seen/received on - * this Channel. Invoked by the Jitsi Videobridge server, not - * its clients. - * - * @param ssrc the (RTP) SSRC to be removed from the list of SSRCs - * seen/received on this Channel - * @return true if the list of SSRCs seen/received on this - * Channel has been modified as part of the method call; - * otherwise, false + * Indicates whether there are some contents that should be printed as + * child elements of this IQ. If true is returned + * {@link #printContent(StringBuilder)} method will be called when + * XML representation of this IQ is being constructed. + * @return true if there are content to be printed as child + * elements of this IQ or false otherwise. */ - public synchronized boolean removeSSRC(int ssrc) - { - if (ssrcs.length == 1) - { - if (ssrcs[0] == ssrc) - { - ssrcs = NO_SSRCS; - return true; - } - else - return false; - } - else - { - for (int i = 0; i < ssrcs.length; i++) - { - if (ssrcs[i] == ssrc) - { - int[] newSSRCs = new int[ssrcs.length - 1]; - - if (i != 0) - System.arraycopy(ssrcs, 0, newSSRCs, 0, i); - if (i != newSSRCs.length) - { - System.arraycopy( - ssrcs, i + 1, - newSSRCs, i, - newSSRCs.length - i); - } - ssrcs = newSSRCs; - return true; - } - } - return false; - } - } + protected abstract boolean hasContent(); /** - * Sets the direction of this Channel + * Gets the indicator which determines whether the conference focus is + * the initiator/offerer (as opposed to the responder/answerer) of the + * media negotiation associated with this instance. * - * @param direction the MediaDirection to set the - * direction of this Channel to. + * @return {@link Boolean#TRUE} if the conference focus is the + * initiator/offerer of the media negotiation associated with this + * instance, {@link Boolean#FALSE} if the conference focus is the + * responder/answerer or null if the initiator state + * is unspecified */ - public void setDirection(MediaDirection direction) + public Boolean isInitiator() { - this.direction = direction; + return initiator; } /** - * Sets the IP address (as a String value) of the host on which - * the channel represented by this instance has been allocated. - * - * @param host a String value which represents the IP address - * of the host on which the channel represented by this - * instance has been allocated - * - * @deprecated The method is supported for the purposes of compatibility - * with legacy versions of Jitsi and Jitsi Videobridge. + * Derived class implements this method in order to print additional + * attributes to main XML element. + * @param xml StringBuilder to which the XML + * String representation of this Channel + * is to be appended */ - @Deprecated - public void setHost(String host) - { - this.host = host; - } + protected abstract void printAttributes(StringBuilder xml); /** - * Sets the ID of the channel represented by this instance. + * Implement in order to print content child elements of this IQ using + * given StringBuilder. Called during construction of XML + * representation if {@link #hasContent()} returns true. * - * @param id the ID of the channel represented by this instance + * @param xml the StringBuilder to which the XML + * String representation of this Channel + * is to be appended. */ - public void setID(String id) - { - this.id = id; - } + protected abstract void printContent(StringBuilder xml); /** - * Sets the maximum number of video RTP streams to be sent from Jitsi - * Videobridge to the endpoint associated with this video - * Channel. - * - * @param lastN the maximum number of video RTP streams to be sent from - * Jitsi Videobridge to the endpoint associated with this video - * Channel + * Sets the channel-bundle-id attribute of this CommonChannel. + * @param channelBundleId the value to set. */ - public void setLastN(Integer lastN) + public void setChannelBundleId(String channelBundleId) { - this.lastN = lastN; + this.channelBundleId = channelBundleId; } /** - * Sets the port which has been allocated to this channel for - * the purposes of transmitting RTCP packets. - * - * @param rtcpPort the port which has been allocated to this - * channel for the purposes of transmitting RTCP packets + * Sets the identifier of the endpoint of the conference participant + * associated with this Channel. * - * @deprecated The method is supported for the purposes of compatibility - * with legacy versions of Jitsi and Jitsi Videobridge. + * @param endpoint the identifier of the endpoint of the conference + * participant associated with this Channel */ - @Deprecated - public void setRTCPPort(int rtcpPort) + public void setEndpoint(String endpoint) { - this.rtcpPort = rtcpPort; + this.endpoint = endpoint; } /** - * Sets the type of RTP-level relay (in the terms specified by RFC 3550 - * "RTP: A Transport Protocol for Real-Time Applications" in - * section 2.3 "Mixers and Translators") used for this - * Channel. + * Sets the number of seconds of inactivity after which the + * channel represented by this instance expires. * - * @param rtpLevelRelayType the type of RTP-level relay used for - * this Channel + * @param expire the number of seconds of activity after which the + * channel represented by this instance expires + * @throws IllegalArgumentException if the value of the specified + * expire is other than {@link #EXPIRE_NOT_SPECIFIED} and + * negative */ - public void setRTPLevelRelayType(RTPLevelRelayType rtpLevelRelayType) + public void setExpire(int expire) { - this.rtpLevelRelayType = rtpLevelRelayType; + if ((expire != EXPIRE_NOT_SPECIFIED) && (expire < 0)) + throw new IllegalArgumentException("expire"); + + this.expire = expire; } - /** - * Sets the type of RTP-level relay (in the terms specified by RFC 3550 - * "RTP: A Transport Protocol for Real-Time Applications" in - * section 2.3 "Mixers and Translators") used for this - * Channel. + /* + * Sets the ID of the channel represented by this instance. * - * @param s the type of RTP-level relay used for this Channel + * @param id the ID of the channel represented by this instance */ - public void setRTPLevelRelayType(String s) + public void setID(String id) { - setRTPLevelRelayType(RTPLevelRelayType.parseRTPLevelRelayType(s)); + this.id = id; } /** - * Sets the port which has been allocated to this channel for - * the purposes of transmitting RTP packets. - * - * @param rtpPort the port which has been allocated to this - * channel for the purposes of transmitting RTP packets + * Sets the indicator which determines whether the conference focus is + * the initiator/offerer (as opposed to the responder/answerer) of the + * media negotiation associated with this instance. * - * @deprecated The method is supported for the purposes of compatibility - * with legacy versions of Jitsi and Jitsi Videobridge. + * @param initiator {@link Boolean#TRUE} if the conference focus is the + * initiator/offerer of the media negotiation associated with this + * instance, {@link Boolean#FALSE} if the conference focus is the + * responder/answerer or null if the initiator state + * is to be unspecified */ - @Deprecated - public void setRTPPort(int rtpPort) + public void setInitiator(Boolean initiator) { - this.rtpPort = rtpPort; + this.initiator = initiator; + } + + public void setTransport(IceUdpTransportPacketExtension transport) + { + this.transport = transport; } /** - * Sets the list of (RTP) SSRCs seen/received on this Channel. + * Appends the XML String representation of this + * Channel to a specific StringBuilder. * - * @param ssrcs the list of (RTP) SSRCs to be set as seen/received on - * this Channel + * @param xml the StringBuilder to which the XML + * String representation of this Channel is to be + * appended */ - public void setSSRCs(int[] ssrcs) + public void toXML(StringBuilder xml) { - /* - * TODO Make sure that the SSRCs set on this instance do not contain - * duplicates. - */ - this.ssrcs - = ((ssrcs == null) || (ssrcs.length == 0)) - ? NO_SSRCS - : ssrcs.clone(); - } + xml.append('<').append(elementName); - @Override - protected void printAttributes(StringBuilder xml) - { - // direction - MediaDirection direction = getDirection(); + // endpoint + String endpoint = getEndpoint(); - if ((direction != null) && (direction != MediaDirection.SENDRECV)) + if (endpoint != null) { - xml.append(' ').append(DIRECTION_ATTR_NAME).append("='") - .append(direction.toString()).append('\''); + xml.append(' ').append(ENDPOINT_ATTR_NAME).append("='") + .append(endpoint).append('\''); } - // host - String host = getHost(); + // expire + int expire = getExpire(); - if (host != null) + if (expire >= 0) { - xml.append(' ').append(HOST_ATTR_NAME).append("='").append(host) - .append('\''); + xml.append(' ').append(EXPIRE_ATTR_NAME).append("='") + .append(expire).append('\''); } // id @@ -1348,79 +1463,43 @@ protected void printAttributes(StringBuilder xml) if (id != null) { - xml.append(' ').append(ID_ATTR_NAME).append("='").append(id) - .append('\''); - } - - // lastN - Integer lastN = getLastN(); - - if (lastN != null) - { - xml.append(' ').append(LAST_N_ATTR_NAME).append("='") - .append(lastN).append('\''); + xml.append(' ').append(ID_ATTR_NAME).append("='") + .append(id).append('\''); } - // rtcpPort - int rtcpPort = getRTCPPort(); + // initiator + Boolean initiator = isInitiator(); - if (rtcpPort > 0) + if (initiator != null) { - xml.append(' ').append(RTCP_PORT_ATTR_NAME).append("='") - .append(rtcpPort).append('\''); + xml.append(' ').append(INITIATOR_ATTR_NAME).append("='") + .append(initiator).append('\''); } - // rtpLevelRelayType - RTPLevelRelayType rtpLevelRelayType = getRTPLevelRelayType(); - - if (rtpLevelRelayType != null) + String channelBundleId = getChannelBundleId(); + if (channelBundleId != null) { - xml.append(' ').append(RTP_LEVEL_RELAY_TYPE_ATTR_NAME) - .append("='").append(rtpLevelRelayType).append('\''); + xml.append(' ').append(CHANNEL_BUNDLE_ID_ATTR_NAME) + .append("='").append(channelBundleId).append('\''); } - // rtpPort - int rtpPort = getRTPPort(); + // Print derived class attributes + printAttributes(xml); - if (rtpPort > 0) + IceUdpTransportPacketExtension transport = getTransport(); + boolean hasTransport = (transport != null); + if (hasTransport || hasContent()) { - xml.append(' ').append(RTP_PORT_ATTR_NAME).append("='") - .append(rtpPort).append('\''); + xml.append('>'); + if(hasContent()) + printContent(xml); + if (hasTransport) + xml.append(transport.toXML()); + xml.append("'); } - } - - @Override - protected boolean hasContent() - { - List payloadTypes = getPayloadTypes(); - boolean hasPayloadTypes = !payloadTypes.isEmpty(); - List sources = getSources(); - boolean hasSources = !sources.isEmpty(); - int[] ssrcs = getSSRCs(); - boolean hasSSRCs = (ssrcs.length != 0); - - return hasPayloadTypes || hasSources || hasSSRCs; - } - - @Override - protected void printContent(StringBuilder xml) - { - List payloadTypes = getPayloadTypes(); - List sources = getSources(); - int[] ssrcs = getSSRCs(); - - for (PayloadTypePacketExtension payloadType : payloadTypes) - xml.append(payloadType.toXML()); - - for (SourcePacketExtension source : sources) - xml.append(source.toXML()); - - for (int i = 0; i < ssrcs.length; i++) + else { - xml.append('<').append(SSRC_ELEMENT_NAME).append('>') - .append(Long.toString(ssrcs[i] & 0xFFFFFFFFL)) - .append("'); + xml.append(" />"); } } } @@ -1450,6 +1529,11 @@ public static class Content */ private final List channels = new LinkedList(); + /** + * The name of the content represented by this instance. + */ + private String name; + /** * The list of {@link SctpConnection}s included into this * content of a conference IQ. @@ -1457,11 +1541,6 @@ public static class Content private final List sctpConnections = new LinkedList(); - /** - * The name of the content represented by this instance. - */ - private String name; - /** * Initializes a new Content instance without a name and * channels. @@ -1501,6 +1580,26 @@ public boolean addChannel(Channel channel) return channels.contains(channel) ? false : channels.add(channel); } + /** + * Adds a specific SctpConnection to the list of + * SctpConnections included into this Content. + * + * @param conn the SctpConnection to be included into this + * Content + * @return true if the list of SctpConnections + * included into this Content was modified as a result of + * the execution of the method; otherwise, false + * @throws NullPointerException if the specified conn is + * null + */ + public boolean addSctpConnection(SctpConnection conn) + { + if(conn == null) + throw new NullPointerException("Sctp connection"); + + return !sctpConnections.contains(conn) && sctpConnections.add(conn); + } + /** * Gets the Channel at a specific index/position within the * list of Channels included in this Content. @@ -1530,8 +1629,25 @@ public Channel getChannel(int channelIndex) public Channel getChannel(String channelID) { for (Channel channel : getChannels()) + { if (channelID.equals(channel.getID())) return channel; + } + return null; + } + + /** + * Finds an SCTP connection identified by given connectionID. + * @param connectionID the ID of the SCTP connection to find. + * @return SctpConnection instance identified by given ID + * or null if no such connection is contained in + * this IQ. + */ + public SctpConnection getSctpConnection(String connectionID) + { + for (SctpConnection conn : getSctpConnections()) + if (connectionID.equals(conn.getID())) + return conn; return null; } @@ -1551,44 +1667,12 @@ public int getChannelCount() * Gets a list of the Channel included into/associated with * this Content. * - * @return an unmodifiable List of the Channels - * included into/associated with this Content - */ - public List getChannels() - { - return Collections.unmodifiableList(channels); - } - - /** - * Adds a specific SctpConnection to the list of - * SctpConnections included into this Content. - * - * @param conn the SctpConnection to be included into this - * Content - * @return true if the list of SctpConnections - * included into this Content was modified as a result of - * the execution of the method; otherwise, false - * @throws NullPointerException if the specified conn is - * null - */ - public boolean addSctpConnection(SctpConnection conn) - { - if(conn == null) - throw new NullPointerException("Sctp connection"); - - return !sctpConnections.contains(conn) && sctpConnections.add(conn); - } - - /** - * Gets a list of the SctpConnections included into/associated - * with this Content. - * - * @return an unmodifiable List of the SctpConnections + * @return an unmodifiable List of the Channels * included into/associated with this Content */ - public List getSctpConnections() + public List getChannels() { - return Collections.unmodifiableList(sctpConnections); + return Collections.unmodifiableList(channels); } /** @@ -1601,6 +1685,18 @@ public String getName() return name; } + /** + * Gets a list of the SctpConnections included into/associated + * with this Content. + * + * @return an unmodifiable List of the SctpConnections + * included into/associated with this Content + */ + public List getSctpConnections() + { + return Collections.unmodifiableList(sctpConnections); + } + /** * Removes a specific Channel from the list of * Channels included into this Content. @@ -1663,94 +1759,95 @@ public void toXML(StringBuilder xml) xml.append("'); } } + + /** + * Removes given SCTP connection from this IQ. + * @param connection the SCTP connection instance to be removed. + * @return true if given connection was contained in + * this IQ and has been removed successfully. + */ + public boolean removeSctpConnection(SctpConnection connection) + { + return sctpConnections.remove(connection); + } } /** - * Represents a SCTP connection included into a content - * of a Jitsi Videobridge conference IQ. - * - * @author Pawel Domas + * Represents an 'endpoint' element. */ - public static class SctpConnection - extends ChannelCommon + public static class Endpoint { /** - * The XML element name of a content of a Jitsi Videobridge - * conference IQ. + * The name of the 'displayname' attribute. */ - public static final String ELEMENT_NAME = "sctpconnection"; + public static final String DISPLAYNAME_ATTR_NAME = "displayname"; /** - * The XML name of the port attribute of a - * SctpConnection of a conference IQ which represents - * the SCTP port property of - * ColibriConferenceIQ.SctpConnection. + * The name of the 'endpoint' element. */ - public static final String PORT_ATTR_NAME = "port"; + public static final String ELEMENT_NAME = "endpoint"; /** - * SCTP port attribute. 5000 by default. + * The name of the 'id' attribute. */ - private int port = 5000; + public static final String ID_ATTR_NAME = "id"; /** - * Initializes a new SctpConnection instance without an - * endpoint name and with default port value set. + * The 'display name' of this Endpoint. */ - public SctpConnection() - { - super(SctpConnection.ELEMENT_NAME); - } + private String displayName; /** - * Gets the SCTP port of the SctpConnection described by this - * instance. - * - * @return the SCTP port of the SctpConnection represented by - * this instance. + * The 'id' of this Endpoint. */ - public int getPort() + private String id; + + /** + * Initializes a new Endpoint with the given ID and display + * name. + * @param id the ID. + * @param displayName the display name. + */ + public Endpoint(String id, String displayName) { - return port; + this.id = id; + this.displayName = displayName; } /** - * Sets the SCTP port of the SctpConnection represented by this - * instance. - * - * @param port the SCTP port of the SctpConnection - * represented by this instance + * Returns the display name of this Endpoint. + * @return the display name of this Endpoint. */ - public void setPort(int port) + public String getDisplayName() { - this.port = port; + return displayName; } /** - * {@inheritDoc} + * Returns the ID of this Endpoint. + * @return the ID of this Endpoint. */ - @Override - protected void printAttributes(StringBuilder xml) + public String getId() { - xml.append(' ').append(PORT_ATTR_NAME).append("='") - .append(getPort()).append('\''); + return id; } /** - * {@inheritDoc} - * - * No content other than transport for SctpConnection. + * Sets the display name of this Endpoint. + * @param displayName the display name to set. */ - @Override - protected boolean hasContent() + public void setDisplayName(String displayName) { - return false; + this.displayName = displayName; } - @Override - protected void printContent(StringBuilder xml) + /** + * Sets the ID of this Endpoint. + * @param id the ID to set. + */ + public void setId(String id) { - // No other content than the transport shared from ChannelCommon + this.id = id; } } @@ -1764,6 +1861,11 @@ public static class Recording */ public static final String ELEMENT_NAME = "recording"; + /** + * The XML name of the path attribute. + */ + public static final String PATH_ATTR_NAME = "path"; + /** * The XML name of the state attribute. */ @@ -1774,14 +1876,11 @@ public static class Recording */ public static final String TOKEN_ATTR_NAME = "token"; - /** - * The XML name of the path attribute. - */ - public static final String PATH_ATTR_NAME = "path"; + private String path; - private String token = null; private boolean state; - private String path = null; + + private String token; public Recording(boolean state) { @@ -1795,24 +1894,24 @@ public Recording(boolean state, String token) this.token = token; } - public String getToken() + public String getPath() { - return token; + return path; } - public String getPath() + public boolean getState() { - return path; + return state; } - public void setPath(String path) + public String getToken() { - this.path = path; + return token; } - public boolean getState() + public void setPath(String path) { - return state; + this.path = path; } public void toXML(StringBuilder xml) @@ -1834,82 +1933,119 @@ public void toXML(StringBuilder xml) } } + public static class RTCPTerminationStrategy + { + public static final String ELEMENT_NAME = "rtcp-termination-strategy"; + + public static final String NAME_ATTR_NAME = "name"; + + private String name; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public void toXML(StringBuilder xml) + { + xml.append('<').append(ELEMENT_NAME); + xml.append(' ').append(NAME_ATTR_NAME).append("='") + .append(name).append('\''); + xml.append("/>"); + } + } + /** - * Represents an 'endpoint' element. + * Represents a SCTP connection included into a content + * of a Jitsi Videobridge conference IQ. + * + * @author Pawel Domas */ - public static class Endpoint + public static class SctpConnection + extends ChannelCommon { /** - * The name of the 'endpoint' element. - */ - public static final String ELEMENT_NAME = "endpoint"; - - /** - * The name of the 'id' attribute. + * The XML element name of a content of a Jitsi Videobridge + * conference IQ. */ - public static final String ID_ATTR_NAME = "id"; + public static final String ELEMENT_NAME = "sctpconnection"; /** - * The name of the 'displayname' attribute. + * The XML name of the port attribute of a + * SctpConnection of a conference IQ which represents + * the SCTP port property of + * ColibriConferenceIQ.SctpConnection. */ - public static final String DISPLAYNAME_ATTR_NAME = "displayname"; + public static final String PORT_ATTR_NAME = "port"; /** - * The 'id' of this Endpoint. + * SCTP port attribute. 5000 by default. */ - private String id; + private int port = 5000; /** - * The 'display name' of this Endpoint. + * Initializes a new SctpConnection instance without an + * endpoint name and with default port value set. */ - private String displayName; + public SctpConnection() + { + super(SctpConnection.ELEMENT_NAME); + } /** - * Initializes a new Endpoint with the given ID and display - * name. - * @param id the ID. - * @param displayName the display name. + * Gets the SCTP port of the SctpConnection described by this + * instance. + * + * @return the SCTP port of the SctpConnection represented by + * this instance. */ - public Endpoint(String id, String displayName) + public int getPort() { - this.id = id; - this.displayName = displayName; + return port; } /** - * Sets the ID of this Endpoint. - * @param id the ID to set. + * {@inheritDoc} + * + * No content other than transport for SctpConnection. */ - public void setId(String id) + @Override + protected boolean hasContent() { - this.id = id; + return false; } /** - * Returns the ID of this Endpoint. - * @return the ID of this Endpoint. + * {@inheritDoc} */ - public String getId() + @Override + protected void printAttributes(StringBuilder xml) { - return id; + xml.append(' ').append(PORT_ATTR_NAME).append("='") + .append(getPort()).append('\''); } - /** - * Sets the display name of this Endpoint. - * @param displayName the display name to set. - */ - public void setDisplayName(String displayName) + @Override + protected void printContent(StringBuilder xml) { - this.displayName = displayName; + // No other content than the transport shared from ChannelCommon } /** - * Returns the display name of this Endpoint. - * @return the display name of this Endpoint. + * Sets the SCTP port of the SctpConnection represented by this + * instance. + * + * @param port the SCTP port of the SctpConnection + * represented by this instance */ - public String getDisplayName() + public void setPort(int port) { - return displayName; + this.port = port; } } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java index 8ce696733..0e2acebfb 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java @@ -40,6 +40,11 @@ public ColibriIQProvider() SourcePacketExtension.NAMESPACE, new DefaultPacketExtensionProvider( SourcePacketExtension.class)); + providerManager.addExtensionProvider( + SourceGroupPacketExtension.ELEMENT_NAME, + SourceGroupPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider( + SourceGroupPacketExtension.class)); PacketExtensionProvider parameterProvider = new DefaultPacketExtensionProvider( @@ -84,6 +89,13 @@ else if (childExtension instanceof IceUdpTransportPacketExtension) channel.setTransport(transport); } + else if (childExtension instanceof SourceGroupPacketExtension) + { + SourceGroupPacketExtension sourceGroup + = (SourceGroupPacketExtension)childExtension; + + channel.addSourceGroup(sourceGroup); + } } private void addChildExtension( @@ -187,7 +199,9 @@ else if (ColibriConferenceIQ.Channel.ELEMENT_NAME.equals( else if (ColibriConferenceIQ.SctpConnection.ELEMENT_NAME .equals(name)) { - content.addSctpConnection(sctpConnection); + if (sctpConnection != null) + content.addSctpConnection(sctpConnection); + sctpConnection = null; } else if (ColibriConferenceIQ.Endpoint.ELEMENT_NAME @@ -327,6 +341,18 @@ else if (ColibriConferenceIQ.Recording.ELEMENT_NAME.equals( if ((lastN != null) && (lastN.length() != 0)) channel.setLastN(Integer.parseInt(lastN)); + // receiving simulcast layer + String receivingSimulcastLayer + = parser.getAttributeValue( + "", + ColibriConferenceIQ.Channel + .RECEIVING_SIMULCAST_LAYER); + + if ((receivingSimulcastLayer != null) + && (receivingSimulcastLayer.length() != 0)) + channel.setReceivingSimulcastLayer( + Integer.parseInt(receivingSimulcastLayer)); + // rtcpPort String rtcpPort = parser.getAttributeValue( @@ -426,20 +452,28 @@ else if (ColibriConferenceIQ.SctpConnection.ELEMENT_NAME ColibriConferenceIQ. SctpConnection.ENDPOINT_ATTR_NAME); - if(!StringUtils.isNullOrEmpty(endpoint)) - { - sctpConnection - = new ColibriConferenceIQ.SctpConnection(); - } - else + // id + String connID + = parser.getAttributeValue( + "", + ColibriConferenceIQ. + ChannelCommon.ID_ATTR_NAME); + + if(StringUtils.isNullOrEmpty(connID) + && StringUtils.isNullOrEmpty(endpoint)) { - throw new RuntimeException( - "Endpoint is mandatory attribute" - + " for SCTP connection" - ); + sctpConnection = null; + continue; } - sctpConnection.setEndpoint(endpoint); + sctpConnection + = new ColibriConferenceIQ.SctpConnection(); + + if (!StringUtils.isNullOrEmpty(connID)) + sctpConnection.setID(connID); + + if (!StringUtils.isNullOrEmpty(endpoint)) + sctpConnection.setEndpoint(endpoint); // port String port @@ -538,7 +572,14 @@ else if (SourcePacketExtension.ELEMENT_NAME.equals(name) peName = name; peNamespace = SourcePacketExtension.NAMESPACE; } - + else if (SourceGroupPacketExtension.ELEMENT_NAME + .equals(name) + && SourceGroupPacketExtension.NAMESPACE + .equals(parser.getNamespace())) + { + peName = name; + peNamespace = SourceGroupPacketExtension.NAMESPACE; + } if (peName == null) { throwAway(parser, name); diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java index bc46b8f3e..a0d4e45f7 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java @@ -157,6 +157,13 @@ public JingleIQProvider() new DefaultPacketExtensionProvider( CallIdPacketExtension.class)); + //rtcp-fb + providerManager.addExtensionProvider( + RtcpFbPacketExtension.ELEMENT_NAME, + RtcpFbPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider( + RtcpFbPacketExtension.class)); + //rtcp-mux providerManager.addExtensionProvider( RtcpmuxPacketExtension.ELEMENT_NAME, diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/PayloadTypePacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/PayloadTypePacketExtension.java index 1dbcceb20..03746672a 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/PayloadTypePacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/PayloadTypePacketExtension.java @@ -217,4 +217,27 @@ public List getParameters() { return getChildExtensionsOfType(ParameterPacketExtension.class); } + + /** + * Adds an RTCP feedback type to the list that we already have registered + * for this payload type. + * + * @param rtcpFbPacketExtension RTCP feedback type for this encoding. + */ + public void addRtcpFeedbackType(RtcpFbPacketExtension rtcpFbPacketExtension) + { + addChildExtension(rtcpFbPacketExtension); + } + + /** + * Returns the list of RTCP feedback types currently registered for this + * payload type. + * + * @return the list of RTCP feedback types currently registered for this + * payload type. + */ + public List getRtcpFeedbackTypeList() + { + return getChildExtensionsOfType(RtcpFbPacketExtension.class); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpFbPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpFbPacketExtension.java new file mode 100644 index 000000000..c807f055a --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpFbPacketExtension.java @@ -0,0 +1,89 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +/** + * Packet extension that holds RTCP feedback types of the + * {@link PayloadTypePacketExtension}. Defined in XEP-0293. + * + * @author Pawel Domas + */ +public class RtcpFbPacketExtension + extends AbstractPacketExtension +{ + /** + * The name space for RTP feedback elements. + */ + public static final String NAMESPACE = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0"; + + /** + * The name of the RTCP feedback element. + */ + public static final String ELEMENT_NAME = "rtcp-fb"; + + /** + * The name the attribute that holds the feedback type. + */ + public static final String TYPE_ATTR_NAME = "type"; + + /** + * The name the attribute that holds the feedback subtype. + */ + public static final String SUBTYPE_ATTR_NAME = "subtype"; + + /** + * Creates new empty instance of RtcpFbPacketExtension. + */ + public RtcpFbPacketExtension() + { + super(NAMESPACE, ELEMENT_NAME); + } + + /** + * Sets RTCP feedback type attribute. + * @param feedbackType the RTCP feedback type to set. + */ + public void setFeedbackType(String feedbackType) + { + setAttribute(TYPE_ATTR_NAME, feedbackType); + } + + /** + * Returns RTCP feedback type attribute value if already set + * or null otherwise. + * + * @return RTCP feedback type attribute if already set or null + * otherwise. + */ + public String getFeedbackType() + { + return getAttributeAsString(TYPE_ATTR_NAME); + } + + /** + * Sets RTCP feedback subtype attribute. + * @param feedbackSubType the RTCP feedback subtype to set. + */ + public void setFeedbackSubtype(String feedbackSubType) + { + setAttribute(SUBTYPE_ATTR_NAME, feedbackSubType); + } + + /** + * Returns RTCP feedback subtype attribute value if already set + * or null otherwise. + * + * @return RTCP feedback subtype attribute if already set or null + * otherwise. + */ + public String getFeedbackSubtype() + { + return getAttributeAsString(SUBTYPE_ATTR_NAME); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/SourceGroupPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/SourceGroupPacketExtension.java new file mode 100644 index 000000000..9b540025a --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/SourceGroupPacketExtension.java @@ -0,0 +1,89 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*; + +import java.util.*; + +/** + * Represents ssrc-group elements described in XEP-0339. + * + * Created by gp on 07/08/14. + */ +public class SourceGroupPacketExtension + extends AbstractPacketExtension +{ + + /** + * The name of the "ssrc-group" element. + */ + public static final String ELEMENT_NAME = "ssrc-group"; + + /** + * The namespace for the "ssrc-group" element. + */ + public static final String NAMESPACE = "urn:xmpp:jingle:apps:rtp:ssma:0"; + + /** + * The name of the payload id SDP argument. + */ + public static final String SEMANTICS_ATTR_NAME = "semantics"; + + /** + * Creates a new {@link SourceGroupPacketExtension} instance with the proper + * element name and namespace. + */ + public SourceGroupPacketExtension() + { + super(NAMESPACE, ELEMENT_NAME); + } + + /** + * Gets the semantics of this source group. + * + * @return the semantics of this source group. + */ + public String getSemantics() + { + return getAttributeAsString(SEMANTICS_ATTR_NAME); + } + + /** + * Sets the semantics of this source group. + */ + public void setSemantics(String semantics) + { + this.setAttribute(SEMANTICS_ATTR_NAME, semantics); + } + + /** + * Gets the sources of this source group. + * + * @return the sources of this source group. + */ + public List getSources() + { + return getChildExtensionsOfType(SourcePacketExtension.class); + } + + /** + * Sets the sources of this source group. + * + * @param sources the sources of this source group. + */ + public void addSources(List sources) + { + if (sources != null && sources.size() != 0) + { + for (SourcePacketExtension source : sources) + this.addChildExtension(source); + } + + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java index 5aa1b0fbe..89feea774 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java @@ -26,6 +26,20 @@ public class UriHandlerSipImpl implements UriHandler, ServiceListener, AccountManagerListener { + /** + * Property to set the amount of time to wait for SIP registration + * to complete before trying to dial a URI from the command line. + * (value in milliseconds). + */ + public static final String INITIAL_REGISTRATION_TIMEOUT_PROP + = "net.java.sip.communicator.impl.protocol.sip.call.INITIAL_REGISTRATION_TIMEOUT"; + + /** + * Default value for INITIAL_REGISTRATION_TIMEOUT (milliseconds) + */ + public static final long DEFAULT_INITIAL_REGISTRATION_TIMEOUT + = 5000; + /** * The Logger used by the UriHandlerSipImpl class and its * instances for logging output. @@ -254,12 +268,11 @@ public String getProtocol() } /** - * Parses the specified URI and creates a call with the currently active - * telephony operation set. + * Parses the specified URI and tries to create a call when online. * * @param uri the SIP URI that we have to call. */ - public void handleUri(String uri) + public void handleUri(final String uri) { /* * TODO If the requirement to register the factory service after @@ -278,7 +291,7 @@ public void handleUri(String uri) } } - ProtocolProviderService provider; + final ProtocolProviderService provider; try { provider = selectHandlingProvider(uri); @@ -301,6 +314,73 @@ public void handleUri(String uri) return; } + if(provider.getRegistrationState() == RegistrationState.REGISTERED) + { + handleUri(uri, provider); + } + else + { + // Allow a grace period for the provider to register in case + // we have just started up + long initialRegistrationTimeout = + SipActivator.getConfigurationService() + .getLong(INITIAL_REGISTRATION_TIMEOUT_PROP, + DEFAULT_INITIAL_REGISTRATION_TIMEOUT); + final DelayRegistrationStateChangeListener listener = + new DelayRegistrationStateChangeListener(uri, provider); + provider.addRegistrationStateChangeListener(listener); + new Timer().schedule(new TimerTask() + { + @Override + public void run() + { + provider.removeRegistrationStateChangeListener(listener); + // Even if not registered after the timeout, try the call + // anyway and the error popup will appear to ask the + // user if they want to register + handleUri(uri, provider); + } + }, initialRegistrationTimeout); + } + } + + /** + * Listener on provider state changes that handles the passed URI if the + * provider becomes registered. + */ + private class DelayRegistrationStateChangeListener + implements RegistrationStateChangeListener + { + private String uri; + private ProtocolProviderService provider; + private boolean handled = false; + + public DelayRegistrationStateChangeListener(String uri, + ProtocolProviderService provider) + { + this.uri = uri; + this.provider = provider; + } + + @Override + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + if (evt.getNewState() == RegistrationState.REGISTERED && !handled) + { + provider.removeRegistrationStateChangeListener(this); + handled = true; + handleUri(uri, provider); + } + } + } + + /** + * Creates a call with the currently active telephony operation set. + * + * @param uri the SIP URI that we have to call. + */ + protected void handleUri(String uri, ProtocolProviderService provider) + { //handle "sip://" URIs as "sip:" if(uri != null) uri = uri.replace("sip://", "sip:"); diff --git a/src/net/java/sip/communicator/service/muc/MUCService.java b/src/net/java/sip/communicator/service/muc/MUCService.java index 8b5b9dae0..1fb75bf0f 100644 --- a/src/net/java/sip/communicator/service/muc/MUCService.java +++ b/src/net/java/sip/communicator/service/muc/MUCService.java @@ -26,6 +26,11 @@ public abstract class MUCService public static final String DISABLED_PROPERTY = "net.java.sip.communicator.impl.muc.MUC_SERVICE_DISABLED"; + /** + * Key for auto-open configuration entry. + */ + private static String AUTO_OPEN_CONFIG_KEY = "openAutomatically"; + /** * The value for chat room configuration property to open automatically on * activity @@ -44,6 +49,11 @@ public abstract class MUCService */ public static String OPEN_ON_IMPORTANT_MESSAGE = "on_important_message"; + /** + * The default for chat room auto-open behaviour. + */ + public static String DEFAULT_AUTO_OPEN_BEHAVIOUR = OPEN_ON_MESSAGE; + /** * Map for the auto open configuration values and their text representation */ @@ -73,7 +83,7 @@ public static void setChatRoomAutoOpenOption( { ConfigurationUtils.updateChatRoomProperty( pps, - chatRoomId, "openAutomatically", value); + chatRoomId, AUTO_OPEN_CONFIG_KEY, value); } /** @@ -88,7 +98,7 @@ public static String getChatRoomAutoOpenOption( { return ConfigurationUtils.getChatRoomProperty( pps, - chatRoomId, "openAutomatically"); + chatRoomId, AUTO_OPEN_CONFIG_KEY); } /**