From 9383a5e000aa64c308892dc71fcefd3ab7a2d542 Mon Sep 17 00:00:00 2001 From: yanas Date: Mon, 25 Nov 2013 17:38:14 +0100 Subject: [PATCH] Added improvements to OTR user interface, among which new icons and tooltips. Patch provided by Marin Dzhigarov on Nov 25, 2013. --- resources/images/images.properties | 4 +- .../plugin/otr/encrypted_verified22x22.png | Bin 0 -> 1561 bytes .../images/plugin/otr/padlockBrokenOpen.png | Bin 0 -> 1572 bytes .../images/plugin/otr/padlockLoading.gif | Bin 0 -> 419 bytes .../images/plugin/otr/plaintext22x22.png | Bin 408 -> 1456 bytes resources/languages/resources.properties | 5 + .../plugin/otr/OtrContactMenu.java | 171 ++---------------- .../plugin/otr/OtrMetaContactButton.java | 159 ++++++++-------- .../plugin/otr/OtrTransformLayer.java | 16 +- .../plugin/otr/OtrWeakListener.java | 155 ++++++++++++++++ .../communicator/plugin/otr/ScOtrEngine.java | 9 +- .../plugin/otr/ScOtrEngineImpl.java | 114 +++++++++++- .../plugin/otr/ScSessionStatus.java | 29 +++ 13 files changed, 424 insertions(+), 238 deletions(-) create mode 100644 resources/images/plugin/otr/encrypted_verified22x22.png create mode 100644 resources/images/plugin/otr/padlockBrokenOpen.png create mode 100644 resources/images/plugin/otr/padlockLoading.gif create mode 100644 src/net/java/sip/communicator/plugin/otr/OtrWeakListener.java create mode 100644 src/net/java/sip/communicator/plugin/otr/ScSessionStatus.java diff --git a/resources/images/images.properties b/resources/images/images.properties index 525cd0c70..3ca43dad0 100644 --- a/resources/images/images.properties +++ b/resources/images/images.properties @@ -543,13 +543,15 @@ plugin.securityconfig.ICON=resources/images/plugin/securityconfig/security.png # otr plugin icons plugin.otr.ENCRYPTED_ICON_16x16=resources/images/plugin/otr/encrypted16x16.png -plugin.otr.ENCRYPTED_ICON_22x22=resources/images/plugin/otr/encrypted22x22.png +plugin.otr.ENCRYPTED_ICON_22x22=resources/images/plugin/otr/encrypted_verified22x22.png plugin.otr.ENCRYPTED_UNVERIFIED_ICON_16x16=resources/images/plugin/otr/encrypted_unverified16x16.png plugin.otr.ENCRYPTED_UNVERIFIED_ICON_22x22=resources/images/plugin/otr/encrypted_unverified22x22.png plugin.otr.PLAINTEXT_ICON_16x16=resources/images/plugin/otr/plaintext16x16.png plugin.otr.PLAINTEXT_ICON_22x22=resources/images/plugin/otr/plaintext22x22.png plugin.otr.FINISHED_ICON_16x16=resources/images/plugin/otr/finished16x16.png plugin.otr.FINISHED_ICON_22x22=resources/images/plugin/otr/finished22x22.png +plugin.otr.LOADING_ICON_22x22=resources/images/plugin/otr/padlockLoading.gif +plugin.otr.BROKEN_ICON_22x22=resources/images/plugin/otr/padlockBrokenOpen.png plugin.otr.HELP_ICON_15x15=resources/images/plugin/otr/help15x15.png plugin.otr.MENU_ITEM_ICON_16x16=resources/images/plugin/otr/otr_menu_icon.png diff --git a/resources/images/plugin/otr/encrypted_verified22x22.png b/resources/images/plugin/otr/encrypted_verified22x22.png new file mode 100644 index 0000000000000000000000000000000000000000..22bd87739bbee35bc700b883c115785801caf177 GIT binary patch literal 1561 zcmbVMeM}Q)7(Xc@pibGC@-f4C>eK=4eU$5mR95aBj8tkh=Y0{9~89d*1iC z=l6R)-siaw&3W6F$0o%B0I)n~J82Q+8tI9S65mBfD^`diQ7~AA0=8VJrZ@)B)2xjF zb6ivfV__(|X3upd698lmXQ5TFn%>mfS(luWY~)^-M??caW|r4O*>^JnXk#jzZXGoB zbpQgLv<}*tVS-H_f^j&v`#7e+mse=_?Y3)ZC~GU2>D7t^E=Hh0udC9{YrQ&Xo>wcb zCA9(q=S_s&I_ODIR+AYdSdIZRO8jZw(qUoYVI$z@!C~vx(Ul(Q| z8Q#u0J%W>UgAyZUW2*!mBu4rq1((NUdP?l(7YZeoOyQ+G3PcVoTrMfEd23#j)rOef^8Z(fX9>Fw(iXc#E0Zad% zswu=^6jFQqr(MD-u?MB))8-d9PsfLGi!;NC!|_^Q%vJ!1*p@@;3%z%2bD>a{?3+a| z^#HXyazAK2llFc*jG}cz9vQNFB{pu3x)BkmeVDp31WeTX1@e!MuPqhscNaPQ-R)~pek@~`BM zJvWHPeP9{O2I5NZjU6aBc!{#vB0et?fHw-QK=Ou|m}SGaZzryjmpCHn%YD$HlT8Wj zhkc_pFVbt_mX`ZX_O`Fi3`TBj=!w{I!usdTWd8p4_ICc9B_^?U>9X4u%L1kW41RZ< zF56P6Uz1DqrhFMqoq+_I=5xg46vI>t$DcCx29q+Ex}_9^@r+SnLCy6q2HpNtrfN@;sLG5APWJZN)U8uaE*H~>*J9=tV%HZw^VBR}1E6KY%R zNs5`3^)}RZ9jm|e(d^wTz7;oUS2xE6mv$P~|MsBD{EL5)GL-s5+8WMet_5Vku982z72cMY PrJr?5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8KW+g=7RhMR$W{Yl!|Z$R@KEJl?AE#L8-<0 zrA5iW_()TRX$FQJev3c~fv&OgFUkZ)N@9*nesXDUYF>$_i>(q+MlU5Z#mddx+|AI? z+{D1#$iU3P(9qJ=z|qCf$<@it+1$+0(Gq3`HoZnJ#uhH-7H%eHhCtwIZ0Tn1YU%6> zxx&em2**QVo82cNPd0}ECmE)3<^5uDGey=-{Hnd3qD~O)tf^AHqQ_E}r}MEALl`eKm#a z){Xq+iCNntujz`hcnY?Z6n5M{_oO!c{fXX`W1W1ST{0GT7Jole`|RH5LU&W&;L1!+ zjr9uanRs=J0z#+S8LnXCwc26mru@RTq+x=jwj<}6r$yo`LtdFk{5fyJbGS~?d->&+ zi(daK-F(v~d;9HB#`u>NTT_fGy)IPPY?~tRD#d7HU9p~6_tH%_->CjMtS!I3X35v8 zzf11F|9`0O=N!%t5k70hyN~Xl>Xj;9cAx*n1v7>{hYGr7XB8LrGyIsiZ)s5Gf&DSz zDRXyesxaLZkShGldLd@ZysgXFS7;^47kobav5IMz6KluPO``qB`<)gBhgTe~DWM4fGr>jt literal 0 HcmV?d00001 diff --git a/resources/images/plugin/otr/padlockLoading.gif b/resources/images/plugin/otr/padlockLoading.gif new file mode 100644 index 0000000000000000000000000000000000000000..4aeacde9baa07431d53d2e3f7519ed6563569bc6 GIT binary patch literal 419 zcmZ?wbhEHblx2`*I3vUm85Q;9$&-kPh*z&({r&s*>({SgVPWUaol8haxOeYfL_}nK zLc)LRPZmfRFz7Y9IF-F%?P6|F2;$;kxGs}uaA2a)Ef+y&gUK>`+quOOWHO9S zOm|EX)o|eAvW;gGW@8hm)rw^5bLe+qWAfIHi}#$s6`!c(9>ra5!Z#y9XFdb(T&1|E zWz(XV7FLADGp*KG;lRPLXWg9TGY>`WP+x1yvb1I0QME|Vef!o;)nes2Jb{5f?<#Y} zeP*HT$4{O5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8KW+g=7RhMR$W{Yl!|Z$R@KEJl?AE#L8-<0 zrA5iW_()TRX$FQJev3c~fv&OgFUkZ)N@9*nesXDUYF>$_i>(q+MlU5Z#mddx+|AI? z+{D1#$iU3P(9qJ=z|qCf$<@it+1$+0(Gq3`HoY#EW|q#zCdMX)CdP(_uErM5#;%qw zh87lP7B0>PhK?}3o_WP3iFwJXFncqB_Coa<;ni#9T$Gwvl3x^(pPvIu0Rb8LCHch} z`2`Bj!Db2?zKO}1c_0BzPy|8z>ylcOS(cjOR+OKs01jcROe`)ib9QqwadmcdG&M2; z`P{<5!r0Zt*wN6~(%IbE*+>bhH-(%~(+4_6ACx?hk_$`-mwaqDkSoXVKse@}g0uaV?U+TOVLMJ7_Vhi+Y7GU?(!CJ*tGDIR*$7xxve4KseUMEH8J zW!6WHWsB0pCukc^T#+cYHte;`p~52t0n1l&Xdj zi!VN(U3bBvIGFwEr3dO~qWBBzYI0u*lt+~DO$q7L%HHu@@ukJ-l&rGdcezgl9h~&& zrsQ+S1Afbk*`{}Hs4~2*aIIv1?gOu;SBY)H5!d~AcKv3(+xV~gf;=M|L)L|x2Oe1- Q3I&y+p00i_>zopr07;w+T>t<8 delta 345 zcmV-f0jB=23z!3tNPhu3Nkl(Y=fq7*Z+aQBAjgw2>AVjL=DO~q<2VsK-;G4CI=uIN6wh>| zt1t{-&j}(4W9%8lI?}Cy&z)_oOqlwrxM)KCL6g r(}dz5pSUp`!( this, OtrActivator.scOtrEngine, OtrActivator.scOtrKeyManager); @@ -258,6 +256,19 @@ private void buildMenu() switch (this.sessionStatus) { + case LOADING: + if (separateMenu != null) + { + separateMenu.add(endOtr); + separateMenu.add(refreshOtr); + } + else + { + parentMenu.add(endOtr); + parentMenu.add(refreshOtr); + } + break; + case ENCRYPTED: JMenuItem authBuddy = new JMenuItem(); authBuddy.setText(OtrActivator.resourceService @@ -293,6 +304,7 @@ private void buildMenu() } break; + case TIMED_OUT: case PLAINTEXT: if (separateMenu != null) separateMenu.add(startOtr); @@ -406,9 +418,9 @@ public void sessionStatusChanged(Contact contact) * icon and, if necessary, rebuilds the menuitems to match the passed in * sessionStatus. * - * @param sessionStatus the {@link SessionStatus}. + * @param sessionStatus the {@link ScSessionStatus}. */ - private void setSessionStatus(SessionStatus sessionStatus) + private void setSessionStatus(ScSessionStatus sessionStatus) { if (sessionStatus != this.sessionStatus) { @@ -478,147 +490,4 @@ private void updateIcon() separateMenu.setIcon(OtrActivator.resourceService.getImage(imageID)); } - - /** - * Implements a ScOtrEngineListener and - * ScOtrKeyManagerListener listener for the purposes of - * OtrContactMenu which listens to ScOtrEngine and - * ScOtrKeyManager while weakly referencing the - * OtrContactMenu. Fixes a memory leak of OtrContactMenu - * instances because these cannot determine when they are to be explicitly - * disposed. - * - * @author Lyubomir Marinov - */ - private static class WeakListener - implements ScOtrEngineListener, - ScOtrKeyManagerListener - { - /** - * The ScOtrEngine the OtrContactMenu associated with - * this instance is to listen to. - */ - private final ScOtrEngine engine; - - /** - * The ScOtrKeyManager the OtrContactMenu associated - * with this instance is to listen to. - */ - private final ScOtrKeyManager keyManager; - - /** - * The OtrContactMenu which is associated with this instance - * and which is to listen to {@link #engine} and {@link #keyManager}. - */ - private final WeakReference listener; - - /** - * Initializes a new WeakListener instance which is to allow - * a specific OtrContactMenu to listener to a specific - * ScOtrEngine and a specific ScOtrKeyManager without - * being retained by them forever (because they live forever). - * - * @param listener the OtrContactMenu which is to listen to the - * specified engine and keyManager - * @param engine the ScOtrEngine which is to be listened to by - * the specified OtrContactMenu - * @param keyManager the ScOtrKeyManager which is to be - * listened to by the specified OtrContactMenu - */ - public WeakListener( - OtrContactMenu listener, - ScOtrEngine engine, ScOtrKeyManager keyManager) - { - if (listener == null) - throw new NullPointerException("listener"); - - this.listener = new WeakReference(listener); - this.engine = engine; - this.keyManager = keyManager; - - this.engine.addListener(this); - this.keyManager.addListener(this); - } - - /** - * {@inheritDoc} - * - * Forwards the event/notification to the associated - * OtrContactMenu if it is still needed by the application. - */ - public void contactPolicyChanged(Contact contact) - { - ScOtrEngineListener l = getListener(); - - if (l != null) - l.contactPolicyChanged(contact); - } - - /** - * {@inheritDoc} - * - * Forwards the event/notification to the associated - * OtrContactMenu if it is still needed by the application. - */ - public void contactVerificationStatusChanged(Contact contact) - { - ScOtrKeyManagerListener l = getListener(); - - if (l != null) - l.contactVerificationStatusChanged(contact); - } - - /** - * Gets the OtrContactMenu which is listening to - * {@link #engine} and {@link #keyManager}. If the - * OtrContactMenu is no longer needed by the application, this - * instance seizes listening to engine and keyManager - * and allows the memory used by this instance to be reclaimed by the - * Java virtual machine. - * - * @return the OtrContactMenu which is listening to - * engine and keyManager if it is still needed by the - * application; otherwise, null - */ - private OtrContactMenu getListener() - { - OtrContactMenu l = this.listener.get(); - - if (l == null) - { - engine.removeListener(this); - keyManager.removeListener(this); - } - - return l; - } - - /** - * {@inheritDoc} - * - * Forwards the event/notification to the associated - * OtrContactMenu if it is still needed by the application. - */ - public void globalPolicyChanged() - { - ScOtrEngineListener l = getListener(); - - if (l != null) - l.globalPolicyChanged(); - } - - /** - * {@inheritDoc} - * - * Forwards the event/notification to the associated - * OtrContactMenu if it is still needed by the application. - */ - public void sessionStatusChanged(Contact contact) - { - ScOtrEngineListener l = getListener(); - - if (l != null) - l.sessionStatusChanged(contact); - } - } } diff --git a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java index 2ca8bba40..b5b5f125f 100644 --- a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java +++ b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java @@ -8,17 +8,15 @@ import java.awt.*; import java.awt.event.*; -import java.io.*; - -import javax.imageio.*; import net.java.otr4j.*; import net.java.otr4j.session.*; +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.*; +import net.java.sip.communicator.util.*; /** * A {@link AbstractPluginComponent} that registers the Off-the-Record button in @@ -28,69 +26,78 @@ */ public class OtrMetaContactButton extends AbstractPluginComponent + implements ScOtrEngineListener, + ScOtrKeyManagerListener { + /** + * The logger + */ + private final Logger logger = Logger.getLogger(OtrMetaContactButton.class); + private SIPCommButton button; private Contact contact; - private final ScOtrEngineListener scOtrEngineListener = - new ScOtrEngineListener() + /** + * The timer task that changes the padlock icon to "loading" and + * then to "broken" if the specified timeout passed + */ + public void sessionStatusChanged(Contact contact) + { + // OtrMetaContactButton.this.contact can be null. + if (contact.equals(OtrMetaContactButton.this.contact)) { - public void sessionStatusChanged(Contact contact) - { - // OtrMetaContactButton.this.contact can be null. - if (contact.equals(OtrMetaContactButton.this.contact)) - { - setStatus( - OtrActivator.scOtrEngine.getSessionStatus(contact)); - } - } - public void contactPolicyChanged(Contact contact) - { - // OtrMetaContactButton.this.contact can be null. - if (contact.equals(OtrMetaContactButton.this.contact)) - { - setPolicy( - OtrActivator.scOtrEngine.getContactPolicy(contact)); - } - } + setStatus( + OtrActivator.scOtrEngine.getSessionStatus(contact)); + } + } - public void globalPolicyChanged() - { - if (OtrMetaContactButton.this.contact != null) - setPolicy( - OtrActivator.scOtrEngine.getContactPolicy(contact)); - } - }; - - private final ScOtrKeyManagerListener scOtrKeyManagerListener = - new ScOtrKeyManagerListener() + public void contactPolicyChanged(Contact contact) + { + // OtrMetaContactButton.this.contact can be null. + if (contact.equals(OtrMetaContactButton.this.contact)) { - public void contactVerificationStatusChanged(Contact contact) - { - // OtrMetaContactButton.this.contact can be null. - if (contact.equals(OtrMetaContactButton.this.contact)) - { - setStatus( - OtrActivator.scOtrEngine.getSessionStatus(contact)); - } - } - }; + setPolicy( + OtrActivator.scOtrEngine.getContactPolicy(contact)); + } + } + + public void globalPolicyChanged() + { + if (OtrMetaContactButton.this.contact != null) + setPolicy( + OtrActivator.scOtrEngine.getContactPolicy(contact)); + } + public void contactVerificationStatusChanged(Contact contact) + { + // OtrMetaContactButton.this.contact can be null. + if (contact.equals(OtrMetaContactButton.this.contact)) + { + setStatus( + OtrActivator.scOtrEngine.getSessionStatus(contact)); + } + } public OtrMetaContactButton(Container container, PluginComponentFactory parentFactory) { super(container, parentFactory); - OtrActivator.scOtrEngine.addListener(scOtrEngineListener); - OtrActivator.scOtrKeyManager.addListener(scOtrKeyManagerListener); - } - - void dispose() - { - OtrActivator.scOtrEngine.removeListener(scOtrEngineListener); - OtrActivator.scOtrKeyManager.removeListener(scOtrKeyManagerListener); + /* + * XXX This OtrMetaContactButton instance cannot be added as a listener + * to scOtrEngine and scOtrKeyManager without being removed later on + * because the latter live forever. Unfortunately, the dispose() method + * of this instance is never executed. OtrWeakListener will keep this + * instance as a listener of scOtrEngine and scOtrKeyManager for as long + * as this instance is necessary. And this instance will be strongly + * referenced by the JMenuItems which depict it. So when the JMenuItems + * are gone, this instance will become obsolete and OtrWeakListener will + * remove it as a listener of scOtrEngine and scOtrKeyManager. + */ + new OtrWeakListener( + this, + OtrActivator.scOtrEngine, OtrActivator.scOtrKeyManager); } /** @@ -122,13 +129,15 @@ public void actionPerformed(ActionEvent e) { case ENCRYPTED: case FINISHED: - // Default action for finished and encrypted sessions is - // end session. + case LOADING: + // Default action for finished, encrypted and loading + // sessions is end session. OtrActivator.scOtrEngine.endSession(contact); break; + case TIMED_OUT: case PLAINTEXT: - // Default action for finished and plaintext sessions is - // start session. + // Default action for timed_out and plaintext sessions + // is start session. OtrActivator.scOtrEngine.startSession(contact); break; } @@ -173,7 +182,7 @@ public void setCurrentContact(Contact contact) } else { - this.setStatus(SessionStatus.PLAINTEXT); + this.setStatus(ScSessionStatus.PLAINTEXT); this.setPolicy(null); } } @@ -205,7 +214,7 @@ private void setPolicy(OtrPolicy contactPolicy) * * @param status the {@link SessionStatus}. */ - private void setStatus(SessionStatus status) + private void setStatus(ScSessionStatus status) { String urlKey; String tipKey; @@ -216,31 +225,37 @@ private void setStatus(SessionStatus status) = OtrActivator.scOtrKeyManager.isVerified(contact) ? "plugin.otr.ENCRYPTED_ICON_22x22" : "plugin.otr.ENCRYPTED_UNVERIFIED_ICON_22x22"; - tipKey = "plugin.otr.menu.END_OTR"; + tipKey = + OtrActivator.scOtrKeyManager.isVerified(contact) + ? "plugin.otr.menu.VERIFIED" + : "plugin.otr.menu.UNVERIFIED"; break; case FINISHED: urlKey = "plugin.otr.FINISHED_ICON_22x22"; - tipKey = "plugin.otr.menu.END_OTR"; + tipKey = "plugin.otr.menu.FINISHED"; break; case PLAINTEXT: urlKey = "plugin.otr.PLAINTEXT_ICON_22x22"; tipKey = "plugin.otr.menu.START_OTR"; break; + case LOADING: + urlKey = "plugin.otr.LOADING_ICON_22x22"; + tipKey = "plugin.otr.menu.LOADING_OTR"; + break; + case TIMED_OUT: + urlKey = "plugin.otr.BROKEN_ICON_22x22"; + tipKey = "plugin.otr.menu.TIMED_OUT"; + break; default: return; } - try - { - SIPCommButton button = getButton(); - button.setImage( - ImageIO.read(OtrActivator.resourceService.getImageURL(urlKey))); - button.setToolTipText(OtrActivator.resourceService - .getI18NString(tipKey)); - } - catch (IOException e) - { - e.printStackTrace(); - } + SIPCommButton button = getButton(); + button.setIconImage( + Toolkit.getDefaultToolkit().getImage( + OtrActivator.resourceService.getImageURL(urlKey))); + button.setToolTipText(OtrActivator.resourceService + .getI18NString(tipKey)); + button.repaint(); } } diff --git a/src/net/java/sip/communicator/plugin/otr/OtrTransformLayer.java b/src/net/java/sip/communicator/plugin/otr/OtrTransformLayer.java index bedbe32b8..5111d9e5b 100644 --- a/src/net/java/sip/communicator/plugin/otr/OtrTransformLayer.java +++ b/src/net/java/sip/communicator/plugin/otr/OtrTransformLayer.java @@ -7,7 +7,6 @@ package net.java.sip.communicator.plugin.otr; import net.java.otr4j.*; -import net.java.otr4j.session.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; @@ -27,12 +26,13 @@ public MessageDeliveredEvent messageDelivered(MessageDeliveredEvent evt) Contact contact = evt.getDestinationContact(); OtrPolicy policy = OtrActivator.scOtrEngine.getContactPolicy(contact); - SessionStatus sessionStatus = + ScSessionStatus sessionStatus = OtrActivator.scOtrEngine.getSessionStatus(contact); // If OTR is disabled and we are not over an encrypted session, don't // process anything. if (!policy.getEnableManual() - && sessionStatus == SessionStatus.PLAINTEXT) + && sessionStatus != ScSessionStatus.ENCRYPTED + && sessionStatus != ScSessionStatus.FINISHED) return evt; if (OtrActivator.scOtrEngine.isMessageUIDInjected(evt @@ -63,12 +63,13 @@ public MessageDeliveredEvent messageDeliveryPending( Contact contact = evt.getDestinationContact(); OtrPolicy policy = OtrActivator.scOtrEngine.getContactPolicy(contact); - SessionStatus sessionStatus = + ScSessionStatus sessionStatus = OtrActivator.scOtrEngine.getSessionStatus(contact); // If OTR is disabled and we are not over an encrypted session, don't // process anything. if (!policy.getEnableManual() - && sessionStatus == SessionStatus.PLAINTEXT) + && sessionStatus != ScSessionStatus.ENCRYPTED + && sessionStatus != ScSessionStatus.FINISHED) return evt; // If this is a message otr4j injected earlier, return the event as is. @@ -111,12 +112,13 @@ public MessageReceivedEvent messageReceived(MessageReceivedEvent evt) Contact contact = evt.getSourceContact(); OtrPolicy policy = OtrActivator.scOtrEngine.getContactPolicy(contact); - SessionStatus sessionStatus = + ScSessionStatus sessionStatus = OtrActivator.scOtrEngine.getSessionStatus(contact); // If OTR is disabled and we are not over an encrypted session, don't // process anything. if (!policy.getEnableManual() - && sessionStatus == SessionStatus.PLAINTEXT) + && sessionStatus != ScSessionStatus.ENCRYPTED + && sessionStatus != ScSessionStatus.FINISHED) return evt; // Process the incoming message. diff --git a/src/net/java/sip/communicator/plugin/otr/OtrWeakListener.java b/src/net/java/sip/communicator/plugin/otr/OtrWeakListener.java new file mode 100644 index 000000000..423cf23f2 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/OtrWeakListener.java @@ -0,0 +1,155 @@ +/* + * 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.lang.ref.*; + +import net.java.sip.communicator.service.protocol.*; + +/** + * Implements a ScOtrEngineListener and + * ScOtrKeyManagerListener listener for the purposes of + * OtrContactMenu and OtrMetaContactButton which listen to + * ScOtrEngine and ScOtrKeyManager while weakly referencing + * them. Fixes a memory leak of OtrContactMenu and + * OtrMetaContactButton instances because these cannot determine when + * they are to be explicitly disposed. + * + * @author Lyubomir Marinov + */ +public class OtrWeakListener + + implements ScOtrEngineListener, + ScOtrKeyManagerListener +{ + /** + * The ScOtrEngine the T associated with + * this instance is to listen to. + */ + private final ScOtrEngine engine; + + /** + * The ScOtrKeyManager the T associated + * with this instance is to listen to. + */ + private final ScOtrKeyManager keyManager; + + /** + * The T which is associated with this instance + * and which is to listen to {@link #engine} and {@link #keyManager}. + */ + private final WeakReference listener; + + /** + * Initializes a new OtrWeakListener instance which is to allow + * a specific T to listener to a specific + * ScOtrEngine and a specific ScOtrKeyManager without + * being retained by them forever (because they live forever). + * + * @param listener the T which is to listen to the + * specified engine and keyManager + * @param engine the ScOtrEngine which is to be listened to by + * the specified T + * @param keyManager the ScOtrKeyManager which is to be + * listened to by the specified T + */ + public OtrWeakListener( + T listener, + ScOtrEngine engine, ScOtrKeyManager keyManager) + { + if (listener == null) + throw new NullPointerException("listener"); + + this.listener = new WeakReference(listener); + this.engine = engine; + this.keyManager = keyManager; + + this.engine.addListener(this); + this.keyManager.addListener(this); + } + + /** + * {@inheritDoc} + * + * Forwards the event/notification to the associated + * T if it is still needed by the application. + */ + public void contactPolicyChanged(Contact contact) + { + ScOtrEngineListener l = getListener(); + + if (l != null) + l.contactPolicyChanged(contact); + } + + /** + * {@inheritDoc} + * + * Forwards the event/notification to the associated + * T if it is still needed by the application. + */ + public void contactVerificationStatusChanged(Contact contact) + { + ScOtrKeyManagerListener l = getListener(); + + if (l != null) + l.contactVerificationStatusChanged(contact); + } + + /** + * Gets the T which is listening to {@link #engine} + * and {@link #keyManager}. If the T is no longer needed by + * the application, this instance seizes listening to engine and + * keyManager and allows the memory used by this instance to be + * reclaimed by the Java virtual machine. + * + * @return the T which is listening to + * engine and keyManager if it is still needed by the + * application; otherwise, null + */ + private T getListener() + { + T l = this.listener.get(); + + if (l == null) + { + engine.removeListener(this); + keyManager.removeListener(this); + } + + return l; + } + + /** + * {@inheritDoc} + * + * Forwards the event/notification to the associated + * T if it is still needed by the application. + */ + public void globalPolicyChanged() + { + ScOtrEngineListener l = getListener(); + + if (l != null) + l.globalPolicyChanged(); + } + + /** + * {@inheritDoc} + * + * Forwards the event/notification to the associated + * T if it is still needed by the application. + */ + public void sessionStatusChanged(Contact contact) + { + ScOtrEngineListener l = getListener(); + + if (l != null) + l.sessionStatusChanged(contact); + } +} diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java b/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java index dfa0803c1..853c5fc5b 100644 --- a/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java @@ -7,7 +7,6 @@ package net.java.sip.communicator.plugin.otr; import net.java.otr4j.*; -import net.java.otr4j.session.*; import net.java.sip.communicator.service.protocol.*; /** @@ -101,13 +100,13 @@ public abstract void respondSmp( public abstract void refreshSession(Contact contact); /** - * Gets the {@link SessionStatus} for the given {@link Contact}. + * Gets the {@link ScSessionStatus} for the given {@link Contact}. * - * @param contact the {@link Contact} whose {@link SessionStatus} we are + * @param contact the {@link Contact} whose {@link ScSessionStatus} we are * interested in. - * @return the {@link SessionStatus}. + * @return the {@link ScSessionStatus}. */ - public abstract SessionStatus getSessionStatus(Contact contact); + public abstract ScSessionStatus getSessionStatus(Contact contact); // New Methods (Misc) diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java index 594c8f1aa..b1797fd5a 100644 --- a/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java @@ -314,6 +314,27 @@ public String getFallbackMessage(SessionID sessionID) } } + /** + * The max timeout period elapsed prior to establishing a TIMED_OUT session. + */ + private static final int SESSION_TIMEOUT = + OtrActivator.configService.getInt( + "net.java.sip.communicator.plugin.otr.SESSION_STATUS_TIMEOUT", + 30000); + + /** + * Manages the scheduling of TimerTasks that are used to set Contact's + * ScSessionStatus (to TIMED_OUT) after a period of time. + */ + private ScSessionStatusScheduler scheduler = new ScSessionStatusScheduler(); + + /** + * This mapping is used for taking care of keeping SessionStatus and + * ScSessionStatus in sync for every Session object. + */ + private Map scSessionStatusMap = + new ConcurrentHashMap(); + private static final Map contactsMap = new Hashtable(); @@ -388,6 +409,7 @@ public ScOtrEngineImpl() // Clears the map after previous instance // This is required because of OSGi restarts in the same VM on Android contactsMap.clear(); + scSessionStatusMap.clear(); this.otrEngine.addOtrEngineListener(new OtrEngineListener() { @@ -397,10 +419,17 @@ public void sessionStatusChanged(SessionID sessionID) if (contact == null) return; + // Cancels any scheduled tasks that will change the + // ScSessionStatus for this Contact + scheduler.cancel(contact); + + ScSessionStatus scSessionStatus = getSessionStatus(contact); String message = ""; switch (otrEngine.getSessionStatus(sessionID)) { case ENCRYPTED: + scSessionStatus = ScSessionStatus.ENCRYPTED; + scSessionStatusMap.put(sessionID, scSessionStatus); PublicKey remotePubKey = otrEngine.getRemotePublicKey(sessionID); @@ -490,6 +519,8 @@ public void sessionStatusChanged(SessionID sessionID) break; case FINISHED: + scSessionStatus = ScSessionStatus.FINISHED; + scSessionStatusMap.put(sessionID, scSessionStatus); message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.sessionfinished", @@ -497,6 +528,8 @@ public void sessionStatusChanged(SessionID sessionID) { contact.getDisplayName() }); break; case PLAINTEXT: + scSessionStatus = ScSessionStatus.PLAINTEXT; + scSessionStatusMap.put(sessionID, scSessionStatus); message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.sessionlost", new String[] @@ -575,6 +608,8 @@ public void endSession(Contact contact) SessionID sessionID = getSessionID(contact); try { + setSessionStatus(contact, ScSessionStatus.PLAINTEXT); + otrEngine.endSession(sessionID); } catch (OtrException e) @@ -617,9 +652,73 @@ private ScOtrEngineListener[] getListeners() } } - public SessionStatus getSessionStatus(Contact contact) + /** + * Manages the scheduling of TimerTasks that are used to set Contact's + * ScSessionStatus after a period of time. + * + * @author Marin Dzhigarov + */ + private class ScSessionStatusScheduler + { + private final Timer timer = new Timer(); + + private final Map tasks = + new ConcurrentHashMap(); + + public void scheduleScSessionStatusChange( + final Contact contact, final ScSessionStatus status) + { + cancel(contact); + + TimerTask task = new TimerTask() { + @Override + public void run() + { + setSessionStatus(contact, status); + } + }; + timer.schedule(task, SESSION_TIMEOUT); + tasks.put(contact, task); + } + + public void cancel(final Contact contact) + { + TimerTask task = tasks.get(contact); + if (task != null) + task.cancel(); + tasks.remove(contact); + } + } + + private void setSessionStatus(Contact contact, ScSessionStatus status) + { + scSessionStatusMap.put(getSessionID(contact), status); + for (ScOtrEngineListener l : getListeners()) + l.sessionStatusChanged(contact); + } + + public ScSessionStatus getSessionStatus(Contact contact) { - return otrEngine.getSessionStatus(getSessionID(contact)); + SessionID sessionID = getSessionID(contact); + SessionStatus sessionStatus = otrEngine.getSessionStatus(sessionID); + ScSessionStatus scSessionStatus = null; + if (!scSessionStatusMap.containsKey(sessionID)) + { + switch (sessionStatus) + { + case PLAINTEXT: + scSessionStatus = ScSessionStatus.PLAINTEXT; + break; + case ENCRYPTED: + scSessionStatus = ScSessionStatus.ENCRYPTED; + break; + case FINISHED: + scSessionStatus = ScSessionStatus.FINISHED; + break; + } + scSessionStatusMap.put(sessionID, scSessionStatus); + } + return scSessionStatusMap.get(sessionID); } public boolean isMessageUIDInjected(String mUID) @@ -751,6 +850,17 @@ public void showError(SessionID sessionID, String err) public void startSession(Contact contact) { SessionID sessionID = getSessionID(contact); + + ScSessionStatus scSessionStatus = getSessionStatus(contact); + scSessionStatus = ScSessionStatus.LOADING; + scSessionStatusMap.put(sessionID, scSessionStatus); + for (ScOtrEngineListener l : getListeners()) + { + l.sessionStatusChanged(contact); + scheduler.scheduleScSessionStatusChange( + contact, ScSessionStatus.TIMED_OUT); + } + try { otrEngine.startSession(sessionID); diff --git a/src/net/java/sip/communicator/plugin/otr/ScSessionStatus.java b/src/net/java/sip/communicator/plugin/otr/ScSessionStatus.java new file mode 100644 index 000000000..6151db5c0 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/ScSessionStatus.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * Extends otr4j's SessionStatus with two additional states. + * + * @author Marin Dzhigarov + */ +public enum ScSessionStatus +{ + PLAINTEXT, + ENCRYPTED, + FINISHED, + /* + * A Session transitions in LOADING state right before + * Session.startSession() is invoked. + */ + LOADING, + /* + * A Session transitions in TIMED_OUT state after being in LOADING state for + * a long period of time. + */ + TIMED_OUT +}