/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.impl.protocol.jabber;
import java.util.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.jabberconstants.*;
import net.java.sip.communicator.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.util.*;
import org.jivesoftware.smackx.packet.*;
/**
* The Jabber implementation of a Persistent Presence Operation set. This class
* manages our own presence status as well as subscriptions for the presence
* status of our buddies. It also offers methods for retrieving and modifying
* the buddy contact list and adding listeners for changes in its layout.
*
* @author Damian Minkov
* @author Lyubomir Marinov
* @author Hristo Terezov
*/
public class OperationSetPersistentPresenceJabberImpl
extends AbstractOperationSetPersistentPresence<
ProtocolProviderServiceJabberImpl>
{
/**
* The logger.
*/
private static final Logger logger =
Logger.getLogger(OperationSetPersistentPresenceJabberImpl.class);
/**
* Contains our current status message. Note that this field would only
* be changed once the server has confirmed the new status message and
* not immediately upon setting a new one..
*/
private String currentStatusMessage = "";
/**
* The presence status that we were last notified of entering.
* The initial one is OFFLINE
*/
private PresenceStatus currentStatus;
/**
* A map containing bindings between Jitsi's jabber presence
* status instances and Jabber status codes
*/
private static Map<String, Presence.Mode> scToJabberModesMappings
= new Hashtable<String, Presence.Mode>();
static
{
scToJabberModesMappings.put(JabberStatusEnum.AWAY,
Presence.Mode.away);
scToJabberModesMappings.put(JabberStatusEnum.ON_THE_PHONE,
Presence.Mode.away);
scToJabberModesMappings.put(JabberStatusEnum.IN_A_MEETING,
Presence.Mode.away);
scToJabberModesMappings.put(JabberStatusEnum.EXTENDED_AWAY,
Presence.Mode.xa);
scToJabberModesMappings.put(JabberStatusEnum.DO_NOT_DISTURB,
Presence.Mode.dnd);
scToJabberModesMappings.put(JabberStatusEnum.FREE_FOR_CHAT,
Presence.Mode.chat);
scToJabberModesMappings.put(JabberStatusEnum.AVAILABLE,
Presence.Mode.available);
}
/**
* A map containing bindings between Jitsi's xmpp presence
* status instances and priorities to use for statuses.
*/
private static Map<String, Integer> statusToPriorityMappings
= new Hashtable<String, Integer>();
/**
* The server stored contact list that will be encapsulating smack's
* buddy list.
*/
private final ServerStoredContactListJabberImpl ssContactList;
/**
* Listens for subscriptions.
*/
private JabberSubscriptionListener subscribtionPacketListener = null;
/**
* Current resource priority.
*/
private int resourcePriorityAvailable = 30;
/**
* Manages statuses and different user resources.
*/
private ContactChangesListener contactChangesListener = null;
/**
* Manages the presence extension to advertise the SHA-1 hash of this
* account avatar as defined in XEP-0153.
*/
private VCardTempXUpdatePresenceExtension vCardTempXUpdatePresenceExtension
= null;
/**
* Handles all the logic about mobile indicator for contacts.
*/
private final MobileIndicator mobileIndicator;
/**
* The last sent presence to server, contains the status, the resource
* and its priority.
*/
private Presence currentPresence = null;
/**
* The local contact presented by the provider.
*/
private ContactJabberImpl localContact = null;
/**
* Creates the OperationSet.
* @param provider the parent provider.
* @param infoRetreiver retrieve contact information.
*/
public OperationSetPersistentPresenceJabberImpl(
ProtocolProviderServiceJabberImpl provider,
InfoRetreiver infoRetreiver)
{
super(provider);
currentStatus =
parentProvider.getJabberStatusEnum().getStatus(
JabberStatusEnum.OFFLINE);
initializePriorities();
ssContactList = new ServerStoredContactListJabberImpl(
this , provider, infoRetreiver);
parentProvider.addRegistrationStateChangeListener(
new RegistrationStateListener());
mobileIndicator = new MobileIndicator(parentProvider, ssContactList);
}
/**
* Registers a listener that would receive events upon changes in server
* stored groups.
*
* @param listener a ServerStoredGroupChangeListener impl that would
* receive events upon group changes.
*/
@Override
public void addServerStoredGroupChangeListener(ServerStoredGroupListener
listener)
{
ssContactList.addGroupListener(listener);
}
/**
* Creates a group with the specified name and parent in the server
* stored contact list.
*
* @param parent the group where the new group should be created
* @param groupName the name of the new group to create.
* @throws OperationFailedException if such group already exists
*/
public void createServerStoredContactGroup(ContactGroup parent,
String groupName)
throws OperationFailedException
{
assertConnected();
if (!parent.canContainSubgroups())
throw new IllegalArgumentException(
"The specified contact group cannot contain child groups. Group:"
+ parent );
ssContactList.createGroup(groupName);
}
/**
* Creates a non persistent contact for the specified address. This would
* also create (if necessary) a group for volatile contacts that would not
* be added to the server stored contact list. The volatile contact would
* remain in the list until it is really added to the contact list or
* until the application is terminated.
* @param id the address of the contact to create.
* @return the newly created volatile <tt>ContactImpl</tt>
*/
public synchronized ContactJabberImpl createVolatileContact(String id)
{
return createVolatileContact(id, null);
}
/**
* Creates a non persistent contact for the specified address. This would
* also create (if necessary) a group for volatile contacts that would not
* be added to the server stored contact list. The volatile contact would
* remain in the list until it is really added to the contact list or
* until the application is terminated.
* @param id the address of the contact to create.
* @param displayName the display name of the contact.
* @return the newly created volatile <tt>ContactImpl</tt>
*/
public synchronized ContactJabberImpl createVolatileContact(String id,
String displayName)
{
return createVolatileContact(id, false, displayName);
}
/**
* Creates a non persistent contact for the specified address. This would
* also create (if necessary) a group for volatile contacts that would not
* be added to the server stored contact list. The volatile contact would
* remain in the list until it is really added to the contact list or
* until the application is terminated.
* @param id the address of the contact to create.
* @param isPrivateMessagingContact indicates whether the contact should be private
* messaging contact or not.
* @return the newly created volatile <tt>ContactImpl</tt>
*/
public synchronized ContactJabberImpl createVolatileContact(String id,
boolean isPrivateMessagingContact)
{
return createVolatileContact(id, isPrivateMessagingContact, null);
}
/**
* Creates a non persistent contact for the specified address. This would
* also create (if necessary) a group for volatile contacts that would not
* be added to the server stored contact list. The volatile contact would
* remain in the list until it is really added to the contact list or
* until the application is terminated.
* @param id the address of the contact to create.
* @param isPrivateMessagingContact indicates whether the contact should be private
* messaging contact or not.
* @param displayName the display name of the contact.
* @return the newly created volatile <tt>ContactImpl</tt>
*/
public synchronized ContactJabberImpl createVolatileContact(String id,
boolean isPrivateMessagingContact, String displayName)
{
// first check for already created one.
ContactGroupJabberImpl notInContactListGroup =
ssContactList.getNonPersistentGroup();
ContactJabberImpl sourceContact;
if(notInContactListGroup != null
&& (sourceContact = notInContactListGroup.findContact(
StringUtils.parseBareAddress(id)))
!= null)
return sourceContact;
else
{
sourceContact = ssContactList.createVolatileContact(
id, isPrivateMessagingContact, displayName);
if(isPrivateMessagingContact
&& StringUtils.parseResource(id) != null)
{
updateResources(sourceContact, false);
}
return sourceContact;
}
}
/**
* Creates and returns a unresolved contact from the specified
* <tt>address</tt> and <tt>persistentData</tt>.
*
* @param address an identifier of the contact that we'll be creating.
* @param persistentData a String returned Contact's getPersistentData()
* method during a previous run and that has been persistently stored
* locally.
* @param parentGroup the group where the unresolved contact is supposed
* to belong to.
* @return the unresolved <tt>Contact</tt> created from the specified
* <tt>address</tt> and <tt>persistentData</tt>
*/
public Contact createUnresolvedContact(String address,
String persistentData,
ContactGroup parentGroup)
{
if(! (parentGroup instanceof ContactGroupJabberImpl ||
parentGroup instanceof RootContactGroupJabberImpl) )
throw new IllegalArgumentException(
"Argument is not an jabber contact group (group="
+ parentGroup + ")");
ContactJabberImpl contact =
ssContactList.createUnresolvedContact(parentGroup, address);
contact.setPersistentData(persistentData);
return contact;
}
/**
* Creates and returns a unresolved contact from the specified
* <tt>address</tt> and <tt>persistentData</tt>.
*
* @param address an identifier of the contact that we'll be creating.
* @param persistentData a String returned Contact's getPersistentData()
* method during a previous run and that has been persistently stored
* locally.
* @return the unresolved <tt>Contact</tt> created from the specified
* <tt>address</tt> and <tt>persistentData</tt>
*/
public Contact createUnresolvedContact(String address,
String persistentData)
{
return createUnresolvedContact( address
, persistentData
, getServerStoredContactListRoot());
}
/**
* Creates and returns a unresolved contact group from the specified
* <tt>address</tt> and <tt>persistentData</tt>.
*
* @param groupUID an identifier, returned by ContactGroup's
* getGroupUID, that the protocol provider may use in order to create
* the group.
* @param persistentData a String returned ContactGroups's
* getPersistentData() method during a previous run and that has been
* persistently stored locally.
* @param parentGroup the group under which the new group is to be
* created or null if this is group directly underneath the root.
* @return the unresolved <tt>ContactGroup</tt> created from the
* specified <tt>uid</tt> and <tt>persistentData</tt>
*/
public ContactGroup createUnresolvedContactGroup(String groupUID,
String persistentData, ContactGroup parentGroup)
{
return ssContactList.createUnresolvedContactGroup(groupUID);
}
/**
* Returns a reference to the contact with the specified ID in case we
* have a subscription for it and null otherwise/
*
* @param contactID a String identifier of the contact which we're
* seeking a reference of.
* @return a reference to the Contact with the specified
* <tt>contactID</tt> or null if we don't have a subscription for the
* that identifier.
*/
public Contact findContactByID(String contactID)
{
return ssContactList.findContactById(contactID);
}
/**
* Returns the status message that was confirmed by the serfver
*
* @return the last status message that we have requested and the aim
* server has confirmed.
*/
public String getCurrentStatusMessage()
{
return currentStatusMessage;
}
/**
* Returns the protocol specific contact instance representing the local
* user.
*
* @return the Contact (address, phone number, or uin) that the Provider
* implementation is communicating on behalf of.
*/
public Contact getLocalContact()
{
if(localContact != null)
return localContact;
final String id = parentProvider.getAccountID().getUserID();
localContact
= new ContactJabberImpl(null, ssContactList, false, true);
localContact.setLocal(true);
localContact.updatePresenceStatus(currentStatus);
localContact.setJid(parentProvider.getOurJID());
Map<String, ContactResourceJabberImpl> rs
= localContact.getResourcesMap();
if(currentPresence != null)
rs.put(parentProvider.getOurJID(),
createResource( currentPresence,
parentProvider.getOurJID(),
localContact));
Iterator<Presence> presenceIterator = ssContactList.getPresences(id);
while(presenceIterator.hasNext())
{
Presence p = presenceIterator.next();
String fullJid = p.getFrom();
rs.put(fullJid, createResource(p, p.getFrom(), localContact));
}
// adds xmpp listener for changes in the local contact resources
PacketFilter presenceFilter = new PacketTypeFilter(Presence.class);
parentProvider.getConnection()
.addPacketListener(
new PacketListener()
{
@Override
public void processPacket(Packet packet)
{
Presence presence = (Presence) packet;
String from = presence.getFrom();
if(from == null
|| !StringUtils.parseBareAddress(from).equals(id))
return;
// own resource update, let's process it
updateResource(localContact, null, presence);
}
}, presenceFilter);
return localContact;
}
/**
* Creates ContactResource from the presence, full jid and contact.
* @param presence the presence object.
* @param fullJid the full jid for the resource.
* @param contact the contact.
* @return the newly created resource.
*/
private ContactResourceJabberImpl createResource(
Presence presence,
String fullJid,
Contact contact)
{
String resource = StringUtils.parseResource(fullJid);
return new ContactResourceJabberImpl(
fullJid,
contact,
resource,
jabberStatusToPresenceStatus(presence, parentProvider),
presence.getPriority(),
mobileIndicator.isMobileResource(resource, fullJid));
}
/**
* Clear resources used for local contact and before that update its
* resources in order to fire the needed events.
*/
private void clearLocalContactResources()
{
if(localContact != null)
removeResource(localContact, localContact.getAddress());
currentPresence = null;
localContact = null;
}
/**
* Returns a PresenceStatus instance representing the state this provider
* is currently in.
*
* @return the PresenceStatus last published by this provider.
*/
public PresenceStatus getPresenceStatus()
{
return currentStatus;
}
/**
* Returns the root group of the server stored contact list.
*
* @return the root ContactGroup for the ContactList stored by this
* service.
*/
public ContactGroup getServerStoredContactListRoot()
{
return ssContactList.getRootGroup();
}
/**
* Returns the set of PresenceStatus objects that a user of this service
* may request the provider to enter.
*
* @return Iterator a PresenceStatus array containing "enterable" status
* instances.
*/
public Iterator<PresenceStatus> getSupportedStatusSet()
{
return parentProvider.getJabberStatusEnum().getSupportedStatusSet();
}
/**
* Checks if the contact address is associated with private messaging
* contact or not.
* @param contactAddress the address of the contact.
* @return <tt>true</tt> the contact address is associated with private
* messaging contact and <tt>false</tt> if not.
*/
public boolean isPrivateMessagingContact(String contactAddress)
{
return ssContactList.isPrivateMessagingContact(contactAddress);
}
/**
* Removes the specified contact from its current parent and places it
* under <tt>newParent</tt>.
*
* @param contactToMove the <tt>Contact</tt> to move
* @param newParent the <tt>ContactGroup</tt> where <tt>Contact</tt>
* would be placed.
*/
public void moveContactToGroup(Contact contactToMove,
ContactGroup newParent)
throws OperationFailedException
{
assertConnected();
if( !(contactToMove instanceof ContactJabberImpl) )
throw new IllegalArgumentException(
"The specified contact is not an jabber contact." +
contactToMove);
if( !(newParent instanceof AbstractContactGroupJabberImpl) )
throw new IllegalArgumentException(
"The specified group is not an jabber contact group."
+ newParent);
ssContactList.moveContact((ContactJabberImpl)contactToMove,
(AbstractContactGroupJabberImpl)newParent);
}
/**
* Requests the provider to enter into a status corresponding to the
* specified parameters.
*
* @param status the PresenceStatus as returned by
* getRequestableStatusSet
* @param statusMessage the message that should be set as the reason to
* enter that status
* @throws IllegalArgumentException if the status requested is not a
* valid PresenceStatus supported by this provider.
* @throws IllegalStateException if the provider is not currently
* registered.
* @throws OperationFailedException with code NETWORK_FAILURE if
* publishing the status fails due to a network error.
*/
public void publishPresenceStatus(PresenceStatus status,
String statusMessage)
throws IllegalArgumentException,
IllegalStateException,
OperationFailedException
{
assertConnected();
JabberStatusEnum jabberStatusEnum =
parentProvider.getJabberStatusEnum();
boolean isValidStatus = false;
for (Iterator<PresenceStatus> supportedStatusIter
= jabberStatusEnum.getSupportedStatusSet();
supportedStatusIter.hasNext();)
{
if (supportedStatusIter.next().equals(status))
{
isValidStatus = true;
break;
}
}
if (!isValidStatus)
throw new IllegalArgumentException(status
+ " is not a valid Jabber status");
// if we got publish presence and we are still in a process of
// initializing the roster, just save the status and we will dispatch
// it when we are ready with the roster as sending initial presence
// is recommended to be done after requesting the roster, but we want
// to also dispatch it
synchronized(ssContactList.getRosterInitLock())
{
if(!ssContactList.isRosterInitialized())
{
// store it
ssContactList.setInitialStatus(status);
ssContactList.setInitialStatusMessage(statusMessage);
return;
}
}
if (status.equals(jabberStatusEnum.getStatus(JabberStatusEnum.OFFLINE)))
{
parentProvider.unregister();
clearLocalContactResources();
}
else
{
Presence presence = new Presence(Presence.Type.available);
currentPresence = presence;
presence.setMode(presenceStatusToJabberMode(status));
presence.setPriority(
getPriorityForPresenceStatus(status.getStatusName()));
// on the phone is a special status which is away
// with custom status message
if(status.equals(jabberStatusEnum.getStatus(
JabberStatusEnum.ON_THE_PHONE)))
{
presence.setStatus(JabberStatusEnum.ON_THE_PHONE);
}
else if(status.equals(jabberStatusEnum.getStatus(
JabberStatusEnum.IN_A_MEETING)))
{
presence.setStatus(JabberStatusEnum.IN_A_MEETING);
}
else
presence.setStatus(statusMessage);
//presence.addExtension(new Version());
parentProvider.getConnection().sendPacket(presence);
if(localContact != null)
updateResource(localContact,
parentProvider.getOurJID(), presence);
}
fireProviderStatusChangeEvent(currentStatus, status);
String oldStatusMessage = getCurrentStatusMessage();
/*
* XXX Use StringUtils.isEquals instead of String.equals to avoid a
* NullPointerException.
*/
if(!org.jitsi.util.StringUtils.isEquals(
oldStatusMessage,
statusMessage))
{
currentStatusMessage = statusMessage;
fireProviderStatusMessageChangeEvent(
oldStatusMessage,
getCurrentStatusMessage());
}
}
/**
* Gets the <tt>PresenceStatus</tt> of a contact with a specific
* <tt>String</tt> identifier.
*
* @param contactIdentifier the identifier of the contact whose status we're
* interested in.
* @return the <tt>PresenceStatus</tt> of the contact with the specified
* <tt>contactIdentifier</tt>
* @throws IllegalArgumentException if the specified
* <tt>contactIdentifier</tt> does not identify a contact known to the
* underlying protocol provider
* @throws IllegalStateException if the underlying protocol provider is not
* registered/signed on a public service
* @throws OperationFailedException with code NETWORK_FAILURE if retrieving
* the status fails due to errors experienced during network communication
*/
public PresenceStatus queryContactStatus(String contactIdentifier)
throws IllegalArgumentException,
IllegalStateException,
OperationFailedException
{
/*
* As stated by the javadoc, IllegalStateException signals that the
* ProtocolProviderService is not registered.
*/
assertConnected();
Connection xmppConnection = parentProvider.getConnection();
if (xmppConnection == null)
{
throw
new IllegalArgumentException(
"The provider/account must be signed on in order to"
+ " query the status of a contact in its roster");
}
Presence presence
= xmppConnection.getRoster().getPresence(contactIdentifier);
if(presence != null)
return jabberStatusToPresenceStatus(presence, parentProvider);
else
{
return
parentProvider.getJabberStatusEnum().getStatus(
JabberStatusEnum.OFFLINE);
}
}
/**
* Removes the specified group from the server stored contact list.
*
* @param group the group to remove.
*/
public void removeServerStoredContactGroup(ContactGroup group)
throws OperationFailedException
{
assertConnected();
if( !(group instanceof ContactGroupJabberImpl) )
throw new IllegalArgumentException(
"The specified group is not an jabber contact group: " + group);
ssContactList.removeGroup(((ContactGroupJabberImpl)group));
}
/**
* Removes the specified group change listener so that it won't receive
* any further events.
*
* @param listener the ServerStoredGroupChangeListener to remove
*/
@Override
public void removeServerStoredGroupChangeListener(ServerStoredGroupListener
listener)
{
ssContactList.removeGroupListener(listener);
}
/**
* Renames the specified group from the server stored contact list.
*
* @param group the group to rename.
* @param newName the new name of the group.
*/
public void renameServerStoredContactGroup(ContactGroup group,
String newName)
{
assertConnected();
if( !(group instanceof ContactGroupJabberImpl) )
throw new IllegalArgumentException(
"The specified group is not an jabber contact group: " + group);
ssContactList.renameGroup((ContactGroupJabberImpl)group, newName);
}
/**
* Handler for incoming authorization requests.
*
* @param handler an instance of an AuthorizationHandler for
* authorization requests coming from other users requesting
* permission add us to their contact list.
*/
public void setAuthorizationHandler(AuthorizationHandler handler)
{
subscribtionPacketListener.setHandler(handler);
}
/**
* Persistently adds a subscription for the presence status of the
* contact corresponding to the specified contactIdentifier and indicates
* that it should be added to the specified group of the server stored
* contact list.
*
* @param parent the parent group of the server stored contact list
* where the contact should be added. <p>
* @param contactIdentifier the contact whose status updates we are
* subscribing for.
* @throws IllegalArgumentException if <tt>contact</tt> or
* <tt>parent</tt> are not a contact known to the underlying protocol
* provider.
* @throws IllegalStateException if the underlying protocol provider is
* not registered/signed on a public service.
* @throws OperationFailedException with code NETWORK_FAILURE if
* subscribing fails due to errors experienced during network
* communication
*/
public void subscribe(ContactGroup parent, String contactIdentifier)
throws IllegalArgumentException, IllegalStateException,
OperationFailedException
{
assertConnected();
if(! (parent instanceof ContactGroupJabberImpl) )
throw new IllegalArgumentException(
"Argument is not an jabber contact group (group="
+ parent + ")");
ssContactList.addContact(parent, contactIdentifier);
}
/**
* Adds a subscription for the presence status of the contact
* corresponding to the specified contactIdentifier.
*
* @param contactIdentifier the identifier of the contact whose status
* updates we are subscribing for. <p>
* @throws IllegalArgumentException if <tt>contact</tt> is not a contact
* known to the underlying protocol provider
* @throws IllegalStateException if the underlying protocol provider is
* not registered/signed on a public service.
* @throws OperationFailedException with code NETWORK_FAILURE if
* subscribing fails due to errors experienced during network
* communication
*/
public void subscribe(String contactIdentifier) throws
IllegalArgumentException, IllegalStateException,
OperationFailedException
{
assertConnected();
ssContactList.addContact(contactIdentifier);
}
/**
* Removes a subscription for the presence status of the specified
* contact.
*
* @param contact the contact whose status updates we are unsubscribing
* from.
* @throws IllegalArgumentException if <tt>contact</tt> is not a contact
* known to the underlying protocol provider
* @throws IllegalStateException if the underlying protocol provider is
* not registered/signed on a public service.
* @throws OperationFailedException with code NETWORK_FAILURE if
* unsubscribing fails due to errors experienced during network
* communication
*/
public void unsubscribe(Contact contact) throws IllegalArgumentException,
IllegalStateException, OperationFailedException
{
assertConnected();
if(! (contact instanceof ContactJabberImpl) )
throw new IllegalArgumentException(
"Argument is not an jabber contact (contact=" + contact + ")");
ssContactList.removeContact((ContactJabberImpl)contact);
}
/**
* Converts the specified jabber status to one of the status fields of the
* JabberStatusEnum class.
*
* @param presence the Jabber Status
* @param jabberProvider the parent provider.
* @return a PresenceStatus instance representation of the Jabber Status
* parameter. The returned result is one of the JabberStatusEnum fields.
*/
public static PresenceStatus jabberStatusToPresenceStatus(
Presence presence, ProtocolProviderServiceJabberImpl jabberProvider)
{
JabberStatusEnum jabberStatusEnum =
jabberProvider.getJabberStatusEnum();
// fixing issue: 336
// from the smack api :
// A null presence mode value is interpreted to be the same thing
// as Presence.Mode.available.
if(presence.getMode() == null && presence.isAvailable())
return jabberStatusEnum.getStatus(JabberStatusEnum.AVAILABLE);
else if(presence.getMode() == null && !presence.isAvailable())
return jabberStatusEnum.getStatus(JabberStatusEnum.OFFLINE);
Presence.Mode mode = presence.getMode();
if(mode.equals(Presence.Mode.available))
return jabberStatusEnum.getStatus(JabberStatusEnum.AVAILABLE);
else if(mode.equals(Presence.Mode.away))
{
// on the phone a special status
// which is away with custom status message
if(presence.getStatus() != null
&& presence.getStatus().contains(JabberStatusEnum.ON_THE_PHONE))
return jabberStatusEnum.getStatus(JabberStatusEnum.ON_THE_PHONE);
else if(presence.getStatus() != null
&& presence.getStatus().contains(JabberStatusEnum.IN_A_MEETING))
return jabberStatusEnum.getStatus(JabberStatusEnum.IN_A_MEETING);
else
return jabberStatusEnum.getStatus(JabberStatusEnum.AWAY);
}
else if(mode.equals(Presence.Mode.chat))
return jabberStatusEnum.getStatus(JabberStatusEnum.FREE_FOR_CHAT);
else if(mode.equals(Presence.Mode.dnd))
return jabberStatusEnum.getStatus(JabberStatusEnum.DO_NOT_DISTURB);
else if(mode.equals(Presence.Mode.xa))
return jabberStatusEnum.getStatus(JabberStatusEnum.EXTENDED_AWAY);
else
{
//unknown status
if(presence.isAway())
return jabberStatusEnum.getStatus(JabberStatusEnum.AWAY);
if(presence.isAvailable())
return jabberStatusEnum.getStatus(JabberStatusEnum.AVAILABLE);
return jabberStatusEnum.getStatus(JabberStatusEnum.OFFLINE);
}
}
/**
* Converts the specified JabberStatusEnum member to the corresponding
* Jabber Mode
*
* @param status the jabberStatus
* @return a PresenceStatus instance
*/
public static Presence.Mode presenceStatusToJabberMode(
PresenceStatus status)
{
return scToJabberModesMappings.get(status
.getStatusName());
}
/**
* Utility method throwing an exception if the stack is not properly
* initialized.
*
* @throws IllegalStateException if the underlying stack is not registered
* and initialized.
*/
void assertConnected()
throws IllegalStateException
{
if (parentProvider == null)
{
throw
new IllegalStateException(
"The provider must be non-null and signed on the"
+ " Jabber service before being able to"
+ " communicate.");
}
if (!parentProvider.isRegistered())
{
// if we are not registered but the current status is online
// change the current status
if((currentStatus != null) && currentStatus.isOnline())
{
fireProviderStatusChangeEvent(
currentStatus,
parentProvider.getJabberStatusEnum().getStatus(
JabberStatusEnum.OFFLINE));
}
throw
new IllegalStateException(
"The provider must be signed on the Jabber service"
+ " before being able to communicate.");
}
}
/**
* Fires provider status change.
*
* @param oldStatus old status
* @param newStatus new status
*/
@Override
public void fireProviderStatusChangeEvent(
PresenceStatus oldStatus,
PresenceStatus newStatus)
{
if (!oldStatus.equals(newStatus))
{
currentStatus = newStatus;
super.fireProviderStatusChangeEvent(oldStatus, newStatus);
PresenceStatus offlineStatus =
parentProvider.getJabberStatusEnum().getStatus(
JabberStatusEnum.OFFLINE);
if(newStatus.equals(offlineStatus))
{
//send event notifications saying that all our buddies are
//offline. The protocol does not implement top level buddies
//nor subgroups for top level groups so a simple nested loop
//would be enough.
Iterator<ContactGroup> groupsIter =
getServerStoredContactListRoot().subgroups();
while(groupsIter.hasNext())
{
ContactGroup group = groupsIter.next();
Iterator<Contact> contactsIter = group.contacts();
while(contactsIter.hasNext())
{
ContactJabberImpl contact
= (ContactJabberImpl)contactsIter.next();
updateContactStatus(contact, offlineStatus);
}
}
//do the same for all contacts in the root group
Iterator<Contact> contactsIter
= getServerStoredContactListRoot().contacts();
while (contactsIter.hasNext())
{
ContactJabberImpl contact
= (ContactJabberImpl) contactsIter.next();
updateContactStatus(contact, offlineStatus);
}
}
}
}
/**
* Sets the display name for <tt>contact</tt> to be <tt>newName</tt>.
* <p>
* @param contact the <tt>Contact</tt> that we are renaming
* @param newName a <tt>String</tt> containing the new display name for
* <tt>metaContact</tt>.
* @throws IllegalArgumentException if <tt>contact</tt> is not an
* instance that belongs to the underlying implementation.
*/
@Override
public void setDisplayName(Contact contact, String newName)
throws IllegalArgumentException
{
assertConnected();
if(! (contact instanceof ContactJabberImpl) )
throw new IllegalArgumentException(
"Argument is not an jabber contact (contact=" + contact + ")");
RosterEntry entry = ((ContactJabberImpl)contact).getSourceEntry();
if(entry != null)
entry.setName(newName);
}
/**
* Our listener that will tell us when we're registered to server
* and is ready to accept us as a listener.
*/
private class RegistrationStateListener
implements RegistrationStateChangeListener
{
/**
* The method is called by a ProtocolProvider implementation whenever
* a change in the registration state of the corresponding provider had
* occurred.
* @param evt ProviderStatusChangeEvent the event describing the status
* change.
*/
public void registrationStateChanged(RegistrationStateChangeEvent evt)
{
if (logger.isDebugEnabled())
logger.debug("The Jabber provider changed state from: "
+ evt.getOldState()
+ " to: " + evt.getNewState());
if(evt.getNewState() == RegistrationState.REGISTERING)
{
// we will add listener for RosterPackets
// as this will indicate when one is received
// and we are ready to dispatch the contact list
// note that our listener will be added just before the
// one used in the Roster itself, but later we
// will wait for it to be ready
// (inside method XMPPConnection.getRoster())
parentProvider.getConnection().addPacketListener(
new ServerStoredListInit(),
new PacketTypeFilter(RosterPacket.class)
);
// will be used to store presence events till roster is
// initialized
contactChangesListener = new ContactChangesListener();
// Adds subscription listener as soon as connection is created
// or we can miss some subscription requests
if(subscribtionPacketListener == null)
{
subscribtionPacketListener =
new JabberSubscriptionListener();
parentProvider
.getConnection()
.addPacketListener(
subscribtionPacketListener,
new PacketTypeFilter(Presence.class));
}
}
else if(evt.getNewState() == RegistrationState.REGISTERED)
{
createContactPhotoPresenceListener();
// we cannot manipulate our vcards when using anonymous
if (!((JabberAccountIDImpl)parentProvider.getAccountID())
.isAnonymousAuthUsed())
{
createAccountPhotoPresenceInterceptor();
}
}
else if(evt.getNewState() == RegistrationState.UNREGISTERED
|| evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED
|| evt.getNewState() == RegistrationState.CONNECTION_FAILED)
{
//since we are disconnected, we won't receive any further status
//updates so we need to change by ourselves our own status as
//well as set to offline all contacts in our contact list that
//were online
PresenceStatus oldStatus = currentStatus;
PresenceStatus offlineStatus =
parentProvider.getJabberStatusEnum().getStatus(
JabberStatusEnum.OFFLINE);
currentStatus = offlineStatus;
clearLocalContactResources();
fireProviderStatusChangeEvent(oldStatus, currentStatus);
ssContactList.cleanup();
Connection connection = parentProvider.getConnection();
if(connection != null)
{
connection.removePacketListener(subscribtionPacketListener);
// the roster is guaranteed to be non-null
connection.getRoster()
.removeRosterListener(contactChangesListener);
}
subscribtionPacketListener = null;
contactChangesListener = null;
}
}
}
/**
* Updates the resources for the contact.
* @param contact the contact which resources to update.
* @param removeUnavailable whether to remove unavailable resources.
* @return whether resource has been updated
*/
private boolean updateResources(ContactJabberImpl contact,
boolean removeUnavailable)
{
if (!contact.isResolved()
|| (contact instanceof VolatileContactJabberImpl
&& ((VolatileContactJabberImpl)contact)
.isPrivateMessagingContact()))
return false;
boolean eventFired = false;
Map<String, ContactResourceJabberImpl> resources =
contact.getResourcesMap();
// Do not obtain getRoster if we are not connected, or new Roster
// will be created, all the resources that will be returned will be
// unavailable. As we are not connected if set remove all resources
if( parentProvider.getConnection() == null
|| !parentProvider.getConnection().isConnected())
{
if(removeUnavailable)
{
Iterator<Map.Entry<String, ContactResourceJabberImpl>>
iter = resources.entrySet().iterator();
while(iter.hasNext())
{
Map.Entry<String, ContactResourceJabberImpl> entry
= iter.next();
iter.remove();
contact.fireContactResourceEvent(
new ContactResourceEvent(contact, entry.getValue(),
ContactResourceEvent.RESOURCE_REMOVED));
eventFired = true;
}
}
return eventFired;
}
Iterator<Presence> it =
parentProvider.getConnection().getRoster()
.getPresences(contact.getAddress());
// Choose the resource which has the highest priority AND supports
// Jingle, if we have two resources with same priority take
// the most available.
while(it.hasNext())
{
Presence presence = it.next();
eventFired = updateResource(contact, null, presence) || eventFired;
}
if(!removeUnavailable)
return eventFired;
Iterator<String> resourceIter = resources.keySet().iterator();
while (resourceIter.hasNext())
{
String fullJid = resourceIter.next();
if(!parentProvider.getConnection().getRoster()
.getPresenceResource(fullJid).isAvailable())
{
eventFired = removeResource(contact, fullJid) || eventFired;
}
}
return eventFired;
}
/**
* Update the resources for the contact for the received presence.
* @param contact the contact which resources to update.
* @param fullJid the full jid to use, if null will use those from the
* presence packet
* @param presence the presence packet to use to get info.
* @return whether resource has been updated
*/
private boolean updateResource(ContactJabberImpl contact,
String fullJid,
Presence presence)
{
if(fullJid == null)
fullJid = presence.getFrom();
String resource = StringUtils.parseResource(fullJid);
if (resource != null && resource.length() > 0)
{
Map<String, ContactResourceJabberImpl> resources =
contact.getResourcesMap();
ContactResourceJabberImpl contactResource
= resources.get(fullJid);
PresenceStatus newPresenceStatus
= OperationSetPersistentPresenceJabberImpl
.jabberStatusToPresenceStatus(presence, parentProvider);
if (contactResource == null)
{
contactResource = createResource(presence, fullJid, contact);
resources.put(fullJid, contactResource);
contact.fireContactResourceEvent(
new ContactResourceEvent(contact, contactResource,
ContactResourceEvent.RESOURCE_ADDED));
return true;
}
else
{
boolean oldIndicator = contactResource.isMobile();
boolean newIndicator =
mobileIndicator.isMobileResource(resource, fullJid);
int oldPriority = contactResource.getPriority();
// update mobile indicator, as cabs maybe added after
// creating the resource for the contact
contactResource.setMobile(newIndicator);
contactResource.setPriority(presence.getPriority());
if(oldPriority != contactResource.getPriority())
{
// priority has been updated so update and the
// mobile indicator before firing an event
mobileIndicator.resourcesUpdated(contact);
}
if (contactResource.getPresenceStatus().getStatus()
!= newPresenceStatus.getStatus()
|| (oldIndicator != newIndicator)
|| (oldPriority != contactResource.getPriority()))
{
contactResource.setPresenceStatus(newPresenceStatus);
contact.fireContactResourceEvent(
new ContactResourceEvent(contact, contactResource,
ContactResourceEvent.RESOURCE_MODIFIED));
return true;
}
}
}
return false;
}
/**
* Removes the resource indicated by the fullJid from the list with
* resources for the contact.
* @param contact from its list of resources to remove
* @param fullJid the full jid.
* @return whether resource has been updated
*/
private boolean removeResource(ContactJabberImpl contact, String fullJid)
{
Map<String, ContactResourceJabberImpl> resources =
contact.getResourcesMap();
if (resources.containsKey(fullJid))
{
ContactResource removedResource = resources.remove(fullJid);
contact.fireContactResourceEvent(
new ContactResourceEvent(contact, removedResource,
ContactResourceEvent.RESOURCE_REMOVED));
return true;
}
return false;
}
/**
* Fires the status change, respecting resource priorities.
* @param presence the presence changed.
*/
void firePresenceStatusChanged(Presence presence)
{
if(contactChangesListener != null)
contactChangesListener.firePresenceStatusChanged(presence);
}
/**
* Updates contact status and its resources, fires PresenceStatusChange
* events.
*
* @param contact the contact which presence to update if needed.
* @param newStatus the new status.
*/
private void updateContactStatus(
ContactJabberImpl contact, PresenceStatus newStatus)
{
// When status changes this may be related to a change in the
// available resources.
boolean oldMobileIndicator = contact.isMobile();
boolean resourceUpdated = updateResources(contact, true);
mobileIndicator.resourcesUpdated(contact);
PresenceStatus oldStatus
= contact.getPresenceStatus();
// when old and new status are the same do nothing
// no change
if(oldStatus.equals(newStatus)
&& oldMobileIndicator == contact.isMobile())
{
return;
}
contact.updatePresenceStatus(newStatus);
if (logger.isDebugEnabled())
logger.debug("Will Dispatch the contact status event.");
fireContactPresenceStatusChangeEvent(
contact, contact.getParentContactGroup(),
oldStatus, newStatus,
resourceUpdated);
}
/**
* Manage changes of statuses by resource.
*/
class ContactChangesListener
implements RosterListener
{
/**
* Store events for later processing, used when
* initializing contactlist.
*/
private boolean storeEvents = false;
/**
* Stored presences for later processing.
*/
private List<Presence> storedPresences = null;
/**
* Map containing all statuses for a userID.
*/
private final Map<String, TreeSet<Presence>> statuses =
new Hashtable<String, TreeSet<Presence>>();
/**
* Not used here.
* @param addresses list of addresses added
*/
public void entriesAdded(Collection<String> addresses)
{}
/**
* Not used here.
* @param addresses list of addresses updated
*/
public void entriesUpdated(Collection<String> addresses)
{}
/**
* Not used here.
* @param addresses list of addresses deleted
*/
public void entriesDeleted(Collection<String> addresses)
{}
/**
* Not used here.
*/
public void rosterError(XMPPError error, Packet packet)
{}
/**
* Received on resource status change.
* @param presence presence that has changed
*/
public void presenceChanged(Presence presence)
{
firePresenceStatusChanged(presence);
}
/**
* Whether listener is currently storing presence events.
* @return
*/
boolean isStoringPresenceEvents()
{
return storeEvents;
}
/**
* Adds presence packet to the list.
* @param presence presence packet
*/
void addPresenceEvent(Presence presence)
{
storedPresences.add(presence);
}
/**
* Sets store events to true.
*/
void storeEvents()
{
this.storedPresences = new ArrayList<Presence>();
this.storeEvents = true;
}
/**
* Process stored presences.
*/
void processStoredEvents()
{
storeEvents = false;
for(Presence p : storedPresences)
{
firePresenceStatusChanged(p);
}
storedPresences.clear();
storedPresences = null;
}
/**
* Fires the status change, respecting resource priorities.
*
* @param presence the presence changed.
*/
void firePresenceStatusChanged(Presence presence)
{
if(storeEvents && storedPresences != null)
{
storedPresences.add(presence);
return;
}
try
{
String userID
= StringUtils.parseBareAddress(presence.getFrom());
OperationSetMultiUserChat mucOpSet =
parentProvider.getOperationSet(
OperationSetMultiUserChat.class);
if(mucOpSet != null)
{
List<ChatRoom> chatRooms
= mucOpSet.getCurrentlyJoinedChatRooms();
for(ChatRoom chatRoom : chatRooms)
{
if(chatRoom.getName().equals(userID))
{
userID = presence.getFrom();
break;
}
}
}
if (logger.isDebugEnabled())
logger.debug("Received a status update for buddy=" + userID);
// all contact statuses that are received from all its resources
// ordered by priority(higher first) and those with equal
// priorities order with the one that is most connected as
// first
TreeSet<Presence> userStats = statuses.get(userID);
if(userStats == null)
{
userStats = new TreeSet<Presence>(new Comparator<Presence>()
{
public int compare(Presence o1, Presence o2)
{
int res = o2.getPriority() - o1.getPriority();
// if statuses are with same priorities
// return which one is more available
// counts the JabberStatusEnum order
if(res == 0)
{
res = jabberStatusToPresenceStatus(
o2, parentProvider).getStatus()
- jabberStatusToPresenceStatus(
o1, parentProvider).getStatus();
// We have run out of "logical" ways to order
// the presences inside the TreeSet. We have
// make sure we are consinstent with equals.
// We do this by comparing the unique resource
// names. If this evaluates to 0 again, then we
// can safely assume this presence object
// represents the same resource and by that the
// same client.
if(res == 0)
{
res = o1.getFrom().compareTo(
o2.getFrom());
}
}
return res;
}
});
statuses.put(userID, userStats);
}
else
{
String resource = StringUtils.parseResource(
presence.getFrom());
// remove the status for this resource
// if we are online we will update its value with the new
// status
for (Iterator<Presence> iter = userStats.iterator();
iter.hasNext();)
{
Presence p = iter.next();
if (StringUtils.parseResource(p.getFrom()).equals(
resource))
iter.remove();
}
}
if(!jabberStatusToPresenceStatus(presence, parentProvider)
.equals(
parentProvider
.getJabberStatusEnum()
.getStatus(JabberStatusEnum.OFFLINE)))
{
userStats.add(presence);
}
Presence currentPresence;
if (userStats.size() == 0)
{
currentPresence = presence;
/*
* We no longer have statuses for userID so it doesn't make
* sense to retain (1) the TreeSet and (2) its slot in the
* statuses Map.
*/
statuses.remove(userID);
}
else
currentPresence = userStats.first();
ContactJabberImpl sourceContact
= ssContactList.findContactById(userID);
if (sourceContact == null)
{
logger.warn("No source contact found for id=" + userID);
return;
}
// statuses may be the same and only change in status message
sourceContact.setStatusMessage(currentPresence.getStatus());
updateContactStatus(
sourceContact,
jabberStatusToPresenceStatus(
currentPresence, parentProvider));
}
catch (IllegalStateException ex)
{
logger.error("Failed changing status", ex);
}
catch (IllegalArgumentException ex)
{
logger.error("Failed changing status", ex);
}
}
}
/**
* Listens for subscription events coming from stack.
*/
private class JabberSubscriptionListener
implements PacketListener
{
/**
* The authorization handler.
*/
private AuthorizationHandler handler = null;
/**
* List of early subscriptions.
*/
private Map<String, String> earlySubscriptions
= new HashMap<String, String>();
/**
* Adds auth handler.
*
* @param handler
*/
private synchronized void setHandler(AuthorizationHandler handler)
{
this.handler = handler;
handleEarlySubscribeReceived();
}
/**
* Process packets.
* @param packet packet received to be processed
*/
public void processPacket(Packet packet)
{
Presence presence = (Presence) packet;
if (presence == null)
return;
Presence.Type presenceType = presence.getType();
final String fromID = presence.getFrom();
if (presenceType == Presence.Type.subscribe)
{
String displayName = null;
Nick ext = (Nick) presence.getExtension(Nick.NAMESPACE);
if(ext != null)
displayName = ext.getName();
synchronized(this)
{
if(handler == null)
{
earlySubscriptions.put(fromID, displayName);
// nothing to handle
return;
}
}
handleSubscribeReceived(fromID, displayName);
}
else if (presenceType == Presence.Type.unsubscribed)
{
if (logger.isTraceEnabled())
logger.trace(fromID + " does not allow your subscription");
if(handler == null)
{
logger.warn(
"No to handle unsubscribed AuthorizationHandler for "
+ fromID);
return;
}
ContactJabberImpl contact
= ssContactList.findContactById(fromID);
if(contact != null)
{
AuthorizationResponse response
= new AuthorizationResponse(
AuthorizationResponse.REJECT,
"");
handler.processAuthorizationResponse(response, contact);
try{
ssContactList.removeContact(contact);
}
catch(OperationFailedException e)
{
logger.error(
"Cannot remove contact that unsubscribed.");
}
}
}
else if (presenceType == Presence.Type.subscribed)
{
if(handler == null)
{
logger.warn(
"No AuthorizationHandler to handle subscribed for "
+ fromID);
return;
}
ContactJabberImpl contact
= ssContactList.findContactById(fromID);
AuthorizationResponse response
= new AuthorizationResponse(
AuthorizationResponse.ACCEPT,
"");
handler.processAuthorizationResponse(response, contact);
}
else if (presenceType == Presence.Type.available
&& contactChangesListener != null
&& contactChangesListener.isStoringPresenceEvents())
{
contactChangesListener.addPresenceEvent(presence);
}
}
/**
* Handles early presence subscribe that were received.
*/
private void handleEarlySubscribeReceived()
{
for(String from : earlySubscriptions.keySet())
{
handleSubscribeReceived(from, earlySubscriptions.get(from));
}
earlySubscriptions.clear();
}
/**
* Handles receiving a presence subscribe
* @param fromID sender
*/
private void handleSubscribeReceived(final String fromID,
final String displayName)
{
// run waiting for user response in different thread
// as this seems to block the packet dispatch thread
// and we don't receive anything till we unblock it
new Thread(new Runnable() {
public void run()
{
if (logger.isTraceEnabled())
{
logger.trace(
fromID
+ " wants to add you to its contact list");
}
// buddy want to add you to its roster
ContactJabberImpl srcContact
= ssContactList.findContactById(fromID);
Presence.Type responsePresenceType = null;
if(srcContact == null)
{
srcContact = createVolatileContact(fromID, displayName);
}
else
{
if(srcContact.isPersistent())
responsePresenceType = Presence.Type.subscribed;
}
if(responsePresenceType == null)
{
AuthorizationRequest req = new AuthorizationRequest();
AuthorizationResponse response
= handler.processAuthorisationRequest(
req, srcContact);
if(response != null)
{
if(response.getResponseCode()
.equals(AuthorizationResponse.ACCEPT))
{
responsePresenceType
= Presence.Type.subscribed;
if (logger.isInfoEnabled())
logger.info(
"Sending Accepted Subscription");
}
else if(response.getResponseCode()
.equals(AuthorizationResponse.REJECT))
{
responsePresenceType
= Presence.Type.unsubscribed;
if (logger.isInfoEnabled())
logger.info(
"Sending Rejected Subscription");
}
}
}
// subscription ignored
if(responsePresenceType == null)
return;
Presence responsePacket = new Presence(
responsePresenceType);
responsePacket.setTo(fromID);
parentProvider.getConnection().sendPacket(responsePacket);
}}).start();
}
}
/**
* Runnable that resolves our list against the server side roster.
* This thread is the one which will call getRoster for the first time.
* And if roster is currently processing will wait for it (the wait
* is internal into XMPPConnection.getRoster method).
*/
private class ServerStoredListInit
implements Runnable,
PacketListener
{
public void run()
{
// we are already notified lets remove us from the packet
// listener
parentProvider.getConnection()
.removePacketListener(this);
// init ssList
ssContactList.init(contactChangesListener);
// as we have dispatched the contact list and Roster is ready
// lets start the jingle nodes discovery
parentProvider.startJingleNodesDiscovery();
}
/**
* When roster packet with no error is received we are ready to
* to dispatch the contact list, doing it in different thread
* to avoid blocking xmpp packet receiving.
* @param packet the roster packet
*/
public void processPacket(Packet packet)
{
// don't process packets that are errors
if(packet.getError() != null)
{
return;
}
new Thread(this, getClass().getName()).start();
}
}
/**
* Creates an interceptor which modifies presence packet in order to add the
* the element name "x" and the namespace "vcard-temp:x:update" in order to
* advertise the avatar SHA-1 hash.
*/
public void createAccountPhotoPresenceInterceptor()
{
// Verifies that we creates only one interceptor of this type.
if(this.vCardTempXUpdatePresenceExtension == null)
{
byte[] avatar = null;
try
{
// Retrieves the current server avatar.
VCard vCard = new VCard();
vCard.load(parentProvider.getConnection());
avatar = vCard.getAvatar();
}
catch(XMPPException ex)
{
logger.info("Can not retrieve account avatar for "
+ parentProvider.getOurJID() + ": " + ex.getMessage());
}
// Creates the presence extension to generates the the element
// name "x" and the namespace "vcard-temp:x:update" containing
// the avatar SHA-1 hash.
this.vCardTempXUpdatePresenceExtension =
new VCardTempXUpdatePresenceExtension(avatar);
// Intercepts all sent presence packet in order to add the
// photo tag.
parentProvider.getConnection().addPacketInterceptor(
this.vCardTempXUpdatePresenceExtension,
new PacketTypeFilter(Presence.class));
}
}
/**
* Updates the presence extension to advertise a new photo SHA-1 hash
* corresponding to the new avatar given in parameter.
*
* @param imageBytes The new avatar set for this account.
*/
public void updateAccountPhotoPresenceExtension(byte[] imageBytes)
{
try
{
// If the image has changed, then updates the presence extension and
// send immediately a presence packet to advertise the photo update.
if(this.vCardTempXUpdatePresenceExtension.updateImage(imageBytes))
{
this.publishPresenceStatus(currentStatus, currentStatusMessage);
}
}
catch(OperationFailedException ex)
{
logger.info(
"Can not send presence extension to broadcast photo update",
ex);
}
}
/**
* Creates a listener to call a parser which manages presence packets with
* the element name "x" and the namespace "vcard-temp:x:update".
*/
public void createContactPhotoPresenceListener()
{
// Registers the listener.
parentProvider.getConnection().addPacketListener(
new PacketListener()
{
public void processPacket(Packet packet)
{
// Calls the parser to manages this presence packet.
parseContactPhotoPresence(packet);
}
},
// Creates a filter to only listen to presence packet with the
// element name "x" and the namespace "vcard-temp:x:update".
new AndFilter(new PacketTypeFilter(Presence.class),
new PacketExtensionFilter(
VCardTempXUpdatePresenceExtension.ELEMENT_NAME,
VCardTempXUpdatePresenceExtension.NAMESPACE)
)
);
}
/**
* Parses a contact presence packet with the element name "x" and the
* namespace "vcard-temp:x:update", in order to decide if the SHA-1 avatar
* contained in the photo tag represents a new avatar for this contact.
*
* @param packet The packet received to parse.
*/
public void parseContactPhotoPresence(Packet packet)
{
// Retrieves the contact ID and its avatar that Jitsi currently
// managed concerning the peer that has send this presence packet.
String userID
= StringUtils.parseBareAddress(packet.getFrom());
ContactJabberImpl sourceContact
= ssContactList.findContactById(userID);
/**
* If this contact is not yet in our contact list, then there is no need
* to manage this photo update.
*/
if(sourceContact == null)
{
return;
}
byte[] currentAvatar = sourceContact.getImage(false);
// Get the packet extension which contains the photo tag.
DefaultPacketExtension defaultPacketExtension =
(DefaultPacketExtension) packet.getExtension(
VCardTempXUpdatePresenceExtension.ELEMENT_NAME,
VCardTempXUpdatePresenceExtension.NAMESPACE);
if(defaultPacketExtension != null)
{
try
{
String packetPhotoSHA1 =
defaultPacketExtension.getValue("photo");
// If this presence packet has a photo tag with a SHA-1 hash
// which differs from the current avatar SHA-1 hash, then Jitsi
// retrieves the new avatar image and updates this contact image
// in the contact list.
if(packetPhotoSHA1 != null
&& !packetPhotoSHA1.equals(
VCardTempXUpdatePresenceExtension.getImageSha1(
currentAvatar))
)
{
byte[] newAvatar = null;
// If there is an avatar image, retrieves it.
if(packetPhotoSHA1.length() != 0)
{
// Retrieves the new contact avatar image.
VCard vCard = new VCard();
vCard.load(parentProvider.getConnection(), userID);
newAvatar = vCard.getAvatar();
}
// Else removes the current avatar image, since the contact
// has removed it from the server.
else
{
newAvatar = new byte[0];
}
// Sets the new avatar image to the Jitsi contact.
sourceContact.setImage(newAvatar);
// Fires a property change event to update the contact list.
this.fireContactPropertyChangeEvent(
ContactPropertyChangeEvent.PROPERTY_IMAGE,
sourceContact,
currentAvatar,
newAvatar);
}
}
catch(XMPPException ex)
{
logger.info("Cannot retrieve vCard from: " + packet.getFrom());
if(logger.isTraceEnabled())
logger.trace("vCard retrieval exception was: ", ex);
}
}
}
/**
* Initializes the map with priorities and statuses which we will use when
* changing statuses.
*/
private void initializePriorities()
{
try
{
this.resourcePriorityAvailable =
Integer.parseInt(parentProvider.getAccountID()
.getAccountPropertyString(
ProtocolProviderFactory.RESOURCE_PRIORITY));
}
catch(NumberFormatException ex)
{
logger.error("Wrong value for resource priority", ex);
}
addDefaultValue(JabberStatusEnum.AWAY, -5);
addDefaultValue(JabberStatusEnum.EXTENDED_AWAY, -10);
addDefaultValue(JabberStatusEnum.ON_THE_PHONE, -15);
addDefaultValue(JabberStatusEnum.IN_A_MEETING, -16);
addDefaultValue(JabberStatusEnum.DO_NOT_DISTURB, -20);
addDefaultValue(JabberStatusEnum.FREE_FOR_CHAT, +5);
}
/**
* Checks for account property that can override this status.
* If missing use the shift value to create the priority to use, make sure
* it is not zero or less than it.
* @param statusName the status to check/create priority
* @param availableShift the difference from available resource
* value to use.
*/
private void addDefaultValue(String statusName, int availableShift)
{
String resourcePriority = getAccountPriorityForStatus(statusName);
if(resourcePriority != null)
{
try
{
addPresenceToPriorityMapping(
statusName,
Integer.parseInt(resourcePriority));
}
catch(NumberFormatException ex)
{
logger.error(
"Wrong value for resource priority for status: "
+ statusName, ex);
}
}
else
{
// if priority is less than zero, use the available priority
int priority = resourcePriorityAvailable + availableShift;
if(priority <= 0)
priority = resourcePriorityAvailable;
addPresenceToPriorityMapping(statusName, priority);
}
}
/**
* Adds the priority mapping for the <tt>statusName</tt>.
* Make sure we replace ' ' with '_' and use upper case as this will be
* and the property names used in account properties that can override
* this values.
* @param statusName the status name to use
* @param value and its priority
*/
private static void addPresenceToPriorityMapping(String statusName,
int value)
{
statusToPriorityMappings.put(
statusName.replaceAll(" ", "_").toUpperCase(), value);
}
/**
* Returns the priority which will be used for <tt>statusName</tt>.
* Make sure we replace ' ' with '_' and use upper case as this will be
* and the property names used in account properties that can override
* this values.
* @param statusName the status name
* @return the priority which will be used for <tt>statusName</tt>.
*/
private int getPriorityForPresenceStatus(String statusName)
{
Integer priority = statusToPriorityMappings.get(
statusName.replaceAll(" ", "_").toUpperCase());
if(priority == null)
return resourcePriorityAvailable;
return priority;
}
/**
* Returns the account property value for a status name, if missing return
* null.
* Make sure we replace ' ' with '_' and use upper case as this will be
* and the property names used in account properties that can override
* this values.
* @param statusName
* @return the account property value for a status name, if missing return
* null.
*/
private String getAccountPriorityForStatus(String statusName)
{
return parentProvider.getAccountID().getAccountPropertyString(
ProtocolProviderFactory.RESOURCE_PRIORITY + "_" +
statusName.replaceAll(" ", "_").toUpperCase());
}
/**
* Returns the contactlist impl.
* @return
*/
public ServerStoredContactListJabberImpl getSsContactList()
{
return ssContactList;
}
}