/*
* 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.plugin.msofficecomm;
import java.beans.*;
import java.util.*;
import javax.swing.*;
import net.java.sip.communicator.service.contactlist.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.ServerStoredDetails.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.jabberconstants.*;
import net.java.sip.communicator.service.protocol.yahooconstants.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.configuration.*;
import org.jitsi.util.xml.*;
import org.osgi.framework.*;
import org.w3c.dom.*;
/**
* Represents the Java counterpart of a native IMessenger
* implementation.
*
* @author Lyubomir Marinov
*/
public class Messenger
{
/**
* The Logger used by the Messenger class and its
* instances for logging output.
*/
private static final Logger logger = Logger.getLogger(Messenger.class);
static final int CONVERSATION_TYPE_AUDIO = 8;
static final int CONVERSATION_TYPE_IM = 1;
static final int CONVERSATION_TYPE_LIVEMEETING = 4;
static final int CONVERSATION_TYPE_PHONE = 2;
static final int CONVERSATION_TYPE_PSTN = 32;
static final int CONVERSATION_TYPE_VIDEO = 16;
static final int MISTATUS_AWAY = 0x0022;
static final int MISTATUS_MAY_BE_AVAILABLE = 0x00A2;
static final int MISTATUS_OFFLINE = 0x0001;
/**
* The MISTATUS value which indicates that the local or remote
* client user is on the phone.
*/
static final int MISTATUS_ON_THE_PHONE = 0x0032;
static final int MISTATUS_IN_A_MEETING = 0x0052;
static final int MISTATUS_ONLINE = 0x0002;
static final int MISTATUS_UNKNOWN = 0x0000;
static final int MPHONE_TYPE_CUSTOM = 3;
/**
* The MPHONE_TYPE value which indicates a home phone number.
*/
static final int MPHONE_TYPE_HOME = 0;
/**
* The MPHONE_TYPE value which indicates a mobile phone number.
*/
static final int MPHONE_TYPE_MOBILE = 2;
/**
* The MPHONE_TYPE value which indicates a work phone number.
*/
static final int MPHONE_TYPE_WORK = 1;
/**
* The name of the boolean ConfigurationService property which
* indicates whether {@link #startConversation(int, String[], String)} is
* to invoke {@link UIService#createCall(String[])} with a phone number
* associated with a specific IMessengerContact instead of the
* Contact address.
*/
private static final String PNAME_CREATE_CALL_BY_PHONE_NUMBER
= "net.java.sip.communicator.plugin.msofficecomm."
+ "CREATE_CALL_BY_PHONE_NUMBER";
/**
* The name of the String ConfigurationService property
* which specifies the sort order of the MPHONE_TYPE_* enumerated
* type values. The default value orders them as follows:
* {@link #MPHONE_TYPE_WORK}, {@link #MPHONE_TYPE_HOME},
* {@link #MPHONE_TYPE_MOBILE}, {@link #MPHONE_TYPE_CUSTOM}.
*/
private static final String PNAME_MPHONE_TYPE_SORT_ORDER
= "net.java.sip.communicator.plugin.msofficecomm."
+ "MPHONE_TYPE_SORT_ORDER";
/**
* The BundleContext in which the msofficecomm bundle has
* been started.
*/
private static BundleContext bundleContext;
/**
* The MetaContactListService which the Messenger class
* looks through in order to locate Contacts associated with a
* specific sign-in name.
*/
private static MetaContactListService metaContactListService;
/**
* The MPHONE_TYPE_* enumerated type values indexed by their sort
* order position.
*/
private static final int[] MPHONE_TYPE_SORT_ORDER
= new int[]
{
MPHONE_TYPE_WORK,
MPHONE_TYPE_HOME,
MPHONE_TYPE_MOBILE,
MPHONE_TYPE_CUSTOM
};
/**
* The list of (local) accounts by sign-in name which correspond to
* IMessengerContact implementations having true as the
* value of their self boolean property.
*/
private static final Map selves = new HashMap();
/**
* The ServiceListener which listens to the BundleContext
* in which the msofficecomm bundle has been started for service
* changes.
*/
private static final ServiceListener serviceListener
= new ServiceListener()
{
public void serviceChanged(ServiceEvent event)
{
Messenger.serviceChanged(event);
}
};
static
{
String lib = "jmsofficecomm";
try
{
System.loadLibrary(lib);
}
catch (Throwable t)
{
logger.error(
"Failed to load native library " + lib + ": "
+ t.getMessage());
RegistryHandler.checkRegistryKeys();
throw new RuntimeException(t);
}
}
private static synchronized void addSelf(
String signinName,
ProtocolProviderService pps,
OperationSetPresence presenceOpSet)
{
Self self = selves.get(signinName);
if (self == null)
{
self = new Self(signinName);
selves.put(signinName, self);
}
self.addProtocolProviderService(pps, presenceOpSet);
}
/**
* Finds the Contact instances which are associated with a specific
* IMessengerContact sign-in name and which originate from a
* specific ProtocolProviderService instance.
*
* @param pps the ProtocolProviderService from which possibly
* found Contact instances are to originate
* @param presenceOpSet the OperationSetPresence associated with
* the specified pps
* @param signinName the IMessengerContact sign-in name for which
* the associated Contact instances are to be found
* @return a list of Contact instances which are associated with
* the specified signinName and which originate from the specified
* pps if such Contact instances have been found;
* otherwise, an empty list
*/
private static List findContactsBySigninName(
ProtocolProviderService pps,
OperationSetPresence presenceOpSet,
String signinName)
{
List contacts = new ArrayList();
for (Iterator metaContactIt
= metaContactListService.findAllMetaContactsForProvider(
pps);
metaContactIt.hasNext();)
{
MetaContact metaContact = metaContactIt.next();
for (Iterator contactIt = metaContact.getContacts();
contactIt.hasNext();)
{
Contact contact = contactIt.next();
if (signinName.equalsIgnoreCase(getSigninName(contact, pps)))
{
/*
* Prefer matches by Contact address over
* EmailAddressDetail.
*/
contacts.add(0, contact);
continue;
}
OperationSetServerStoredContactInfo serverStoredContactInfoOpSet
= pps.getOperationSet(
OperationSetServerStoredContactInfo.class);
if (serverStoredContactInfoOpSet != null)
{
for (Iterator emailAddressDetailIt
= serverStoredContactInfoOpSet
.getDetailsAndDescendants(
contact,
EmailAddressDetail.class);
emailAddressDetailIt.hasNext();)
{
EmailAddressDetail emailAddressDetail
= emailAddressDetailIt.next();
if (signinName.equalsIgnoreCase(
emailAddressDetail.getEMailAddress()))
{
contacts.add(contact);
break;
}
}
}
}
}
return contacts;
}
/**
* Gets the Contact instances which are associated with a specific
* IMessengerContact sign-in name and which support a specific
* OperationSet.
*
* @param signinName the IMessengerContact sign-in name for which
* the associated Contact instances are to be found
* @param opSetClass the OperationSet class to be supported by the
* possibly found Contact instances
* @param limit the maximum number of found Contacts at which the
* search should stop or {@link Integer#MAX_VALUE} if the search is to be
* unbound with respect to the number of found Contacts
* @return a list of Contact instances which are associated with
* the specified signinName and which support the specified
* opSetClass if such Contact instances have been found;
* otherwise, an empty list
*/
private static List findContactsBySigninName(
String signinName,
Class extends OperationSet> opSetClass,
int limit)
{
List contacts = new ArrayList();
for (Self self : selves.values())
{
self.findContactsBySigninName(
signinName, opSetClass, limit,
contacts);
/* Obey the specified limit of the number of found Contacts. */
if (contacts.size() >= limit)
break;
}
return contacts;
}
/**
* Gets the PhoneNumberDetail instances which are associated with a
* specific IMessengerContact sign-in name.
*
* @param signinName the IMessengerContact sign-in name for which
* the associated PhoneNumberDetail instances are to be found
* @param limit the maximum number of found PhoneNumberDetails at
* which the search should stop or {@link Integer#MAX_VALUE} if the search
* is to be unbound with respect to the number of found
* PhoneNumberDetails
* @return a list of PhoneNumberDetail instances which are
* associated with the specified signinName if such
* PhoneNumberDetail instances have been found; otherwise, an empty
* list
*/
private static Set findPhoneNumbersBySigninName(
String signinName,
int limit)
{
/*
* XXX The limit is not being obeyed at this time because the
* PhoneNumberDetails are ordered by MPHONE_TYPE.
*/
Set phoneNumbers
= new TreeSet(
new Comparator()
{
public int compare(
PhoneNumberDetail pn1,
PhoneNumberDetail pn2)
{
int so1
= getMPHONE_TYPESortOrder(getMPHONE_TYPE(pn1));
int so2
= getMPHONE_TYPESortOrder(getMPHONE_TYPE(pn2));
return
(so1 == so2)
? pn1.getNumber().compareTo(
pn2.getNumber())
: (so1 - so2);
}
});
for (Self self : selves.values())
{
self.findPhoneNumbersBySigninName(
signinName,
Integer.MAX_VALUE,
phoneNumbers);
}
return phoneNumbers;
}
/**
* Gets an MPHONE_TYPE enumerated type value which indicates the
* phone number type of a specific PhoneNumberDetail.
*
* @param phoneNumber the PhoneNumberDetail for which a matching
* MPHONE_TYPE enumerated type value is to be retrieved
* @return an MPHONE_TYPE enumerated type value which indicates the
* phone number type of the speciifed phoneNumber
*/
private static int getMPHONE_TYPE(PhoneNumberDetail phoneNumber)
{
if (phoneNumber.getClass().equals(PhoneNumberDetail.class))
return MPHONE_TYPE_HOME;
else if (phoneNumber instanceof MobilePhoneDetail)
return MPHONE_TYPE_MOBILE;
else if (phoneNumber instanceof WorkPhoneDetail)
return MPHONE_TYPE_WORK;
else
return MPHONE_TYPE_CUSTOM;
}
/**
* Gets an int value which specifies the sort order position of a
* specific MPHONE_TYPE enumerated type value which can be used
* with a Comparator implementation.
*
* @param mphonetype the MPHONE_TYPE enumerated type value for
* which the sort order position is to be retrieved
* @return an int value which specifies the sort order position of
* the specified MPHONE_TYPE enumerated type value which can be
* used with a Comparator implementation
*/
private static int getMPHONE_TYPESortOrder(int mphonetype)
{
for (int i = 0; i < MPHONE_TYPE_SORT_ORDER.length; i++)
if (MPHONE_TYPE_SORT_ORDER[i] == mphonetype)
return i;
return MPHONE_TYPE_SORT_ORDER.length;
}
/**
* Gets the phone number information of the contact associated with a
* specific MessengerContact instance.
*
* @param messengerContact a MessengerContact instance which
* specifies the contact for which the phone number information is to be
* retrieved
* @param type member of the MPHONE_TYPE enumerated type which
* specifies the type of the phone number information to be retrieved
* @return the phone number information of the contact associated with the
* specified messengerContact
*/
static String getPhoneNumber(MessengerContact messengerContact, int type)
{
Set phoneNumbers
= findPhoneNumbersBySigninName(
messengerContact.signinName,
Integer.MAX_VALUE);
for (PhoneNumberDetail phoneNumber : phoneNumbers)
if (getMPHONE_TYPE(phoneNumber) == type)
return phoneNumber.getNumber();
return null;
}
/**
* Gets the (local) account associated with a specific sign-in name in the
* form of a Self instance if the specified sign-in name is
* associated with such a (local) account.
*
* @param signinName the sign-in name associated with the (local) account to
* be retrieved
* @return a Self instance describing a (local) account associated
* with the specified signinName if such a Self instance
* exists; otherwise, null
*/
private static Self getSelf(String signinName)
{
Self self = selves.get(signinName);
if (self == null)
{
for (Self aSelf : selves.values())
{
if (aSelf.isSelf(signinName))
{
self = aSelf;
break;
}
}
}
return self;
}
/**
* Gets the IMessengerContact sign-in name associated with a
* specific Contact from a specific
* ProtocolProviderService. If no Contact is specified,
* gets the sign-in name associated with the AccountID of the
* specified ProtocolProviderService.
*
* @param contact the Contact to retrieve the sign-in name of or
* null to retrieve the sign-in name associated with the
* AccountID of the specified pps
* @param pps the ProtocolProviderService of contact if
* contact is other than null or of the AccountID
* to get the sign-in name of if contact is null
* @return the sign-in name associated with the specified contact
* from the specified pps if contact is other than
* null or with the AccountID of the specified
* pps if contact is null
*/
private static String getSigninName(
Contact contact,
ProtocolProviderService pps)
{
String address
= (contact == null)
? pps.getAccountID().getAccountAddress()
: contact.getAddress();
String signinName;
if (address.contains("@"))
{
String protocol
= ((pps == null) ? contact.getProtocolProvider() : pps)
.getProtocolName()
+ ":";
if (address.toLowerCase().startsWith(protocol.toLowerCase()))
signinName = address.substring(protocol.length());
else
signinName = address;
}
else
signinName = null;
return signinName;
}
/**
* Gets the connection/presence status of the contact associated with a
* specific MessengerContact instance in the form of a
* MISTATUS value.
*
* @param messengerContact a MessengerContact instance which
* specifies the contact for which the connection/presence status is to be
* retrieved
* @return a MISTATUS value which represents the
* connection/presence status of the contact associated with the specified
* messengerContact
*/
static int getStatus(MessengerContact messengerContact)
{
String signinName = messengerContact.signinName;
ProtocolPresenceStatus presenceStatus;
if(logger.isTraceEnabled())
logger.trace("Got getStatus for " + signinName);
if (signinName == null)
presenceStatus = null;
else
{
Self self = getSelf(signinName);
if (self == null)
{
presenceStatus = null;
for (Self aSelf : selves.values())
{
ProtocolPresenceStatus aPresenceStatus
= aSelf.getPresenceStatus(signinName);
if (aPresenceStatus != null)
{
if (presenceStatus == null)
presenceStatus = aPresenceStatus;
else if (presenceStatus.compareTo(aPresenceStatus) < 0)
presenceStatus = aPresenceStatus;
if (presenceStatus.toInt()
>= PresenceStatus.MAX_STATUS_VALUE)
break;
}
}
}
else
presenceStatus = self.getPresenceStatus();
}
return ProtocolPresenceStatus.toMISTATUS(presenceStatus);
}
/**
* Gets the indicator which determines whether a specific
* MessengerContact is the same user as the current client user.
*
* @param messengerContact the MessengerContact which is to be
* determined whether it is the same user as the current client user
* @return true if the specified messengerContact is the
* same user as the current client user; otherwise, false
*/
static boolean isSelf(MessengerContact messengerContact)
{
String signinName = messengerContact.signinName;
return (signinName == null) ? false : (getSelf(signinName) != null);
}
private static native void onContactStatusChange(
String signinName,
int status);
private static synchronized void removeSelf(
String signinName,
ProtocolProviderService pps)
{
Self self = selves.get(signinName);
if ((self != null) && (self.removeProtocolProviderService(pps) < 1))
{
for (Iterator it = selves.values().iterator(); it.hasNext();)
{
if (it.next() == self)
it.remove();
}
self.dispose();
}
}
/**
* Notifies the Messenger class about a service change in the
* BundleContext in which the msofficecomm bundle has been
* started
*
* @param event a ServiceEvent describing the service change in the
* BundleContext in which the msofficecomm bundle has been
* started
*/
private static void serviceChanged(ServiceEvent event)
{
Object service = bundleContext.getService(event.getServiceReference());
if (service instanceof ProtocolProviderService)
{
ProtocolProviderService pps = (ProtocolProviderService) service;
/*
* The Messenger class implements an integration of Jitsi presence
* into Microsoft Office so the only accounts of interest to it are
* the ones which support presence.
*/
OperationSetPresence presenceOpSet
= pps.getOperationSet(OperationSetPresence.class);
if (presenceOpSet != null)
{
String signinName = getSigninName(null, pps);
if (signinName != null)
{
switch (event.getType())
{
case ServiceEvent.REGISTERED:
addSelf(signinName, pps, presenceOpSet);
break;
case ServiceEvent.UNREGISTERING:
removeSelf(signinName, pps);
break;
}
}
}
}
}
/**
* Starts the Messenger class and instance functionality in a
* specific BundleContext.
*
* @param bundleContext the BundleContext in which the
* Messenger class and instance functionality is to be started
* @throws Exception if anything goes wrong while starting the
* Messenger class and instance functionality in the specified
* BundleContext
*/
static synchronized void start(BundleContext bundleContext)
throws Exception
{
Messenger.bundleContext = bundleContext;
metaContactListService
= ServiceUtils.getService(
bundleContext,
MetaContactListService.class);
bundleContext.addServiceListener(serviceListener);
ServiceReference[] serviceReferences
= bundleContext.getServiceReferences(
ProtocolProviderService.class.getName(),
null);
if ((serviceReferences != null) && (serviceReferences.length != 0))
{
for (ServiceReference serviceReference : serviceReferences)
{
serviceListener.serviceChanged(
new ServiceEvent(
ServiceEvent.REGISTERED,
serviceReference));
}
}
if (logger.isInfoEnabled())
logger.info("Messenger [REGISTERED] as service listener.");
}
/**
* Stops the Messenger class and instance functionality in a
* specific BundleContext.
*
* @param bundleContext the BundleContext in which the
* Messenger class and instance functionality is to be stopped
* @throws Exception if anything goes wrong while stopping the
* Messenger class and instance functionality in the specified
* BundleContext
*/
static synchronized void stop(BundleContext bundleContext)
throws Exception
{
bundleContext.removeServiceListener(serviceListener);
Messenger.bundleContext = null;
metaContactListService = null;
// selves
for (Iterator it = selves.values().iterator(); it.hasNext();)
{
it.next().dispose();
it.remove();
}
}
/**
* Initializes a new Messenger instance which is to represent the
* Java counterpart of a native IMessenger implementation.
*/
public Messenger()
{
}
/**
* Starts a conversation with one or more other users using text, voice,
* video, or data.
*
* @param conversationType a CONVERSATION_TYPE value specifying the
* type of the conversation to be started
* @param participants an array of String values specifying the
* other users to start a conversation with
* @param conversationData an XML BLOB specifying the phone numbers to be
* dialed in order to start the conversation
*/
public void startConversation(
final int conversationType,
String[] participants,
String conversationData)
{
if(logger.isTraceEnabled())
logger.trace("Got startConversation participants:"
+ participants == null? "" : Arrays.asList(participants)
+ ", conversationData=" + conversationData);
/*
* Firstly, resolve the participants into Contacts which may include
* looking up their vCards.
*/
Class extends OperationSet> opSetClass;
switch (conversationType)
{
case CONVERSATION_TYPE_AUDIO:
case CONVERSATION_TYPE_PHONE:
case CONVERSATION_TYPE_PSTN:
opSetClass = OperationSetBasicTelephony.class;
if ((conversationData != null) && (conversationData.length() != 0))
{
if (logger.isTraceEnabled())
{
logger.trace(
"conversationData = \"" + conversationData + "\"");
}
// According to MSDN, vConversationData could be an XML BLOB.
if (conversationData.startsWith("<"))
{
try
{
Document document
= XMLUtils.createDocument(conversationData);
Element documentElement = document.getDocumentElement();
if ("TelURIs".equalsIgnoreCase(
documentElement.getTagName()))
{
NodeList childNodes
= documentElement.getChildNodes();
if (childNodes != null)
{
int childNodeCount = childNodes.getLength();
List phoneNumbers
= new ArrayList(childNodeCount);
for (int childNodeIndex = 0;
childNodeIndex < childNodeCount;
childNodeIndex++)
{
Node childNode
= childNodes.item(childNodeIndex);
if (childNode.getNodeType()
== Node.ELEMENT_NODE)
{
phoneNumbers.add(
childNode.getTextContent());
}
}
int count = participants.length;
if (phoneNumbers.size() == count)
{
for (int i = 0; i < count; i++)
{
String phoneNumber
= phoneNumbers.get(i);
if ((phoneNumber != null)
&& (phoneNumber.length() != 0))
{
if (phoneNumber
.toLowerCase()
.startsWith("tel:"))
{
phoneNumber
= phoneNumber.substring(4);
}
participants[i] = phoneNumber;
}
}
}
}
}
}
catch (Exception e)
{
logger.error(
"Failed to parse"
+ " IMessengerAdvanced::StartConversation"
+ " vConversationData: "
+ conversationData,
e);
}
}
else
{
/*
* Practice/testing shows that vConversationData is the
* phone number in the case of a single participant.
*/
if (participants.length == 1)
participants[0] = conversationData;
}
}
break;
case CONVERSATION_TYPE_IM:
opSetClass = OperationSetBasicInstantMessaging.class;
break;
default:
throw new UnsupportedOperationException();
}
List contactList = new ArrayList();
ConfigurationService cfg
= ServiceUtils.getService(
bundleContext,
ConfigurationService.class);
boolean createCallByPhoneNumber
= (cfg != null)
&& cfg.getBoolean(PNAME_CREATE_CALL_BY_PHONE_NUMBER, false);
for (String participant : participants)
{
List participantContacts
= findContactsBySigninName(participant, opSetClass, 1);
if (participantContacts.size() > 0)
{
contactList.add(
getSigninName(participantContacts.get(0), null));
}
else if (opSetClass.equals(OperationSetBasicTelephony.class))
{
/*
* The boolean ConfigurationService property
* PNAME_CREATE_CALL_BY_PHONE_NUMBER enables instructing whether
* a sign-in name which does not resolve to a Contact with
* support for OperationSetBasicTelephony is to be resolved to
* a phone number (via an associated vCard).
*/
if (createCallByPhoneNumber)
{
Set participantPhoneNumbers
= findPhoneNumbersBySigninName(participant, 1);
if (participantPhoneNumbers.size() > 0)
{
contactList.add(
participantPhoneNumbers.iterator().next()
.getNumber());
}
}
else
{
/*
* There is no Contact for the specified participant which
* supports OperationSetBasicTelephony. Try without the
* support restriction.
*/
participantContacts
= findContactsBySigninName(participant, null, 1);
if (participantContacts.size() > 0)
{
contactList.add(
getSigninName(
participantContacts.get(0),
null));
}
else
{
/*
* Well, just try to start a conversation with the
* unresolved contact.
*/
contactList.add(participant);
}
}
}
}
final String[] contactArray
= contactList.toArray(new String[contactList.size()]);
/*
* Secondly, start the conversation of the specified type with the
* resolved Contacts.
*/
SwingUtilities.invokeLater(
new Runnable()
{
public void run()
{
BundleContext bundleContext = Messenger.bundleContext;
if (bundleContext != null)
{
UIService uiService
= ServiceUtils.getService(
bundleContext,
UIService.class);
if (uiService != null)
{
switch (conversationType)
{
case CONVERSATION_TYPE_AUDIO:
case CONVERSATION_TYPE_PHONE:
case CONVERSATION_TYPE_PSTN:
uiService.createCall(contactArray);
break;
case CONVERSATION_TYPE_IM:
uiService.startChat(contactArray);
break;
}
}
}
}
});
}
/**
* Represents a presence status reported by a specific protocol. Allows
* distinguishing statuses more specific than the ranges defined by the
* PresenceStatus class.
*/
private static class ProtocolPresenceStatus
{
/**
* The PresenceStatus instance represented by this instance.
*/
private PresenceStatus presenceStatus;
/**
* The name of the protocol from which {@link #presenceStatus} has
* originated. Allows translating presenceStatus to a
* MISTATUS value which is equivalent to a protocol-specific
* status not defined in the generic PresenceStatus class.
*/
private String protocolName;
/**
* Initializes a new ProtocolPresenceStatus instance which is
* to represent a specific PresenceStatus originating from a
* specific protocol.
*
* @param protocolName the name of the protocol from which the specified
* PresenceStatus has originated
* @param presenceStatus the PresenceStatus to be represented
* by the new instance
*/
public ProtocolPresenceStatus(
String protocolName,
PresenceStatus presenceStatus)
{
setPresenceStatus(protocolName, presenceStatus);
}
/**
* Returns -1, 0 or 1 if the
* PresenceStatus represented by this instance is,
* respectively, less than, equal to or greater than a specific
* PresenceStatus.
*
* @param presenceStatus the PresenceStatus this instance is to
* be compared to
* @return -1, 0 or 1 if the
* PresenceStatus represented by this instance is, respectively,
* less than, equal to or greater than the specified
* presenceStatus
*/
public int compareTo(PresenceStatus presenceStatus)
{
return this.presenceStatus.compareTo(presenceStatus);
}
/**
* Returns -1, 0 or 1 if the
* PresenceStatus represented by this instance is,
* respectively, less than, equal to or greater than the
* PresenceStatus represented by a specific
* ProtocolPresenceStatus instance.
*
* @param protocolPresenceStatus the ProtocolPresenceStatus
* this instance is to be compared to
* @return -1, 0 or 1 if the
* PresenceStatus represented by this instance is, respectively,
* less than, equal to or greater than the specified
* protocolPresenceStatus
*/
public int compareTo(ProtocolPresenceStatus protocolPresenceStatus)
{
return compareTo(protocolPresenceStatus.presenceStatus);
}
/**
* Sets the PresenceStatus to be represented by this instance.
*
* @param protocolName the name of the protocol from which the
* PresenceStatus to be set on this instance has originated
* @param presenceStatus the PresenceStatus to be represented
* by this instance
*/
public void setPresenceStatus(
String protocolName,
PresenceStatus presenceStatus)
{
this.protocolName = protocolName;
this.presenceStatus = presenceStatus;
}
/**
* Gets an int value in the terms of PresenceStatus
* which is equivalent to the PresenceStatus represented by
* this instance.
*
* @return an int value in the terms of PresenceStatus
* which is equivalent to the PresenceStatus represented by
* this instance
*/
public int toInt()
{
return presenceStatus.getStatus();
}
/**
* Gets a MISTATUS value which is equivalent to the
* PresenceStatus represented by this instance.
*
* @return a MISTATUS value which is equivalent to the
* PresenceStatus represented by this instance
*/
public int toMISTATUS()
{
return toMISTATUS(protocolName, presenceStatus);
}
/**
* Gets a MISTATUS value which is equivalent to the
* PresenceStatus represented by a specific
* ProtocolPresenceStatus instance.
*
* @param protocolPresenceStatus the ProtocolPresenceStatus to
* get an equivalent MISTATUS value for
* @return a MISTATUS value which is equivalent to the
* PresenceStatus represented by the specified
* protocolPresenceStatus
*/
public static int toMISTATUS(
ProtocolPresenceStatus protocolPresenceStatus)
{
return
(protocolPresenceStatus == null)
? MISTATUS_UNKNOWN
: protocolPresenceStatus.toMISTATUS();
}
/**
* Gets a MISTATUS value which is equivalent to a specific
* PresenceStatus which has originated from a protocol with a
* specific name.
*
* @param protocolName the name of the protocol from which the specified
* PresenceStatus has originated
* @param presenceStatus the PresenceStatus for which an
* equivalent MISTATUS value is to be retrieved
* @return a MISTATUS value which is equivalent to the
* specified presenceStatus in the context of the protocol with
* the specified protocolName
*/
public static int toMISTATUS(
String protocolName,
PresenceStatus presenceStatus)
{
int i
= (presenceStatus == null)
? Integer.MIN_VALUE
: presenceStatus.getStatus();
int mistatus;
if (i == Integer.MIN_VALUE)
mistatus = MISTATUS_UNKNOWN;
else
{
if ((i == 31 /* FIXME */)
&& ProtocolNames.JABBER.equalsIgnoreCase(protocolName)
&& JabberStatusEnum.ON_THE_PHONE.equalsIgnoreCase(
presenceStatus.getStatusName()))
{
mistatus = MISTATUS_ON_THE_PHONE;
}
else if (ProtocolNames.YAHOO.equalsIgnoreCase(protocolName)
&& YahooStatusEnum.ON_THE_PHONE.equals(presenceStatus))
{
mistatus = MISTATUS_ON_THE_PHONE;
}
else if ((i == 32 /* FIXME */)
&& ProtocolNames.JABBER.equalsIgnoreCase(protocolName)
&& JabberStatusEnum.IN_A_MEETING.equalsIgnoreCase(
presenceStatus.getStatusName()))
{
mistatus = MISTATUS_IN_A_MEETING;
}
else if (i < PresenceStatus.ONLINE_THRESHOLD)
mistatus = MISTATUS_OFFLINE;
else if (i < PresenceStatus.AWAY_THRESHOLD)
mistatus = MISTATUS_MAY_BE_AVAILABLE;
else if (i < PresenceStatus.AVAILABLE_THRESHOLD)
mistatus = MISTATUS_AWAY;
else
mistatus = MISTATUS_ONLINE;
}
return mistatus;
}
}
/**
* Describes a (local) account which corresponds to an
* IMessengerContact implementation having true as the
* value of its self boolean property.
*/
private static class Self
implements ContactPresenceStatusListener,
ProviderPresenceStatusListener
{
private final Map ppss
= new HashMap();
/**
* The PresenceStatus of this (local) account and the name of
* the protocol from which it has originated.
*/
private ProtocolPresenceStatus presenceStatus;
/**
* The sign-in name associated with this (local) account.
*/
public final String signinName;
/**
* Initializes a new Self instance which is to describe a
* (local) account associated with a specific sign-in name.
*
* @param signinName the sign-in name to be associated with the new
* instance
*/
public Self(String signinName)
{
this.signinName = signinName;
}
void addProtocolProviderService(
ProtocolProviderService pps,
OperationSetPresence presenceOpSet)
{
if (!ppss.containsKey(pps))
{
ppss.put(pps, presenceOpSet);
presenceOpSet.addContactPresenceStatusListener(this);
presenceOpSet.addProviderPresenceStatusListener(this);
providerStatusChanged(null);
}
}
public void contactPresenceStatusChanged(
ContactPresenceStatusChangeEvent event)
{
String signinName
= getSigninName(
event.getSourceContact(),
event.getSourceProvider());
if (signinName != null)
{
String oldProtocolName
= event.getSourceProvider().getProtocolName();
PresenceStatus oldStatus = event.getOldStatus();
Messenger.onContactStatusChange(
signinName,
ProtocolPresenceStatus.toMISTATUS(
oldProtocolName,
oldStatus));
}
}
/**
* Disposes this instance by releasing the resources it has acquired by
* now. Removes this instance as a listener from the associated
* OperationSetPresence instances.
*/
void dispose()
{
Iterator>
it
= ppss.entrySet().iterator();
while (it.hasNext())
{
Map.Entry e
= it.next();
OperationSetPresence presenceOpSet = e.getValue();
presenceOpSet.removeContactPresenceStatusListener(this);
presenceOpSet.removeProviderPresenceStatusListener(this);
it.remove();
}
}
/**
* Gets the Contact instances which are associated with a
* specific IMessengerContact sign-in name and which support a
* specific OperationSet.
*
* @param signinName the IMessengerContact sign-in name for
* which the associated Contact instances are to be found
* @param opSetClass the OperationSet class to be supported by
* the possibly found Contact instances or null if no
* specific OperationSet class is required of the possibly
* found Contact instances
* @param limit the maximum number of found Contacts at which
* the search should stop or {@link Integer#MAX_VALUE} if the search is
* to be unbound with respect to the number of found Contacts
* @param contacts a list with Contact element type which is to
* receive the possibly found Contact instances
*/
void findContactsBySigninName(
String signinName,
Class extends OperationSet> opSetClass,
int limit,
List contacts)
{
for (Map.Entry e
: ppss.entrySet())
{
ProtocolProviderService pps = e.getKey();
OperationSetContactCapabilities contactCapabilitiesOpSet
= (opSetClass == null)
? null
: pps.getOperationSet(
OperationSetContactCapabilities.class);
for (Contact contact
: Messenger.findContactsBySigninName(
pps,
e.getValue(),
signinName))
{
if ((contactCapabilitiesOpSet == null)
|| (contactCapabilitiesOpSet.getOperationSet(
contact,
opSetClass)
!= null))
{
contacts.add(contact);
/*
* Obey the specified limit of the number of found
* Contacts.
*/
if (contacts.size() >= limit)
break;
}
}
/* Obey the specified limit of the number of found Contacts. */
if (contacts.size() >= limit)
break;
}
}
/**
* Gets the PhoneNumberDetail instances which are associated
* with a specific IMessengerContact sign-in name.
*
* @param signinName the IMessengerContact sign-in name for
* which the associated PhoneNumberDetail instances are to be
* found
* @param limit the maximum number of found PhoneNumberDetails
* at which the search should stop or {@link Integer#MAX_VALUE} if the
* search is to be unbound with respect to the number of found
* PhoneNumberDetails
* @param phoneNumbers a list with PhoneNumberDetail element
* type which is to receive the possibly found
* PhoneNumberDetail instances
*/
void findPhoneNumbersBySigninName(
String signinName,
int limit,
Set phoneNumbers)
{
for (Map.Entry e
: ppss.entrySet())
{
ProtocolProviderService pps = e.getKey();
OperationSetServerStoredContactInfo serverStoredContactInfoOpSet
= pps.getOperationSet(
OperationSetServerStoredContactInfo.class);
if (serverStoredContactInfoOpSet == null)
continue;
for (Contact contact
: Messenger.findContactsBySigninName(
pps,
e.getValue(),
signinName))
{
Iterator iter
= serverStoredContactInfoOpSet.getDetailsAndDescendants(
contact,
PhoneNumberDetail.class);
if (iter == null)
continue;
while (iter.hasNext())
{
PhoneNumberDetail phoneNumber = iter.next();
if (getMPHONE_TYPE(phoneNumber) != MPHONE_TYPE_CUSTOM)
{
phoneNumbers.add(phoneNumber);
/*
* Obey the specified limit of the number of found
* PhoneNumberDetails.
*/
if (phoneNumbers.size() >= limit)
break;
}
}
}
/*
* Obey the specified limit of the number of found
* PhoneNumberDetails.
*/
if (phoneNumbers.size() >= limit)
break;
}
}
/**
* Gets the PresenceStatus of this instance and the name of the
* protocol from which it has originated.
*
* @return the PresenceStatus of this instance and the name of
* the protocol from which it has originated
*/
ProtocolPresenceStatus getPresenceStatus()
{
return presenceStatus;
}
/**
* Gets the PresenceStatus of a Contact known to this
* instance to be associated with a specific IMessengerContact
* sign-in name.
*
* @param signinName the sign-in name associated with the
* IMessengerContact whose PresenceStatus is to be
* retrieved
* @return the PresenceStatus and the name of the protocol from
* which it has originated of a Contact known to this instance
* to be associated with the specified signinName or
* null if no such association is known to this instance
*/
ProtocolPresenceStatus getPresenceStatus(String signinName)
{
ProtocolPresenceStatus presenceStatus;
if (this.signinName.equalsIgnoreCase(signinName))
presenceStatus = getPresenceStatus();
else
{
presenceStatus = null;
for (Map.Entry e
: ppss.entrySet())
{
try
{
ProtocolProviderService pps = e.getKey();
Iterable contacts
= Messenger.findContactsBySigninName(
pps,
e.getValue(),
signinName);
String protocolName = pps.getProtocolName();
for (Contact contact : contacts)
{
PresenceStatus contactPresenceStatus
= contact.getPresenceStatus();
if (contactPresenceStatus != null)
{
if (presenceStatus == null)
{
presenceStatus
= new ProtocolPresenceStatus(
protocolName,
contactPresenceStatus);
}
else if (presenceStatus.compareTo(
contactPresenceStatus)
< 0)
{
presenceStatus.setPresenceStatus(
protocolName,
contactPresenceStatus);
}
if (presenceStatus.toInt()
>= PresenceStatus.MAX_STATUS_VALUE)
break;
}
}
if ((presenceStatus != null)
&& (presenceStatus.toInt()
>= PresenceStatus.MAX_STATUS_VALUE))
break;
}
catch (Throwable t)
{
/*
* It does not sound like it makes a lot of sense to
* fail the getting of the presence status of the
* specified signinName just because one of the possibly
* many OperationSetPresence instances has failed.
* Additionally, the native counterpart will swallow any
* Java exception anyway.
*/
if (t instanceof ThreadDeath)
throw (ThreadDeath) t;
else
t.printStackTrace(System.err);
}
}
}
return presenceStatus;
}
/**
* Gets an indicator which determines whether the (local) account
* described by this Self instance is associated with a
* specific sign-in name.
*
* @param signinName the sign-in name to be determined whether it is
* associated with this Self instance
* @return true if the specified signinName is
* associated with the (local) account described by this Self
* instance
*/
boolean isSelf(String signinName)
{
boolean self;
if (this.signinName.equalsIgnoreCase(signinName))
self = true;
else
{
self = false;
for (ProtocolProviderService pps : ppss.keySet())
{
if(!pps.isRegistered())
{
continue;
}
OperationSetServerStoredAccountInfo
serverStoredAccountInfoOpSet
= pps.getOperationSet(
OperationSetServerStoredAccountInfo.class);
if (serverStoredAccountInfoOpSet != null)
{
for (Iterator emailAddressDetailIt
= serverStoredAccountInfoOpSet
.getDetailsAndDescendants(
EmailAddressDetail.class);
emailAddressDetailIt.hasNext();)
{
EmailAddressDetail emailAddressDetail
= emailAddressDetailIt.next();
if (signinName.equalsIgnoreCase(
emailAddressDetail.getEMailAddress()))
{
self = true;
break;
}
}
if (self)
break;
}
}
}
return self;
}
public void providerStatusChanged(
ProviderPresenceStatusChangeEvent event)
{
ProtocolPresenceStatus protocolPresenceStatus = null;
for (Map.Entry e
: ppss.entrySet())
{
OperationSetPresence presenceOpSet = e.getValue();
PresenceStatus presenceOpSetStatus
= presenceOpSet.getPresenceStatus();
if (presenceOpSetStatus != null)
{
if (protocolPresenceStatus == null)
{
protocolPresenceStatus
= new ProtocolPresenceStatus(
e.getKey().getProtocolName(),
presenceOpSetStatus);
}
else if (protocolPresenceStatus.compareTo(
presenceOpSetStatus)
< 0)
{
protocolPresenceStatus.setPresenceStatus(
e.getKey().getProtocolName(),
presenceOpSetStatus);
}
if (protocolPresenceStatus.toInt()
>= PresenceStatus.MAX_STATUS_VALUE)
break;
}
}
setPresenceStatus(protocolPresenceStatus);
}
public void providerStatusMessageChanged(PropertyChangeEvent event) {}
int removeProtocolProviderService(ProtocolProviderService pps)
{
OperationSetPresence presenceOpSet = ppss.get(pps);
if (presenceOpSet != null)
{
presenceOpSet.removeContactPresenceStatusListener(this);
presenceOpSet.removeProviderPresenceStatusListener(this);
ppss.remove(pps);
providerStatusChanged(null);
}
return ppss.size();
}
/**
* Sets the PresenceStatus of this instance.
*
* @param presenceStatus the PresenceStatus and the name of the
* protocol from which it has originated
*/
private void setPresenceStatus(ProtocolPresenceStatus presenceStatus)
{
int oldMISTATUS
= ProtocolPresenceStatus.toMISTATUS(this.presenceStatus);
int newMISTATUS = ProtocolPresenceStatus.toMISTATUS(presenceStatus);
if (oldMISTATUS != newMISTATUS)
{
this.presenceStatus = presenceStatus;
Messenger.onContactStatusChange(
signinName,
oldMISTATUS);
}
}
}
}