/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.protocol.icq; import java.util.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import net.kano.joscar.*; import net.kano.joustsim.oscar.oscar.service.chatrooms.*; /** * A ICQ implementation of the ad-hoc multi user chat operation set. * * @author Valentin Martinet */ public class OperationSetAdHocMultiUserChatIcqImpl implements OperationSetAdHocMultiUserChat { private static final Logger logger = Logger.getLogger(OperationSetAdHocMultiUserChatIcqImpl.class); /** * The currently valid ICQ protocol provider service implementation. */ private ProtocolProviderServiceIcqImpl icqProvider = null; /** * A list of listeners subscribed for invitations multi user chat events. */ private Vector<AdHocChatRoomInvitationListener> invitationListeners = new Vector<AdHocChatRoomInvitationListener>(); /** * A list of listeners subscribed for events indicating rejection of a multi * user chat invitation sent by us. */ private Vector<AdHocChatRoomInvitationRejectionListener> invitationRejectionListeners = new Vector<AdHocChatRoomInvitationRejectionListener>(); /** * Listeners that will be notified of changes in our status in the room such * as us being kicked, banned, or granted admin permissions. */ private Vector<LocalUserAdHocChatRoomPresenceListener> presenceListeners = new Vector<LocalUserAdHocChatRoomPresenceListener>(); /** * A list of the rooms that are currently open by this account. Note that we * have not necessarily joined these rooms, we might have simply been * searching through them. */ private Hashtable<String, AdHocChatRoom> chatRoomCache = new Hashtable<String, AdHocChatRoom>(); /** * The registration listener that would get notified when the underlying ICQ * provider gets registered. */ private RegistrationStateListener providerRegListener = new RegistrationStateListener(); /** * A reference to the persistent presence operation set that we use to match * incoming messages to <tt>Contact</tt>s and vice versa. */ protected OperationSetPersistentPresenceIcqImpl opSetPersPresence = null; /** * Hash table that contains all invitations, this is needed if the user * wants to reject an invitation. */ private Hashtable<AdHocChatRoom, ChatInvitation> invitations = new Hashtable<AdHocChatRoom, ChatInvitation>(); /** * Instantiates the user operation set with a currently valid instance of * the Icq protocol provider. * * @param icqProvider a currently valid instance of * ProtocolProviderServiceIcqImpl. */ OperationSetAdHocMultiUserChatIcqImpl( ProtocolProviderServiceIcqImpl icqProvider) { this.icqProvider = icqProvider; icqProvider.addRegistrationStateChangeListener(providerRegListener); } /** * Adds a listener to invitation notifications. * * @param listener an invitation listener. */ public void addInvitationListener(AdHocChatRoomInvitationListener listener) { synchronized (invitationListeners) { if (!invitationListeners.contains(listener)) invitationListeners.add(listener); } } /** * Subscribes <tt>listener</tt> so that it would receive events indicating * rejection of a multi user chat invitation that we've sent earlier. * * @param listener the listener that we'll subscribe for invitation * rejection events. */ public void addInvitationRejectionListener( AdHocChatRoomInvitationRejectionListener listener) { synchronized (invitationRejectionListeners) { if (!invitationRejectionListeners.contains(listener)) invitationRejectionListeners.add(listener); } } /** * Adds a listener that will be notified of changes in our status in a chat * room such as us being kicked, banned or dropped. * * @param listener the <tt>LocalUserAdHocChatRoomPresenceListener</tt>. */ public void addPresenceListener( LocalUserAdHocChatRoomPresenceListener listener) { synchronized (presenceListeners) { if (!presenceListeners.contains(listener)) presenceListeners.add(listener); } } /** * Returns a list of all currently joined <tt>AdHocChatRoom</tt>-s. * * @return a list of all currently joined <tt>AdHocChatRoom</tt>-s */ public List<AdHocChatRoom> getAdHocChatRooms() { return new ArrayList<AdHocChatRoom>(chatRoomCache.values()); } /** * Creates a room with the named <tt>roomName</tt> and according to the * specified <tt>roomProperties</tt> on the server that this protocol * provider is currently connected to. * * @param roomName the name of the <tt>AdHocChatRoom</tt> to create. * @param roomProperties properties specifying how the room should be * created. Contains list of invitees and the invitation message. * * @throws OperationFailedException if the room couldn't be created for some * reason (e.g. room already exists; user already joined to an * existent room or user has no permissions to create an ad-hoc * chat room). * @throws OperationNotSupportedException if ad-hoc chat room creation is * not supported by this server * * @return AdHocChatRoom the ad-hoc chat room that we've just created. */ public AdHocChatRoom createAdHocChatRoom(String roomName, Map<String, Object> roomProperties) throws OperationFailedException, OperationNotSupportedException { AdHocChatRoom chatRoom = null; ChatRoomManager chatRoomManager = icqProvider.getAimConnection().getChatRoomManager(); ChatRoomSession chatRoomSession = chatRoomManager.joinRoom(roomName); if (chatRoomSession != null) { chatRoom = createLocalChatRoomInstance( roomName, chatRoomSession); } return chatRoom; } /** * Creates an ad-hoc room with the named <tt>adHocRoomName</tt> and inviting * the specified list of <tt>contacts</tt>. * * @param adHocRoomName the name of the ad-hoc room * (however, it won't/does not have to be used since the only way to create * an ICQ room is having a room name which begins by the "chat" prefix * followed by a random number (this is what is used in the ICQ 7 software). * * @param contacts the list of contacts ID * @param reason the reason to be sent with the invitation for contacts. * * @return the ad-hoc room that has been just created * @throws OperationFailedException * @throws OperationNotSupportedException */ public AdHocChatRoom createAdHocChatRoom(String adHocRoomName, List<String> contacts, String reason) throws OperationFailedException, OperationNotSupportedException { // It's strongly recommended to don't change room's name below: AdHocChatRoom adHocChatRoom = createAdHocChatRoom( "chat"+new Date().getTime(), new Hashtable<String, Object>()); if (adHocChatRoom != null && contacts != null) { for (String address : contacts) { adHocChatRoom.invite(address, reason); } } return adHocChatRoom; } /** * Creates an <tt>AdHocChatRoom</tt> from the specified * <tt>chatInvitation</tt>. * * @param chatInvitation the chat invitation we received from the * chatRoomManager * * @return the chat room that we've just created. */ private AdHocChatRoom createLocalChatRoomInstance( ChatInvitation chatInvitation) { synchronized (chatRoomCache) { AdHocChatRoom newChatRoom = new AdHocChatRoomIcqImpl(chatInvitation, icqProvider); chatRoomCache.put(chatInvitation.getRoomName(), newChatRoom); return newChatRoom; } } /** * Creates an <tt>AdHocChatRoom</tt> from the specified <tt>roomName</tt> * and Icq <tt>chatRoomSession</tt>. * * @param roomName the name of the room * @param chatRoomSession the Icq chat room session corresponding to the * room to create * @return an <tt>AdHocChatRoom</tt> from the specified <tt>roomName</tt> * and Icq <tt>chatRoomSession</tt> */ private AdHocChatRoom createLocalChatRoomInstance( String roomName, ChatRoomSession chatRoomSession) { synchronized (chatRoomCache) { AdHocChatRoom newChatRoom = new AdHocChatRoomIcqImpl( roomName, chatRoomSession, icqProvider); chatRoomCache.put(roomName, newChatRoom); return newChatRoom; } } /** * Informs the sender of an invitation that we decline their invitation. * * @param invitation the connection to use for sending the rejection. * @param rejectReason the reason to reject the given invitation */ public void rejectInvitation(AdHocChatRoomInvitation invitation, String rejectReason) { ChatInvitation inv = invitations.get(invitation.getTargetAdHocChatRoom()); if (inv != null) { // send the rejection inv.reject(); } // remove the invitation invitations.remove(invitation.getTargetAdHocChatRoom()); } /** * Removes <tt>listener</tt> from the list of invitation listeners * registered to receive invitation events. * * @param listener the invitation listener to remove. */ public void removeInvitationListener( AdHocChatRoomInvitationListener listener) { synchronized (invitationListeners) { invitationListeners.remove(listener); } } /** * Removes <tt>listener</tt> from the list of invitation listeners * registered to receive invitation rejection events. * * @param listener the invitation listener to remove. */ public void removeInvitationRejectionListener( AdHocChatRoomInvitationRejectionListener listener) { synchronized (invitationRejectionListeners) { invitationRejectionListeners.remove(listener); } } /** * Removes a listener that was being notified of changes in our status in a * room such as us being joined or dropped. * * @param listener the <tt>LocalUserAdHocChatRoomPresenceListener</tt>. */ public void removePresenceListener( LocalUserAdHocChatRoomPresenceListener listener) { synchronized (presenceListeners) { presenceListeners.remove(listener); } } /** * Delivers a <tt>AdHocChatRoomInvitationReceivedEvent</tt> to all * registered <tt>AdHocChatRoomInvitationListener</tt>s. * * @param targetChatRoom the ad-hoc room that invitation refers to * @param inviter the inviter that sent the invitation * @param reason the reason why the inviter sent the invitation * @param password the password to use when joining the room */ public void fireInvitationEvent(AdHocChatRoom targetChatRoom, String inviter, String reason, byte[] password) { AdHocChatRoomInvitationIcqImpl invitation = new AdHocChatRoomInvitationIcqImpl(targetChatRoom, inviter, reason, password); AdHocChatRoomInvitationReceivedEvent evt = new AdHocChatRoomInvitationReceivedEvent(this, invitation, new Date(System.currentTimeMillis())); Iterable<AdHocChatRoomInvitationListener> listeners; synchronized (invitationListeners) { listeners = new ArrayList<AdHocChatRoomInvitationListener>( invitationListeners); } for (AdHocChatRoomInvitationListener listener : listeners) listener.invitationReceived(evt); } /** * Delivers a <tt>LocalUserAdHocChatRoomPresenceChangeEvent</tt> to all * registered <tt>LocalUserAdHocChatRoomPresenceListener</tt>s. * * @param chatRoom the <tt>AdHocChatRoom</tt> which has been joined, left, * etc. * @param eventType the type of this event; one of LOCAL_USER_JOINED, * LOCAL_USER_LEFT, etc. * @param reason the reason */ public void fireLocalUserPresenceEvent(AdHocChatRoom chatRoom, String eventType, String reason) { LocalUserAdHocChatRoomPresenceChangeEvent evt = new LocalUserAdHocChatRoomPresenceChangeEvent(this, chatRoom, eventType, reason); Iterable<LocalUserAdHocChatRoomPresenceListener> listeners = null; synchronized (presenceListeners) { listeners = new ArrayList<LocalUserAdHocChatRoomPresenceListener>( presenceListeners); } for (LocalUserAdHocChatRoomPresenceListener listener : listeners) listener.localUserAdHocPresenceChanged(evt); } /** * Our listener that will tell us when we're registered to icq and joust sim * is ready to accept us as a listener. */ private class RegistrationStateListener implements RegistrationStateChangeListener { /** * The method is called by a ProtocolProvider implementation whenever a * change in the registration state of the corresponding provider had * occurred. * * @param evt ProviderStatusChangeEvent the event describing the status * change. */ public void registrationStateChanged(RegistrationStateChangeEvent evt) { if (logger.isDebugEnabled()) logger.debug("The ICQ provider changed state from: " + evt.getOldState() + " to: " + evt.getNewState()); if (evt.getNewState() == RegistrationState.REGISTERED) { String customMessageEncoding = null; if ((customMessageEncoding = System.getProperty("icq.custom.message.charset")) != null) OscarTools.setDefaultCharset(customMessageEncoding); opSetPersPresence = (OperationSetPersistentPresenceIcqImpl) icqProvider .getOperationSet(OperationSetPersistentPresence.class); // add ChatRoomMangagerListener icqProvider.getAimConnection().getChatRoomManager() .addListener(new ChatRoomManagerListenerImpl()); } } } /** * Our listener for chat room invitations. */ private class ChatRoomManagerListenerImpl implements ChatRoomManagerListener { public void handleInvitation(ChatRoomManager chatRoomManager, ChatInvitation chatInvitation) { if (logger.isDebugEnabled()) logger.debug( "Invitation received: " + chatInvitation.getRoomName()); AdHocChatRoom chatRoom = createLocalChatRoomInstance(chatInvitation); // save chatInvitation, for a possible rejection invitations.put(chatRoom, chatInvitation); fireInvitationEvent(chatRoom, chatInvitation.getScreenname() .toString(), chatInvitation.getMessage(), null); } } }