/* * 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 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 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 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); } } } }