From 0280ea2aa901f169c1dee4f007456d6fe57559b5 Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Tue, 12 Mar 2013 18:02:21 +0000
Subject: [PATCH] Android UI for peer moderation.

---
 .../res/drawable-hdpi/rating_bad.png          | Bin 0 -> 1541 bytes
 .../res/drawable-hdpi/rating_good.png         | Bin 0 -> 1524 bytes
 .../res/drawable-mdpi/rating_bad.png          | Bin 0 -> 1293 bytes
 .../res/drawable-mdpi/rating_good.png         | Bin 0 -> 1290 bytes
 .../res/drawable-xhdpi/rating_bad.png         | Bin 0 -> 1725 bytes
 .../res/drawable-xhdpi/rating_good.png        | Bin 0 -> 1705 bytes
 .../briar/android/groups/GroupActivity.java   |  66 +++++++++++--
 .../sf/briar/android/groups/GroupAdapter.java |  36 +++++--
 .../sf/briar/android/groups/GroupItem.java    |  50 ++++++++++
 .../android/groups/GroupListActivity.java     |   4 +
 .../groups/ReadGroupMessageActivity.java      |  92 +++++++++++++++++-
 .../messages/ConversationActivity.java        |  17 +++-
 .../messages/ReadPrivateMessageActivity.java  |   8 ++
 .../sf/briar/api/crypto/CryptoExecutor.java   |   3 +-
 .../api/db/event/RatingChangedEvent.java      |   2 +-
 .../messaging/duplex/DuplexConnection.java    |   8 +-
 .../duplex/DuplexConnectionFactoryImpl.java   |  10 +-
 .../duplex/IncomingDuplexConnection.java      |   8 +-
 .../duplex/OutgoingDuplexConnection.java      |   8 +-
 .../simplex/IncomingSimplexConnection.java    |   8 +-
 .../simplex/SimplexConnectionFactoryImpl.java |  13 +--
 21 files changed, 281 insertions(+), 52 deletions(-)
 create mode 100644 briar-android/res/drawable-hdpi/rating_bad.png
 create mode 100644 briar-android/res/drawable-hdpi/rating_good.png
 create mode 100644 briar-android/res/drawable-mdpi/rating_bad.png
 create mode 100644 briar-android/res/drawable-mdpi/rating_good.png
 create mode 100644 briar-android/res/drawable-xhdpi/rating_bad.png
 create mode 100644 briar-android/res/drawable-xhdpi/rating_good.png
 create mode 100644 briar-android/src/net/sf/briar/android/groups/GroupItem.java

diff --git a/briar-android/res/drawable-hdpi/rating_bad.png b/briar-android/res/drawable-hdpi/rating_bad.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d332cc92e47bbaaa094105944721a7400b6abe4
GIT binary patch
literal 1541
zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O`
z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y
zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP
zs8ErclUHn2VXFi-*9yo63F|8<fR&VF+bTgE72zA8;GAESs$i;Tpqp%9W~g9hqGxDg
zU}<8hqhMrUXrOOsq;FuZYiM9)YHnp<r~m~@K--E^(yW49+@N*=dA3R!B_#z``ugSN
z<$C4Ddih1^`i7R4mih)p`bI{&Koz>hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83
zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0<RzFwUtj!6b93RUi%Wu15$?rmaB)aw
zL8^XGYH@yPQ8F;%(v(3~6<9eJr6!i-7lq{K=fFZSAS1sdzc?emK*2fKRL@YsH!(Rg
z4<rKC;p=PVnO9trn3tUD>0+w{G(#^lGsVip$iURV+|k6;$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19yq1P0rUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnqfW
zG!Lpb1-Dy_aq86vIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1zZhD#G$iTo<?&;zf
zQgQ3e%+ub{jv~iog?+b#=q_yv^L~9Uc504RREBHJ)zI)qoBmJMe!D3n_9r_>&;{Qb
z?X71QDQ*24Sd*RXJExBMe(S?Y6MXhO_ATK1BNjJl_UE&Y&y>3<E?u+g$%=KWJ`^y`
zVNgH7S-|vedqc)Xc^?&_opSc9YabYPG!_@}=pE#=&{f@-VkEhyLFS{-9`^npa*Uf)
z4j=h`Q0@j}^dD>CQ#?N&F-kC=eZb}MwzkWnf@5AI#}2mogN#4e!W_EJI|~MIznb}{
z_uHNKJbN#S{pl0B!g#MC{t3sDpO&W8w;x#lNcVQ&mN+xHVBdr%ZHx6Zp0RRwUTBeP
zGTe~k?7;hj!D?ru_t!_0oc?%waIRoJ-z}OD<u<>;te~*_33ux)<KAuO8l9)PC2rip
zJ$Y*4rWUCK+1IM07wgXqp3KY0oY3*wEV?sn(cIU@*_A(2eEjl-8M|#lbAPL679E(W
zvYElmg4;~_w3he}xfkr~nPQxpm|wU@_RTkAG1<(tl-a|^ZS?}TU;KApbF%(n%Qj<K
z@$|Cq<g&FlneS?yP^$Ak`(RDcnUWRLw->HZV|sQ_@<sC8zRx)yraAdG+KMKZ{^Xc;
zXw&7_Du+_`zCV6bp6B@t&hz*GFg%v|df_+Y`ofQ==H9B<?GyH^#<#^gZPS%^XTsc{
z^~>#h?^RtstMh!@Jz?>V?F+)QoffSzSo|P+=Y6jgZU1Cvi>}Ol#d-TAyXt~&C8MaV
zk*vj2i}bIo`pO<S?bdlQ-SP)aE$_pUZXMy8(%hiD&;9BzUxVOve;xMyIJ8<Im2+9s
zz8$-|jaH>S5YBadwa(h;;Xm(}EEbG@4Z;tAl;W{^MmC1H^yyz!{{(#i70jNlelF{r
G5}E*M9zu)&

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-hdpi/rating_good.png b/briar-android/res/drawable-hdpi/rating_good.png
new file mode 100644
index 0000000000000000000000000000000000000000..f612bab60e2553211c7c55a836ccac63fc44951d
GIT binary patch
literal 1524
zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O`
z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y
zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP
zs8ErclUHn2VXFi-*9yo63F|8<fR&VF+bTgE72zA8;GAESs$i;Tpqp%9W~g9hqGxDg
zU}<8hqhMrUXrOOsq;FuZYiM9)YHnp<r~m~@K--E^(yW49+@N*=dA3R!B_#z``ugSN
z<$C4Ddih1^`i7R4mih)p`bI{&Koz>hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83
zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0<RzFwUtj!6b93RUi%Wu15$?rmaB)aw
zL8^XGYH@yPQ8F;%(v(3~6<9eJr6!i-7lq{K=fFZSAS1sdzc?emK*2fKRL@YsH!(Rg
z4<rKC;p=PVnO9trn3tUD>0+w{G(#^lGsVip$kol+%*4pl$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19yq1PCvUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnqfV
zG!Lpb1-Dxaaq86vIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1zemt{l3IhXEnx~6n
zNX4x;Gf#U*JBl3hUGLeu%yWuat;Da0DO!`fa>b4+vHovxT6lFT_uuuq-ffe9xoT}$
zqN_Qh_{Mdtw--$N!4&`X*vs+<+&qe$_fF4M5r2Mwzi4vx`!nXn&*m6U?g(0?eWq-&
z_LDT$2*$7m(FE3*e8#)sdkyEGXTSL_yMU#uH%~)^>mV1$g?UYq8`$fb{~GL^z;mPe
z0=HaK=Z3Y7o(3#_2UTD2Pj1ku;EsuNZ{X@?c=?9Cz*|^hu70cQ0gDR0b6d9tuw^F-
z81O86`MGPe;@i4~9oL>P&i}^d(<pbgH0go*kBgUFo?V>uW{1ieud|1yp4Zkre?;z$
z;S6Sf3B7FP-kAH$w;%M}-&wNJYd7m2hRt&tCtK~;R@(g8FIwDPq}btmRQ}Y4ytGis
zQq5^`jPBiLy=NY77Cig(NwLV21L_;3wluKR{<3q|W4-ji%=O5P!tY$~gLo&+I9=xE
z!*OC`UuUp`ZaSl?jbux#u-=qZW_8CY48Aehk~*_IQfhM!ED3#Kr|-+rk=1=}N~iWc
z)?_snm6t_o{K2y44uvhBA{6&}Q}PE{otXj!2MojwO}MXeY<Zx%sIpM;`i9>c&!#a3
zKmD?Be$>&!FTNK}{(n70cgx9@vI$#O^xaw-R;V?9?)3Eijq4vAn$i&eK)GT`>t(sT
zGx8HGtb;Ry^sWdin7x1AakRUETh2vP`U!{Sdr8*w2V}cCgM8L1EG_+~cK36<^>=2a
zyyg?u#%luDXU;H+k}K?ZRBZjg*g3;JCnIK_*_16TGm?(ZO4~X!;e^iTefImp_y3)K
oiE9JX8V2nHK<ZY;e^v>Grn3`LT`nEl2P%g>UHx3vIVCg!0Gih^kN^Mx

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-mdpi/rating_bad.png b/briar-android/res/drawable-mdpi/rating_bad.png
new file mode 100644
index 0000000000000000000000000000000000000000..5c5982ac649369ba8fa4c379c462a0f550ff2903
GIT binary patch
literal 1293
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O`
z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y
zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP
zs8ErclUHn2VXFi-*9yo63F|8<fR&VF+bTgE72zA8;GAESs$i;Tpqp%9W~g9hqGxDg
zU}<8hqhMrUXrOOsq;FuZYiM9)YHnp<r~m~@K--E^(yW49+@N*=dA3R!B_#z``ugSN
z<$C4Ddih1^`i7R4mih)p`bI{&Koz>hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83
zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0<RzFwUtj!6b93RUi%Wu15$?rmaB)aw
zL8^XGYH@yPQ8F;%(v(3~6<9eJr6!i-7lq{K=fFZSAS1sdzc?emK*2fKRL@YsH!(Rg
z4<rKC;p=PVnO9trn3tUD>0+w{G(#^lGsVip$iURV+|k0+$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19yq1O_pUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnv1>
zG!Lpb1-Dzwaq86vIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1zKHEBnnSp_^-qXb~
zq~g|_>3h8oJ4hUp&dt*KvGv6wr}E=Ed`;c=AIyJb|3t}8OV7`WtIv&n?h!|E(OhxS
z=qqN=i$AdR1qJ_(%b8aF(EZZ0qVId+rymLS68*HL(dpbX77NCm+#1Vbc@7&`&SiR?
zFzNf1gVrmW+&?hPb6kDdCQ#%gi^Mk0X$RQrtJ%7m;trVZU^4x1GMiWKDATfoI<|Ss
z&KNUmCA4;L`|)4WV!|~C$(%qJ<J1_QeZ2YY2RS5IiyuiPba=;#<(sFr#N2;>yX2q}
z$EBhl?1qiK4XJJpS_hJ|jxgz2boSQXyy6h~U}L_8*oNvOOj>gc%q|K1pCmN@;l78*
zLY96nH)G;EsC8RwLAtYS&~oKc{<4F)Y0-vn=1j2&)8E5*ZO3KRrY|#OKTLbMdBNHV
zlaiz!7fyJ2gZcO+Kh19zb&O|>ukU)W?PYDe+;O|_l}9(szomC?bwi<n<N5gq0{6#z
zOp?05y7T_p{r?xU#U14A{$RA)T<Lu63=2j-hkHzQED{VG`@;-wyq^04R6u#U`njxg
HN@xNA(D}({

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-mdpi/rating_good.png b/briar-android/res/drawable-mdpi/rating_good.png
new file mode 100644
index 0000000000000000000000000000000000000000..16fce26e1c2e484a855638960cca2ddebb93f115
GIT binary patch
literal 1290
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O`
z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y
zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP
zs8ErclUHn2VXFi-*9yo63F|8<fR&VF+bTgE72zA8;GAESs$i;Tpqp%9W~g9hqGxDg
zU}<8hqhMrUXrOOsq;FuZYiM9)YHnp<r~m~@K--E^(yW49+@N*=dA3R!B_#z``ugSN
z<$C4Ddih1^`i7R4mih)p`bI{&Koz>hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83
zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0<RzFwUtj!6b93RUi%Wu15$?rmaB)aw
zL8^XGYH@yPQ8F;%(v(3~6<9eJr6!i-7lq{K=fFZSAS1sdzc?emK*2fKRL@YsH!(Rg
z4<rKC;p=PVnO9trn3tUD>0+w{G(#^lGsVip$kol+%*4#p$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19yq1POzUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnv1=
zG!Lpb1-DyFaq86vIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1z=1+a=!@$5;<LTlU
zQgQ3e^u6AP14NDrn||Fac7*?mE4#a_3142H)ypN_9sj5OZ|ZJVc%{OjmsVORc;w_l
ztt+#(Z98B5kVEmpzF%=3)5;&3?@`%nyYu_J=vUfH_b4SF(6*SvpuV7h(RpuPTjG!V
zHSQ0DV^s9r0;hPhUOT9FgK_Gf=m56%1t*I&^BJoX<W5A@vddR;-C()BfU%~4H)<x&
zLbaN2HS?b{+-cr@Cgy^&+QCkV*8F>EdW8pAUNp=<;*+A4z$(+M8sHz3zd<xwM4&mp
zfK|?iRY!Q{E6EKfiYNT~v|l2H)h4QNMtU+M`<L=F#qG@3a!-ido+$GveYRM~{_Ho)
zGq2?4L~Po!A|&Ooo5KN>WoDw{cW!WW%sdg&Q1kCv8e3G2+tL^1$4yGlzh<#%(&Q5C
z?rjp}OP#&$#kB9Db6&Lts6Xi0|JKgdmiO8Ltr@Jzd#0~DXHl_`W3}%Jq5Kc5s~&L9
zX|~i`#;Ui{h}E<7-htHk=12c#STOoITx6<am0-BMHB?i?Ot}P9I(fSKxvX<aXaWGB
C9l;*}

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-xhdpi/rating_bad.png b/briar-android/res/drawable-xhdpi/rating_bad.png
new file mode 100644
index 0000000000000000000000000000000000000000..a8ad06ad9926fb4948f790f4699b1dd740c47d4c
GIT binary patch
literal 1725
zcmaJ?4NMbf7(NOWtb!uU4V3LD14P!|U1_Q9TA`(&1&aKMpc$dGCsb^&KL;%!SWpxZ
zL|BH1L@>hykU7BkHxUR+@TZ`aP2*+|=MPjNXb^Ql#a$8Dl8s$*_kHi1_j%s;{od!x
z9f*$F;7D<&007`9-YAMC)?n-Fu!#6JD>V0s#SITl#N)8-xL&G70f8J#MM1Grnuf-r
zQhB!K8p;Cz+b<RIiFl$Uk}JcMbg4ClZcwTTGyw2|3~H$?1I58qG)<x6Q=ebzqJj!J
zpSp!5fhFn?G+nVVM~lYgM8(T;GGrV%HE2D^GjIt4C5lTygL0=z$2IV&GrU}4Z*@ad
za3%!L;8Q;ql_-e@Loh80vgj~P1_vTwAd`+TVGfh?707@Q7(y5j%%&kQm&NA72sryt
ziELW=c5bXFY&I8R@u}%JuI56JUazO?1L&AG4MI2^4g@nG27^XK&~(`<Txy`HbY61|
zB2*{SD%7|FQ-M}SX)2b9^QlCoA6rnW=V?{C*<&IO3^GX75JHEoEzJc=B>x|(RL)21
z@L2TkdjFGH7oV+0p;%OhWol)_;I?~NQ>nQjT2zW-+IS4xId_WD=@^db(lIp{633z=
zphPNDsH~nDghav>t8}<jB}2s`K9%60D-?1r6Imx@u>v_PAuK|WP=v!oIH62oh#-uG
zu$e++4lBZBnMzcJ&tc_%V+9LhtzuBBiOeEYtH?s-VOmTH&NR(cEL;nFLB3h6eBoNw
zEr^APVj$~i|1s)07oi{Pc3!xIF)w{oMd)2i2z%|J)s4h`fy5#~yy5q;8dHW#jB78J
z{6opv+p(@+L<^Ra^-0D4p(hIKb8qdw+7+z!qZSvgDW?Av!w$ELalKWtb!}&3bxp$C
z>WlgN^KFlKHnqLH;G%Qd#biE>yaA0I-xAk;rzZbgB`F%91lz0u$n}7$an>;IZq2!T
z|7nvZ-IqyG?%5Fy<P`}mt0tFBEq-qt9#A*jN^co`-mK`^8x`VWUM_!XkfWVp`P2Kh
zIspYQZEC3X-*r$y31D9zu&cvNZs|9}1;sRh$?gRuXYpn4#^XS%y}NPvl0oU}Ay`_{
zQTL5G*<#0(EDtcJHM(8u^k#0e1Ef9XvRa2~ukb$KM;+sox+NYd2hF|~icPi+++!ZP
z2}hY)bbQUZf#LTmc~9QBOLw*f4k~+6dZ@YVJ+7>{a?|8|*T&=V#mU>ziB<P9lQ@6$
zvteFd4Lb3P^uUXGtS7UQ)Ht<{w8tFd<2&ls-M9?6Qm-3(MjE1BF85OJb-R21rH}YS
zg<ZGL%@fb3UJTkBKkc&(ZSA%<j+$M31VbZ3Cg;N|alLbvdo8H@k>q;<Nhs)8P5U~x
zs4`$#-=6?n`}0Gpd&Mc<h$O0n<`lkpef~3BBW`@lC<N=YWd}bwI+?5+Q%>k<PRS=5
zY!g=iZ!VBasGj(1(qN<}g99CS<%!>GIc;M2pBDpHr^iVfq>9k<6aD3!ku$=~drhHB
ze$6ls@>WC)Xbd%sC{9anl{e#5UD3IBCzB&Kc}5JJGJoF@zOlx?tzHs0QM5U=04{X$
zgdUBpva}}+Ivq>4YfU)&*`X(k-b_~pjR&(i+p0e}8cX{^l(nl;u9}2rq`ag%fR95;
zlcfFIv3%#8k=)Ab*X3QiOT7Df$Nl>IyoYY2IX*D#SGDzip4#jnlP3TT4IVpwIqAqS
zZCWWWy|J`A%cm30g07L-k@^qoM-{-6Ky*|4be`p?-(i#tmY(f;_%7E{<yVc8Q>vuN
znth&%tl=qgH_zj=X3^oWkv6}2^fw27iltoL?*I5m#biY6zw`=_&E>B*7;S*kCQ3%+
S9??<j4@DdrB|0rkDf|oNt&5)k

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-xhdpi/rating_good.png b/briar-android/res/drawable-xhdpi/rating_good.png
new file mode 100644
index 0000000000000000000000000000000000000000..225eaea5f258984ccc6d015598dc431e9af8978a
GIT binary patch
literal 1705
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+*
zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn
zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%uvD1M9<K~
zz|zE0N5ROz&_LhNNZ-I**U-Sq)ZEIzPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj
z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5
zFD<cE0=g99h1>$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;Np<V
zf>iyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr4|esh**NZ(?$0
z9!LbN!`Ii!Gq1QLF)umQ)5TT^Xog;9W{Q=Gk*k}tnX#*>lYxt&p{t>#iJ_&diIcOV
zo2j9>iIX8ruS<S%X>Mv>2~2MaLa!T6y`aR9TL84#CABECEH%ZgC_h&L>}jh^tZp$g
zG6T8;r+HAlDOlYC){EO8`as9%gQ6BGs$oLF^aEnT6E2VgPx`5O!1P`OOxVq(W~#va
zBJJto7*cWT&FtIR!hr(ECg%z1uid7>*;T&h0jrCFM#rxO{JKp_FPg<`F4w#N39k_l
z`N-7tLx4q8#4%t^xJgz>*5=(OfAQ9JpH`eU<L=v=Y3UD=XP-5j^K-6cahjipAxD$b
zyxb)dih~)r92hkiSQjveG%y8#I8QEnHUCTA8|Po0BYZ*R3-2!0%3|9)879xf9ZrR{
zoO5h{v4i{WxywQ~)E(Js)f<v8WW+J(EI;hJ@7;x;Q!5z@m~Nfez_06(y(W@*gS1tS
zg?!&;>o*K4XZ#&S4@XKq=6~^c1G5EJZo{o(e~rE@IsSJc(+1XAZ#G^(ZFV<F)?mxj
z2Wl^bwjAO3t5nKcHG8L*f#T%7|7=#<8Gj6o_;cq(il)qEF-P@<$}-I__Z&V_ZoYZ1
z1J7iaa>iR;$N$#roE0+-Vff~pF8OJ5v5or91WiTX&5B%S`$IaoxekbYuUy`*tE*G5
z|LMKo5~V*b4)F`szA#kH4P}^gH2Xr~3kKPb|B9zeB|7!IlX_UK8rB!SKztj+6ieeG
z+2j{}&Z`&ZUf{d;nN1^|`Eiti-Qm>FCYj6*qE^n!YtLPh`2M-DZI}Pn;2tA=zvU9_
z2jpHTe9<=YW=ucvdgIomGfzaDF|27#dv@_++%p5sxb1;l;k{ckPv3KRpTnS$%Fyse
zt9s#OGX@LEmny0U-)&+{aE)4S!hA)_c-ypw+~OM%ceg)mdHYp=@^{Hy!ppOJgc;8s
zy1zI(dg1+tGaYZUG2Az5(9=F(T6j1kpjelgWBS82x(x9;Od6YG>$qw+l*$>LWjpbH
zbGLWV+wRNf7p^wrcad}EUs!slugN3C^3R*1CCT1gZml!_rv=TuvOsNf!Aw85>4!9K
zKMc*8QReje=NxIxc})_@es3)HPjL_`s-CiJl6>5!t#br()H>egEIOr=a46^AL(><@
zZ+FMk&1AUH|F=ahP?c#ye$$hZ9mj;{ayCWDFkUp(vg<Um)SCXDbDpjWL)zO9^L8Dt
zR%}RZwO{TT+E&rqSKYHh?e@L!&a{xfM?KE@q9#mWDxKi}TlWCNJf~$FxL7+9L6wK6
LtDnm{r-UW|%Q$@h

literal 0
HcmV?d00001

diff --git a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
index 8ed287306a..c0ae2820ec 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
@@ -4,11 +4,14 @@ import static android.view.Gravity.CENTER_HORIZONTAL;
 import static android.widget.LinearLayout.VERTICAL;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static net.sf.briar.api.Rating.UNRATED;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -19,17 +22,20 @@ import net.sf.briar.android.BriarService;
 import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.CommonLayoutParams;
 import net.sf.briar.android.widgets.HorizontalBorder;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseExecutor;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.db.NoSuchSubscriptionException;
 import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.MessageAddedEvent;
 import net.sf.briar.api.db.event.MessageExpiredEvent;
-import net.sf.briar.api.db.event.SubscriptionAddedEvent;
+import net.sf.briar.api.db.event.RatingChangedEvent;
 import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
 import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.GroupId;
 import android.content.Intent;
 import android.os.Bundle;
@@ -121,8 +127,23 @@ OnClickListener, OnItemClickListener {
 							db.getMessageHeaders(groupId);
 					if(LOG.isLoggable(INFO))
 						LOG.info("Loaded " + headers.size() + " headers");
+					// Load the ratings for the authors
+					Map<Author, Rating> ratings = new HashMap<Author, Rating>();
+					for(GroupMessageHeader h : headers) {
+						Author a = h.getAuthor();
+						if(a != null && !ratings.containsKey(a))
+							ratings.put(a, db.getRating(a.getId()));
+					}
+					ratings = Collections.unmodifiableMap(ratings);
 					// Update the conversation
-					updateConversation(headers);
+					updateConversation(headers, ratings);
+				} catch(NoSuchSubscriptionException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
+					runOnUiThread(new Runnable() {
+						public void run() {
+							finish();
+						}
+					});
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -136,7 +157,8 @@ OnClickListener, OnItemClickListener {
 	}
 
 	private void updateConversation(
-			final Collection<GroupMessageHeader> headers) {
+			final Collection<GroupMessageHeader> headers,
+			final Map<Author, Rating> ratings) {
 		runOnUiThread(new Runnable() {
 			public void run() {
 				List<GroupMessageHeader> sort =
@@ -147,7 +169,9 @@ OnClickListener, OnItemClickListener {
 				for(GroupMessageHeader h : sort) {
 					if(firstUnread == -1 && !h.isRead())
 						firstUnread = adapter.getCount();
-					adapter.add(h);
+					Author a = h.getAuthor();
+					if(a == null) adapter.add(new GroupItem(h, UNRATED));
+					else adapter.add(new GroupItem(h, ratings.get(a)));
 				}
 				if(firstUnread == -1) list.setSelection(adapter.getCount() - 1);
 				else list.setSelection(firstUnread);
@@ -169,15 +193,36 @@ OnClickListener, OnItemClickListener {
 		} else if(e instanceof MessageExpiredEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
 			reloadMessageHeaders();
-		} else if(e instanceof SubscriptionAddedEvent) {
-			if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
-			reloadMessageHeaders();
+		} else if(e instanceof RatingChangedEvent) {
+			RatingChangedEvent r = (RatingChangedEvent) e;
+			updateRating(r.getAuthorId(), r.getRating());
 		} else if(e instanceof SubscriptionRemovedEvent) {
-			if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
-			reloadMessageHeaders();
+			SubscriptionRemovedEvent s = (SubscriptionRemovedEvent) e;
+			if(s.getGroupId().equals(groupId)) {
+				if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
+				finish();
+			}
 		}
 	}
 
+	private void updateRating(final AuthorId a, final Rating r) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				boolean affected = false;
+				int count = adapter.getCount();
+				for(int i = 0; i < count; i++) {
+					GroupItem item = adapter.getItem(i);
+					Author author = item.getAuthor();
+					if(author != null && author.getId().equals(a)) {
+						item.setRating(r);
+						affected = true;
+					}
+				}
+				if(affected) list.invalidate();
+			}
+		});
+	}
+
 	public void onClick(View view) {
 		Intent i = new Intent(this, WriteGroupMessageActivity.class);
 		i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
@@ -191,7 +236,7 @@ OnClickListener, OnItemClickListener {
 	}
 
 	private void showMessage(int position) {
-		GroupMessageHeader item = adapter.getItem(position);
+		GroupItem item = adapter.getItem(position);
 		Intent i = new Intent(this, ReadGroupMessageActivity.class);
 		i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
 		i.putExtra("net.sf.briar.GROUP_NAME", groupName);
@@ -203,6 +248,7 @@ OnClickListener, OnItemClickListener {
 			i.putExtra("net.sf.briar.ANONYMOUS", false);
 			i.putExtra("net.sf.briar.AUTHOR_ID", author.getId().getBytes());
 			i.putExtra("net.sf.briar.AUTHOR_NAME", author.getName());
+			i.putExtra("net.sf.briar.RATING", item.getRating().toString());
 		}
 		i.putExtra("net.sf.briar.CONTENT_TYPE", item.getContentType());
 		i.putExtra("net.sf.briar.TIMESTAMP", item.getTimestamp());
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
index 17915c8d45..10f8b11722 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
@@ -5,13 +5,15 @@ import static android.view.Gravity.CENTER_VERTICAL;
 import static android.widget.LinearLayout.HORIZONTAL;
 import static android.widget.LinearLayout.VERTICAL;
 import static java.text.DateFormat.SHORT;
+import static net.sf.briar.api.Rating.BAD;
+import static net.sf.briar.api.Rating.GOOD;
 
 import java.util.ArrayList;
 
 import net.sf.briar.R;
 import net.sf.briar.android.widgets.CommonLayoutParams;
 import net.sf.briar.android.widgets.HorizontalSpace;
-import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.messaging.Author;
 import android.content.Context;
 import android.content.res.Resources;
@@ -23,16 +25,16 @@ import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
+class GroupAdapter extends ArrayAdapter<GroupItem> {
 
 	GroupAdapter(Context ctx) {
 		super(ctx, android.R.layout.simple_expandable_list_item_1,
-				new ArrayList<GroupMessageHeader>());
+				new ArrayList<GroupItem>());
 	}
 
 	@Override
 	public View getView(int position, View convertView, ViewGroup parent) {
-		GroupMessageHeader item = getItem(position);
+		GroupItem item = getItem(position);
 		Context ctx = getContext();
 		// FIXME: Use a RelativeLayout
 		LinearLayout layout = new LinearLayout(ctx);
@@ -44,12 +46,28 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
 		innerLayout.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
 		innerLayout.setOrientation(VERTICAL);
 
-		Author author = item.getAuthor();
+		LinearLayout innerInnerLayout = new LinearLayout(ctx);
+		innerInnerLayout.setOrientation(HORIZONTAL);
+		innerInnerLayout.setGravity(CENTER_VERTICAL);
+
+		Rating rating = item.getRating();
+		if(rating == GOOD) {
+			ImageView good = new ImageView(ctx);
+			good.setPadding(0, 10, 10, 10);
+			good.setImageResource(R.drawable.rating_good);
+			innerInnerLayout.addView(good);
+		} else if(rating == BAD) {
+			ImageView bad = new ImageView(ctx);
+			bad.setPadding(0, 10, 10, 10);
+			bad.setImageResource(R.drawable.rating_bad);
+			innerInnerLayout.addView(bad);
+		}
 
 		TextView name = new TextView(ctx);
 		name.setTextSize(18);
 		name.setMaxLines(1);
 		name.setPadding(10, 10, 10, 10);
+		Author author = item.getAuthor();
 		Resources res = ctx.getResources();
 		if(author == null) {
 			name.setTextColor(res.getColor(R.color.anonymous_author));
@@ -58,7 +76,8 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
 			name.setTextColor(res.getColor(R.color.pseudonymous_author));
 			name.setText(author.getName());
 		}
-		innerLayout.addView(name);
+		innerInnerLayout.addView(name);
+		innerLayout.addView(innerInnerLayout);
 
 		if(item.getContentType().equals("text/plain")) {
 			TextView subject = new TextView(ctx);
@@ -69,15 +88,12 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
 			subject.setText(item.getSubject());
 			innerLayout.addView(subject);
 		} else {
-			LinearLayout innerInnerLayout = new LinearLayout(ctx);
-			innerInnerLayout.setOrientation(HORIZONTAL);
 			ImageView attachment = new ImageView(ctx);
 			attachment.setPadding(10, 0, 10, 10);
 			attachment.setImageResource(R.drawable.content_attachment);
 			innerInnerLayout.addView(attachment);
-			innerInnerLayout.addView(new HorizontalSpace(ctx));
-			innerLayout.addView(innerInnerLayout);
 		}
+		innerInnerLayout.addView(new HorizontalSpace(ctx));
 		layout.addView(innerLayout);
 
 		TextView date = new TextView(ctx);
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupItem.java b/briar-android/src/net/sf/briar/android/groups/GroupItem.java
new file mode 100644
index 0000000000..3e6cb18213
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/GroupItem.java
@@ -0,0 +1,50 @@
+package net.sf.briar.android.groups;
+
+import net.sf.briar.api.Rating;
+import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.MessageId;
+
+// This class is not thread-safe
+class GroupItem {
+
+	private final GroupMessageHeader header;
+	private Rating rating;
+
+	GroupItem(GroupMessageHeader header, Rating rating) {
+		this.header = header;
+		this.rating = rating;
+	}
+
+	MessageId getId() {
+		return header.getId();
+	}
+
+	Author getAuthor() {
+		return header.getAuthor();
+	}
+
+	String getContentType() {
+		return header.getContentType();
+	}
+
+	String getSubject() {
+		return header.getSubject();
+	}
+
+	long getTimestamp() {
+		return header.getTimestamp();
+	}
+
+	boolean isRead() {
+		return header.isRead();
+	}
+
+	Rating getRating() {
+		return rating;
+	}
+
+	void setRating(Rating rating) {
+		this.rating = rating;
+	}
+}
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
index 5c0c3beecf..ee38bfd8d8 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -4,6 +4,8 @@ import static android.view.Gravity.CENTER_HORIZONTAL;
 import static android.widget.LinearLayout.VERTICAL;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static net.sf.briar.api.Rating.BAD;
+import static net.sf.briar.api.Rating.GOOD;
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
@@ -131,8 +133,10 @@ implements OnClickListener, DatabaseListener {
 					PrivateKey privateKey = keyPair.getPrivate();
 					Author author = authorFactory.createAuthor("Batman",
 							publicKey);
+					db.setRating(author.getId(), BAD);
 					Author author1 = authorFactory.createAuthor("Duckman",
 							publicKey);
+					db.setRating(author1.getId(), GOOD);
 					// Insert some fake groups and make them visible
 					Group group = groupFactory.createGroup("DisneyLeaks");
 					db.subscribe(group);
diff --git a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java
index d5750019ba..61b4183e58 100644
--- a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java
@@ -2,11 +2,16 @@ package net.sf.briar.android.groups;
 
 import static android.view.Gravity.CENTER;
 import static android.view.Gravity.CENTER_VERTICAL;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
 import static android.widget.LinearLayout.HORIZONTAL;
 import static android.widget.LinearLayout.VERTICAL;
 import static java.text.DateFormat.SHORT;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static net.sf.briar.api.Rating.BAD;
+import static net.sf.briar.api.Rating.GOOD;
+import static net.sf.briar.api.Rating.UNRATED;
 
 import java.io.UnsupportedEncodingException;
 import java.util.concurrent.Executor;
@@ -19,10 +24,12 @@ import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.CommonLayoutParams;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.android.BundleEncrypter;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseExecutor;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.NoSuchMessageException;
 import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.MessageId;
@@ -33,6 +40,7 @@ import android.text.format.DateUtils;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.ImageButton;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
 import android.widget.TextView;
@@ -60,8 +68,11 @@ implements OnClickListener {
 	private MessageId messageId = null;
 	private AuthorId authorId = null;
 	private String authorName = null;
+	private Rating rating = UNRATED;
 	private boolean read;
-	private ImageButton readButton = null, prevButton = null, nextButton = null;
+	private ImageView thumb = null;
+	private ImageButton goodButton = null, badButton = null, readButton = null;
+	private ImageButton prevButton = null, nextButton = null;
 	private ImageButton replyButton = null;
 	private TextView content = null;
 
@@ -86,6 +97,8 @@ implements OnClickListener {
 			authorId = new AuthorId(id);
 			authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME");
 			if(authorName == null) throw new IllegalStateException();
+			String r = i.getStringExtra("net.sf.briar.RATING");
+			if(r != null) rating = Rating.valueOf(r);
 		}
 		String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE");
 		if(contentType == null) throw new IllegalStateException();
@@ -117,6 +130,13 @@ implements OnClickListener {
 		header.setOrientation(HORIZONTAL);
 		header.setGravity(CENTER_VERTICAL);
 
+		thumb = new ImageView(this);
+		thumb.setPadding(0, 10, 10, 10);
+		if(rating == GOOD) thumb.setImageResource(R.drawable.rating_good);
+		else if(rating == BAD) thumb.setImageResource(R.drawable.rating_bad);
+		else thumb.setVisibility(GONE);
+		header.addView(thumb);
+
 		TextView author = new TextView(this);
 		// Give me all the unused width
 		author.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
@@ -158,6 +178,23 @@ implements OnClickListener {
 		footer.setOrientation(HORIZONTAL);
 		footer.setGravity(CENTER);
 
+		goodButton = new ImageButton(this);
+		goodButton.setBackgroundResource(0);
+		goodButton.setImageResource(R.drawable.rating_good);
+		if(authorName == null) goodButton.setEnabled(false);
+		else goodButton.setOnClickListener(this);
+		footer.addView(goodButton);
+		footer.addView(new HorizontalSpace(this));
+
+		badButton = new ImageButton(this);
+		badButton.setBackgroundResource(0);
+		badButton.setImageResource(R.drawable.rating_bad);
+		badButton.setOnClickListener(this);
+		if(authorName == null) badButton.setEnabled(false);
+		else badButton.setOnClickListener(this);
+		footer.addView(badButton);
+		footer.addView(new HorizontalSpace(this));
+
 		readButton = new ImageButton(this);
 		readButton.setBackgroundResource(0);
 		if(read) readButton.setImageResource(R.drawable.content_unread);
@@ -241,6 +278,13 @@ implements OnClickListener {
 							content.setText(text);
 						}
 					});
+				} catch(NoSuchMessageException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Message removed");
+					runOnUiThread(new Runnable() {
+						public void run() {
+							finish();
+						}
+					});
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -268,7 +312,13 @@ implements OnClickListener {
 	}
 
 	public void onClick(View view) {
-		if(view == readButton) {
+		if(view == goodButton) {
+			if(rating == BAD) setRatingInDatabase(UNRATED);
+			else if(rating == UNRATED) setRatingInDatabase(GOOD);
+		} else if(view == badButton) {
+			if(rating == GOOD) setRatingInDatabase(UNRATED);
+			else if(rating == UNRATED) setRatingInDatabase(BAD);
+		} else if(view == readButton) {
 			setReadInDatabase(!read);
 		} else if(view == prevButton) {
 			setResult(RESULT_PREV);
@@ -285,4 +335,42 @@ implements OnClickListener {
 			finish();
 		}
 	}
+
+	private void setRatingInDatabase(final Rating r) {
+		final DatabaseComponent db = this.db;
+		final AuthorId authorId = this.authorId;
+		dbExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					serviceConnection.waitForStartup();
+					db.setRating(authorId, r);
+					setRatingInUi(r);
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(InterruptedException e) {
+					if(LOG.isLoggable(INFO))
+						LOG.info("Interrupted while waiting for service");
+					Thread.currentThread().interrupt();
+				}
+			}
+		});
+	}
+
+	private void setRatingInUi(final Rating r) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				ReadGroupMessageActivity.this.rating = r;
+				if(r == GOOD) {
+					thumb.setImageResource(R.drawable.rating_good);
+					thumb.setVisibility(VISIBLE);
+				} else if(r == BAD) {
+					thumb.setImageResource(R.drawable.rating_bad);
+					thumb.setVisibility(VISIBLE);
+				} else {
+					thumb.setVisibility(GONE);
+				}
+			}
+		});
+	}
 }
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
index b382e920ee..f130f17374 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
@@ -23,7 +23,9 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseExecutor;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.db.PrivateMessageHeader;
+import net.sf.briar.api.db.event.ContactRemovedEvent;
 import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.MessageAddedEvent;
@@ -120,6 +122,13 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 						LOG.info("Loaded " + headers.size() + " headers");
 					// Update the conversation
 					updateConversation(headers);
+				} catch(NoSuchContactException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
+					runOnUiThread(new Runnable() {
+						public void run() {
+							finish();
+						}
+					});
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -173,7 +182,13 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 	}
 
 	public void eventOccurred(DatabaseEvent e) {
-		if(e instanceof MessageAddedEvent) {
+		if(e instanceof ContactRemovedEvent) {
+			ContactRemovedEvent c = (ContactRemovedEvent) e;
+			if(c.getContactId().equals(contactId)) {
+				if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
+				finish();
+			}
+		} else if(e instanceof MessageAddedEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
 			reloadMessageHeaders();
 		} else if(e instanceof MessageExpiredEvent) {
diff --git a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
index afe0944578..222bb307f2 100644
--- a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
@@ -24,6 +24,7 @@ import net.sf.briar.api.android.BundleEncrypter;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseExecutor;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.NoSuchMessageException;
 import net.sf.briar.api.messaging.MessageId;
 import android.content.Intent;
 import android.os.Bundle;
@@ -226,6 +227,13 @@ implements OnClickListener {
 							content.setText(text);
 						}
 					});
+				} catch(NoSuchMessageException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Message removed");
+					runOnUiThread(new Runnable() {
+						public void run() {
+							finish();
+						}
+					});
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
diff --git a/briar-api/src/net/sf/briar/api/crypto/CryptoExecutor.java b/briar-api/src/net/sf/briar/api/crypto/CryptoExecutor.java
index 346ed89387..8e3ba8dc9c 100644
--- a/briar-api/src/net/sf/briar/api/crypto/CryptoExecutor.java
+++ b/briar-api/src/net/sf/briar/api/crypto/CryptoExecutor.java
@@ -1,5 +1,6 @@
 package net.sf.briar.api.crypto;
 
+import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -10,6 +11,6 @@ import com.google.inject.BindingAnnotation;
 
 /** Annotation for injecting the executor for long-running crypto tasks. */
 @BindingAnnotation
-@Target({ PARAMETER })
+@Target({ FIELD, PARAMETER })
 @Retention(RUNTIME)
 public @interface CryptoExecutor {}
diff --git a/briar-api/src/net/sf/briar/api/db/event/RatingChangedEvent.java b/briar-api/src/net/sf/briar/api/db/event/RatingChangedEvent.java
index aec11c50f2..2c2bf6d7aa 100644
--- a/briar-api/src/net/sf/briar/api/db/event/RatingChangedEvent.java
+++ b/briar-api/src/net/sf/briar/api/db/event/RatingChangedEvent.java
@@ -13,7 +13,7 @@ public class RatingChangedEvent extends DatabaseEvent {
 		this.rating = rating;
 	}
 
-	public AuthorId getAuthor() {
+	public AuthorId getAuthorId() {
 		return author;
 	}
 
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
index 9b56a720bf..2681ec67b5 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
@@ -86,7 +86,7 @@ abstract class DuplexConnection implements DatabaseListener {
 	protected final ContactId contactId;
 	protected final TransportId transportId;
 
-	private final Executor dbExecutor, verificationExecutor;
+	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final long maxLatency;
 	private final AtomicBoolean canSendOffer, disposed;
@@ -97,7 +97,7 @@ abstract class DuplexConnection implements DatabaseListener {
 	private volatile PacketWriter writer = null;
 
 	DuplexConnection(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -106,7 +106,7 @@ abstract class DuplexConnection implements DatabaseListener {
 			PacketWriterFactory packetWriterFactory, ConnectionContext ctx,
 			DuplexTransportConnection transport) {
 		this.dbExecutor = dbExecutor;
-		this.verificationExecutor = verificationExecutor;
+		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
 		this.connRegistry = connRegistry;
@@ -171,7 +171,7 @@ abstract class DuplexConnection implements DatabaseListener {
 					dbExecutor.execute(new ReceiveAck(a));
 				} else if(reader.hasMessage()) {
 					UnverifiedMessage m = reader.readMessage();
-					verificationExecutor.execute(new VerifyMessage(m));
+					cryptoExecutor.execute(new VerifyMessage(m));
 				} else if(reader.hasOffer()) {
 					Offer o = reader.readOffer();
 					dbExecutor.execute(new ReceiveOffer(o));
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnectionFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnectionFactoryImpl.java
index 2c98cbf690..f141fce031 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnectionFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnectionFactoryImpl.java
@@ -28,7 +28,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 	private static final Logger LOG =
 			Logger.getLogger(DuplexConnectionFactoryImpl.class.getName());
 
-	private final Executor dbExecutor, verificationExecutor;
+	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final DatabaseComponent db;
 	private final KeyManager keyManager;
@@ -40,7 +40,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 
 	@Inject
 	DuplexConnectionFactoryImpl(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			KeyManager keyManager, ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -48,7 +48,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory) {
 		this.dbExecutor = dbExecutor;
-		this.verificationExecutor = verificationExecutor;
+		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
 		this.keyManager = keyManager;
@@ -62,7 +62,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 	public void createIncomingConnection(ConnectionContext ctx,
 			DuplexTransportConnection transport) {
 		final DuplexConnection conn = new IncomingDuplexConnection(dbExecutor,
-				verificationExecutor, messageVerifier, db, connRegistry,
+				cryptoExecutor, messageVerifier, db, connRegistry,
 				connReaderFactory, connWriterFactory, packetReaderFactory,
 				packetWriterFactory, ctx, transport);
 		Runnable write = new Runnable() {
@@ -88,7 +88,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 			return;
 		}
 		final DuplexConnection conn = new OutgoingDuplexConnection(dbExecutor,
-				verificationExecutor, messageVerifier, db, connRegistry,
+				cryptoExecutor, messageVerifier, db, connRegistry,
 				connReaderFactory, connWriterFactory, packetReaderFactory,
 				packetWriterFactory, ctx, transport);
 		Runnable write = new Runnable() {
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/IncomingDuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/IncomingDuplexConnection.java
index 08e955af9b..61fe58c97a 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/IncomingDuplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/IncomingDuplexConnection.java
@@ -20,7 +20,7 @@ import net.sf.briar.api.transport.ConnectionWriterFactory;
 class IncomingDuplexConnection extends DuplexConnection {
 
 	IncomingDuplexConnection(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -28,9 +28,9 @@ class IncomingDuplexConnection extends DuplexConnection {
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory,
 			ConnectionContext ctx, DuplexTransportConnection transport) {
-		super(dbExecutor, verificationExecutor, messageVerifier, db,
-				connRegistry, connReaderFactory, connWriterFactory,
-				packetReaderFactory, packetWriterFactory, ctx, transport);
+		super(dbExecutor, cryptoExecutor, messageVerifier, db, connRegistry,
+				connReaderFactory, connWriterFactory, packetReaderFactory,
+				packetWriterFactory, ctx, transport);
 	}
 
 	@Override
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/OutgoingDuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/OutgoingDuplexConnection.java
index 2a852a45c1..e59adb983f 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/OutgoingDuplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/OutgoingDuplexConnection.java
@@ -20,7 +20,7 @@ import net.sf.briar.api.transport.ConnectionWriterFactory;
 class OutgoingDuplexConnection extends DuplexConnection {
 
 	OutgoingDuplexConnection(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -28,9 +28,9 @@ class OutgoingDuplexConnection extends DuplexConnection {
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory, ConnectionContext ctx,
 			DuplexTransportConnection transport) {
-		super(dbExecutor, verificationExecutor, messageVerifier, db,
-				connRegistry, connReaderFactory, connWriterFactory,
-				packetReaderFactory, packetWriterFactory, ctx, transport);
+		super(dbExecutor, cryptoExecutor, messageVerifier, db, connRegistry,
+				connReaderFactory, connWriterFactory, packetReaderFactory,
+				packetWriterFactory, ctx, transport);
 	}
 
 	@Override
diff --git a/briar-core/src/net/sf/briar/messaging/simplex/IncomingSimplexConnection.java b/briar-core/src/net/sf/briar/messaging/simplex/IncomingSimplexConnection.java
index bac3930cab..cfb18c5039 100644
--- a/briar-core/src/net/sf/briar/messaging/simplex/IncomingSimplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/simplex/IncomingSimplexConnection.java
@@ -39,7 +39,7 @@ class IncomingSimplexConnection {
 	private static final Logger LOG =
 			Logger.getLogger(IncomingSimplexConnection.class.getName());
 
-	private final Executor dbExecutor, verificationExecutor;
+	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final DatabaseComponent db;
 	private final ConnectionRegistry connRegistry;
@@ -51,14 +51,14 @@ class IncomingSimplexConnection {
 	private final TransportId transportId;
 
 	IncomingSimplexConnection(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
 			PacketReaderFactory packetReaderFactory, ConnectionContext ctx,
 			SimplexTransportReader transport) {
 		this.dbExecutor = dbExecutor;
-		this.verificationExecutor = verificationExecutor;
+		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
 		this.connRegistry = connRegistry;
@@ -84,7 +84,7 @@ class IncomingSimplexConnection {
 					dbExecutor.execute(new ReceiveAck(a));
 				} else if(reader.hasMessage()) {
 					UnverifiedMessage m = reader.readMessage();
-					verificationExecutor.execute(new VerifyMessage(m));
+					cryptoExecutor.execute(new VerifyMessage(m));
 				} else if(reader.hasRetentionAck()) {
 					RetentionAck a = reader.readRetentionAck();
 					dbExecutor.execute(new ReceiveRetentionAck(a));
diff --git a/briar-core/src/net/sf/briar/messaging/simplex/SimplexConnectionFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/simplex/SimplexConnectionFactoryImpl.java
index 5764c512b3..b7f5ecdd3b 100644
--- a/briar-core/src/net/sf/briar/messaging/simplex/SimplexConnectionFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/simplex/SimplexConnectionFactoryImpl.java
@@ -29,7 +29,7 @@ class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
 	private static final Logger LOG =
 			Logger.getLogger(SimplexConnectionFactoryImpl.class.getName());
 
-	private final Executor dbExecutor, verificationExecutor;
+	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final DatabaseComponent db;
 	private final KeyManager keyManager;
@@ -41,7 +41,7 @@ class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
 
 	@Inject
 	SimplexConnectionFactoryImpl(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			KeyManager keyManager, ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -49,7 +49,7 @@ class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory) {
 		this.dbExecutor = dbExecutor;
-		this.verificationExecutor = verificationExecutor;
+		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
 		this.keyManager = keyManager;
@@ -60,10 +60,11 @@ class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
 		this.packetWriterFactory = packetWriterFactory;
 	}
 
-	public void createIncomingConnection(ConnectionContext ctx, SimplexTransportReader r) {
+	public void createIncomingConnection(ConnectionContext ctx,
+			SimplexTransportReader r) {
 		final IncomingSimplexConnection conn = new IncomingSimplexConnection(
-				dbExecutor, verificationExecutor, messageVerifier, db,
-				connRegistry, connReaderFactory, packetReaderFactory, ctx, r);
+				dbExecutor, cryptoExecutor, messageVerifier, db, connRegistry,
+				connReaderFactory, packetReaderFactory, ctx, r);
 		Runnable read = new Runnable() {
 			public void run() {
 				conn.read();
-- 
GitLab