001/** 002 * 003 * Copyright © 2014-2021 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.muc; 018 019import java.lang.ref.WeakReference; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027import java.util.WeakHashMap; 028import java.util.concurrent.CopyOnWriteArrayList; 029import java.util.concurrent.CopyOnWriteArraySet; 030import java.util.logging.Level; 031import java.util.logging.Logger; 032 033import org.jivesoftware.smack.ConnectionCreationListener; 034import org.jivesoftware.smack.ConnectionListener; 035import org.jivesoftware.smack.Manager; 036import org.jivesoftware.smack.SmackException.NoResponseException; 037import org.jivesoftware.smack.SmackException.NotConnectedException; 038import org.jivesoftware.smack.StanzaListener; 039import org.jivesoftware.smack.XMPPConnection; 040import org.jivesoftware.smack.XMPPConnectionRegistry; 041import org.jivesoftware.smack.XMPPException.XMPPErrorException; 042import org.jivesoftware.smack.filter.AndFilter; 043import org.jivesoftware.smack.filter.ExtensionElementFilter; 044import org.jivesoftware.smack.filter.MessageTypeFilter; 045import org.jivesoftware.smack.filter.NotFilter; 046import org.jivesoftware.smack.filter.StanzaExtensionFilter; 047import org.jivesoftware.smack.filter.StanzaFilter; 048import org.jivesoftware.smack.filter.StanzaTypeFilter; 049import org.jivesoftware.smack.packet.Message; 050import org.jivesoftware.smack.packet.MessageBuilder; 051import org.jivesoftware.smack.packet.Stanza; 052import org.jivesoftware.smack.util.Async; 053import org.jivesoftware.smack.util.CleaningWeakReferenceMap; 054 055import org.jivesoftware.smackx.disco.AbstractNodeInformationProvider; 056import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 057import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 058import org.jivesoftware.smackx.disco.packet.DiscoverItems; 059import org.jivesoftware.smackx.muc.MultiUserChatException.MucNotJoinedException; 060import org.jivesoftware.smackx.muc.MultiUserChatException.NotAMucServiceException; 061import org.jivesoftware.smackx.muc.packet.GroupChatInvitation; 062import org.jivesoftware.smackx.muc.packet.MUCInitialPresence; 063import org.jivesoftware.smackx.muc.packet.MUCUser; 064 065import org.jxmpp.jid.DomainBareJid; 066import org.jxmpp.jid.EntityBareJid; 067import org.jxmpp.jid.EntityFullJid; 068import org.jxmpp.jid.EntityJid; 069import org.jxmpp.jid.Jid; 070import org.jxmpp.jid.parts.Resourcepart; 071import org.jxmpp.util.cache.ExpirationCache; 072 073/** 074 * A manager for Multi-User Chat rooms. 075 * <p> 076 * Use {@link #getMultiUserChat(EntityBareJid)} to retrieve an object representing a Multi-User Chat room. 077 * </p> 078 * <p> 079 * <b>Automatic rejoin:</b> The manager supports automatic rejoin of MultiUserChat rooms once the connection got 080 * re-established. This mechanism is disabled by default. To enable it, use {@link #setAutoJoinOnReconnect(boolean)}. 081 * You can set a {@link AutoJoinFailedCallback} via {@link #setAutoJoinFailedCallback(AutoJoinFailedCallback)} to get 082 * notified if this mechanism failed for some reason. Note that as soon as rejoining for a single room failed, no 083 * further attempts will be made for the other rooms. 084 * </p> 085 * 086 * Note: 087 * For inviting other users to a group chat or listening for such invitations, take a look at the 088 * {@link DirectMucInvitationManager} which provides an implementation of XEP-0249: Direct MUC Invitations. 089 * 090 * @see <a href="http://xmpp.org/extensions/xep-0045.html">XEP-0045: Multi-User Chat</a> 091 */ 092public final class MultiUserChatManager extends Manager { 093 private static final String DISCO_NODE = MUCInitialPresence.NAMESPACE + "#rooms"; 094 095 private static final Logger LOGGER = Logger.getLogger(MultiUserChatManager.class.getName()); 096 097 static { 098 XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { 099 @Override 100 public void connectionCreated(final XMPPConnection connection) { 101 // Set on every established connection that this client supports the Multi-User 102 // Chat protocol. This information will be used when another client tries to 103 // discover whether this client supports MUC or not. 104 ServiceDiscoveryManager.getInstanceFor(connection).addFeature(MUCInitialPresence.NAMESPACE); 105 106 // Set the NodeInformationProvider that will provide information about the 107 // joined rooms whenever a disco request is received 108 final WeakReference<XMPPConnection> weakRefConnection = new WeakReference<XMPPConnection>(connection); 109 ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(DISCO_NODE, 110 new AbstractNodeInformationProvider() { 111 @Override 112 public List<DiscoverItems.Item> getNodeItems() { 113 XMPPConnection connection = weakRefConnection.get(); 114 if (connection == null) 115 return Collections.emptyList(); 116 Set<EntityBareJid> joinedRooms = MultiUserChatManager.getInstanceFor(connection).getJoinedRooms(); 117 List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>(); 118 for (EntityBareJid room : joinedRooms) { 119 answer.add(new DiscoverItems.Item(room)); 120 } 121 return answer; 122 } 123 }); 124 } 125 }); 126 } 127 128 private static final Map<XMPPConnection, MultiUserChatManager> INSTANCES = new WeakHashMap<XMPPConnection, MultiUserChatManager>(); 129 130 /** 131 * Get a instance of a multi user chat manager for the given connection. 132 * 133 * @param connection TODO javadoc me please 134 * @return a multi user chat manager. 135 */ 136 public static synchronized MultiUserChatManager getInstanceFor(XMPPConnection connection) { 137 MultiUserChatManager multiUserChatManager = INSTANCES.get(connection); 138 if (multiUserChatManager == null) { 139 multiUserChatManager = new MultiUserChatManager(connection); 140 INSTANCES.put(connection, multiUserChatManager); 141 } 142 return multiUserChatManager; 143 } 144 145 private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()), 146 new NotFilter(MessageTypeFilter.ERROR)); 147 148 private static final StanzaFilter DIRECT_INVITATION_FILTER = 149 new AndFilter(StanzaTypeFilter.MESSAGE, 150 new ExtensionElementFilter<GroupChatInvitation>(GroupChatInvitation.class), 151 NotFilter.of(MUCUser.class), 152 new NotFilter(MessageTypeFilter.ERROR)); 153 154 private static final ExpirationCache<DomainBareJid, DiscoverInfo> KNOWN_MUC_SERVICES = new ExpirationCache<>( 155 100, 1000 * 60 * 60 * 24); 156 157 private static final Set<MucMessageInterceptor> DEFAULT_MESSAGE_INTERCEPTORS = new HashSet<>(); 158 159 private final Set<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>(); 160 161 /** 162 * The XMPP addresses of currently joined rooms. 163 */ 164 private final Set<EntityBareJid> joinedRooms = new CopyOnWriteArraySet<>(); 165 166 /** 167 * A Map of MUC JIDs to {@link MultiUserChat} instances. We use weak references for the values in order to allow 168 * those instances to get garbage collected. Note that MultiUserChat instances can not get garbage collected while 169 * the user is joined, because then the MUC will have PacketListeners added to the XMPPConnection. 170 */ 171 private final Map<EntityBareJid, WeakReference<MultiUserChat>> multiUserChats = new CleaningWeakReferenceMap<>(); 172 173 private boolean autoJoinOnReconnect; 174 175 private AutoJoinFailedCallback autoJoinFailedCallback; 176 177 private AutoJoinSuccessCallback autoJoinSuccessCallback; 178 179 private final ServiceDiscoveryManager serviceDiscoveryManager; 180 181 private MultiUserChatManager(XMPPConnection connection) { 182 super(connection); 183 serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); 184 // Listens for all messages that include a MUCUser extension and fire the invitation 185 // listeners if the message includes an invitation. 186 StanzaListener invitationPacketListener = new StanzaListener() { 187 @Override 188 public void processStanza(Stanza packet) { 189 final Message message = (Message) packet; 190 // Get the MUCUser extension 191 final MUCUser mucUser = MUCUser.from(message); 192 // Check if the MUCUser extension includes an invitation 193 if (mucUser.getInvite() != null) { 194 EntityBareJid mucJid = message.getFrom().asEntityBareJidIfPossible(); 195 if (mucJid == null) { 196 LOGGER.warning("Invite to non bare JID: '" + message.toXML() + "'"); 197 return; 198 } 199 // Fire event for invitation listeners 200 final MultiUserChat muc = getMultiUserChat(mucJid); 201 final XMPPConnection connection = connection(); 202 final MUCUser.Invite invite = mucUser.getInvite(); 203 final EntityJid from = invite.getFrom(); 204 final String reason = invite.getReason(); 205 final String password = mucUser.getPassword(); 206 for (final InvitationListener listener : invitationsListeners) { 207 listener.invitationReceived(connection, muc, from, reason, password, message, invite); 208 } 209 } 210 } 211 }; 212 connection.addAsyncStanzaListener(invitationPacketListener, INVITATION_FILTER); 213 214 // Listens for all messages that include an XEP-0249 GroupChatInvitation extension and fire the invitation 215 // listeners 216 StanzaListener directInvitationStanzaListener = new StanzaListener() { 217 @Override 218 public void processStanza(Stanza stanza) { 219 final Message message = (Message) stanza; 220 GroupChatInvitation invite = 221 stanza.getExtension(GroupChatInvitation.class); 222 223 // Fire event for invitation listeners 224 final MultiUserChat muc = getMultiUserChat(invite.getRoomAddress()); 225 final XMPPConnection connection = connection(); 226 final EntityJid from = message.getFrom().asEntityJidIfPossible(); 227 if (from == null) { 228 LOGGER.warning("Group Chat Invitation from non entity JID in '" + message + "'"); 229 return; 230 } 231 final String reason = invite.getReason(); 232 final String password = invite.getPassword(); 233 final MUCUser.Invite mucInvite = new MUCUser.Invite(reason, from, connection.getUser().asEntityBareJid()); 234 for (final InvitationListener listener : invitationsListeners) { 235 listener.invitationReceived(connection, muc, from, reason, password, message, mucInvite); 236 } 237 } 238 }; 239 connection.addAsyncStanzaListener(directInvitationStanzaListener, DIRECT_INVITATION_FILTER); 240 241 connection.addConnectionListener(new ConnectionListener() { 242 @Override 243 public void authenticated(XMPPConnection connection, boolean resumed) { 244 if (resumed) return; 245 if (!autoJoinOnReconnect) return; 246 247 final Set<EntityBareJid> mucs = getJoinedRooms(); 248 if (mucs.isEmpty()) return; 249 250 Async.go(new Runnable() { 251 @Override 252 public void run() { 253 final AutoJoinFailedCallback failedCallback = autoJoinFailedCallback; 254 final AutoJoinSuccessCallback successCallback = autoJoinSuccessCallback; 255 for (EntityBareJid mucJid : mucs) { 256 MultiUserChat muc = getMultiUserChat(mucJid); 257 258 if (!muc.isJoined()) return; 259 260 Resourcepart nickname = muc.getNickname(); 261 if (nickname == null) return; 262 263 try { 264 muc.leave(); 265 } catch (NotConnectedException | InterruptedException | MucNotJoinedException 266 | NoResponseException | XMPPErrorException e) { 267 if (failedCallback != null) { 268 failedCallback.autoJoinFailed(muc, e); 269 } else { 270 LOGGER.log(Level.WARNING, "Could not leave room", e); 271 } 272 return; 273 } 274 try { 275 muc.join(nickname); 276 if (successCallback != null) { 277 successCallback.autoJoinSuccess(muc, nickname); 278 } 279 } catch (NotAMucServiceException | NoResponseException | XMPPErrorException 280 | NotConnectedException | InterruptedException e) { 281 if (failedCallback != null) { 282 failedCallback.autoJoinFailed(muc, e); 283 } else { 284 LOGGER.log(Level.WARNING, "Could not leave room", e); 285 } 286 return; 287 } 288 } 289 } 290 291 }); 292 } 293 }); 294 } 295 296 /** 297 * Creates a multi user chat. Note: no information is sent to or received from the server until you attempt to 298 * {@link MultiUserChat#join(org.jxmpp.jid.parts.Resourcepart) join} the chat room. On some server implementations, the room will not be 299 * created until the first person joins it. 300 * <p> 301 * Most XMPP servers use a sub-domain for the chat service (eg chat.example.com for the XMPP server example.com). 302 * You must ensure that the room address you're trying to connect to includes the proper chat sub-domain. 303 * </p> 304 * 305 * @param jid the name of the room in the form "roomName@service", where "service" is the hostname at which the 306 * multi-user chat service is running. Make sure to provide a valid JID. 307 * @return MultiUserChat instance of the room with the given jid. 308 */ 309 public synchronized MultiUserChat getMultiUserChat(EntityBareJid jid) { 310 WeakReference<MultiUserChat> weakRefMultiUserChat = multiUserChats.get(jid); 311 if (weakRefMultiUserChat == null) { 312 return createNewMucAndAddToMap(jid); 313 } 314 MultiUserChat multiUserChat = weakRefMultiUserChat.get(); 315 if (multiUserChat == null) { 316 return createNewMucAndAddToMap(jid); 317 } 318 return multiUserChat; 319 } 320 321 public static boolean addDefaultMessageInterceptor(MucMessageInterceptor messageInterceptor) { 322 synchronized (DEFAULT_MESSAGE_INTERCEPTORS) { 323 return DEFAULT_MESSAGE_INTERCEPTORS.add(messageInterceptor); 324 } 325 } 326 327 public static boolean removeDefaultMessageInterceptor(MucMessageInterceptor messageInterceptor) { 328 synchronized (DEFAULT_MESSAGE_INTERCEPTORS) { 329 return DEFAULT_MESSAGE_INTERCEPTORS.remove(messageInterceptor); 330 } 331 } 332 333 private MultiUserChat createNewMucAndAddToMap(EntityBareJid jid) { 334 MultiUserChat multiUserChat = new MultiUserChat(connection(), jid, this); 335 multiUserChats.put(jid, new WeakReference<MultiUserChat>(multiUserChat)); 336 return multiUserChat; 337 } 338 339 /** 340 * Returns true if the specified user supports the Multi-User Chat protocol. 341 * 342 * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. 343 * @return a boolean indicating whether the specified user supports the MUC protocol. 344 * @throws XMPPErrorException if there was an XMPP error returned. 345 * @throws NoResponseException if there was no response from the remote entity. 346 * @throws NotConnectedException if the XMPP connection is not connected. 347 * @throws InterruptedException if the calling thread was interrupted. 348 */ 349 public boolean isServiceEnabled(Jid user) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 350 return serviceDiscoveryManager.supportsFeature(user, MUCInitialPresence.NAMESPACE); 351 } 352 353 /** 354 * Returns a Set of the rooms where the user has joined. The Iterator will contain Strings where each String 355 * represents a room (e.g. room@muc.jabber.org). 356 * 357 * Note: In order to get a list of bookmarked (but not necessarily joined) conferences, use 358 * {@link org.jivesoftware.smackx.bookmarks.BookmarkManager#getBookmarkedConferences()}. 359 * 360 * @return a List of the rooms where the user has joined using a given connection. 361 */ 362 public Set<EntityBareJid> getJoinedRooms() { 363 return Collections.unmodifiableSet(joinedRooms); 364 } 365 366 /** 367 * Returns a List of the rooms where the requested user has joined. The Iterator will contain Strings where each 368 * String represents a room (e.g. room@muc.jabber.org). 369 * 370 * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. 371 * @return a List of the rooms where the requested user has joined. 372 * @throws XMPPErrorException if there was an XMPP error returned. 373 * @throws NoResponseException if there was no response from the remote entity. 374 * @throws NotConnectedException if the XMPP connection is not connected. 375 * @throws InterruptedException if the calling thread was interrupted. 376 */ 377 public List<EntityBareJid> getJoinedRooms(EntityFullJid user) throws NoResponseException, XMPPErrorException, 378 NotConnectedException, InterruptedException { 379 // Send the disco packet to the user 380 DiscoverItems result = serviceDiscoveryManager.discoverItems(user, DISCO_NODE); 381 List<DiscoverItems.Item> items = result.getItems(); 382 List<EntityBareJid> answer = new ArrayList<>(items.size()); 383 // Collect the entityID for each returned item 384 for (DiscoverItems.Item item : items) { 385 EntityBareJid muc = item.getEntityID().asEntityBareJidIfPossible(); 386 if (muc == null) { 387 LOGGER.warning("Not a bare JID: " + item.getEntityID()); 388 continue; 389 } 390 answer.add(muc); 391 } 392 return answer; 393 } 394 395 /** 396 * Returns the discovered information of a given room without actually having to join the room. The server will 397 * provide information only for rooms that are public. 398 * 399 * @param room the name of the room in the form "roomName@service" of which we want to discover its information. 400 * @return the discovered information of a given room without actually having to join the room. 401 * @throws XMPPErrorException if there was an XMPP error returned. 402 * @throws NoResponseException if there was no response from the remote entity. 403 * @throws NotConnectedException if the XMPP connection is not connected. 404 * @throws InterruptedException if the calling thread was interrupted. 405 */ 406 public RoomInfo getRoomInfo(EntityBareJid room) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 407 DiscoverInfo info = serviceDiscoveryManager.discoverInfo(room); 408 return new RoomInfo(info); 409 } 410 411 /** 412 * Returns a collection with the XMPP addresses of the Multi-User Chat services. 413 * 414 * @return a collection with the XMPP addresses of the Multi-User Chat services. 415 * @throws XMPPErrorException if there was an XMPP error returned. 416 * @throws NoResponseException if there was no response from the remote entity. 417 * @throws NotConnectedException if the XMPP connection is not connected. 418 * @throws InterruptedException if the calling thread was interrupted. 419 */ 420 public List<DomainBareJid> getMucServiceDomains() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 421 return serviceDiscoveryManager.findServices(MUCInitialPresence.NAMESPACE, false, false); 422 } 423 424 /** 425 * Returns a collection with the XMPP addresses of the Multi-User Chat services. 426 * 427 * @return a collection with the XMPP addresses of the Multi-User Chat services. 428 * @throws XMPPErrorException if there was an XMPP error returned. 429 * @throws NoResponseException if there was no response from the remote entity. 430 * @throws NotConnectedException if the XMPP connection is not connected. 431 * @throws InterruptedException if the calling thread was interrupted. 432 * @deprecated use {@link #getMucServiceDomains()} instead. 433 */ 434 // TODO: Remove in Smack 4.5 435 @Deprecated 436 public List<DomainBareJid> getXMPPServiceDomains() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 437 return getMucServiceDomains(); 438 } 439 440 /** 441 * Check if the provided domain bare JID provides a MUC service. 442 * 443 * @param domainBareJid the domain bare JID to check. 444 * @return <code>true</code> if the provided JID provides a MUC service, <code>false</code> otherwise. 445 * @throws NoResponseException if there was no response from the remote entity. 446 * @throws XMPPErrorException if there was an XMPP error returned. 447 * @throws NotConnectedException if the XMPP connection is not connected. 448 * @throws InterruptedException if the calling thread was interrupted. 449 * @see <a href="http://xmpp.org/extensions/xep-0045.html#disco-service-features">XEP-45 § 6.2 Discovering the Features Supported by a MUC Service</a> 450 * @since 4.2 451 */ 452 public boolean providesMucService(DomainBareJid domainBareJid) throws NoResponseException, 453 XMPPErrorException, NotConnectedException, InterruptedException { 454 return getMucServiceDiscoInfo(domainBareJid) != null; 455 } 456 457 DiscoverInfo getMucServiceDiscoInfo(DomainBareJid mucServiceAddress) 458 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 459 DiscoverInfo discoInfo = KNOWN_MUC_SERVICES.get(mucServiceAddress); 460 if (discoInfo != null) { 461 return discoInfo; 462 } 463 464 discoInfo = serviceDiscoveryManager.discoverInfo(mucServiceAddress); 465 if (!discoInfo.containsFeature(MUCInitialPresence.NAMESPACE)) { 466 return null; 467 } 468 469 KNOWN_MUC_SERVICES.put(mucServiceAddress, discoInfo); 470 return discoInfo; 471 } 472 473 /** 474 * Returns a Map of HostedRooms where each HostedRoom has the XMPP address of the room and the room's name. 475 * Once discovered the rooms hosted by a chat service it is possible to discover more detailed room information or 476 * join the room. 477 * 478 * @param serviceName the service that is hosting the rooms to discover. 479 * @return a map from the room's address to its HostedRoom information. 480 * @throws XMPPErrorException if there was an XMPP error returned. 481 * @throws NoResponseException if there was no response from the remote entity. 482 * @throws NotConnectedException if the XMPP connection is not connected. 483 * @throws InterruptedException if the calling thread was interrupted. 484 * @throws NotAMucServiceException if the entity is not a MUC serivce. 485 * @since 4.3.1 486 */ 487 public Map<EntityBareJid, HostedRoom> getRoomsHostedBy(DomainBareJid serviceName) throws NoResponseException, XMPPErrorException, 488 NotConnectedException, InterruptedException, NotAMucServiceException { 489 if (!providesMucService(serviceName)) { 490 throw new NotAMucServiceException(serviceName); 491 } 492 DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(serviceName); 493 List<DiscoverItems.Item> items = discoverItems.getItems(); 494 495 Map<EntityBareJid, HostedRoom> answer = new HashMap<>(items.size()); 496 for (DiscoverItems.Item item : items) { 497 HostedRoom hostedRoom = new HostedRoom(item); 498 HostedRoom previousRoom = answer.put(hostedRoom.getJid(), hostedRoom); 499 assert previousRoom == null; 500 } 501 502 return answer; 503 } 504 505 /** 506 * Informs the sender of an invitation that the invitee declines the invitation. The rejection will be sent to the 507 * room which in turn will forward the rejection to the inviter. 508 * 509 * @param room the room that sent the original invitation. 510 * @param inviter the inviter of the declined invitation. 511 * @param reason the reason why the invitee is declining the invitation. 512 * @throws NotConnectedException if the XMPP connection is not connected. 513 * @throws InterruptedException if the calling thread was interrupted. 514 */ 515 public void decline(EntityBareJid room, EntityBareJid inviter, String reason) throws NotConnectedException, InterruptedException { 516 XMPPConnection connection = connection(); 517 518 MessageBuilder messageBuilder = connection.getStanzaFactory().buildMessageStanza().to(room); 519 520 // Create the MUCUser packet that will include the rejection 521 MUCUser mucUser = new MUCUser(); 522 MUCUser.Decline decline = new MUCUser.Decline(reason, inviter); 523 mucUser.setDecline(decline); 524 // Add the MUCUser packet that includes the rejection 525 messageBuilder.addExtension(mucUser); 526 527 connection.sendStanza(messageBuilder.build()); 528 } 529 530 /** 531 * Adds a listener to invitation notifications. The listener will be fired anytime an invitation is received. 532 * 533 * @param listener an invitation listener. 534 */ 535 public void addInvitationListener(InvitationListener listener) { 536 invitationsListeners.add(listener); 537 } 538 539 /** 540 * Removes a listener to invitation notifications. The listener will be fired anytime an invitation is received. 541 * 542 * @param listener an invitation listener. 543 */ 544 public void removeInvitationListener(InvitationListener listener) { 545 invitationsListeners.remove(listener); 546 } 547 548 /** 549 * If automatic join on reconnect is enabled, then the manager will try to auto join MUC rooms after the connection 550 * got re-established. 551 * 552 * @param autoJoin <code>true</code> to enable, <code>false</code> to disable. 553 */ 554 public void setAutoJoinOnReconnect(boolean autoJoin) { 555 autoJoinOnReconnect = autoJoin; 556 } 557 558 /** 559 * Set a callback invoked by this manager when automatic join on reconnect failed. If failedCallback is not 560 * <code>null</code>, then automatic rejoin get also enabled. 561 * 562 * @param failedCallback the callback. 563 */ 564 public void setAutoJoinFailedCallback(AutoJoinFailedCallback failedCallback) { 565 autoJoinFailedCallback = failedCallback; 566 if (failedCallback != null) { 567 setAutoJoinOnReconnect(true); 568 } 569 } 570 571 /** 572 * Set a callback invoked by this manager when automatic join on reconnect success. 573 * If successCallback is not <code>null</code>, automatic rejoin will also 574 * be enabled. 575 * 576 * @param successCallback the callback 577 */ 578 public void setAutoJoinSuccessCallback(AutoJoinSuccessCallback successCallback) { 579 autoJoinSuccessCallback = successCallback; 580 if (successCallback != null) { 581 setAutoJoinOnReconnect(true); 582 } 583 } 584 585 586 void addJoinedRoom(EntityBareJid room) { 587 joinedRooms.add(room); 588 } 589 590 void removeJoinedRoom(EntityBareJid room) { 591 joinedRooms.remove(room); 592 } 593 594 static CopyOnWriteArrayList<MucMessageInterceptor> getMessageInterceptors() { 595 synchronized (DEFAULT_MESSAGE_INTERCEPTORS) { 596 return new CopyOnWriteArrayList<>(DEFAULT_MESSAGE_INTERCEPTORS); 597 } 598 } 599}