From e0e0cf4921b76b3ba8a8167dc27d8f9aff518815 Mon Sep 17 00:00:00 2001 From: Marin Date: Fri, 10 Jan 2014 12:37:25 +0200 Subject: [PATCH] Adds the ability for the OTR plugin to work with Jabber resources --- lib/installer-exclude/otr4j.jar | Bin 94169 -> 94426 bytes resources/images/images.properties | 4 + .../plugin/otr/encrypted-black16x16.png | Bin 0 -> 1085 bytes .../otr/encrypted_unverified-black16x16.png | Bin 0 -> 1278 bytes .../otr/encrypted_verified-black16x16.png | Bin 0 -> 1209 bytes .../images/plugin/otr/finished-black16x16.png | Bin 0 -> 1410 bytes .../plugin/otr/padlockBrokenOpen16x16.png | Bin 0 -> 1233 bytes .../images/plugin/otr/plaintext16x16.png | Bin 415 -> 1110 bytes resources/languages/resources.properties | 2 +- .../impl/gui/main/chat/ChatWritePanel.java | 37 +- .../gui/main/chat/toolBars/MainToolBar.java | 4 +- ...ionSetBasicInstantMessagingJabberImpl.java | 6 +- .../communicator/plugin/otr/OtrActivator.java | 3 +- .../plugin/otr/OtrContactManager.java | 180 ++++++++ .../plugin/otr/OtrContactMenu.java | 74 ++-- .../plugin/otr/OtrMetaContactButton.java | 91 +++-- .../plugin/otr/OtrMetaContactMenu.java | 50 ++- .../plugin/otr/OtrTransformLayer.java | 17 +- .../plugin/otr/OtrWeakListener.java | 9 +- .../communicator/plugin/otr/ScOtrEngine.java | 71 ++-- .../plugin/otr/ScOtrEngineImpl.java | 385 +++++++++++------- .../plugin/otr/ScOtrEngineListener.java | 7 +- .../plugin/otr/ScOtrKeyManager.java | 7 +- .../plugin/otr/ScOtrKeyManagerImpl.java | 54 +-- .../plugin/otr/ScOtrKeyManagerListener.java | 4 +- .../plugin/otr/SwingOtrActionHandler.java | 12 +- .../FingerprintAuthenticationPanel.java | 32 +- .../authdialog/KnownFingerprintsPanel.java | 6 +- .../KnownFingerprintsTableModel.java | 4 +- ...java => OTRv3OutgoingSessionSwitcher.java} | 221 +++++++--- .../OtrBuddyAuthenticationDialog.java | 5 +- .../otr/authdialog/OtrConfigurationPanel.java | 3 +- .../SmpAuthenticateBuddyDialog.java | 24 +- .../plugin/spellcheck/LanguageMenuBar.java | 5 + .../service/gui/AbstractPluginComponent.java | 8 + .../service/gui/PluginComponent.java | 11 + .../protocol/event/MessageDeliveredEvent.java | 53 +++ 37 files changed, 995 insertions(+), 394 deletions(-) create mode 100644 resources/images/plugin/otr/encrypted-black16x16.png create mode 100644 resources/images/plugin/otr/encrypted_unverified-black16x16.png create mode 100644 resources/images/plugin/otr/encrypted_verified-black16x16.png create mode 100644 resources/images/plugin/otr/finished-black16x16.png create mode 100644 resources/images/plugin/otr/padlockBrokenOpen16x16.png create mode 100644 src/net/java/sip/communicator/plugin/otr/OtrContactManager.java rename src/net/java/sip/communicator/plugin/otr/authdialog/{OtrV3OutgoingSessionSwitcher.java => OTRv3OutgoingSessionSwitcher.java} (58%) diff --git a/lib/installer-exclude/otr4j.jar b/lib/installer-exclude/otr4j.jar index f755a6a3f9623e8033915d1714af33be35f35b64..6e4a280093cb6937dc6325360a6c0711c2f8e23c 100644 GIT binary patch delta 23201 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 9t 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 delta 22916 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 literal 0 HcmV?d00001 diff --git a/resources/images/plugin/otr/finished-black16x16.png b/resources/images/plugin/otr/finished-black16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..3b3c6bff5fa797e1c67c432af8ffda0b18b7e774 GIT binary patch literal 1410 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 literal 0 HcmV?d00001 diff --git a/resources/images/plugin/otr/plaintext16x16.png b/resources/images/plugin/otr/plaintext16x16.png index 1362d64b39887dacda78e54de009f68c13950d08..bc4a115b9053c2a879a6e9ea30cabea15897bec5 100644 GIT binary patch literal 1110 zcmaJ=O-$546z&FX43TKmC_ga{U4PK9+je0~Xju7asjRUCmyNFRLTP8a&DtN^!QJJu z2=QQo7vsV8A_gydFeYA1Jdh9%LiFN66E8*+Ogt&X1O6aRfdvn)P1~7y^Sf~bl2i3vRJ!r$&4B>vBxJF*QAb*MLm2JJM;DJ~?sHG3F>xT)wc0Tr!q zc@B0EMAcy0_hDl@Vp zz>Lv1>cYX%{-ipZRogYH^CalV^O(SdNCA2ClI8LF7`4XB7U?C+Rvb51wY-gE!Usm@6^CZSj9*gOQI`MjYMPtg9!kKieE&)8B?}Iu6VS6qTopGi9rQyv zyx>9w*>2LdFO^SmAY&uj%h(PO2073qE2?4nhU04pS?1%GhZIYNaWO_=j<8{9Jj+QU z)6KQCF+C#7_O^7hTvUieBOKlkI8iENMOz&)p@qs=Z42945$lV=bZ}%5y2cf#Nv>^z zwW4{WaxQE|z74EaIhRO9ERB<){nq}~>aq#z$DeKr7cVxY4=t>B7YlpOlW7%SiWhOQ zJDGns{A+c!vnm)2UKGXWCrFYklCx9U?@J2{fZUtxn(Vs)21xaB*XO%TVCuo-Q`9R- zzfXOdFiCzTT2(i8t+wHE;3!#5Oy9hjxK}D&IMjG;_4kUpyz;Z{_N)2%d1v{nQj~`F ztsHxs-oLZRuvb4G6h6G4DK2Kyx~``l4+V^~+jkL?v?Mg#O2(_rZ$#DiQmIrYG&bK~ zBx{>aciqnD6TePBEGl=71ytiL`)H5{x E2l)bPKL7v# delta 387 zcmV-}0et?}2%iHXiBL{Q4GJ0x0000DNk~Le0000T0000T2nGNE09KP{50N1!e*r;B zL_t(Y$L*HgYJ)HofYYjMS;bz}5}FP4b};6(F!loOVhH}DudsL8gY7lC8+VQv$eflf zO*<$CJ}{c(Gs(#hY1@{Z8!}7(MJgo|SOW~;6YPKua04tw#bc!FFbv=GJa4P2>L`k$ zqb$oVkKdvw`tUrjf$}RIQkJG^fBRdY{8opQiv}sBuaLhp48sRJX?O!k%5_LLgI+Ai z&%||0uX`DCMaGjh>9P+BX503YWmy3{-%doYJiPaP6fabyE8q9u{t`q2#@GvrRis-L zq10nnMaqW_;d&HnRAxfTkCxVTjW2_d`FxDy`12&vrfI%Krj&Ld-Cs^mS;d@+{a5B> zbE*)%>e4LBI?g3^v2v0mdmL6g#tt!DI;!iGI*#+KGPI^?KHxsBBE{2$;vXNlF^u7g hgJHgy&C<)0egWg1e?M}x&)EO~002ovPDHLkV1mM1t;PTV diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index 00bb3b9a8..3de514982 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -1612,7 +1612,7 @@ plugin.otr.activator.fallbackmessage={0} is try Off-The-Record messaging. For more information see \ http://en.wikipedia.org/wiki/Off-the-Record_Messaging plugin.otr.activator.multipleinstancesdetected=Your buddy {0} is logged in multiple times and OTR has established multiple sessions. You can select \ -an outgoing session from the menu above. +an outgoing session from the menu below. plugin.otr.activator.msgfromanotherinstance={0} sent you a message that was intended for another session. If you are logged in multiple times \ another session may have received this message. diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatWritePanel.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatWritePanel.java index 14afdc8d7..1ef526e9e 100755 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatWritePanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatWritePanel.java @@ -24,7 +24,6 @@ import net.java.sip.communicator.impl.gui.main.chat.menus.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.plugin.desktoputil.*; -import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.gui.event.*; import net.java.sip.communicator.service.protocol.*; @@ -1559,12 +1558,20 @@ void initPluginComponents() PluginComponent component = factory.getPluginComponentInstance(this); + ChatSession chatSession = chatPanel.getChatSession(); - if (chatSession instanceof MetaContactChatSession) + if (chatSession != null) { - MetaContact metaContact = - (MetaContact) chatSession.getDescriptor(); - component.setCurrentContact(metaContact); + ChatTransport currentTransport = + chatSession.getCurrentChatTransport(); + Object currentDescriptor = currentTransport.getDescriptor(); + if (currentDescriptor instanceof Contact) + { + Contact contact = (Contact) currentDescriptor; + + component.setCurrentContact( + contact, currentTransport.getResourceName()); + } } if (component.getComponent() == null) continue; @@ -1589,14 +1596,24 @@ public void pluginComponentAdded(PluginComponentEvent event) net.java.sip.communicator.service. gui.Container.CONTAINER_CHAT_WRITE_PANEL)) return; - PluginComponent c = factory.getPluginComponentInstance(this); + + PluginComponent component = factory.getPluginComponentInstance(this); + ChatSession chatSession = chatPanel.getChatSession(); - if (chatSession instanceof MetaContactChatSession) + if (chatSession != null) { - MetaContact metaContact = (MetaContact) chatSession.getDescriptor(); - c.setCurrentContact(metaContact); + ChatTransport currentTransport = + chatSession.getCurrentChatTransport(); + Object currentDescriptor = currentTransport.getDescriptor(); + if (currentDescriptor instanceof Contact) + { + Contact contact = (Contact) currentDescriptor; + + component.setCurrentContact( + contact, currentTransport.getResourceName()); + } } - centerPanel.add((Component) c.getComponent()); + centerPanel.add((Component) component.getComponent()); this.centerPanel.repaint(); } diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java b/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java index 31f53f718..2c3baf5bc 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java @@ -365,6 +365,8 @@ else if(contact != null) setCallButtonsName(); setCallButtonsIcons(); + + currentChatTransportChanged(chatSession); } } @@ -435,7 +437,7 @@ public void currentChatTransportChanged(ChatSession chatSession) Contact contact = (Contact) currentDescriptor; for (PluginComponent c : pluginContainer.getPluginComponents()) - c.setCurrentContact(contact); + c.setCurrentContact(contact, currentTransport.getResourceName()); } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java index 84cb59fb1..94c69188d 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java @@ -20,7 +20,7 @@ import org.jivesoftware.smack.filter.*; import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.provider.*; -import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.*; import org.jivesoftware.smackx.*; import org.jivesoftware.smackx.packet.*; @@ -456,7 +456,7 @@ private MessageDeliveredEvent sendMessage( Contact to, + " chat.tid=" + chat.getThreadID()); MessageDeliveredEvent msgDeliveryPendingEvt - = new MessageDeliveredEvent(message, to); + = new MessageDeliveredEvent(message, to, toResource); msgDeliveryPendingEvt = messageDeliveryPendingTransform(msgDeliveryPendingEvt); @@ -496,7 +496,7 @@ private MessageDeliveredEvent sendMessage( Contact to, chat.sendMessage(msg); MessageDeliveredEvent msgDeliveredEvt - = new MessageDeliveredEvent(message, to); + = new MessageDeliveredEvent(message, to, toResource); // msgDeliveredEvt = messageDeliveredTransform(msgDeliveredEvt); diff --git a/src/net/java/sip/communicator/plugin/otr/OtrActivator.java b/src/net/java/sip/communicator/plugin/otr/OtrActivator.java index 5e359429c..f50905e18 100644 --- a/src/net/java/sip/communicator/plugin/otr/OtrActivator.java +++ b/src/net/java/sip/communicator/plugin/otr/OtrActivator.java @@ -308,6 +308,7 @@ public void start(Object dependentService) // Register Transformation Layer bundleContext.addServiceListener(this); bundleContext.addServiceListener((ServiceListener) scOtrEngine); + bundleContext.addServiceListener(new OtrContactManager()); ServiceReference[] protocolProviderRefs = ServiceUtils.getServiceReferences( @@ -380,7 +381,7 @@ public void start(Object dependentService) { protected PluginComponent getPluginInstance() { - return new OtrV3OutgoingSessionSwitcher( + return new OTRv3OutgoingSessionSwitcher( getContainer(), this); } }, diff --git a/src/net/java/sip/communicator/plugin/otr/OtrContactManager.java b/src/net/java/sip/communicator/plugin/otr/OtrContactManager.java new file mode 100644 index 000000000..789c8df86 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/OtrContactManager.java @@ -0,0 +1,180 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.otr; + +import java.util.*; +import java.util.concurrent.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +import org.osgi.framework.*; + +/** + * The OtrContactManager is used for accessing OtrContacts in a static + * way. + * + * The OtrContact class is just a wrapper of [Contact, ContactResource] + * pairs. Its purpose is for the otr plugin to be able to create different + * Sessions for every ContactResource that a Contact has. + * + * Currently, only the Jabber protocol supports ContactResources. + * + * @author Marin Dzhigarov + * + */ +public class OtrContactManager implements ServiceListener +{ + + /** + * The logger + */ + private final Logger logger = Logger.getLogger(OtrContactManager.class); + + /** + * A map that caches OtrContacts to minimize memory usage. + */ + private static final Map> contactsMap = + new ConcurrentHashMap>(); + + /** + * The OtrContact class is just a wrapper of + * [Contact, ContactResource] pairs. Its purpose is for the otr plugin to be + * able to create different Sessions for every ContactResource that + * a Contact has. + * + * @author Marin Dzhigarov + * + */ + public static class OtrContact + { + public final Contact contact; + + public final ContactResource resource; + + private OtrContact(Contact contact, ContactResource resource) + { + this.contact = contact; + this.resource = resource; + } + + public boolean equals(Object obj) + { + if (this == obj) + return true; + + if (!(obj instanceof OtrContact)) + return false; + + OtrContact other = (OtrContact) obj; + + if (this.contact != null && this.contact.equals(other.contact)) + { + if (this.resource != null && resource.equals(other.resource)) + return true; + if (this.resource == null && other.resource == null) + return true; + return false; + } + return false; + } + + public int hashCode() + { + int result = 17; + + result = 31 * result + (contact == null ? 0 : contact.hashCode()); + result = 31 * result + (resource == null ? 0 : resource.hashCode()); + + return result; + } + } + + /** + * Gets the OtrContact that represents this + * [Contact, ContactResource] pair from the cache. If such pair does not + * still exist it is then created and cached for further usage. + * + * @param contact the Contact that the returned OtrContact + * represents. + * @param resource the ContactResource that the returned OtrContact + * represents. + * @return The OtrContact that represents this + * [Contact, ContactResource] pair. + */ + public static OtrContact getOtrContact( + Contact contact, ContactResource resource) + { + if (contact == null) + return null; + + List otrContactsList = contactsMap.get(contact); + if (otrContactsList != null) + { + for (OtrContact otrContact : otrContactsList) + { + if (resource != null && resource.equals(otrContact.resource)) + return otrContact; + } + OtrContact otrContact = new OtrContact(contact, resource); + synchronized (otrContactsList) + { + while (!otrContactsList.contains(otrContact)) + otrContactsList.add(otrContact); + } + return otrContact; + } + else + { + synchronized (contactsMap) + { + while (!contactsMap.containsKey(contact)) + { + otrContactsList = new ArrayList(); + contactsMap.put(contact, otrContactsList); + } + } + return getOtrContact(contact, resource); + } + } + + /** + * Cleans up unused cached up Contacts. + */ + public void serviceChanged(ServiceEvent event) + { + Object service + = OtrActivator.bundleContext.getService(event.getServiceReference()); + + if (!(service instanceof ProtocolProviderService)) + return; + + if (event.getType() == ServiceEvent.UNREGISTERING) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Unregistering a ProtocolProviderService, cleaning" + + " OTR's Contact to OtrContact map"); + } + + ProtocolProviderService provider + = (ProtocolProviderService) service; + + synchronized(contactsMap) + { + Iterator i = contactsMap.keySet().iterator(); + + while (i.hasNext()) + { + if (provider.equals(i.next().getProtocolProvider())) + i.remove(); + } + } + } + } +} diff --git a/src/net/java/sip/communicator/plugin/otr/OtrContactMenu.java b/src/net/java/sip/communicator/plugin/otr/OtrContactMenu.java index 613b0f874..19a44a103 100644 --- a/src/net/java/sip/communicator/plugin/otr/OtrContactMenu.java +++ b/src/net/java/sip/communicator/plugin/otr/OtrContactMenu.java @@ -13,6 +13,7 @@ import net.java.otr4j.*; import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.protocol.*; /** @@ -46,7 +47,7 @@ class OtrContactMenu private static final String ACTION_COMMAND_START_OTR = "START_OTR"; - private final Contact contact; + private final OtrContact contact; /** * The indicator which determines whether this JMenu is displayed @@ -70,23 +71,27 @@ class OtrContactMenu /** * The OtrContactMenu constructor. * - * @param contact the Contact this menu refers to. + * @param otrContact the OtrContact this menu refers to. * @param inMacOSXScreenMenuBar true if the new menu is to be * displayed in the Mac OS X screen menu bar; false, otherwise * @param menu the parent menu */ - public OtrContactMenu( Contact contact, + public OtrContactMenu( OtrContact otrContact, boolean inMacOSXScreenMenuBar, JMenu menu, boolean isSeparateMenu) { - this.contact = contact; + this.contact = otrContact; this.inMacOSXScreenMenuBar = inMacOSXScreenMenuBar; this.parentMenu = menu; - + String resourceName = + otrContact.resource != null + ? "/" + otrContact.resource.getResourceName() + : ""; separateMenu = isSeparateMenu - ? new SIPCommMenu(contact.getDisplayName()) + ? new SIPCommMenu(otrContact.contact.getDisplayName() + + resourceName) : null; /* @@ -104,8 +109,10 @@ public OtrContactMenu( Contact contact, this, OtrActivator.scOtrEngine, OtrActivator.scOtrKeyManager); - setSessionStatus(OtrActivator.scOtrEngine.getSessionStatus(contact)); - setOtrPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact)); + setSessionStatus( + OtrActivator.scOtrEngine.getSessionStatus(this.contact)); + setOtrPolicy( + OtrActivator.scOtrEngine.getContactPolicy(otrContact.contact)); buildMenu(); } @@ -136,22 +143,23 @@ else if (ACTION_COMMAND_AUTHENTICATE_BUDDY.equals(actionCommand)) else if (ACTION_COMMAND_CB_ENABLE.equals(actionCommand)) { OtrPolicy policy = - OtrActivator.scOtrEngine.getContactPolicy(contact); + OtrActivator.scOtrEngine.getContactPolicy(contact.contact); boolean state = ((JCheckBoxMenuItem) e.getSource()).isSelected(); policy.setEnableManual(state); - OtrActivator.scOtrEngine.setContactPolicy(contact, policy); + OtrActivator.scOtrEngine.setContactPolicy(contact.contact, policy); } else if (ACTION_COMMAND_CB_AUTO.equals(actionCommand)) { OtrPolicy policy = - OtrActivator.scOtrEngine.getContactPolicy(contact); + OtrActivator.scOtrEngine.getContactPolicy(contact.contact); boolean state = ((JCheckBoxMenuItem) e.getSource()).isSelected(); policy.setEnableAlways(state); + policy.setSendWhitespaceTag(state); - OtrActivator.scOtrEngine.setContactPolicy(contact, policy); + OtrActivator.scOtrEngine.setContactPolicy(contact.contact, policy); } else if (ACTION_COMMAND_CB_AUTO_ALL.equals(actionCommand)) @@ -161,6 +169,8 @@ else if (ACTION_COMMAND_CB_AUTO_ALL.equals(actionCommand)) boolean state = ((JCheckBoxMenuItem) e.getSource()).isSelected(); globalPolicy.setEnableAlways(state); + globalPolicy.setSendWhitespaceTag(state); + OtrActivator.configService.setProperty( OtrActivator.AUTO_INIT_OTR_PROP, Boolean.toString(state)); @@ -171,17 +181,17 @@ else if (ACTION_COMMAND_CB_AUTO_ALL.equals(actionCommand)) else if (ACTION_COMMAND_CB_REQUIRE.equals(actionCommand)) { OtrPolicy policy = - OtrActivator.scOtrEngine.getContactPolicy(contact); + OtrActivator.scOtrEngine.getContactPolicy(contact.contact); boolean state = ((JCheckBoxMenuItem) e.getSource()).isSelected(); policy.setRequireEncryption(state); OtrActivator.configService.setProperty( OtrActivator.OTR_MANDATORY_PROP, Boolean.toString(state)); - OtrActivator.scOtrEngine.setContactPolicy(contact, policy); + OtrActivator.scOtrEngine.setContactPolicy(contact.contact, policy); } else if (ACTION_COMMAND_CB_RESET.equals(actionCommand)) - OtrActivator.scOtrEngine.setContactPolicy(contact, null); + OtrActivator.scOtrEngine.setContactPolicy(contact.contact, null); } /* @@ -198,10 +208,11 @@ public void contactPolicyChanged(Contact contact) * Implements ScOtrKeyManagerListener#contactVerificationStatusChanged( * Contact). */ - public void contactVerificationStatusChanged(Contact contact) + public void contactVerificationStatusChanged(OtrContact otrContact) { - if (contact.equals(OtrContactMenu.this.contact)) - setSessionStatus(OtrActivator.scOtrEngine.getSessionStatus(contact)); + if (otrContact.equals(OtrContactMenu.this.contact)) + setSessionStatus( + OtrActivator.scOtrEngine.getSessionStatus(otrContact)); } /** @@ -221,7 +232,7 @@ void dispose() */ public void globalPolicyChanged() { - setOtrPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact)); + setOtrPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact.contact)); } /** @@ -233,7 +244,8 @@ private void buildMenu() if(separateMenu != null) separateMenu.removeAll(); - OtrPolicy policy = OtrActivator.scOtrEngine.getContactPolicy(contact); + OtrPolicy policy = + OtrActivator.scOtrEngine.getContactPolicy(contact.contact); JMenuItem endOtr = new JMenuItem(); endOtr.setText(OtrActivator.resourceService @@ -327,7 +339,8 @@ private void buildMenu() OtrActivator.resourceService .getI18NString( "plugin.otr.menu.CB_AUTO", - new String[] {contact.getDisplayName()}))); + new String[] + {contact.contact.getDisplayName()}))); cbAlways.setEnabled(policy.getEnableManual()); cbAlways.setSelected(policy.getEnableAlways()); @@ -408,10 +421,11 @@ else if (!isMandatory && defaultOtrPropValue != null) /* * Implements ScOtrEngineListener#sessionStatusChanged(Contact). */ - public void sessionStatusChanged(Contact contact) + public void sessionStatusChanged(OtrContact otrContact) { - if (contact.equals(OtrContactMenu.this.contact)) - setSessionStatus(OtrActivator.scOtrEngine.getSessionStatus(contact)); + if (otrContact.equals(OtrContactMenu.this.contact)) + setSessionStatus( + OtrActivator.scOtrEngine.getSessionStatus(otrContact)); } /** @@ -477,7 +491,8 @@ private void updateIcon() OtrActivator.scOtrKeyManager. getFingerprintFromPublicKey(pubKey); imageID - = OtrActivator.scOtrKeyManager.isVerified(contact, fingerprint) + = OtrActivator.scOtrKeyManager.isVerified( + contact.contact, fingerprint) ? "plugin.otr.ENCRYPTED_ICON_16x16" : "plugin.otr.ENCRYPTED_UNVERIFIED_ICON_16x16"; break; @@ -498,12 +513,13 @@ private void updateIcon() } @Override - public void multipleInstancesDetected(Contact contact) {} + public void multipleInstancesDetected(OtrContact contact) {} @Override - public void outgoingSessionChanged(Contact contact) + public void outgoingSessionChanged(OtrContact otrContact) { - if (contact.equals(OtrContactMenu.this.contact)) - setSessionStatus(OtrActivator.scOtrEngine.getSessionStatus(contact)); + if (otrContact.equals(OtrContactMenu.this.contact)) + setSessionStatus( + OtrActivator.scOtrEngine.getSessionStatus(otrContact)); } } diff --git a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java index 40810ce2e..63741f3f0 100644 --- a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java +++ b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java @@ -16,6 +16,7 @@ import net.java.otr4j.*; import net.java.otr4j.session.*; import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.gui.Container; @@ -40,7 +41,7 @@ public class OtrMetaContactButton private SIPCommButton button; - private Contact contact; + private OtrContact otrContact; private AnimatedImage animatedPadlockImage; @@ -58,21 +59,20 @@ public class OtrMetaContactButton * The timer task that changes the padlock icon to "loading" and * then to "broken" if the specified timeout passed */ - public void sessionStatusChanged(Contact contact) + public void sessionStatusChanged(OtrContact otrContact) { // OtrMetaContactButton.this.contact can be null. - if (contact.equals(OtrMetaContactButton.this.contact)) + if (otrContact.equals(OtrMetaContactButton.this.otrContact)) { - setStatus( - OtrActivator.scOtrEngine.getSessionStatus(contact)); + OtrActivator.scOtrEngine.getSessionStatus(otrContact)); } } public void contactPolicyChanged(Contact contact) { // OtrMetaContactButton.this.contact can be null. - if (contact.equals(OtrMetaContactButton.this.contact)) + if (contact.equals(OtrMetaContactButton.this.otrContact.contact)) { setPolicy( OtrActivator.scOtrEngine.getContactPolicy(contact)); @@ -81,18 +81,18 @@ public void contactPolicyChanged(Contact contact) public void globalPolicyChanged() { - if (OtrMetaContactButton.this.contact != null) + if (OtrMetaContactButton.this.otrContact != null) setPolicy( - OtrActivator.scOtrEngine.getContactPolicy(contact)); + OtrActivator.scOtrEngine.getContactPolicy(otrContact.contact)); } - public void contactVerificationStatusChanged(Contact contact) + public void contactVerificationStatusChanged(OtrContact otrContact) { // OtrMetaContactButton.this.contact can be null. - if (contact.equals(OtrMetaContactButton.this.contact)) + if (otrContact.equals(OtrMetaContactButton.this.otrContact)) { setStatus( - OtrActivator.scOtrEngine.getSessionStatus(contact)); + OtrActivator.scOtrEngine.getSessionStatus(otrContact)); } } @@ -173,23 +173,23 @@ private SIPCommButton getButton() { public void actionPerformed(ActionEvent e) { - if (contact == null) + if (otrContact == null) return; - switch (OtrActivator.scOtrEngine.getSessionStatus(contact)) + switch (OtrActivator.scOtrEngine.getSessionStatus(otrContact)) { case ENCRYPTED: case FINISHED: case LOADING: // Default action for finished, encrypted and loading // sessions is end session. - OtrActivator.scOtrEngine.endSession(contact); + OtrActivator.scOtrEngine.endSession(otrContact); break; case TIMED_OUT: case PLAINTEXT: // Default action for timed_out and plaintext sessions // is start session. - OtrActivator.scOtrEngine.startSession(contact); + OtrActivator.scOtrEngine.startSession(otrContact); break; } } @@ -222,20 +222,49 @@ public String getName() @Override public void setCurrentContact(Contact contact) { - if (this.contact == contact) + setCurrentContact(contact, null); + } + + public void setCurrentContact(Contact contact, String resourceName) + { + if (contact == null) + { + this.otrContact = null; + this.setPolicy(null); + this.setStatus(ScSessionStatus.PLAINTEXT); return; + } - this.contact = contact; - if (contact != null) + if (resourceName == null) { - this.setStatus(OtrActivator.scOtrEngine.getSessionStatus(contact)); - this.setPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact)); + OtrContact otrContact = + OtrContactManager.getOtrContact(contact, null); + if (this.otrContact == otrContact) + return; + this.otrContact = otrContact; + this.setStatus( + OtrActivator.scOtrEngine.getSessionStatus(otrContact)); + this.setPolicy( + OtrActivator.scOtrEngine.getContactPolicy(contact)); + return; } - else + for (ContactResource resource : contact.getResources()) { - this.setStatus(ScSessionStatus.PLAINTEXT); - this.setPolicy(null); + if (resource.getResourceName().equals(resourceName)) + { + OtrContact otrContact = + OtrContactManager.getOtrContact(contact, resource); + if (this.otrContact == otrContact) + return; + this.otrContact = otrContact; + this.setStatus( + OtrActivator.scOtrEngine.getSessionStatus(otrContact)); + this.setPolicy( + OtrActivator.scOtrEngine.getContactPolicy(contact)); + return; + } } + logger.debug("Could not find resource for contact " + contact); } /* @@ -274,16 +303,18 @@ private void setStatus(ScSessionStatus status) { case ENCRYPTED: PublicKey pubKey = - OtrActivator.scOtrEngine.getRemotePublicKey(contact); + OtrActivator.scOtrEngine.getRemotePublicKey(otrContact); String fingerprint = OtrActivator.scOtrKeyManager. getFingerprintFromPublicKey(pubKey); image - = OtrActivator.scOtrKeyManager.isVerified(contact, fingerprint) + = OtrActivator.scOtrKeyManager.isVerified( + otrContact.contact, fingerprint) ? verifiedLockedPadlockImage : unverifiedLockedPadlockImage; tipKey = - OtrActivator.scOtrKeyManager.isVerified(contact, fingerprint) + OtrActivator.scOtrKeyManager.isVerified( + otrContact.contact, fingerprint) ? "plugin.otr.menu.VERIFIED" : "plugin.otr.menu.UNVERIFIED"; break; @@ -316,17 +347,17 @@ private void setStatus(ScSessionStatus status) } @Override - public void multipleInstancesDetected(Contact contact) + public void multipleInstancesDetected(OtrContact contact) {} @Override - public void outgoingSessionChanged(Contact contact) + public void outgoingSessionChanged(OtrContact otrContact) { // OtrMetaContactButton.this.contact can be null. - if (contact.equals(OtrMetaContactButton.this.contact)) + if (otrContact.equals(OtrMetaContactButton.this.otrContact)) { setStatus( - OtrActivator.scOtrEngine.getSessionStatus(contact)); + OtrActivator.scOtrEngine.getSessionStatus(otrContact)); } } } diff --git a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactMenu.java b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactMenu.java index 1602cf8f4..024ec5649 100644 --- a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactMenu.java +++ b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactMenu.java @@ -13,11 +13,11 @@ import javax.swing.*; import javax.swing.event.*; +import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.gui.Container; import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.plugin.desktoputil.*; /** * @author George Politis @@ -95,14 +95,54 @@ private void createOtrContactMenus(MetaContact metaContact) if (metaContact.getContactCount() == 1) { - new OtrContactMenu( - contacts.next(), inMacOSXScreenMenuBar, menu, false); + Contact contact = contacts.next(); + Collection resources = contact.getResources(); + if (contact.supportResources() && + resources != null && + resources.size() > 0) + { + for (ContactResource resource : resources) + { + new OtrContactMenu( + OtrContactManager.getOtrContact(contact, resource), + inMacOSXScreenMenuBar, + menu, + true); + } + } + else + new OtrContactMenu( + OtrContactManager.getOtrContact(contact, null), + inMacOSXScreenMenuBar, + menu, + false); } else while (contacts.hasNext()) { - new OtrContactMenu( - contacts.next(), inMacOSXScreenMenuBar, menu, true); + Contact contact = contacts.next(); + Collection resources = + contact.getResources(); + if (contact.supportResources() && + resources != null && + resources.size() > 0) + { + for (ContactResource resource : resources) + { + new OtrContactMenu( + OtrContactManager.getOtrContact( + contact, resource), + inMacOSXScreenMenuBar, + menu, + true); + } + } + else + new OtrContactMenu( + OtrContactManager.getOtrContact(contact, null), + inMacOSXScreenMenuBar, + menu, + true); } } } diff --git a/src/net/java/sip/communicator/plugin/otr/OtrTransformLayer.java b/src/net/java/sip/communicator/plugin/otr/OtrTransformLayer.java index 5ba5a53f6..8d99caae8 100644 --- a/src/net/java/sip/communicator/plugin/otr/OtrTransformLayer.java +++ b/src/net/java/sip/communicator/plugin/otr/OtrTransformLayer.java @@ -7,6 +7,7 @@ package net.java.sip.communicator.plugin.otr; import net.java.otr4j.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; @@ -24,10 +25,12 @@ public class OtrTransformLayer public MessageDeliveredEvent messageDelivered(MessageDeliveredEvent evt) { Contact contact = evt.getDestinationContact(); + OtrContact otrContact = + OtrContactManager.getOtrContact(contact, evt.getContactResource()); OtrPolicy policy = OtrActivator.scOtrEngine.getContactPolicy(contact); ScSessionStatus sessionStatus = - OtrActivator.scOtrEngine.getSessionStatus(contact); + OtrActivator.scOtrEngine.getSessionStatus(otrContact); // If OTR is disabled and we are not over an encrypted session, don't // process anything. if (!policy.getEnableManual() @@ -61,10 +64,12 @@ public MessageDeliveredEvent messageDeliveryPending( MessageDeliveredEvent evt) { Contact contact = evt.getDestinationContact(); + OtrContact otrContact = + OtrContactManager.getOtrContact(contact, evt.getContactResource()); OtrPolicy policy = OtrActivator.scOtrEngine.getContactPolicy(contact); ScSessionStatus sessionStatus = - OtrActivator.scOtrEngine.getSessionStatus(contact); + OtrActivator.scOtrEngine.getSessionStatus(otrContact); // If OTR is disabled and we are not over an encrypted session, don't // process anything. if (!policy.getEnableManual() @@ -80,7 +85,7 @@ public MessageDeliveredEvent messageDeliveryPending( // Process the outgoing message. String msgContent = evt.getSourceMessage().getContent(); String processedMessageContent = - OtrActivator.scOtrEngine.transformSending(contact, msgContent); + OtrActivator.scOtrEngine.transformSending(otrContact, msgContent); if (processedMessageContent == null || processedMessageContent.length() < 1) @@ -114,10 +119,12 @@ public MessageDeliveredEvent messageDeliveryPending( public MessageReceivedEvent messageReceived(MessageReceivedEvent evt) { Contact contact = evt.getSourceContact(); + OtrContact otrContact = + OtrContactManager.getOtrContact(contact, evt.getContactResource()); OtrPolicy policy = OtrActivator.scOtrEngine.getContactPolicy(contact); ScSessionStatus sessionStatus = - OtrActivator.scOtrEngine.getSessionStatus(contact); + OtrActivator.scOtrEngine.getSessionStatus(otrContact); // If OTR is disabled and we are not over an encrypted session, don't // process anything. if (!policy.getEnableManual() @@ -129,7 +136,7 @@ public MessageReceivedEvent messageReceived(MessageReceivedEvent evt) String msgContent = evt.getSourceMessage().getContent(); String processedMessageContent = - OtrActivator.scOtrEngine.transformReceiving(contact, msgContent); + OtrActivator.scOtrEngine.transformReceiving(otrContact, msgContent); if (processedMessageContent == null || processedMessageContent.length() < 1) diff --git a/src/net/java/sip/communicator/plugin/otr/OtrWeakListener.java b/src/net/java/sip/communicator/plugin/otr/OtrWeakListener.java index b94a9bf49..4c55f248d 100644 --- a/src/net/java/sip/communicator/plugin/otr/OtrWeakListener.java +++ b/src/net/java/sip/communicator/plugin/otr/OtrWeakListener.java @@ -8,6 +8,7 @@ import java.lang.ref.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.protocol.*; /** @@ -93,7 +94,7 @@ public void contactPolicyChanged(Contact contact) * Forwards the event/notification to the associated * T if it is still needed by the application. */ - public void contactVerificationStatusChanged(Contact contact) + public void contactVerificationStatusChanged(OtrContact contact) { ScOtrKeyManagerListener l = getListener(); @@ -145,7 +146,7 @@ public void globalPolicyChanged() * Forwards the event/notification to the associated * T if it is still needed by the application. */ - public void sessionStatusChanged(Contact contact) + public void sessionStatusChanged(OtrContact contact) { ScOtrEngineListener l = getListener(); @@ -157,7 +158,7 @@ public void sessionStatusChanged(Contact contact) * Forwards the event/notification to the associated * T if it is still needed by the application. */ - public void multipleInstancesDetected(Contact contact) + public void multipleInstancesDetected(OtrContact contact) { ScOtrEngineListener l = getListener(); @@ -169,7 +170,7 @@ public void multipleInstancesDetected(Contact contact) * Forwards the event/notification to the associated * T if it is still needed by the application. */ - public void outgoingSessionChanged(Contact contact) + public void outgoingSessionChanged(OtrContact contact) { ScOtrEngineListener l = getListener(); diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java b/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java index cade91025..f9ee9098b 100644 --- a/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java @@ -11,6 +11,7 @@ import net.java.otr4j.*; import net.java.otr4j.session.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.protocol.*; /** @@ -33,7 +34,7 @@ public interface ScOtrEngine * @param secret The secret answer for the question. */ public abstract void initSmp( - Contact contact, String question, String secret); + OtrContact contact, String question, String secret); /** * Responds to a question that is asked during the Smp negotiation process. @@ -41,12 +42,16 @@ public abstract void initSmp( * >http://en.wikipedia.org/wiki/Socialist_Millionaire_Problem * * @param contact The contact for whom we want to respond to a question - * during the Smp negotiation process. + * during the Smp negotiation process. + * @param receiverTag The instance tag of the intended receiver of the SMP + * response * @param question The question that is asked during the Smp negotiation. * @param secret The secret answer for the question. */ - public abstract void respondSmp( - Contact contact, String question, String secret); + public abstract void respondSmp(OtrContact contact, + InstanceTag receiverTag, + String question, + String secret); /** * Aborts the Smp negotiation process. @@ -56,52 +61,66 @@ public abstract void respondSmp( * @param contact The contact with whom we want to abort the * Smp negotiation process. */ - public abstract void abortSmp(Contact contact); + public abstract void abortSmp(OtrContact contact); /** * Transforms an outgoing message. * - * @param contact the destination {@link Contact}. + * @param contact the destination {@link OtrContact}. * @param content the original message content. * @return the transformed message content. */ - public abstract String transformSending(Contact contact, String content); + public abstract String transformSending(OtrContact contact, String content); /** * Transforms an incoming message. * - * @param contact the source {@link Contact}. + * @param contact the source {@link OtrContact}. * @param content the original message content. * @return the transformed message content. */ - public abstract String transformReceiving(Contact contact, String content); + public abstract String transformReceiving(OtrContact contact, String content); /** - * Starts the Off-the-Record session for the given {@link Contact}, if it's + * Starts the Off-the-Record session for the given {@link OtrContact}, if it's * not already started. * - * @param contact the {@link Contact} with whom we want to start an OTR + * @param contact the {@link OtrContact} with whom we want to start an OTR * session. */ - public abstract void startSession(Contact contact); + public abstract void startSession(OtrContact contact); /** - * Ends the Off-the-Record session for the given {@link Contact}, if it is + * Ends the Off-the-Record session for the given {@link OtrContact}, if it is * not already started. * - * @param contact the {@link Contact} with whom we want to end the OTR + * @param contact the {@link OtrContact} with whom we want to end the OTR * session. */ - public abstract void endSession(Contact contact); + public abstract void endSession(OtrContact contact); /** - * Refreshes the Off-the-Record session for the given {@link Contact}. If + * Refreshes the Off-the-Record session for the given {@link OtrContact}. If * the session does not exist, a new session is established. * - * @param contact the {@link Contact} with whom we want to refresh the OTR + * @param contact the {@link OtrContact} with whom we want to refresh the OTR * session. */ - public abstract void refreshSession(Contact contact); + public abstract void refreshSession(OtrContact contact); + + /** + * Get the outgoing OTRv3 Session. This could be the 'master' + * session as well as a 'slave' session. + * This method could also be safely used for OTRv2 sessions. In the case of + * version 2 the master session itself will always be returned. + * + * @param contact the {@link OtrContact} for whom we want to get the + * outgoing OTR session. + * + * @return the Session that is currently transforming outgoing all + * messages. + */ + public abstract Session getOutgoingSession(OtrContact contact); /** * Some IM networks always relay all messages to all sessions of a client @@ -113,11 +132,11 @@ public abstract void respondSmp( * Returns a list containing all instances of a session. The 'master' * session is always first in the list. * - * @param contact the {@link Contact} for whom we want to get the instances + * @param contact the {@link OtrContact} for whom we want to get the instances * * @return A list of all instances of the session for the specified contact. */ - public abstract List getSessionInstances(Contact contact); + public abstract List getSessionInstances(OtrContact contact); /** * Some IM networks always relay all messages to all sessions of a client @@ -130,23 +149,23 @@ public abstract void respondSmp( * specific session of his buddy who is logged in multiple times, he can set * the outgoing instance of his buddy by specifying his InstanceTag. * - * @param contact the {@link Contact} to whom we want to set the outgoing + * @param contact the {@link OtrContact} to whom we want to set the outgoing * instance tag. * @param tag the outgoing {@link InstanceTag} * * @return true if an outgoing session with such {@link InstanceTag} exists * . Otherwise false */ - public abstract boolean setOutgoingSession(Contact contact, InstanceTag tag); + public abstract boolean setOutgoingSession(OtrContact contact, InstanceTag tag); /** - * Gets the {@link ScSessionStatus} for the given {@link Contact}. + * Gets the {@link ScSessionStatus} for the given {@link OtrContact}. * - * @param contact the {@link Contact} whose {@link ScSessionStatus} we are + * @param contact the {@link OtrContact} whose {@link ScSessionStatus} we are * interested in. * @return the {@link ScSessionStatus}. */ - public abstract ScSessionStatus getSessionStatus(Contact contact); + public abstract ScSessionStatus getSessionStatus(OtrContact contact); // New Methods (Misc) @@ -175,7 +194,7 @@ public abstract void respondSmp( */ public abstract void removeListener(ScOtrEngineListener listener); - public abstract PublicKey getRemotePublicKey(Contact contact); + public abstract PublicKey getRemotePublicKey(OtrContact otrContact); // New Methods (Policy management) /** diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java index 137122c80..87a399df7 100644 --- a/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java @@ -14,6 +14,7 @@ import net.java.otr4j.*; import net.java.otr4j.crypto.*; import net.java.otr4j.session.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.plugin.otr.authdialog.*; import net.java.sip.communicator.service.browserlauncher.*; import net.java.sip.communicator.service.contactlist.*; @@ -52,12 +53,31 @@ public KeyPair getLocalKeyPair(SessionID sessionID) public OtrPolicy getSessionPolicy(SessionID sessionID) { - return getContactPolicy(getContact(sessionID)); + return getContactPolicy(getOtrContact(sessionID).contact); } public void injectMessage(SessionID sessionID, String messageText) { - Contact contact = getContact(sessionID); + OtrContact otrContact = getOtrContact(sessionID); + Contact contact = otrContact.contact; + ContactResource resource = null; + + if (contact.supportResources()) + { + Collection resources = contact.getResources(); + if (resources != null) + { + for (ContactResource r : resources) + { + if (r.equals(otrContact.resource)) + { + resource = r; + break; + } + } + } + } + OperationSetBasicInstantMessaging imOpSet = contact .getProtocolProvider() @@ -86,7 +106,7 @@ public void injectMessage(SessionID sessionID, String messageText) null); injectedMessageUIDs.add(message.getMessageUID()); - imOpSet.sendInstantMessage(contact, message); + imOpSet.sendInstantMessage(contact, resource, message); } public void showError(SessionID sessionID, String err) @@ -96,10 +116,11 @@ public void showError(SessionID sessionID, String err) public void showWarning(SessionID sessionID, String warn) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + Contact contact = otrContact.contact; OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, warn, @@ -110,14 +131,16 @@ public void showWarning(SessionID sessionID, String warn) public void unreadableMessageReceived(SessionID sessionID) throws OtrException { - Contact contact = getContact(sessionID); - if (contact == null) - return; + OtrContact otrContact = getOtrContact(sessionID); + String resourceName = otrContact.resource != null ? + "/" + otrContact.resource.getResourceName() : ""; + Contact contact = otrContact.contact; String error = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.unreadablemsgreceived", - new String[] {contact.getDisplayName()}); + new String[] + {contact.getDisplayName() + resourceName}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.ERROR_MESSAGE, error, @@ -128,10 +151,11 @@ public void unreadableMessageReceived(SessionID sessionID) public void unencryptedMessageReceived(SessionID sessionID, String msg) throws OtrException { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + Contact contact = otrContact.contact; String warn = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.unencryptedmsgreceived"); @@ -145,10 +169,11 @@ public void unencryptedMessageReceived(SessionID sessionID, String msg) public void smpError(SessionID sessionID, int tlvType, boolean cheated) throws OtrException { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + Contact contact = otrContact.contact; logger.debug("SMP error occurred" + ". Contact: " + contact.getDisplayName() + ". TLV type: " + tlvType @@ -162,11 +187,11 @@ public void smpError(SessionID sessionID, int tlvType, boolean cheated) Chat.ERROR_MESSAGE, error, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); - SmpProgressDialog progressDialog = progressDialogMap.get(contact); + SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); - progressDialogMap.put(contact, progressDialog); + progressDialogMap.put(otrContact, progressDialog); } progressDialog.setProgressFail(); @@ -176,10 +201,11 @@ public void smpError(SessionID sessionID, int tlvType, boolean cheated) @Override public void smpAborted(SessionID sessionID) throws OtrException { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + Contact contact = otrContact.contact; Session session = otrEngine.getSession(sessionID); if (session.isSmpInProgress()) { @@ -191,14 +217,15 @@ public void smpAborted(SessionID sessionID) throws OtrException contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, warn, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); - - SmpProgressDialog progressDialog = progressDialogMap.get(contact); + + SmpProgressDialog progressDialog = + progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); - progressDialogMap.put(contact, progressDialog); + progressDialogMap.put(otrContact, progressDialog); } - + progressDialog.setProgressFail(); progressDialog.setVisible(true); } @@ -208,14 +235,18 @@ public void smpAborted(SessionID sessionID) throws OtrException public void finishedSessionMessage(SessionID sessionID) throws OtrException { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + String resourceName = otrContact.resource != null ? + "/" + otrContact.resource.getResourceName() : ""; + Contact contact = otrContact.contact; String error = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.sessionfinishederror", - new String[] {contact.getDisplayName()}); + new String[] + {contact.getDisplayName() + resourceName}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.ERROR_MESSAGE, error, @@ -226,14 +257,19 @@ public void finishedSessionMessage(SessionID sessionID) public void requireEncryptedMessage(SessionID sessionID, String msgText) throws OtrException { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + String resourceName = otrContact.resource != null ? + "/" + otrContact.resource.getResourceName() : ""; + + Contact contact = otrContact.contact; String error = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.requireencryption", - new String[] {contact.getDisplayName()}); + new String[] + {contact.getDisplayName() + resourceName}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.ERROR_MESSAGE, error, @@ -250,21 +286,24 @@ public byte[] getLocalFingerprintRaw(SessionID sessionID) } @Override - public void askForSecret(SessionID sessionID, String question) + public void askForSecret( + SessionID sessionID, InstanceTag receiverTag, String question) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + Contact contact = otrContact.contact; SmpAuthenticateBuddyDialog dialog = - new SmpAuthenticateBuddyDialog(contact, question); + new SmpAuthenticateBuddyDialog( + otrContact, receiverTag, question); dialog.setVisible(true); - SmpProgressDialog progressDialog = progressDialogMap.get(contact); + SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); - progressDialogMap.put(contact, progressDialog); + progressDialogMap.put(otrContact, progressDialog); } progressDialog.init(); @@ -275,17 +314,18 @@ public void askForSecret(SessionID sessionID, String question) public void verify( SessionID sessionID, String fingerprint, boolean approved) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; - OtrActivator.scOtrKeyManager.verify(contact, fingerprint); + Contact contact = otrContact.contact; + OtrActivator.scOtrKeyManager.verify(otrContact, fingerprint); - SmpProgressDialog progressDialog = progressDialogMap.get(contact); + SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); - progressDialogMap.put(contact, progressDialog); + progressDialogMap.put(otrContact, progressDialog); } progressDialog.setProgressSuccess(); @@ -295,17 +335,18 @@ public void verify( @Override public void unverify(SessionID sessionID, String fingerprint) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; - OtrActivator.scOtrKeyManager.unverify(contact, fingerprint); + Contact contact = otrContact.contact; + OtrActivator.scOtrKeyManager.unverify(otrContact, fingerprint); - SmpProgressDialog progressDialog = progressDialogMap.get(contact); + SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); - progressDialogMap.put(contact, progressDialog); + progressDialogMap.put(otrContact, progressDialog); } progressDialog.setProgressFail(); @@ -338,14 +379,18 @@ public String getFallbackMessage(SessionID sessionID) @Override public void multipleInstancesDetected(SessionID sessionID) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + String resourceName = otrContact.resource != null ? + "/" + otrContact.resource.getResourceName() : ""; + Contact contact = otrContact.contact; String message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.multipleinstancesdetected", - new String[] {contact.getDisplayName()}); + new String[] + {contact.getDisplayName() + resourceName}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, @@ -356,14 +401,18 @@ public void multipleInstancesDetected(SessionID sessionID) @Override public void messageFromAnotherInstanceReceived(SessionID sessionID) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + String resourceName = otrContact.resource != null ? + "/" + otrContact.resource.getResourceName() : ""; + Contact contact = otrContact.contact; String message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.msgfromanotherinstance", - new String[] {contact.getDisplayName()}); + new String[] + {contact.getDisplayName() + resourceName}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, @@ -393,13 +442,13 @@ public void messageFromAnotherInstanceReceived(SessionID sessionID) private Map scSessionStatusMap = new ConcurrentHashMap(); - private static final Map contactsMap = - new Hashtable(); + private static final Map contactsMap = + new Hashtable(); - private static final Map progressDialogMap = - new ConcurrentHashMap(); + private static final Map progressDialogMap = + new ConcurrentHashMap(); - public static Contact getContact(SessionID sessionID) + public static OtrContact getOtrContact(SessionID sessionID) { return contactsMap.get(new ScSessionID(sessionID)); } @@ -422,13 +471,15 @@ public static ScSessionID getScSessionForGuid(UUID guid) return null; } - public static SessionID getSessionID(Contact contact) + public static SessionID getSessionID(OtrContact otrContact) { - ProtocolProviderService pps = contact.getProtocolProvider(); + ProtocolProviderService pps = otrContact.contact.getProtocolProvider(); + String resourceName = otrContact.resource != null ? + "/" + otrContact.resource.getResourceName() : ""; SessionID sessionID = new SessionID( pps.getAccountID().getAccountUniqueID(), - contact.getAddress(), + otrContact.contact.getAddress() + resourceName, pps.getProtocolName()); synchronized (contactsMap) @@ -438,7 +489,7 @@ public static SessionID getSessionID(Contact contact) ScSessionID scSessionID = new ScSessionID(sessionID); - contactsMap.put(scSessionID, contact); + contactsMap.put(scSessionID, otrContact); } return sessionID; @@ -473,15 +524,18 @@ public ScOtrEngineImpl() { public void sessionStatusChanged(SessionID sessionID) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + String resourceName = otrContact.resource != null ? + "/" + otrContact.resource.getResourceName() : ""; + Contact contact = otrContact.contact; // Cancels any scheduled tasks that will change the // ScSessionStatus for this Contact - scheduler.cancel(contact); + scheduler.cancel(otrContact); - ScSessionStatus scSessionStatus = getSessionStatus(contact); + ScSessionStatus scSessionStatus = getSessionStatus(otrContact); String message = ""; switch (otrEngine.getSessionStatus(sessionID)) { @@ -522,7 +576,7 @@ public void sessionStatusChanged(SessionID sessionID) contact, remoteFingerprint)) { OtrActivator.scOtrKeyManager.unverify( - contact, remoteFingerprint); + otrContact, remoteFingerprint); UUID sessionGuid = null; for(ScSessionID scSessionID : contactsMap.keySet()) { @@ -542,7 +596,7 @@ public void sessionStatusChanged(SessionID sessionID) + ".unverifiedsessionwarning", new String[] { - contact.getDisplayName(), + contact.getDisplayName() + resourceName, this.getClass().getName(), "AUTHENTIFICATION", sessionGuid.toString() @@ -590,6 +644,21 @@ public void sessionStatusChanged(SessionID sessionID) otrAndHistoryMessage, OperationSetBasicInstantMessaging.HTML_MIME_TYPE); + message = + OtrActivator.resourceService.getI18NString( + "plugin.otr.activator.multipleinstancesdetected", + new String[] + {contact.getDisplayName()}); + + if (contact.supportResources() + && contact.getResources() != null + && contact.getResources().size() > 1) + OtrActivator.uiService.getChat(contact).addMessage( + contact.getDisplayName(), + new Date(), Chat.SYSTEM_MESSAGE, + message, + OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); + message = OtrActivator.resourceService.getI18NString( OtrActivator.scOtrKeyManager.isVerified( @@ -597,7 +666,8 @@ public void sessionStatusChanged(SessionID sessionID) ? "plugin.otr.activator.sessionstared" : "plugin.otr.activator" + ".unverifiedsessionstared", - new String[] { contact.getDisplayName() }); + new String[] + {contact.getDisplayName() + resourceName}); break; case FINISHED: @@ -607,7 +677,7 @@ public void sessionStatusChanged(SessionID sessionID) OtrActivator.resourceService.getI18NString( "plugin.otr.activator.sessionfinished", new String[] - { contact.getDisplayName() }); + {contact.getDisplayName() + resourceName}); break; case PLAINTEXT: scSessionStatus = ScSessionStatus.PLAINTEXT; @@ -615,7 +685,7 @@ public void sessionStatusChanged(SessionID sessionID) message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.sessionlost", new String[] - { contact.getDisplayName() }); + {contact.getDisplayName() + resourceName}); break; } @@ -625,27 +695,27 @@ public void sessionStatusChanged(SessionID sessionID) OperationSetBasicInstantMessaging.HTML_MIME_TYPE); for (ScOtrEngineListener l : getListeners()) - l.sessionStatusChanged(contact); + l.sessionStatusChanged(otrContact); } public void multipleInstancesDetected(SessionID sessionID) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; for (ScOtrEngineListener l : getListeners()) - l.multipleInstancesDetected(contact); + l.multipleInstancesDetected(otrContact); } public void outgoingSessionChanged(SessionID sessionID) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; for (ScOtrEngineListener l : getListeners()) - l.outgoingSessionChanged(contact); + l.outgoingSessionChanged(otrContact); } }); } @@ -705,12 +775,12 @@ public void chatLinkClicked(URI url) } } - public void endSession(Contact contact) + public void endSession(OtrContact otrContact) { - SessionID sessionID = getSessionID(contact); + SessionID sessionID = getSessionID(otrContact); try { - setSessionStatus(contact, ScSessionStatus.PLAINTEXT); + setSessionStatus(otrContact, ScSessionStatus.PLAINTEXT); otrEngine.endSession(sessionID); } @@ -722,8 +792,14 @@ public void endSession(Contact contact) public OtrPolicy getContactPolicy(Contact contact) { + ProtocolProviderService pps = contact.getProtocolProvider(); + SessionID sessionID + = new SessionID( + pps.getAccountID().getAccountUniqueID(), + contact.getAddress(), + pps.getProtocolName()); int policy = - this.configurator.getPropertyInt(getSessionID(contact) + "policy", + this.configurator.getPropertyInt(sessionID + "policy", -1); if (policy < 0) return getGlobalPolicy(); @@ -734,7 +810,7 @@ public OtrPolicy getContactPolicy(Contact contact) public OtrPolicy getGlobalPolicy() { return new OtrPolicyImpl(this.configurator.getPropertyInt("POLICY", - OtrPolicy.OTRL_POLICY_MANUAL)); + OtrPolicy.OTRL_POLICY_DEFAULT)); } /** @@ -764,31 +840,31 @@ private class ScSessionStatusScheduler { private final Timer timer = new Timer(); - private final Map tasks = - new ConcurrentHashMap(); + private final Map tasks = + new ConcurrentHashMap(); public void scheduleScSessionStatusChange( - final Contact contact, final ScSessionStatus status) + final OtrContact otrContact, final ScSessionStatus status) { - cancel(contact); + cancel(otrContact); TimerTask task = new TimerTask() { @Override public void run() { - setSessionStatus(contact, status); + setSessionStatus(otrContact, status); } }; timer.schedule(task, SESSION_TIMEOUT); - tasks.put(contact, task); + tasks.put(otrContact, task); } - public void cancel(final Contact contact) + public void cancel(final OtrContact otrContact) { - TimerTask task = tasks.get(contact); + TimerTask task = tasks.get(otrContact); if (task != null) task.cancel(); - tasks.remove(contact); + tasks.remove(otrContact); } public void serviceChanged(ServiceEvent ev) @@ -805,13 +881,15 @@ public void serviceChanged(ServiceEvent ev) ProtocolProviderService provider = (ProtocolProviderService) service; - Iterator i = tasks.keySet().iterator(); + Iterator i = tasks.keySet().iterator(); while (i.hasNext()) { - Contact contact = i.next(); - if (provider.equals(contact.getProtocolProvider())) { - cancel(contact); + OtrContact otrContact = i.next(); + if (provider.equals( + otrContact.contact.getProtocolProvider())) + { + cancel(otrContact); i.remove(); } } @@ -819,14 +897,15 @@ public void serviceChanged(ServiceEvent ev) } } - private void setSessionStatus(Contact contact, ScSessionStatus status) + private void setSessionStatus(OtrContact contact, ScSessionStatus status) { scSessionStatusMap.put(getSessionID(contact), status); + scheduler.cancel(contact); for (ScOtrEngineListener l : getListeners()) l.sessionStatusChanged(contact); } - public ScSessionStatus getSessionStatus(Contact contact) + public ScSessionStatus getSessionStatus(OtrContact contact) { SessionID sessionID = getSessionID(contact); SessionStatus sessionStatus = otrEngine.getSessionStatus(sessionID); @@ -871,9 +950,9 @@ public void launchHelp() .getI18NString("plugin.otr.authbuddydialog.HELP_URI")); } - public void refreshSession(Contact contact) + public void refreshSession(OtrContact otrContact) { - SessionID sessionID = getSessionID(contact); + SessionID sessionID = getSessionID(otrContact); try { otrEngine.refreshSession(sessionID); @@ -922,24 +1001,25 @@ public void serviceChanged(ServiceEvent ev) synchronized(contactsMap) { - Iterator i = contactsMap.values().iterator(); + Iterator i = contactsMap.values().iterator(); while (i.hasNext()) - { - Contact contact = i.next(); - if (provider.equals(contact.getProtocolProvider())) + { + OtrContact otrContact = i.next(); + if (provider.equals( + otrContact.contact.getProtocolProvider())) { - scSessionStatusMap.remove(getSessionID(contact)); + scSessionStatusMap.remove(getSessionID(otrContact)); i.remove(); } } } - Iterator i = progressDialogMap.keySet().iterator(); + Iterator i = progressDialogMap.keySet().iterator(); while (i.hasNext()) { - if (provider.equals(i.next().getProtocolProvider())) + if (provider.equals(i.next().contact.getProtocolProvider())) i.remove(); } scheduler.serviceChanged(ev); @@ -948,7 +1028,14 @@ public void serviceChanged(ServiceEvent ev) public void setContactPolicy(Contact contact, OtrPolicy policy) { - String propertyID = getSessionID(contact) + "policy"; + ProtocolProviderService pps = contact.getProtocolProvider(); + SessionID sessionID + = new SessionID( + pps.getAccountID().getAccountUniqueID(), + contact.getAddress(), + pps.getProtocolName()); + + String propertyID = sessionID + "policy"; if (policy == null) this.configurator.removeProperty(propertyID); else @@ -971,28 +1058,29 @@ public void setGlobalPolicy(OtrPolicy policy) public void showError(SessionID sessionID, String err) { - Contact contact = getContact(sessionID); - if (contact == null) + OtrContact otrContact = getOtrContact(sessionID); + if (otrContact == null) return; + Contact contact = otrContact.contact; OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.ERROR_MESSAGE, err, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); } - public void startSession(Contact contact) + public void startSession(OtrContact otrContact) { - SessionID sessionID = getSessionID(contact); + SessionID sessionID = getSessionID(otrContact); - ScSessionStatus scSessionStatus = getSessionStatus(contact); + ScSessionStatus scSessionStatus = getSessionStatus(otrContact); scSessionStatus = ScSessionStatus.LOADING; scSessionStatusMap.put(sessionID, scSessionStatus); for (ScOtrEngineListener l : getListeners()) { - l.sessionStatusChanged(contact); + l.sessionStatusChanged(otrContact); scheduler.scheduleScSessionStatusChange( - contact, ScSessionStatus.TIMED_OUT); + otrContact, ScSessionStatus.TIMED_OUT); } try @@ -1006,9 +1094,9 @@ public void startSession(Contact contact) } } - public String transformReceiving(Contact contact, String msgText) + public String transformReceiving(OtrContact otrContact, String msgText) { - SessionID sessionID = getSessionID(contact); + SessionID sessionID = getSessionID(otrContact); try { return otrEngine.transformReceiving(sessionID, msgText); @@ -1021,9 +1109,9 @@ public String transformReceiving(Contact contact, String msgText) } } - public String transformSending(Contact contact, String msgText) + public String transformSending(OtrContact otrContact, String msgText) { - SessionID sessionID = getSessionID(contact); + SessionID sessionID = getSessionID(otrContact); try { return otrEngine.transformSending(sessionID, msgText); @@ -1036,25 +1124,25 @@ public String transformSending(Contact contact, String msgText) } } - private Session getSession(Contact contact) + private Session getSession(OtrContact contact) { SessionID sessionID = getSessionID(contact); return otrEngine.getSession(sessionID); } @Override - public void initSmp(Contact contact, String question, String secret) + public void initSmp(OtrContact otrContact, String question, String secret) { - Session session = getSession(contact); + Session session = getSession(otrContact); try { session.initSmp(question, secret); - SmpProgressDialog progressDialog = progressDialogMap.get(contact); + SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { - progressDialog = new SmpProgressDialog(contact); - progressDialogMap.put(contact, progressDialog); + progressDialog = new SmpProgressDialog(otrContact.contact); + progressDialogMap.put(otrContact, progressDialog); } progressDialog.init(); @@ -1063,24 +1151,27 @@ public void initSmp(Contact contact, String question, String secret) catch (OtrException e) { logger.error("Error initializing SMP session with contact " - + contact.getDisplayName(), e); + + otrContact.contact.getDisplayName(), e); showError(session.getSessionID(), e.getMessage()); } } @Override - public void respondSmp(Contact contact, String question, String secret) + public void respondSmp( OtrContact otrContact, + InstanceTag receiverTag, + String question, + String secret) { - Session session = getSession(contact); + Session session = getSession(otrContact); try { - session.respondSmp(question, secret); + session.respondSmp(receiverTag, question, secret); - SmpProgressDialog progressDialog = progressDialogMap.get(contact); + SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { - progressDialog = new SmpProgressDialog(contact); - progressDialogMap.put(contact, progressDialog); + progressDialog = new SmpProgressDialog(otrContact.contact); + progressDialogMap.put(otrContact, progressDialog); } progressDialog.incrementProgress(); @@ -1090,24 +1181,24 @@ public void respondSmp(Contact contact, String question, String secret) { logger.error( "Error occured when sending SMP response to contact " - + contact.getDisplayName(), e); + + otrContact.contact.getDisplayName(), e); showError(session.getSessionID(), e.getMessage()); } } @Override - public void abortSmp(Contact contact) + public void abortSmp(OtrContact otrContact) { - Session session = getSession(contact); + Session session = getSession(otrContact); try { session.abortSmp(); - SmpProgressDialog progressDialog = progressDialogMap.get(contact); + SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { - progressDialog = new SmpProgressDialog(contact); - progressDialogMap.put(contact, progressDialog); + progressDialog = new SmpProgressDialog(otrContact.contact); + progressDialogMap.put(otrContact, progressDialog); } progressDialog.dispose(); @@ -1115,30 +1206,30 @@ public void abortSmp(Contact contact) catch (OtrException e) { logger.error("Error aborting SMP session with contact " - + contact.getDisplayName(), e); + + otrContact.contact.getDisplayName(), e); showError(session.getSessionID(), e.getMessage()); } } - public PublicKey getRemotePublicKey(Contact contact) + public PublicKey getRemotePublicKey(OtrContact otrContact) { - if (contact == null) + if (otrContact == null) return null; - Session session = getSession(contact); + Session session = getSession(otrContact); return session.getRemotePublicKey(); } - public List getSessionInstances(Contact contact) + public List getSessionInstances(OtrContact otrContact) { - if (contact == null) + if (otrContact == null) return null; - return getSession(contact).getInstances(); + return getSession(otrContact).getInstances(); } - public boolean setOutgoingSession(Contact contact, InstanceTag tag) + public boolean setOutgoingSession(OtrContact contact, InstanceTag tag) { if (contact == null) return false; @@ -1148,4 +1239,14 @@ public boolean setOutgoingSession(Contact contact, InstanceTag tag) scSessionStatusMap.remove(session.getSessionID()); return session.setOutgoingInstance(tag); } + + public Session getOutgoingSession(OtrContact contact) + { + if (contact == null) + return null; + + SessionID sessionID = getSessionID(contact); + + return otrEngine.getOutgoingSession(sessionID); + } } diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrEngineListener.java b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineListener.java index 8ab95b892..ffd211ae2 100644 --- a/src/net/java/sip/communicator/plugin/otr/ScOtrEngineListener.java +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineListener.java @@ -6,6 +6,7 @@ */ package net.java.sip.communicator.plugin.otr; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.protocol.*; /** @@ -18,9 +19,9 @@ public interface ScOtrEngineListener public void globalPolicyChanged(); - public void sessionStatusChanged(Contact contact); + public void sessionStatusChanged(OtrContact contact); - public void multipleInstancesDetected(Contact contact); + public void multipleInstancesDetected(OtrContact contact); - public void outgoingSessionChanged(Contact contact); + public void outgoingSessionChanged(OtrContact contact); } diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManager.java b/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManager.java index 47ca22d0e..5b36f5855 100755 --- a/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManager.java +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManager.java @@ -9,6 +9,7 @@ import java.security.*; import java.util.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.protocol.*; /** @@ -23,9 +24,9 @@ public interface ScOtrKeyManager public abstract void removeListener(ScOtrKeyManagerListener l); - public abstract void verify(Contact contact, String fingerprint); + public abstract void verify(OtrContact contact, String fingerprint); - public abstract void unverify(Contact contact, String fingerprint); + public abstract void unverify(OtrContact contact, String fingerprint); public abstract boolean isVerified(Contact contact, String fingerprint); @@ -39,8 +40,6 @@ public interface ScOtrKeyManager public abstract void saveFingerprint(Contact contact, String fingerprint); - public abstract PublicKey loadPublicKey(Contact contact); - public abstract KeyPair loadKeyPair(AccountID accountID); public abstract void generateKeyPair(AccountID accountID); diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManagerImpl.java b/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManagerImpl.java index 893353032..eb6d07be3 100755 --- a/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManagerImpl.java +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManagerImpl.java @@ -11,6 +11,7 @@ import java.util.*; import net.java.otr4j.crypto.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.protocol.*; /** @@ -62,28 +63,28 @@ public void removeListener(ScOtrKeyManagerListener l) } } - public void verify(Contact contact, String fingerprint) + public void verify(OtrContact otrContact, String fingerprint) { - if ((fingerprint == null) || contact == null) + if ((fingerprint == null) || otrContact == null) return; - this.configurator.setProperty(contact.getAddress() + fingerprint + this.configurator.setProperty(otrContact.contact.getAddress() + fingerprint + ".fingerprint.verified", true); for (ScOtrKeyManagerListener l : getListeners()) - l.contactVerificationStatusChanged(contact); + l.contactVerificationStatusChanged(otrContact); } - public void unverify(Contact contact, String fingerprint) + public void unverify(OtrContact otrContact, String fingerprint) { - if ((fingerprint == null) || contact == null) + if ((fingerprint == null) || otrContact == null) return; - this.configurator.setProperty(contact.getAddress() + fingerprint + this.configurator.setProperty(otrContact.contact.getAddress() + fingerprint + ".fingerprint.verified", false); for (ScOtrKeyManagerListener l : getListeners()) - l.contactVerificationStatusChanged(contact); + l.contactVerificationStatusChanged(otrContact); } public boolean isVerified(Contact contact, String fingerprint) @@ -139,9 +140,9 @@ public List getAllRemoteFingerprints(Contact contact) // Now we can store the old properties in the new format. if (isVerified) - verify(contact, fingerprint); + verify(OtrContactManager.getOtrContact(contact, null), fingerprint); else - unverify(contact, fingerprint); + unverify(OtrContactManager.getOtrContact(contact, null), fingerprint); // Finally we append the new fingerprint to out stored list of // fingerprints. @@ -229,39 +230,6 @@ public void saveFingerprint(Contact contact, String fingerprint) + ".fingerprint.verified", false); } - public PublicKey loadPublicKey(Contact contact) - { - if (contact == null) - return null; - - String userID = contact.getAddress(); - - byte[] b64PubKey = - this.configurator.getPropertyBytes(userID + ".publicKey"); - if (b64PubKey == null) - return null; - - X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64PubKey); - - // Generate KeyPair. - KeyFactory keyFactory; - try - { - keyFactory = KeyFactory.getInstance("DSA"); - return keyFactory.generatePublic(publicKeySpec); - } - catch (NoSuchAlgorithmException e) - { - e.printStackTrace(); - return null; - } - catch (InvalidKeySpecException e) - { - e.printStackTrace(); - return null; - } - } - public KeyPair loadKeyPair(AccountID account) { if (account == null) diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManagerListener.java b/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManagerListener.java index 7ab2c6f02..ba2213f1c 100755 --- a/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManagerListener.java +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrKeyManagerListener.java @@ -6,7 +6,7 @@ */ package net.java.sip.communicator.plugin.otr; -import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; /** * @@ -14,5 +14,5 @@ */ public interface ScOtrKeyManagerListener { - public void contactVerificationStatusChanged(Contact contact); + public void contactVerificationStatusChanged(OtrContact contact); } diff --git a/src/net/java/sip/communicator/plugin/otr/SwingOtrActionHandler.java b/src/net/java/sip/communicator/plugin/otr/SwingOtrActionHandler.java index 309e38750..0f5343461 100644 --- a/src/net/java/sip/communicator/plugin/otr/SwingOtrActionHandler.java +++ b/src/net/java/sip/communicator/plugin/otr/SwingOtrActionHandler.java @@ -6,12 +6,12 @@ */ package net.java.sip.communicator.plugin.otr; -import net.java.sip.communicator.plugin.otr.authdialog.*; -import net.java.sip.communicator.service.protocol.*; - import java.awt.*; import java.util.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; +import net.java.sip.communicator.plugin.otr.authdialog.*; + /** * Default OtrActionHandler implementation that opens SWING buddy authenticate * dialog. @@ -24,13 +24,13 @@ public class SwingOtrActionHandler { public void onAuthenticateLinkClicked(UUID uuid) { - Contact contact = ScOtrEngineImpl.getContact( + OtrContact otrContact = ScOtrEngineImpl.getOtrContact( ScOtrEngineImpl.getScSessionForGuid(uuid).getSessionID()); - openAuthDialog(contact); + openAuthDialog(otrContact); } - public static void openAuthDialog(Contact contact) + public static void openAuthDialog(OtrContact contact) { // Launch auth buddy dialog. OtrBuddyAuthenticationDialog authenticateBuddyDialog diff --git a/src/net/java/sip/communicator/plugin/otr/authdialog/FingerprintAuthenticationPanel.java b/src/net/java/sip/communicator/plugin/otr/authdialog/FingerprintAuthenticationPanel.java index 31d6848ca..7c6119399 100644 --- a/src/net/java/sip/communicator/plugin/otr/authdialog/FingerprintAuthenticationPanel.java +++ b/src/net/java/sip/communicator/plugin/otr/authdialog/FingerprintAuthenticationPanel.java @@ -14,7 +14,7 @@ import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.otr.*; -import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; /** * @author George Politis @@ -29,7 +29,7 @@ public class FingerprintAuthenticationPanel /** * The Contact that we are authenticating. */ - private final Contact contact; + private final OtrContact otrContact; private SIPCommTextField txtRemoteFingerprintComparison; @@ -61,9 +61,9 @@ public class FingerprintAuthenticationPanel * * @param contact The contact that this panel refers to. */ - FingerprintAuthenticationPanel(Contact contact) + FingerprintAuthenticationPanel(OtrContact contact) { - this.contact = contact; + this.otrContact = contact; initComponents(); loadContact(); @@ -109,11 +109,11 @@ private void initComponents() cbAction.addItem(actionIHave); cbAction.addItem(actionIHaveNot); - PublicKey pubKey = OtrActivator.scOtrEngine.getRemotePublicKey(contact); + PublicKey pubKey = OtrActivator.scOtrEngine.getRemotePublicKey(otrContact); String remoteFingerprint = OtrActivator.scOtrKeyManager.getFingerprintFromPublicKey(pubKey); cbAction.setSelectedItem(OtrActivator.scOtrKeyManager - .isVerified(contact, remoteFingerprint) + .isVerified(otrContact.contact, remoteFingerprint) ? actionIHave : actionIHaveNot); pnlAction.add(cbAction, c); @@ -122,10 +122,14 @@ private void initComponents() c.weightx = 1.0; pnlAction.add(txtAction, c); - txtRemoteFingerprintComparison = new SIPCommTextField( + String resourceName = otrContact.resource != null ? + "/" + otrContact.resource.getResourceName() : ""; + + txtRemoteFingerprintComparison = new SIPCommTextField( OtrActivator.resourceService .getI18NString("plugin.otr.authbuddydialog.FINGERPRINT_CHECK", - new String[]{contact.getDisplayName()})); + new String[] + {otrContact.contact.getDisplayName() + resourceName})); txtRemoteFingerprintComparison.getDocument().addDocumentListener(this); c.gridwidth = 2; @@ -142,23 +146,23 @@ public JComboBox getCbAction() /** * Sets up the {@link OtrBuddyAuthenticationDialog} components so that they - * reflect the {@link OtrBuddyAuthenticationDialog#contact} + * reflect the {@link OtrBuddyAuthenticationDialog#otrContact} */ private void loadContact() { // Local fingerprint. String account = - contact.getProtocolProvider().getAccountID().getDisplayName(); + otrContact.contact.getProtocolProvider().getAccountID().getDisplayName(); String localFingerprint = - OtrActivator.scOtrKeyManager.getLocalFingerprint(contact + OtrActivator.scOtrKeyManager.getLocalFingerprint(otrContact.contact .getProtocolProvider().getAccountID()); txtLocalFingerprint.setText(OtrActivator.resourceService.getI18NString( "plugin.otr.authbuddydialog.LOCAL_FINGERPRINT", new String[] { account, localFingerprint })); // Remote fingerprint. - String user = contact.getDisplayName(); - PublicKey pubKey = OtrActivator.scOtrEngine.getRemotePublicKey(contact); + String user = otrContact.contact.getDisplayName(); + PublicKey pubKey = OtrActivator.scOtrEngine.getRemotePublicKey(otrContact); String remoteFingerprint = OtrActivator.scOtrKeyManager.getFingerprintFromPublicKey(pubKey); txtRemoteFingerprint.setText(OtrActivator.resourceService @@ -189,7 +193,7 @@ public void changedUpdate(DocumentEvent e) public void compareFingerprints() { - PublicKey pubKey = OtrActivator.scOtrEngine.getRemotePublicKey(contact); + PublicKey pubKey = OtrActivator.scOtrEngine.getRemotePublicKey(otrContact); String remoteFingerprint = OtrActivator.scOtrKeyManager.getFingerprintFromPublicKey(pubKey); diff --git a/src/net/java/sip/communicator/plugin/otr/authdialog/KnownFingerprintsPanel.java b/src/net/java/sip/communicator/plugin/otr/authdialog/KnownFingerprintsPanel.java index 099bb67a9..14f79dc8b 100644 --- a/src/net/java/sip/communicator/plugin/otr/authdialog/KnownFingerprintsPanel.java +++ b/src/net/java/sip/communicator/plugin/otr/authdialog/KnownFingerprintsPanel.java @@ -95,7 +95,8 @@ public void valueChanged(ListSelectionEvent e) public void actionPerformed(ActionEvent arg0) { OtrActivator.scOtrKeyManager - .verify(getSelectedContact(), getSelectedFingerprint()); + .verify(OtrContactManager.getOtrContact( + getSelectedContact(), null), getSelectedFingerprint()); openContact(getSelectedContact(), getSelectedFingerprint()); contactsTable.updateUI(); } @@ -113,7 +114,8 @@ public void actionPerformed(ActionEvent arg0) public void actionPerformed(ActionEvent arg0) { OtrActivator.scOtrKeyManager - .unverify(getSelectedContact(), getSelectedFingerprint()); + .unverify(OtrContactManager.getOtrContact( + getSelectedContact(), null), getSelectedFingerprint()); openContact(getSelectedContact(), getSelectedFingerprint()); contactsTable.updateUI(); } diff --git a/src/net/java/sip/communicator/plugin/otr/authdialog/KnownFingerprintsTableModel.java b/src/net/java/sip/communicator/plugin/otr/authdialog/KnownFingerprintsTableModel.java index 41dbbfbb3..991883dbb 100644 --- a/src/net/java/sip/communicator/plugin/otr/authdialog/KnownFingerprintsTableModel.java +++ b/src/net/java/sip/communicator/plugin/otr/authdialog/KnownFingerprintsTableModel.java @@ -13,6 +13,7 @@ import javax.swing.table.*; import net.java.sip.communicator.plugin.otr.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.protocol.*; @@ -218,8 +219,9 @@ public int getColumnCount() } @Override - public void contactVerificationStatusChanged(Contact contact) + public void contactVerificationStatusChanged(OtrContact otrContact) { + Contact contact = otrContact.contact; allContactsFingerprints.put( contact, OtrActivator.scOtrKeyManager.getAllRemoteFingerprints(contact)); diff --git a/src/net/java/sip/communicator/plugin/otr/authdialog/OtrV3OutgoingSessionSwitcher.java b/src/net/java/sip/communicator/plugin/otr/authdialog/OTRv3OutgoingSessionSwitcher.java similarity index 58% rename from src/net/java/sip/communicator/plugin/otr/authdialog/OtrV3OutgoingSessionSwitcher.java rename to src/net/java/sip/communicator/plugin/otr/authdialog/OTRv3OutgoingSessionSwitcher.java index 4ced8c15e..dd56af259 100644 --- a/src/net/java/sip/communicator/plugin/otr/authdialog/OtrV3OutgoingSessionSwitcher.java +++ b/src/net/java/sip/communicator/plugin/otr/authdialog/OTRv3OutgoingSessionSwitcher.java @@ -1,3 +1,9 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ package net.java.sip.communicator.plugin.otr.authdialog; import java.awt.*; @@ -13,13 +19,21 @@ import net.java.otr4j.session.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.otr.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.gui.Container; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; -public class OtrV3OutgoingSessionSwitcher +/** + * A special {@link JMenuBar} that controls the switching of OTRv3 outgoing + * sessions in case the remote party is logged in multiple times. + * + * @author Marin Dzhigarov + * + */ +public class OTRv3OutgoingSessionSwitcher extends SIPCommMenuBar implements PluginComponent, ActionListener, @@ -28,7 +42,9 @@ public class OtrV3OutgoingSessionSwitcher { private static final Logger logger - = Logger.getLogger(OtrV3OutgoingSessionSwitcher.class); + = Logger.getLogger(OTRv3OutgoingSessionSwitcher.class); + + private final PluginComponentFactory parentFactory; private static final long serialVersionUID = 0L; @@ -36,10 +52,20 @@ public class OtrV3OutgoingSessionSwitcher private ButtonGroup buttonGroup = new ButtonGroup(); - Contact contact; + private OtrContact contact; + /** + * A map used for storing each Sessions corresponding JMenuItem + * . + */ private final Map outgoingSessions = new HashMap(); + + /** + * An animated {@link JMenu} + * @author Marin Dzhigarov + * + */ private static class SelectorMenu extends SIPCommMenu { @@ -74,9 +100,10 @@ public void actionPerformed(ActionEvent e) fadeCycles++; } alpha = newAlpha; - if (fadeCycles == 3) + if (fadeCycles >= 3) { alphaChanger.stop(); + fadeCycles = 0; alpha = 1f; } SelectorMenu.this.repaint(); @@ -95,18 +122,22 @@ public void paintComponent(Graphics g) super.paintComponent(g2d); } + /** + * Creates a fade in and out effect for this {@link JMenu} + */ public void fadeAnimation() { alphaChanger.stop(); alpha = 0.85f; - repaint(); + SelectorMenu.this.repaint(); alphaChanger.start(); } }; - private final PluginComponentFactory parentFactory; - - public OtrV3OutgoingSessionSwitcher(Container container, + /** + * The OTRv3OutgoingSessionSwitcher constructor + */ + public OTRv3OutgoingSessionSwitcher(Container container, PluginComponentFactory parentFactory) { this.parentFactory = parentFactory; @@ -140,21 +171,21 @@ public OtrV3OutgoingSessionSwitcher(Container container, * obsolete and OtrWeakListener will remove it as a listener of * scOtrEngine and scOtrKeyManager. */ - new OtrWeakListener( + new OtrWeakListener( this, OtrActivator.scOtrEngine, OtrActivator.scOtrKeyManager); - + try { finishedPadlockImage = new ImageIcon(ImageIO.read( OtrActivator.resourceService.getImageURL( - "plugin.otr.FINISHED_ICON_16x16"))); + "plugin.otr.FINISHED_ICON_BLACK_16x16"))); verifiedLockedPadlockImage = new ImageIcon(ImageIO.read( OtrActivator.resourceService.getImageURL( - "plugin.otr.ENCRYPTED_ICON_16x16"))); + "plugin.otr.ENCRYPTED_ICON_BLACK_16x16"))); unverifiedLockedPadlockImage = new ImageIcon(ImageIO.read( OtrActivator.resourceService.getImageURL( - "plugin.otr.ENCRYPTED_UNVERIFIED_ICON_16x16"))); + "plugin.otr.ENCRYPTED_UNVERIFIED_ICON_BLACK_16x16"))); unlockedPadlockImage = new ImageIcon(ImageIO.read( OtrActivator.resourceService.getImageURL( "plugin.otr.PLAINTEXT_ICON_16x16"))); @@ -170,24 +201,75 @@ public int getPositionIndex() return -1; } - @Override + /** + * Sets the current contact. Meant to be used by plugin components that + * are interested of the current contact. The current contact is the contact + * for the currently selected chat transport. + * + * @param contact the current contact + */ public void setCurrentContact(Contact contact) { - if (this.contact == contact) + if (this.contact != null && this.contact.contact == contact) return; - this.contact = contact; - System.out.println("ot tuk1"); - buildMenu(contact); + this.contact = + OtrContactManager.getOtrContact(contact, null); + buildMenu(this.contact); } - @Override + /** + * Sets the current meta contact. Meant to be used by plugin components that + * are interested of the current contact. The current contact could be the + * contact currently selected in the contact list or the contact for the + * currently selected chat, etc. It depends on the container, where this + * component is meant to be added. + * + * @param metaContact the current meta contact + */ public void setCurrentContact(MetaContact metaContact) { setCurrentContact((metaContact == null) ? null : metaContact .getDefaultContact()); } + /** + * Sets the current contact. Meant to be used by plugin components that + * are interested of the current contact. The current contact is the contact + * for the currently selected chat transport. + * + * @param contact the current contact + * @param resourceName the ContactResource name. Some components + * may be interested in a particular ContactResource of a contact. + */ + public void setCurrentContact(Contact contact, String resourceName) + { + if (this.contact != null && this.contact.contact == contact) + return; + + if (resourceName == null) + { + this.contact = + OtrContactManager.getOtrContact(contact, null); + buildMenu(this.contact); + } + else + { + for (ContactResource resource : contact.getResources()) + { + if (resource.getResourceName().equals(resourceName)) + { + OtrContact otrContact = + OtrContactManager.getOtrContact(contact, resource); + if (this.contact == otrContact) + return; + this.contact = otrContact; + buildMenu(this.contact); + } + } + } + } + @Override public void setCurrentContactGroup(MetaContactGroup metaGroup) {} @@ -200,52 +282,92 @@ public PluginComponentFactory getParentFactory() return parentFactory; } - @Override - public void contactVerificationStatusChanged(Contact contact) + /** + * Implements ScOtrKeyManagerListener#contactVerificationStatusChanged( + * Contact). + */ + public void contactVerificationStatusChanged(OtrContact contact) { - if (contact == null || this.contact != contact) return; + buildMenu(contact); + if (this.menu.isVisible()) + this.menu.fadeAnimation(); } - @Override + /** + * Implements ScOtrEngineListener#contactPolicyChanged(Contact). + */ public void contactPolicyChanged(Contact contact) {} - @Override + /** + * Implements ScOtrKeyManagerListener#globalPolicyChanged(). + */ public void globalPolicyChanged() {} - @Override - public void sessionStatusChanged(Contact contact) + /** + * Implements ScOtrEngineListener#sessionStatusChanged(OtrContact). + */ + public void sessionStatusChanged(OtrContact contact) { - System.out.println("ot tuk2"); buildMenu(contact); + if (this.menu.isVisible()) + this.menu.fadeAnimation(); } - @Override - public void multipleInstancesDetected(Contact contact) + /** + * Implements ScOtrEngineListener#multipleInstancesDetected(OtrContact). + */ + public void multipleInstancesDetected(OtrContact contact) + { + buildMenu(contact); + if (this.menu.isVisible()) + this.menu.fadeAnimation(); + } + + /** + * Implements ScOtrEngineListener#outgoingSessionChanged(OtrContact). + */ + public void outgoingSessionChanged(OtrContact contact) { - System.out.println("ot tuk3"); buildMenu(contact); } private ImageIcon verifiedLockedPadlockImage; + private ImageIcon unverifiedLockedPadlockImage; + private ImageIcon finishedPadlockImage; + private ImageIcon unlockedPadlockImage; - void buildMenu(Contact contact) + + /** + * Builds the JMenu used for switching between outgoing OTRv3 Sessions in + * case the remote party is logged in multiple locations + * + * @param otrContact the contact which is logged in multiple locations + */ + private void buildMenu(OtrContact otrContact) { - if (contact == null || this.contact != contact) { + if (otrContact == null || !this.contact.equals(otrContact)) + { return; } menu.removeAll(); - java.util.List multipleInstances = - OtrActivator.scOtrEngine.getSessionInstances(contact); + OtrActivator.scOtrEngine.getSessionInstances( + otrContact); + Session outgoingSession = + OtrActivator.scOtrEngine.getOutgoingSession(otrContact); int index = 0; for (Session session : multipleInstances) { index++; if (!outgoingSessions.containsKey(session)) - outgoingSessions.put(session, new JRadioButtonMenuItem()); + { + JMenuItem menuItem = new JRadioButtonMenuItem(); + outgoingSessions.put(session, menuItem); + menuItem.addActionListener(this); + } JMenuItem menuItem = outgoingSessions.get(session); menuItem.setText("Session " + index); @@ -260,7 +382,8 @@ void buildMenu(Contact contact) OtrActivator.scOtrKeyManager. getFingerprintFromPublicKey(pubKey); imageIcon - = OtrActivator.scOtrKeyManager.isVerified(contact, fingerprint) + = OtrActivator.scOtrKeyManager.isVerified( + otrContact.contact, fingerprint) ? verifiedLockedPadlockImage : unverifiedLockedPadlockImage; break; @@ -276,17 +399,20 @@ void buildMenu(Contact contact) menu.add(menuItem); SelectedObject selectedObject = new SelectedObject(imageIcon, session); - this.menu.setSelected(selectedObject); - + buttonGroup.add(menuItem); - menuItem.addActionListener(this); - setSelected(menu.getItem(0)); + menuItem.repaint(); + if (session == outgoingSession) + { + this.menu.setSelected(selectedObject); + setSelected(menu.getItem(index - 1)); + } + } - System.out.println("da"); updateEnableStatus(); + menu.repaint(); } - @Override public void actionPerformed(ActionEvent e) { for (Map.Entry entry : outgoingSessions.entrySet()) @@ -301,19 +427,12 @@ public void actionPerformed(ActionEvent e) } } - @Override - public void outgoingSessionChanged(Contact contact) - { - buildMenu(contact); - } - /** - * Sets the menu to enabled or disabled. The menu is enabled, as soon as it - * contains two or more items. If it is empty, it is disabled. + * Sets the menu visibility. The menu is visible as soon as it + * contains two or more items. If it is empty, it is invisible. */ private void updateEnableStatus() { this.menu.setVisible(this.menu.getItemCount() > 1); - this.menu.fadeAnimation(); } } diff --git a/src/net/java/sip/communicator/plugin/otr/authdialog/OtrBuddyAuthenticationDialog.java b/src/net/java/sip/communicator/plugin/otr/authdialog/OtrBuddyAuthenticationDialog.java index f82c01d5a..3c4b49d61 100644 --- a/src/net/java/sip/communicator/plugin/otr/authdialog/OtrBuddyAuthenticationDialog.java +++ b/src/net/java/sip/communicator/plugin/otr/authdialog/OtrBuddyAuthenticationDialog.java @@ -14,6 +14,7 @@ import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.otr.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.plugin.otr.authdialog.FingerprintAuthenticationPanel.ActionComboBoxItem; import net.java.sip.communicator.service.protocol.*; @@ -25,7 +26,7 @@ public class OtrBuddyAuthenticationDialog extends SIPCommDialog { - private final Contact contact; + private final OtrContact contact; /** * The {@link OtrBuddyAuthenticationDialog} ctor. @@ -33,7 +34,7 @@ public class OtrBuddyAuthenticationDialog * @param contact The {@link Contact} this * {@link OtrBuddyAuthenticationDialog} refers to. */ - public OtrBuddyAuthenticationDialog(Contact contact) + public OtrBuddyAuthenticationDialog(OtrContact contact) { super(false); this.contact = contact; diff --git a/src/net/java/sip/communicator/plugin/otr/authdialog/OtrConfigurationPanel.java b/src/net/java/sip/communicator/plugin/otr/authdialog/OtrConfigurationPanel.java index 9435f2395..21097a97c 100644 --- a/src/net/java/sip/communicator/plugin/otr/authdialog/OtrConfigurationPanel.java +++ b/src/net/java/sip/communicator/plugin/otr/authdialog/OtrConfigurationPanel.java @@ -14,10 +14,9 @@ import javax.swing.border.*; import net.java.otr4j.*; -import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.otr.*; -import net.java.sip.communicator.plugin.otr.authdialog.*; +import net.java.sip.communicator.service.protocol.*; /** * A special {@link Panel} that manages the OTR configuration. diff --git a/src/net/java/sip/communicator/plugin/otr/authdialog/SmpAuthenticateBuddyDialog.java b/src/net/java/sip/communicator/plugin/otr/authdialog/SmpAuthenticateBuddyDialog.java index 7d91c7ff3..6f72ce8d0 100644 --- a/src/net/java/sip/communicator/plugin/otr/authdialog/SmpAuthenticateBuddyDialog.java +++ b/src/net/java/sip/communicator/plugin/otr/authdialog/SmpAuthenticateBuddyDialog.java @@ -11,9 +11,10 @@ import javax.swing.*; +import net.java.otr4j.session.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.otr.*; -import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; /** * The dialog that pops up when the remote party send us SMP @@ -26,13 +27,17 @@ public class SmpAuthenticateBuddyDialog extends SIPCommDialog { - private final Contact contact; + private final OtrContact otrContact; private final String question; - public SmpAuthenticateBuddyDialog(Contact contact, String question) + private final InstanceTag receiverTag; + + public SmpAuthenticateBuddyDialog( + OtrContact contact, InstanceTag receiverTag, String question) { - this.contact = contact; + this.otrContact = contact; + this.receiverTag = receiverTag; this.question = question; initComponents(); } @@ -60,12 +65,17 @@ private void initComponents() , Font.BOLD , 14); authenticationFrom.setFont(newFont); + + String resourceName = otrContact.resource != null ? + "/" + otrContact.resource.getResourceName() : ""; String authFromText = String.format( OtrActivator.resourceService .getI18NString( "plugin.otr.authbuddydialog.AUTHENTICATION_FROM", - new String[] {contact.getDisplayName()})); + new String[] + {otrContact.contact.getDisplayName() + + resourceName})); authenticationFrom.setText(authFromText); mainPanel.add(authenticationFrom); @@ -170,7 +180,7 @@ public void actionPerformed(ActionEvent arg0) { public void actionPerformed(ActionEvent e) { - OtrActivator.scOtrEngine.abortSmp(contact); + OtrActivator.scOtrEngine.abortSmp(otrContact); SmpAuthenticateBuddyDialog.this.dispose(); } }); @@ -188,7 +198,7 @@ public void actionPerformed(ActionEvent e) public void actionPerformed(ActionEvent e) { OtrActivator.scOtrEngine.respondSmp( - contact, question, answerTextBox.getText()); + otrContact, receiverTag, question, answerTextBox.getText()); SmpAuthenticateBuddyDialog.this.dispose(); } }); diff --git a/src/net/java/sip/communicator/plugin/spellcheck/LanguageMenuBar.java b/src/net/java/sip/communicator/plugin/spellcheck/LanguageMenuBar.java index 451d373ae..94f535355 100644 --- a/src/net/java/sip/communicator/plugin/spellcheck/LanguageMenuBar.java +++ b/src/net/java/sip/communicator/plugin/spellcheck/LanguageMenuBar.java @@ -692,4 +692,9 @@ public int getPositionIndex() { return -1; } + + @Override + public void setCurrentContact(Contact contact, String resourceName) + { + } } diff --git a/src/net/java/sip/communicator/service/gui/AbstractPluginComponent.java b/src/net/java/sip/communicator/service/gui/AbstractPluginComponent.java index c5ec1da30..9d1804190 100644 --- a/src/net/java/sip/communicator/service/gui/AbstractPluginComponent.java +++ b/src/net/java/sip/communicator/service/gui/AbstractPluginComponent.java @@ -92,6 +92,14 @@ public void setCurrentContact(Contact contact) { } + /* + * Implements PluginComponent#setCurrentContact(Contact). + */ + public void setCurrentContact(Contact contact, String resourceName) + { + setCurrentContact(contact); + } + /* * Implements PluginComponent#setCurrentContact(MetaContact). */ diff --git a/src/net/java/sip/communicator/service/gui/PluginComponent.java b/src/net/java/sip/communicator/service/gui/PluginComponent.java index 48e438d30..0593ebc6b 100644 --- a/src/net/java/sip/communicator/service/gui/PluginComponent.java +++ b/src/net/java/sip/communicator/service/gui/PluginComponent.java @@ -69,6 +69,17 @@ public interface PluginComponent */ public void setCurrentContact(Contact contact); + /** + * Sets the current contact. Meant to be used by plugin components that + * are interested of the current contact. The current contact is the contact + * for the currently selected chat transport. + * + * @param contact the current contact + * @param resourceName the ContactResource name. Some components + * may be interested in a particular ContactResource of a contact. + */ + public void setCurrentContact(Contact contact, String resourceName); + /** * Sets the current meta contact. Meant to be used by plugin components that * are interested of the current contact. The current contact could be the diff --git a/src/net/java/sip/communicator/service/protocol/event/MessageDeliveredEvent.java b/src/net/java/sip/communicator/service/protocol/event/MessageDeliveredEvent.java index 065de96d0..4661c2e41 100644 --- a/src/net/java/sip/communicator/service/protocol/event/MessageDeliveredEvent.java +++ b/src/net/java/sip/communicator/service/protocol/event/MessageDeliveredEvent.java @@ -29,6 +29,11 @@ public class MessageDeliveredEvent */ private Contact to = null; + /** + * The ContactResource, to which the message was sent. + */ + private ContactResource toResource = null; + /** * A timestamp indicating the exact date when the event occurred. */ @@ -88,6 +93,42 @@ public MessageDeliveredEvent(Message source, Contact to, Date timestamp) this.timestamp = timestamp; } + /** + * Creates a MessageDeliveredEvent representing delivery of the + * source message to the specified to contact. + * + * @param source the Message whose delivery this event represents. + * @param to the Contact that this message was sent to. + * @param timestamp a date indicating the exact moment when the event + * ocurred + */ + public MessageDeliveredEvent( + Message source, Contact to, ContactResource toResource, Date timestamp) + { + super(source); + + this.to = to; + this.toResource = toResource; + this.timestamp = timestamp; + } + + /** + * Creates a MessageDeliveredEvent representing delivery of the + * source message to the specified to contact. + * + * @param source the Message whose delivery this event represents. + * @param to the Contact that this message was sent to. + * @param timestamp a date indicating the exact moment when the event + * ocurred + */ + public MessageDeliveredEvent( + Message source, Contact to, ContactResource toResource) + { + this(source, to, new Date()); + + this.toResource = toResource; + } + /** * Returns a reference to the Contact that Message was * sent to. @@ -158,4 +199,16 @@ public boolean isSmsMessage() { return smsMessage; } + + /** + * Returns a reference to the ContactResource that has sent the + * Message whose reception this event represents. + * + * @return a reference to the ContactResource that has sent the + * Message whose reception this event represents. + */ + public ContactResource getContactResource() + { + return toResource; + } }