From 747a06d1ad634919d8e327d538d596c66e70d0c9 Mon Sep 17 00:00:00 2001 From: akwizgran <michael@briarproject.org> Date: Tue, 12 Mar 2013 15:55:41 +0000 Subject: [PATCH] Android UI for group messages (anonymous text only, no moderation yet). --- briar-android/AndroidManifest.xml | 22 +- .../res/drawable-hdpi/social_new_chat.png | Bin 0 -> 716 bytes .../res/drawable-hdpi/social_reply_all.png | Bin 0 -> 1652 bytes .../res/drawable-mdpi/social_new_chat.png | Bin 0 -> 583 bytes .../res/drawable-mdpi/social_reply_all.png | Bin 0 -> 1457 bytes .../res/drawable-xhdpi/social_new_chat.png | Bin 0 -> 830 bytes .../res/drawable-xhdpi/social_reply_all.png | Bin 0 -> 1929 bytes briar-android/res/values/color.xml | 4 +- briar-android/res/values/strings.xml | 17 +- .../sf/briar/android/HomeScreenActivity.java | 4 +- .../android/contact/ContactComparator.java | 4 +- .../android/contact/ContactListAdapter.java | 8 +- .../android/contact/ContactListItem.java | 4 +- .../briar/android/groups/GroupActivity.java | 226 +++++++++++++ .../sf/briar/android/groups/GroupAdapter.java | 92 ++++++ .../android/groups/GroupListActivity.java | 302 ++++++++++++++++++ .../android/groups/GroupListAdapter.java | 85 +++++ .../briar/android/groups/GroupListItem.java | 57 ++++ .../groups/GroupNameSpinnerAdapter.java | 36 +++ .../groups/ReadGroupMessageActivity.java | 288 +++++++++++++++++ .../groups/WriteGroupMessageActivity.java | 217 +++++++++++++ .../android/invitation/ConnectionView.java | 7 +- .../briar/android/invitation/WifiWidget.java | 7 +- .../messages/ContactNameSpinnerAdapter.java | 1 + .../messages/ConversationActivity.java | 67 ++-- .../android/messages/ConversationAdapter.java | 24 +- .../messages/ConversationListActivity.java | 2 +- .../messages/ConversationListAdapter.java | 8 +- .../messages/ConversationListItem.java | 2 +- ...y.java => ReadPrivateMessageActivity.java} | 30 +- ....java => WritePrivateMessageActivity.java} | 25 +- .../android/widgets/HorizontalBorder.java | 2 +- 32 files changed, 1438 insertions(+), 103 deletions(-) create mode 100644 briar-android/res/drawable-hdpi/social_new_chat.png create mode 100644 briar-android/res/drawable-hdpi/social_reply_all.png create mode 100644 briar-android/res/drawable-mdpi/social_new_chat.png create mode 100644 briar-android/res/drawable-mdpi/social_reply_all.png create mode 100644 briar-android/res/drawable-xhdpi/social_new_chat.png create mode 100644 briar-android/res/drawable-xhdpi/social_reply_all.png create mode 100644 briar-android/src/net/sf/briar/android/groups/GroupActivity.java create mode 100644 briar-android/src/net/sf/briar/android/groups/GroupAdapter.java create mode 100644 briar-android/src/net/sf/briar/android/groups/GroupListActivity.java create mode 100644 briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java create mode 100644 briar-android/src/net/sf/briar/android/groups/GroupListItem.java create mode 100644 briar-android/src/net/sf/briar/android/groups/GroupNameSpinnerAdapter.java create mode 100644 briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java create mode 100644 briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java rename briar-android/src/net/sf/briar/android/messages/{ReadMessageActivity.java => ReadPrivateMessageActivity.java} (90%) rename briar-android/src/net/sf/briar/android/messages/{WriteMessageActivity.java => WritePrivateMessageActivity.java} (89%) diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index 0b0771da6e..e402626aff 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -40,6 +40,22 @@ android:name=".android.contact.ContactListActivity" android:label="@string/contact_list_title" > </activity> + <activity + android:name=".android.groups.GroupActivity" + android:label="@string/groups_title" > + </activity> + <activity + android:name=".android.groups.GroupListActivity" + android:label="@string/groups_title" > + </activity> + <activity + android:name=".android.groups.ReadGroupMessageActivity" + android:label="@string/groups_title" > + </activity> + <activity + android:name=".android.groups.WriteGroupMessageActivity" + android:label="@string/compose_group_title" > + </activity> <activity android:name=".android.invitation.AddContactActivity" android:label="@string/add_contact_title" > @@ -53,12 +69,12 @@ android:label="@string/messages_title" > </activity> <activity - android:name=".android.messages.ReadMessageActivity" + android:name="net.sf.briar.android.messages.ReadPrivateMessageActivity" android:label="@string/messages_title" > </activity> <activity - android:name=".android.messages.WriteMessageActivity" - android:label="@string/compose_title" > + android:name="net.sf.briar.android.messages.WritePrivateMessageActivity" + android:label="@string/compose_message_title" > </activity> </application> </manifest> diff --git a/briar-android/res/drawable-hdpi/social_new_chat.png b/briar-android/res/drawable-hdpi/social_new_chat.png new file mode 100644 index 0000000000000000000000000000000000000000..a6a42eeb6779836698a0e2c213955518d2284373 GIT binary patch literal 716 zcmV;-0yF)IP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*e; z5*8vPkK3&P00K-&L_t(&-tCydiqt?5hQDNDoK;UATtN^8z3<XjVFgbrEa<cN2)=;$ z1fCWjK)k9KK`3_Eg9kn6$%`ID(Vf}JOuW>{5YnB_CNVvjf+jGN(Es06^;cEHV1o@d zSf3UF7-M#TYrrBPY%!1nM<Vjs_EMtH9pE-F*N;Lt*fH=L_~m*rS@%RhQLzI5Oho2B z1oFb_6Z~1=m4eTzK1f16c_1^!{QXlztcemNc;62bhXORsi=}5cM!?7g-gf#f0@D^G zH33o&60s`HL0#}R0x9quc-xS`UEl%m+tXXiy5L<NIM&s~rZvMbstevlU`tmM8=qE^ zeZkuZoB$7j{e}e2s26Qs$eV(9dElHXLldhiipx53S%<&~cmsUwu*JxMo4~EE2)qa0 zbzHpWz+O|iwpHTvA5`B~_9*i}2HXL5I}V|Xx>wXTfhBMixZbe`7D`~$Hi4BFGB&_s zHr$dBK^bEr;Iba@HxZfnjFeGNqh;B?W8e{R0eGUa?hK(kKu4syVxJJ20N;DMR*AI# z$~1h6Ep}c>Ktv|Sn8}dD_2ysO91=W%+ym&e(B20tqc?ynh1|<Ec}JQ9yT+JH1*M9C zpCWRYoOY0CN$>Z7dzQKRSP9%OQKA(11RVC_fi<Q13VZ-&1rNl)wm!d<%-4zj-g^kW z0A7|z<reT*3)z9Iko8SqVW~YQRgL8;<U?)dV%v2~5FT&|#`=Y>&-)wSPKQb$(yN{G yRTEnhD1ilVq-ykI6A8?Kr#=OC$brHBcl!gr;iHsZJEg+_0000<MNUMnLSTZ_1UP>H literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-hdpi/social_reply_all.png b/briar-android/res/drawable-hdpi/social_reply_all.png new file mode 100644 index 0000000000000000000000000000000000000000..377f6286ce9d88bcb8e7e4bc2b331d583f5cd6c3 GIT binary patch literal 1652 zcmaJ?c~BE~6ked#h(|%HBG6_HDq_PPArX=qAP3QClYxMU&=y0oAt@vqvWtYsC8Cse ztQB=Ecm?q&AmB-((AsGo8H5P6QmF@c&_FFhEuBH|m~IfTf0XXb?(g^ByzhJ0H?zf2 z@>R3lecS;6m@Qo`R!}G6d}(h|-}SB|Mbxp7jEpB^a1EKMG+}^1jVEKERIg0I6qr(- zmEMf41OT_iy4ZL!UiJY`h3g@uGX^s2jT9OHR)(96N>wUGg2`Bl&cJ63He6zWIyIlM zo+CqKMiHjft+tr37>hhsWl2?W)r{~kaHW|?5$G{e37Yk327+hiGsbv%)ZOWZ8Q@q5 znaXFpDk@$U1&VMJ267-INQJCG!4+%>Wg}cRmkF{E6oF9|jD!TC2#*uOLr`%1VNlsj zY7I{zmW=13EIva^l13g3XJ%$XnZXclN`X->mkT2-n8gaBB7%r41F19z83_Lg1~Epc zOgbZ}!wsO5QJIWqkbDMJ>8lp>#z|TOF+L`0V6a(fgi#1_wlonall_0FUOyR4kP7T| zz5hu}#AX>WSb-6EhDk*&PUG)PW#oxWn3BXzu{fSKF~uk?PU3_XH-e%V4upa-rAlXT zdd3hk8Bb~;NTorANyU5y#R2JbY91$4A`l=fHb;nvQ8W_evQcg%TPPApIA{o4h)!U| zxGF=B8ORB&`Zbm_CDthhy^+c+#!R|Rm|9}O_25|3Jl)i_u%_f2$Ev5UC3s3KOcet= zSNpG3Pq--kIIok!rHo1GV+Km^CQ8`bB^TEKfU83)7Q~uw^_SXr&JuWE>8)t$AEn(F z3XPsW*b8l*@`5i<+CsT&{4VF+W86PA{Q+!D@OmS42?K(>L*~wHewP+d@=;4I8vMAj zr}F6a(n?EpgQh;@VoAXJ^p5jwbzR?dRSg6-rGJ>vT%$<1Ujr-y-1A*GW+fg?V*Bi1 zK0I0E@XNVUekm%T@JU?tJf`cjd6%r+s5YZZp$#E-d7e5?v_FYDOLvdhpVV^0`^HkM zSHD;9G|CD<@=9_nS(U=$BlX{BZ;p5T4D?)Wg!Ap$S=`oVKxgZ!?LX}*+vnQZdb4~( zABVKua4Z3!p>C_R4<scU!=8qDOhfYWD^<aC;$Yg=`6CIs;=#>2{{@sc?$5T4OjY|l zH%VUC!>}g@A6D(jZW^ux(j0q>>kc{W^;K;tJ{tdXMJl@pFYj$%KnI!&zO^4XyxMb9 zVt4K7>S|GUwEVi4HsQ>t!@_;$dTCjm;IQ=%cAe&Bn@6>5Uuwm*t@pMGeuwT(PZpls zlDp(D^H()3+j6~mgRbVc?iS7;?WtOxcl!dZ!?mF$tio3D`Hb|!Gm?95#Pbw7Z7*wo zdCMK!^@v!nDq7u6Zt)3QXH8&FL1)QahK7{AIDUFG-+gaufvoW?G2rHNBq@@$Xz564 z^?|qxn`%wzW!O4<-<&>=b1mtE?Ha7iVg99bs8}51G2@*rks^X=hpoQt{P{(n9h+6X ztbcdhqQJ}V^?e5%`D8?He=fRaP+$1?v1ek=)vsqJ2mCk+Sl+hs_>O_ZwchlU%L5T@ z*~2cr;wJAC3jtx|gB{Vwu7pMhw{yM-hq*gOnQ22h(eAvncYIrYe1BWp7x?$D!=aC7 zMxTjzT2E)rS-vi8d+u`O+7pY#w@5|PqeN(E!=ag_9vm03eW-u2yn)#v6ftR_#&-M7 za6q!K?xu&Y-;3k6_KHTAJ?7pXi|w{DC&_(w6qFkPIfZ5gp4Pd%oPhw`#v#)0(Uy4U Oe?S^37uO0m7W@NZe|=g2 literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-mdpi/social_new_chat.png b/briar-android/res/drawable-mdpi/social_new_chat.png new file mode 100644 index 0000000000000000000000000000000000000000..e78580b8ec8bd72df2953c1cb27afbe201205552 GIT binary patch literal 583 zcmV-N0=WH&P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*e; z5*Q}EVKO=Z00GBIL_t(o!|j&6N&`U@g}+V4BuYXGK}385%kc>;1q;DKAHl-X*2Wje zTlfYd2!e%J=(P-15=AQ^c1qOj+T4X@GMnGsMbHDw!p_dzGv}UrXW$=y9RYx~b^~Yt zVGc1OpayhA<klTD`rZUqfUg_~%mW?Z+BX;z=m8(VcgDeMz&+3sk*@EDQjY1Z0*^oo z=vr&B)}|^D=gK(WMxm$keCxi%!G|qi9L6`m)dV}-)f|VObsyZ|A~KYK0iFSwK!KgO zk~}Dn3hol<0Tx&uGq?fj>Z2ryfl^Kj?h*)rrb-)(S>Lbv4iew$y2i-hE`b_w4xEgd zl!V9Gd%!jm$e2{14y;;hgBiMBeUgsqdSG3BFl&47O+z4_98e&PN5afut!)A&5qV2% z#Z_%6wTzy5pyC+wz~`-4fzynSV*@-Ts$?!%ngawx<idAQWPB$Gi#`pL=mU|6MBW=w zN<*0z4PZ-UkMvnnZ`?VXZwEOzei1m-oQFzaK{xxJ*mxPZG&#uK1&*Bj?*mJ~u~X`O zlat{-eI9T-q=*z|!oc-hS|^+t?n$cVuEc%79XACj(AGO5xhs%@v$V@eQ6Bic_yJ)e VbZitNe@p-X002ovPDHLkV1kog?|=XR literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-mdpi/social_reply_all.png b/briar-android/res/drawable-mdpi/social_reply_all.png new file mode 100644 index 0000000000000000000000000000000000000000..86334552edec456966f3b5bd4b9e00b73a180bd7 GIT binary patch literal 1457 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(#^lGsVi(!p+gqz{tYX$-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_ab1zKB?uz!N9<z?CIhd zQgQ3ebX#v`M}f9t!&wq6fdV=#TLqdE#I3otKXNnIHU2;7^oR8y<I%7Un~XUHr2|=V zMRbx96B8eXFJ82Og{3#}dU=A_wAAlKoZ{<F9-6FdUTyvU+?jK#eF91%PdM6{<VqPE zxjQbn8_oP;wV!v7^gHwB_z$t2pKI*m16iyNZa8XoNGWStQ`rWlD&B3C2E27ystt4W zo8>+%y_x2~CDOQ}mEq+c>6%|}_sn6qYWem~;IrS4?)WyGw6xsCCM@=R(W~Sa2P>65 zn#By#Z~mMw&s6d}v20$T(|T+5<AEEF>{U2&UdaDJo8VKH8D)OobZ%!pS3T_XWiKO- z)_0kwUgz&LF&88XUpRPf_m-yHY=s*hEPT*4?e~^2d1s}vE2T=)+Ml$jsjj)ockq$N z36b^FFZv(Uj$r0jSoWlBqU*AqVlvZXkBKarBNof?byA^0f>m37g0rCFkEcebzpZg? zIh^}LW{%<X1qWVRL>-)T-m@~^zfokNSaK)#1ePZ}#|_SWHru-R2AA7GiT2oY%Y9XM zY`s5IYNDo)s@@TPy+tdw8M}MVm0>;iRq*;G5$R;X!tH(;LXLjF4%*!??%2@lEGGAy zV{Sue!P7D>pO*q&9mO@il@+{~`DeJlW8E$Ct!V8HHKm0&i@eH9x>;^+JK()_Kl_r7 zYUg>ya+efy&h2$^5Z%bv#<Pt_JoHHa^oh4C*p`dk-sr7eChPZiePpb|1*U(j5)8j4 W&2@O=)cFZih<dvExvX<aXaWFQ?+v5? literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-xhdpi/social_new_chat.png b/briar-android/res/drawable-xhdpi/social_new_chat.png new file mode 100644 index 0000000000000000000000000000000000000000..9d5d9049252fe043a5b55b80cc8b5b8b8af37b3a GIT binary patch literal 830 zcmV-E1Ht@>P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*e; z5*j37SuvCV00O>AL_t(|+U=S>i_}0E$A5dty?7Rah^r_FT8M=wGPNJU+OOgFu(KB| zM9;!+AlqnTF^Gzd9BOmufmf|>%WigTo}6Ltb~Bl5HkoAp%@UY|Jik1zGYdgMK|w)5 zK|vi=FMFYsx&xdArk>li0qkk5-{mcgXRdDoSAfJ*!;RQvfG;*T%mO$ETm&Yb0$7vZ zXTVz*025&BDS${qcUNou()OmfCAlp9@=vwagCaM!0YD_byG4y}1AwD14-Ax2#};U< zTMOXm%L4<~hxiO&TGn`50Gk=w%dXjf9Pix%UMw;`&-HBp^n~(z;8#WQyCZOogZqCi zYrGAB1b7bYSLDVIGo9N~ytjz)*({ppZdGV=*$*sgye)uHMF2*18=n^fM#5kRI9~yP zb@>3MF5_(gM8LfYtHAHF0LmC|TL!Bfj^YBal2yMzS_b!lzq6YsE1vPUfc~;Vrf3;_ z0(`Ey>$?ivckCHTyI=w&Wn*ogP8wk5yhCj<I4SU=RcTTOfV2$W%EVt?L(c_y7qe;r z5pcI5)1RpVz#1J_Y)QVg3LelFLzGh6lB^9d)LI{Q3IK3jnx!%D8hG3_06qS1u0*aO z5X+d_Yq+uNbMludS}tb+`~XhV{KL^xGN2V{r>y{dkm+ygfd?Ee<3aJv=EAF*d;ZV| z1OV<DXOef~ngg1uP2kc=)k#9u0GGvZ1DsJxUCSw%L#_3JF97<|<jgxnG2+eXQQHk^ zj20!)H^8IVqfp&m03hAIn`AtDk!>5|RmT@qwe?l9&mRGM%-<6<(!ccOdMG{3qR{{5 zMGlY{{|-FJS$aD&3w*Sg1&mKg(B@nqDY8EG^fr~jo@;Um8zN1XO;UFGb5!tv0Kl(S z*hY~wOJiwtItSn<@JYJCgGx(BLz^Y1ZZ;&r03ZMe3hJ`{0Ve6U!bNUCRR91007*qo IM6N<$f?o?}0RR91 literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-xhdpi/social_reply_all.png b/briar-android/res/drawable-xhdpi/social_reply_all.png new file mode 100644 index 0000000000000000000000000000000000000000..f10a492c0229009a9b358cbd8ccae910ba21cdab GIT binary patch literal 1929 zcmaJ?eN<Cr7#{)&Bp?l)LTuN8f&{zwj)B|Ffn(!i!Y09v;s@$wyRbF4JGN^KMB~gM znK}G`wD1Et5NHY_VCE-`0Hq==^bm9`1ObaAHJSujcO>Q?+CAsq_kEu8{C>~#ywC4F zcW06`&fU$&jY6Tgi{nMf<hsoMy1YVuPpGnPlgoS}CY4CRGl*<dk5L2)JRJkX8Z;A2 z#!y90)-`M?h2j{XlBE)<l0-y~Ygni~hGo>~NHm4AG|H$$<!X!o(y>gHmd_l$aD@q| z6ny5Ya0w*Q2{ENAK39*W<Vt1oT(z91U`9m(ON|Igpuq?fFlshw4TzD?oZ>~uz1<Bm zfvFHe&1e2wRH`Hi5aN0a2xmc|awq}@A~-CV1MxV#5P%KA5D2qDXh|pxA>m692nL=$ zOfs8Zk%1(OVxQ(BEk0985IO_|v$L~V*<mbPp9#V|9uI`rAe$XZMuZx2v;=Al)f)V# z8AO;tu2<;@6|M#BjA%N(ncy?YO8;#^qkBfHH9S2g^1whNssmvbWN&FYP$K#NP>tqU zw1G&*{;T(e#0FW84g-@h1HM@=CkL0|Z%?H|gnA4maJ>x2H%*^nk`gCygA&&P!jy0p z3`kJ9N^AE_AtVw+tThm*R*s29d?v}kQmGV3I5$=xfY?xYG$exI7?{U_c`=-5p&&LK zUc!lnr?DbjzFC85iD|6jKP>!GtX&Km9hq5#=~eGwida3a0j8QpR4=S$Nh~icBKoC# zPqB&@)-r_^z7z|R#enwFJ~!%V7pWin_L*==<C*j^Eva`sDQs>2L_K+5Cd49v%-A!s zcCDH!@IE3*uJr~1liT}c05fu4S6r<uWlq=1griQ3N#`e2#+cZ<@wn%mf^ymEKNWMF zcX`*D+|HWTw5rwb@C&yI3oF0>YE#Dkw6K|xXCpGgh9)laGBYB^Ou+0@uk&u)>2uLJ zac8{lV4=k2`!{)AKl9d}DDPb{y|(q{Usi0lHBh(Bo~)oU=yd514im1v?ybW>$Ry*F zCc0I86ZN?v=}xFt9M0crUT>;W-CcSWd2e8Q2(_uh2JF1@@#sC%Sd}kq`*f}#p~Lo5 zCc~q;GLKe#v@_RNR#K}F<dk~2yaCQC!Ph*Da7ik$)mL0}pTRzrcgbluXtdz%#1qA} zRW2QojLJNZ*57DPPW>_76~JhBKspQ$HD+J4mvtv6n6%G#JX&`pCaC3)N=`8TGlhB= zXO7=*m~-)*)39vf^WsrXVq_D4fN-r1h+1sx4}C<lRy6N>bFRZ~S!9#Z5|?KQso9rb zYtA+gnQw3v-pOTo+-+*hmW60IN=0etA?RwRpLuj_uHbOvR=Q)~z(iTzae?7>VNc>M z#-!yMH-?+gz24K|{LLd9E{KL#72_)#5A=>Mwyv?xAAo*TxP|z8AG^3gUeKPk7cvic z_P3O#{p!fI3gNWn+@iYGKYD=8o6meSG43N82{>fYn|`L$oXCDKNdM}=@{-b5W$MWB zOR=em+z(%)Rre40)NO|Z=W2SL?=RT79;;bZYFK!&({Cfa=V<(V*O5*~<$nM38r|$` zalGdCuM-X$wmxnWE?)cB_`w<LDmE1SBIrVvwYxsdJ{*5}7W)p;Os)5n4gMLzNI!Ul zz5=~;y5zTR#lr6RK;yh&=XEU!eRrE)vHHM|DA@A-?@_uX#&U}|6TBW2cy-9-o8neU zV`rm9XFgpS`{l~*>RY+iVC$CeBCbl$nFNFWA;E{Qy9fTz7P%lZM_T07dCc+NpjTNE z-tuw#<7ELgRDHGe>;=a+ko*cua75Aj&gp|WcY}H^*RdB#H}2BzSnlY`6}0&^e3s+& zca?i>(SkzHtfDUx5AC7VugI;v)3bLEm42*a%L3ZKp2yrazV~fS8-LENdmepZW_FGE z&}&(JlOKHU7xd;$oLglCLK(LBkQ!=Nb-=LG%CS(?xy?Frk*%)bZ)JbtX?md9e{o>q z##x?wAN!UTmao=X6Rc`$N^fELZhiEn2h7gWP1~Y?5p&aZSIKybbMMyGX@SH0*0v$X zfVba0&Kz@!4_vpS9I+jc&ij_vQtww_zW=3ENy`}eeP2WL?yr*;xy)9^ENb3RJwnU< t#`$Ey&=ci}))udyp1*r#SpQLqBgJwut<u%v@U8vlD2|beYN9uq{sE<M_IdyS literal 0 HcmV?d00001 diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml index a55aadfa90..866b33e586 100644 --- a/briar-android/res/values/color.xml +++ b/briar-android/res/values/color.xml @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <color name="HorizontalBorder">#CCCCCC</color> + <color name="horizontal_border">#CCCCCC</color> + <color name="anonymous_author">#999999</color> + <color name="pseudonymous_author">#000000</color> </resources> \ No newline at end of file diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index 92486abf3b..14258e0471 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -11,14 +11,13 @@ <string name="quit_button">Quit</string> <string name="contact_list_title">Contacts</string> <string name="contact_connected">Connected</string> - <string name="contact_last_connected">Last connected <br /> %1$s</string> - <string name="search_button">Search</string> + <string name="format_contact_last_connected">Last connected <br /> %1$s</string> <string name="add_contact_title">Add a Contact</string> <string name="same_network">Briar can add contacts via Wi-Fi or Bluetooth. To use Wi-Fi you must both be connected to the same network.</string> <string name="wifi_not_available">Wi-Fi is not available on this device</string> <string name="wifi_disabled">Wi-Fi is OFF</string> <string name="wifi_disconnected">Wi-Fi is DISCONNECTED</string> - <string name="wifi_connected">Wi-Fi is CONNECTED to %1$s</string> + <string name="format_wifi_connected">Wi-Fi is CONNECTED to %1$s</string> <string name="bluetooth_not_available">Bluetooth is not available on this device</string> <string name="bluetooth_disabled">Bluetooth is OFF</string> <string name="bluetooth_not_discoverable">Bluetooth is NOT DISCOVERABLE</string> @@ -26,7 +25,7 @@ <string name="continue_button">Continue</string> <string name="your_invitation_code">Your invitation code is</string> <string name="enter_invitation_code">Please enter your contact\'s invitation code:</string> - <string name="connecting_wifi">Connecting via %1$s\u2026</string> + <string name="format_connecting_wifi">Connecting via %1$s\u2026</string> <string name="connecting_bluetooth">Connecting via Bluetooth\u2026</string> <string name="connection_failed">Connection failed</string> <string name="check_same_network">Please check that you are both using the same network.</string> @@ -41,7 +40,11 @@ <string name="enter_nickname">Please enter a nickname for this contact:</string> <string name="done_button">Done</string> <string name="messages_title">Messages</string> - <string name="message_from">From: %1$s</string> - <string name="compose_title">New Message</string> - <string name="message_to">To:</string> + <string name="format_from">From: %1$s</string> + <string name="format_to">To: %1$s</string> + <string name="compose_message_title">New Message</string> + <string name="to">To:</string> + <string name="groups_title">Groups</string> + <string name="anonymous">(Anonymous)</string> + <string name="compose_group_title">New Post</string> </resources> diff --git a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java index 090ceca84d..02cea07efb 100644 --- a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java +++ b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java @@ -12,6 +12,7 @@ import net.sf.briar.R; import net.sf.briar.android.BriarService.BriarBinder; import net.sf.briar.android.BriarService.BriarServiceConnection; import net.sf.briar.android.contact.ContactListActivity; +import net.sf.briar.android.groups.GroupListActivity; import net.sf.briar.android.messages.ConversationListActivity; import net.sf.briar.android.widgets.CommonLayoutParams; import android.content.Intent; @@ -88,7 +89,8 @@ public class HomeScreenActivity extends BriarActivity { groupsButton.setText(R.string.groups_button); groupsButton.setOnClickListener(new OnClickListener() { public void onClick(View view) { - // FIXME: Hook this button up to an activity + startActivity(new Intent(HomeScreenActivity.this, + GroupListActivity.class)); } }); buttons.add(groupsButton); diff --git a/briar-android/src/net/sf/briar/android/contact/ContactComparator.java b/briar-android/src/net/sf/briar/android/contact/ContactComparator.java index 6495bf6310..8e9207c6fc 100644 --- a/briar-android/src/net/sf/briar/android/contact/ContactComparator.java +++ b/briar-android/src/net/sf/briar/android/contact/ContactComparator.java @@ -7,7 +7,7 @@ class ContactComparator implements Comparator<ContactListItem> { static final ContactComparator INSTANCE = new ContactComparator(); public int compare(ContactListItem a, ContactListItem b) { - return String.CASE_INSENSITIVE_ORDER.compare(a.contact.getName(), - b.contact.getName()); + return String.CASE_INSENSITIVE_ORDER.compare(a.getContactName(), + b.getContactName()); } } \ No newline at end of file diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java b/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java index 39cb91c6c1..bb10e399cf 100644 --- a/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java +++ b/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import net.sf.briar.R; import net.sf.briar.android.widgets.CommonLayoutParams; import android.content.Context; -import android.content.res.Resources; import android.text.Html; import android.text.format.DateUtils; import android.view.View; @@ -46,8 +45,9 @@ implements OnItemClickListener { // Give me all the unused width name.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1); name.setTextSize(18); + name.setMaxLines(1); name.setPadding(0, 10, 10, 10); - name.setText(item.getName()); + name.setText(item.getContactName()); layout.addView(name); TextView connected = new TextView(ctx); @@ -56,8 +56,8 @@ implements OnItemClickListener { if(item.isConnected()) { connected.setText(R.string.contact_connected); } else { - Resources res = ctx.getResources(); - String format = res.getString(R.string.contact_last_connected); + String format = ctx.getResources().getString( + R.string.format_contact_last_connected); long then = item.getLastConnected(); CharSequence ago = DateUtils.getRelativeTimeSpanString(then); connected.setText(Html.fromHtml(String.format(format, ago))); diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListItem.java b/briar-android/src/net/sf/briar/android/contact/ContactListItem.java index cba805e863..0b030f2f96 100644 --- a/briar-android/src/net/sf/briar/android/contact/ContactListItem.java +++ b/briar-android/src/net/sf/briar/android/contact/ContactListItem.java @@ -6,7 +6,7 @@ import net.sf.briar.api.ContactId; // This class is not thread-safe class ContactListItem { - final Contact contact; + private final Contact contact; private boolean connected; ContactListItem(Contact contact, boolean connected) { @@ -18,7 +18,7 @@ class ContactListItem { return contact.getId(); } - String getName() { + String getContactName() { return contact.getName(); } diff --git a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java new file mode 100644 index 0000000000..8ed287306a --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java @@ -0,0 +1,226 @@ +package net.sf.briar.android.groups; + +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 java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import net.sf.briar.R; +import net.sf.briar.android.AscendingHeaderComparator; +import net.sf.briar.android.BriarActivity; +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.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.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.SubscriptionRemovedEvent; +import net.sf.briar.api.messaging.Author; +import net.sf.briar.api.messaging.GroupId; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.ListView; + +import com.google.inject.Inject; + +public class GroupActivity extends BriarActivity implements DatabaseListener, +OnClickListener, OnItemClickListener { + + private static final Logger LOG = + Logger.getLogger(GroupActivity.class.getName()); + + private final BriarServiceConnection serviceConnection = + new BriarServiceConnection(); + + @Inject private DatabaseComponent db; + @Inject @DatabaseExecutor private Executor dbExecutor; + + private GroupId groupId = null; + private String groupName = null; + private GroupAdapter adapter = null; + private ListView list = null; + + @Override + public void onCreate(Bundle state) { + super.onCreate(null); + + Intent i = getIntent(); + byte[] id = i.getByteArrayExtra("net.sf.briar.GROUP_ID"); + if(id == null) throw new IllegalStateException(); + groupId = new GroupId(id); + groupName = i.getStringExtra("net.sf.briar.GROUP_NAME"); + if(groupName == null) throw new IllegalStateException(); + setTitle(groupName); + + LinearLayout layout = new LinearLayout(this); + layout.setLayoutParams(CommonLayoutParams.MATCH_MATCH); + layout.setOrientation(VERTICAL); + layout.setGravity(CENTER_HORIZONTAL); + + adapter = new GroupAdapter(this); + list = new ListView(this); + // Give me all the width and all the unused height + list.setLayoutParams(CommonLayoutParams.MATCH_WRAP_1); + list.setAdapter(adapter); + list.setOnItemClickListener(this); + layout.addView(list); + + layout.addView(new HorizontalBorder(this)); + + ImageButton composeButton = new ImageButton(this); + composeButton.setBackgroundResource(0); + composeButton.setImageResource(R.drawable.content_new_email); + composeButton.setOnClickListener(this); + layout.addView(composeButton); + + setContentView(layout); + + // Listen for messages and groups being added or removed + db.addListener(this); + // Bind to the service so we can wait for the DB to be opened + bindService(new Intent(BriarService.class.getName()), + serviceConnection, 0); + } + + @Override + public void onResume() { + super.onResume(); + reloadMessageHeaders(); + } + + private void reloadMessageHeaders() { + final DatabaseComponent db = this.db; + final GroupId groupId = this.groupId; + dbExecutor.execute(new Runnable() { + public void run() { + try { + // Wait for the service to be bound and started + serviceConnection.waitForStartup(); + // Load the message headers from the database + Collection<GroupMessageHeader> headers = + db.getMessageHeaders(groupId); + if(LOG.isLoggable(INFO)) + LOG.info("Loaded " + headers.size() + " headers"); + // Update the conversation + updateConversation(headers); + } 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 updateConversation( + final Collection<GroupMessageHeader> headers) { + runOnUiThread(new Runnable() { + public void run() { + List<GroupMessageHeader> sort = + new ArrayList<GroupMessageHeader>(headers); + Collections.sort(sort, AscendingHeaderComparator.INSTANCE); + int firstUnread = -1; + adapter.clear(); + for(GroupMessageHeader h : sort) { + if(firstUnread == -1 && !h.isRead()) + firstUnread = adapter.getCount(); + adapter.add(h); + } + if(firstUnread == -1) list.setSelection(adapter.getCount() - 1); + else list.setSelection(firstUnread); + } + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + db.removeListener(this); + unbindService(serviceConnection); + } + + public void eventOccurred(DatabaseEvent e) { + if(e instanceof MessageAddedEvent) { + if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading"); + reloadMessageHeaders(); + } 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 SubscriptionRemovedEvent) { + if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading"); + reloadMessageHeaders(); + } + } + + public void onClick(View view) { + Intent i = new Intent(this, WriteGroupMessageActivity.class); + i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes()); + i.putExtra("net.sf.briar.GROUP_NAME", groupName); + startActivity(i); + } + + public void onItemClick(AdapterView<?> parent, View view, int position, + long id) { + showMessage(position); + } + + private void showMessage(int position) { + GroupMessageHeader 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); + i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes()); + Author author = item.getAuthor(); + if(author == null) { + i.putExtra("net.sf.briar.ANONYMOUS", true); + } else { + 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.CONTENT_TYPE", item.getContentType()); + i.putExtra("net.sf.briar.TIMESTAMP", item.getTimestamp()); + i.putExtra("net.sf.briar.FIRST", position == 0); + i.putExtra("net.sf.briar.LAST", position == adapter.getCount() - 1); + startActivityForResult(i, position); + } + + @Override + public void onActivityResult(int request, int result, Intent data) { + if(result == ReadGroupMessageActivity.RESULT_PREV) { + int position = request - 1; + if(position >= 0 && position < adapter.getCount()) + showMessage(position); + } else if(result == ReadGroupMessageActivity.RESULT_NEXT) { + int position = request + 1; + if(position >= 0 && position < adapter.getCount()) + showMessage(position); + } + } +} diff --git a/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java new file mode 100644 index 0000000000..17915c8d45 --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java @@ -0,0 +1,92 @@ +package net.sf.briar.android.groups; + +import static android.graphics.Typeface.BOLD; +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 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.messaging.Author; +import android.content.Context; +import android.content.res.Resources; +import android.text.format.DateUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +class GroupAdapter extends ArrayAdapter<GroupMessageHeader> { + + GroupAdapter(Context ctx) { + super(ctx, android.R.layout.simple_expandable_list_item_1, + new ArrayList<GroupMessageHeader>()); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + GroupMessageHeader item = getItem(position); + Context ctx = getContext(); + // FIXME: Use a RelativeLayout + LinearLayout layout = new LinearLayout(ctx); + layout.setOrientation(HORIZONTAL); + layout.setGravity(CENTER_VERTICAL); + + LinearLayout innerLayout = new LinearLayout(ctx); + // Give me all the unused width + innerLayout.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1); + innerLayout.setOrientation(VERTICAL); + + Author author = item.getAuthor(); + + TextView name = new TextView(ctx); + name.setTextSize(18); + name.setMaxLines(1); + name.setPadding(10, 10, 10, 10); + Resources res = ctx.getResources(); + if(author == null) { + name.setTextColor(res.getColor(R.color.anonymous_author)); + name.setText(R.string.anonymous); + } else { + name.setTextColor(res.getColor(R.color.pseudonymous_author)); + name.setText(author.getName()); + } + innerLayout.addView(name); + + if(item.getContentType().equals("text/plain")) { + TextView subject = new TextView(ctx); + subject.setTextSize(14); + subject.setMaxLines(2); + subject.setPadding(10, 0, 10, 10); + if(!item.isRead()) subject.setTypeface(null, BOLD); + 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); + } + layout.addView(innerLayout); + + TextView date = new TextView(ctx); + date.setTextSize(14); + date.setPadding(0, 10, 10, 10); + long then = item.getTimestamp(), now = System.currentTimeMillis(); + date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT)); + layout.addView(date); + + return layout; + } +} diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java new file mode 100644 index 0000000000..5c0c3beecf --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java @@ -0,0 +1,302 @@ +package net.sf.briar.android.groups; + +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 java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import net.sf.briar.R; +import net.sf.briar.android.BriarActivity; +import net.sf.briar.android.BriarService; +import net.sf.briar.android.BriarService.BriarServiceConnection; +import net.sf.briar.android.DescendingHeaderComparator; +import net.sf.briar.android.widgets.CommonLayoutParams; +import net.sf.briar.android.widgets.HorizontalBorder; +import net.sf.briar.api.ContactId; +import net.sf.briar.api.crypto.CryptoComponent; +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.SubscriptionRemovedEvent; +import net.sf.briar.api.messaging.Author; +import net.sf.briar.api.messaging.AuthorFactory; +import net.sf.briar.api.messaging.Group; +import net.sf.briar.api.messaging.GroupFactory; +import net.sf.briar.api.messaging.Message; +import net.sf.briar.api.messaging.MessageFactory; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.ListView; + +import com.google.inject.Inject; + +public class GroupListActivity extends BriarActivity +implements OnClickListener, DatabaseListener { + + private static final Logger LOG = + Logger.getLogger(GroupListActivity.class.getName()); + + private final BriarServiceConnection serviceConnection = + new BriarServiceConnection(); + + @Inject private CryptoComponent crypto; + @Inject private DatabaseComponent db; + @Inject @DatabaseExecutor private Executor dbExecutor; + @Inject private AuthorFactory authorFactory; + @Inject private GroupFactory groupFactory; + @Inject private MessageFactory messageFactory; + + private GroupListAdapter adapter = null; + + @Override + public void onCreate(Bundle state) { + super.onCreate(null); + LinearLayout layout = new LinearLayout(this); + layout.setLayoutParams(CommonLayoutParams.MATCH_MATCH); + layout.setOrientation(VERTICAL); + layout.setGravity(CENTER_HORIZONTAL); + + adapter = new GroupListAdapter(this); + ListView list = new ListView(this); + // Give me all the width and all the unused height + list.setLayoutParams(CommonLayoutParams.MATCH_WRAP_1); + list.setAdapter(adapter); + list.setOnItemClickListener(adapter); + layout.addView(list); + + layout.addView(new HorizontalBorder(this)); + + ImageButton newGroupButton = new ImageButton(this); + newGroupButton.setBackgroundResource(0); + newGroupButton.setImageResource(R.drawable.social_new_chat); + newGroupButton.setOnClickListener(this); + layout.addView(newGroupButton); + + setContentView(layout); + + // Listen for messages and groups being added or removed + db.addListener(this); + // Bind to the service so we can wait for the DB to be opened + bindService(new Intent(BriarService.class.getName()), + serviceConnection, 0); + + // Add some fake messages to the database in a background thread + insertFakeMessages(); + } + + // FIXME: Remove this + private void insertFakeMessages() { + final DatabaseComponent db = this.db; + final GroupFactory groupFactory = this.groupFactory; + final MessageFactory messageFactory = this.messageFactory; + dbExecutor.execute(new Runnable() { + public void run() { + try { + // Wait for the service to be bound and started + serviceConnection.waitForStartup(); + // If there are no groups in the DB, create some fake ones + Collection<Group> groups = db.getSubscriptions(); + if(!groups.isEmpty()) return; + if(LOG.isLoggable(INFO)) + LOG.info("Inserting fake groups and messages"); + // We'll also need a contact to receive messages from + ContactId contactId = db.addContact("Dave"); + // Finally, we'll need some authors for the messages + KeyPair keyPair = crypto.generateSignatureKeyPair(); + byte[] publicKey = keyPair.getPublic().getEncoded(); + PrivateKey privateKey = keyPair.getPrivate(); + Author author = authorFactory.createAuthor("Batman", + publicKey); + Author author1 = authorFactory.createAuthor("Duckman", + publicKey); + // Insert some fake groups and make them visible + Group group = groupFactory.createGroup("DisneyLeaks"); + db.subscribe(group); + db.setVisibility(group.getId(), Arrays.asList(contactId)); + Group group1 = groupFactory.createGroup("Godwin's Lore"); + db.subscribe(group1); + db.setVisibility(group1.getId(), Arrays.asList(contactId)); + // Insert some text messages to the groups + for(int i = 0; i < 20; i++) { + String body; + if(i % 3 == 0) { + body = "Message " + i + " is short."; + } else { + body = "Message " + i + " is long enough to wrap" + + " onto a second line on some screens."; + } + Group g = i % 2 == 0 ? group : group1; + Message m; + if(i % 5 == 0) { + m = messageFactory.createAnonymousMessage(null, g, + "text/plain", body.getBytes("UTF-8")); + } else if(i % 5 == 2) { + m = messageFactory.createPseudonymousMessage(null, + g, author, privateKey, "text/plain", + body.getBytes("UTF-8")); + } else { + m = messageFactory.createPseudonymousMessage(null, + g, author1, privateKey, "text/plain", + body.getBytes("UTF-8")); + } + if(Math.random() < 0.5) db.addLocalGroupMessage(m); + else db.receiveMessage(contactId, m); + db.setReadFlag(m.getId(), i % 4 == 0); + } + // Insert a non-text message + Message m = messageFactory.createAnonymousMessage(null, + group, "image/jpeg", new byte[1000]); + db.receiveMessage(contactId, m); + // Insert a long text message + StringBuilder s = new StringBuilder(); + for(int i = 0; i < 100; i++) + s.append("This is a very tedious message. "); + String body = s.toString(); + m = messageFactory.createAnonymousMessage(m.getId(), + group1, "text/plain", body.getBytes("UTF-8")); + db.addLocalGroupMessage(m); + } catch(DbException e) { + if(LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } catch(GeneralSecurityException 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(); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } + } + }); + } + + @Override + public void onResume() { + super.onResume(); + reloadGroupList(); + } + + private void reloadGroupList() { + final DatabaseComponent db = this.db; + dbExecutor.execute(new Runnable() { + public void run() { + try { + // Wait for the service to be bound and started + serviceConnection.waitForStartup(); + // Load the groups and message headers from the DB + if(LOG.isLoggable(INFO)) LOG.info("Loading groups"); + Collection<Group> groups = db.getSubscriptions(); + if(LOG.isLoggable(INFO)) + LOG.info("Loaded " + groups.size() + " groups"); + List<GroupListItem> items = new ArrayList<GroupListItem>(); + for(Group g : groups) { + // Filter out restricted groups + if(g.getPublicKey() != null) continue; + Collection<GroupMessageHeader> headers; + try { + headers = db.getMessageHeaders(g.getId()); + } catch(NoSuchSubscriptionException e) { + // We'll reload the list when we get the event + continue; + } + if(LOG.isLoggable(INFO)) + LOG.info("Loaded " + headers.size() + " headers"); + if(!headers.isEmpty()) + items.add(createItem(g, headers)); + } + // Update the group list + updateGroupList(Collections.unmodifiableList(items)); + } 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 GroupListItem createItem(Group group, + Collection<GroupMessageHeader> headers) { + List<GroupMessageHeader> sort = + new ArrayList<GroupMessageHeader>(headers); + Collections.sort(sort, DescendingHeaderComparator.INSTANCE); + return new GroupListItem(group, sort); + } + + private void updateGroupList(final Collection<GroupListItem> items) { + runOnUiThread(new Runnable() { + public void run() { + adapter.clear(); + for(GroupListItem i : items) adapter.add(i); + adapter.sort(GroupComparator.INSTANCE); + } + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + db.removeListener(this); + unbindService(serviceConnection); + } + + public void onClick(View view) { + startActivity(new Intent(this, WriteGroupMessageActivity.class)); + } + + public void eventOccurred(DatabaseEvent e) { + if(e instanceof MessageAddedEvent) { + if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading"); + reloadGroupList(); + } else if(e instanceof MessageExpiredEvent) { + if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading"); + reloadGroupList(); + } else if(e instanceof SubscriptionAddedEvent) { + if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading"); + reloadGroupList(); + } else if(e instanceof SubscriptionRemovedEvent) { + if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading"); + reloadGroupList(); + } + } + + private static class GroupComparator implements Comparator<GroupListItem> { + + private static final GroupComparator INSTANCE = new GroupComparator(); + + public int compare(GroupListItem a, GroupListItem b) { + return String.CASE_INSENSITIVE_ORDER.compare(a.getGroupName(), + b.getGroupName()); + } + } +} \ No newline at end of file diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java new file mode 100644 index 0000000000..102a93a06c --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java @@ -0,0 +1,85 @@ +package net.sf.briar.android.groups; + +import static android.graphics.Typeface.BOLD; +import static android.view.Gravity.CENTER_VERTICAL; +import static android.view.Gravity.LEFT; +import static android.widget.LinearLayout.HORIZONTAL; +import static android.widget.LinearLayout.VERTICAL; +import static java.text.DateFormat.SHORT; + +import java.util.ArrayList; + +import net.sf.briar.android.widgets.CommonLayoutParams; +import net.sf.briar.util.StringUtils; +import android.content.Context; +import android.content.Intent; +import android.text.format.DateUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +class GroupListAdapter extends ArrayAdapter<GroupListItem> +implements OnItemClickListener { + + GroupListAdapter(Context ctx) { + super(ctx, android.R.layout.simple_expandable_list_item_1, + new ArrayList<GroupListItem>()); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + GroupListItem item = getItem(position); + Context ctx = getContext(); + LinearLayout layout = new LinearLayout(ctx); + layout.setOrientation(HORIZONTAL); + layout.setGravity(CENTER_VERTICAL); + + LinearLayout innerLayout = new LinearLayout(ctx); + // Give me all the unused width + innerLayout.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1); + innerLayout.setOrientation(VERTICAL); + innerLayout.setGravity(LEFT); + + TextView name = new TextView(ctx); + name.setTextSize(18); + name.setMaxLines(1); + name.setPadding(10, 10, 10, 10); + int unread = item.getUnreadCount(); + if(unread > 0) name.setText(item.getGroupName() + " (" + unread + ")"); + else name.setText(item.getGroupName()); + innerLayout.addView(name); + + if(!StringUtils.isNullOrEmpty(item.getSubject())) { + TextView subject = new TextView(ctx); + subject.setTextSize(14); + subject.setMaxLines(2); + subject.setPadding(10, 0, 10, 10); + if(unread > 0) subject.setTypeface(null, BOLD); + subject.setText(item.getSubject()); + innerLayout.addView(subject); + } + layout.addView(innerLayout); + + TextView date = new TextView(ctx); + date.setTextSize(14); + date.setPadding(0, 10, 10, 10); + long then = item.getTimestamp(), now = System.currentTimeMillis(); + date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT)); + layout.addView(date); + + return layout; + } + + public void onItemClick(AdapterView<?> parent, View view, int position, + long id) { + GroupListItem item = getItem(position); + Intent i = new Intent(getContext(), GroupActivity.class); + i.putExtra("net.sf.briar.GROUP_ID", item.getGroupId().getBytes()); + i.putExtra("net.sf.briar.GROUP_NAME", item.getGroupName()); + getContext().startActivity(i); + } +} diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListItem.java b/briar-android/src/net/sf/briar/android/groups/GroupListItem.java new file mode 100644 index 0000000000..38a4513c21 --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/GroupListItem.java @@ -0,0 +1,57 @@ +package net.sf.briar.android.groups; + +import java.util.Collections; +import java.util.List; + +import net.sf.briar.android.DescendingHeaderComparator; +import net.sf.briar.api.db.GroupMessageHeader; +import net.sf.briar.api.messaging.Author; +import net.sf.briar.api.messaging.Group; +import net.sf.briar.api.messaging.GroupId; + +class GroupListItem { + + private final Group group; + private final String author, subject; + private final long timestamp; + private final int unread; + + GroupListItem(Group group, List<GroupMessageHeader> headers) { + if(headers.isEmpty()) throw new IllegalArgumentException(); + this.group = group; + Collections.sort(headers, DescendingHeaderComparator.INSTANCE); + GroupMessageHeader newest = headers.get(0); + Author a = newest.getAuthor(); + if(a == null) author = null; + else author = a.getName(); + subject = newest.getSubject(); + timestamp = newest.getTimestamp(); + int unread = 0; + for(GroupMessageHeader h : headers) if(!h.isRead()) unread++; + this.unread = unread; + } + + GroupId getGroupId() { + return group.getId(); + } + + String getGroupName() { + return group.getName(); + } + + String getAuthorName() { + return author; + } + + String getSubject() { + return subject; + } + + long getTimestamp() { + return timestamp; + } + + int getUnreadCount() { + return unread; + } +} diff --git a/briar-android/src/net/sf/briar/android/groups/GroupNameSpinnerAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupNameSpinnerAdapter.java new file mode 100644 index 0000000000..2c16ebfaf8 --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/GroupNameSpinnerAdapter.java @@ -0,0 +1,36 @@ +package net.sf.briar.android.groups; + +import java.util.ArrayList; + +import net.sf.briar.api.messaging.Group; +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +class GroupNameSpinnerAdapter extends ArrayAdapter<Group> +implements SpinnerAdapter { + + GroupNameSpinnerAdapter(Context context) { + super(context, android.R.layout.simple_spinner_item, + new ArrayList<Group>()); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView name = new TextView(getContext()); + name.setTextSize(18); + name.setMaxLines(1); + name.setPadding(10, 10, 10, 10); + name.setText(getItem(position).getName()); + return name; + } + + @Override + public View getDropDownView(int position, View convertView, + ViewGroup parent) { + return getView(position, convertView, parent); + } +} diff --git a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java new file mode 100644 index 0000000000..d5750019ba --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java @@ -0,0 +1,288 @@ +package net.sf.briar.android.groups; + +import static android.view.Gravity.CENTER; +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 java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; + +import java.io.UnsupportedEncodingException; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import net.sf.briar.R; +import net.sf.briar.android.BriarActivity; +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.android.widgets.HorizontalSpace; +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.messaging.AuthorId; +import net.sf.briar.api.messaging.GroupId; +import net.sf.briar.api.messaging.MessageId; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.text.format.DateUtils; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.google.inject.Inject; + +public class ReadGroupMessageActivity extends BriarActivity +implements OnClickListener { + + static final int RESULT_REPLY = RESULT_FIRST_USER; + static final int RESULT_PREV = RESULT_FIRST_USER + 1; + static final int RESULT_NEXT = RESULT_FIRST_USER + 2; + + private static final Logger LOG = + Logger.getLogger(ReadGroupMessageActivity.class.getName()); + + private final BriarServiceConnection serviceConnection = + new BriarServiceConnection(); + + @Inject private BundleEncrypter bundleEncrypter; + @Inject private DatabaseComponent db; + @Inject @DatabaseExecutor private Executor dbExecutor; + + private GroupId groupId = null; + private MessageId messageId = null; + private AuthorId authorId = null; + private String authorName = null; + private boolean read; + private ImageButton readButton = null, prevButton = null, nextButton = null; + private ImageButton replyButton = null; + private TextView content = null; + + @Override + public void onCreate(Bundle state) { + super.onCreate(null); + + Intent i = getIntent(); + byte[] id = i.getByteArrayExtra("net.sf.briar.GROUP_ID"); + if(id == null) throw new IllegalStateException(); + groupId = new GroupId(id); + String groupName = i.getStringExtra("net.sf.briar.GROUP_NAME"); + if(groupName == null) throw new IllegalStateException(); + setTitle(groupName); + id = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID"); + if(id == null) throw new IllegalStateException(); + messageId = new MessageId(id); + boolean anonymous = i.getBooleanExtra("net.sf.briar.ANONYMOUS", false); + if(!anonymous) { + id = i.getByteArrayExtra("net.sf.briar.AUTHOR_ID"); + if(id == null) throw new IllegalStateException(); + authorId = new AuthorId(id); + authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME"); + if(authorName == null) throw new IllegalStateException(); + } + String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE"); + if(contentType == null) throw new IllegalStateException(); + long timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1); + if(timestamp == -1) throw new IllegalStateException(); + boolean first = i.getBooleanExtra("net.sf.briar.FIRST", false); + boolean last = i.getBooleanExtra("net.sf.briar.LAST", false); + + if(state != null && bundleEncrypter.decrypt(state)) { + read = state.getBoolean("net.sf.briar.READ"); + } else { + read = false; + setReadInDatabase(true); + } + + LinearLayout layout = new LinearLayout(this); + layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP); + layout.setOrientation(VERTICAL); + + ScrollView scrollView = new ScrollView(this); + // Give me all the width and all the unused height + scrollView.setLayoutParams(CommonLayoutParams.MATCH_WRAP_1); + + LinearLayout message = new LinearLayout(this); + message.setOrientation(VERTICAL); + + LinearLayout header = new LinearLayout(this); + header.setLayoutParams(CommonLayoutParams.MATCH_WRAP); + header.setOrientation(HORIZONTAL); + header.setGravity(CENTER_VERTICAL); + + TextView author = new TextView(this); + // Give me all the unused width + author.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1); + author.setTextSize(18); + author.setMaxLines(1); + author.setPadding(10, 10, 10, 10); + Resources res = getResources(); + if(authorName == null) { + author.setTextColor(res.getColor(R.color.anonymous_author)); + author.setText(R.string.anonymous); + } else { + author.setTextColor(res.getColor(R.color.pseudonymous_author)); + author.setText(authorName); + } + header.addView(author); + + TextView date = new TextView(this); + date.setTextSize(14); + date.setPadding(0, 10, 10, 10); + long now = System.currentTimeMillis(); + date.setText(DateUtils.formatSameDayTime(timestamp, now, SHORT, SHORT)); + header.addView(date); + message.addView(header); + + if(contentType.equals("text/plain")) { + // Load and display the message body + content = new TextView(this); + content.setPadding(10, 0, 10, 10); + message.addView(content); + loadMessageBody(); + } + scrollView.addView(message); + layout.addView(scrollView); + + layout.addView(new HorizontalBorder(this)); + + LinearLayout footer = new LinearLayout(this); + footer.setLayoutParams(CommonLayoutParams.MATCH_WRAP); + footer.setOrientation(HORIZONTAL); + footer.setGravity(CENTER); + + readButton = new ImageButton(this); + readButton.setBackgroundResource(0); + if(read) readButton.setImageResource(R.drawable.content_unread); + else readButton.setImageResource(R.drawable.content_read); + readButton.setOnClickListener(this); + footer.addView(readButton); + footer.addView(new HorizontalSpace(this)); + + prevButton = new ImageButton(this); + prevButton.setBackgroundResource(0); + prevButton.setImageResource(R.drawable.navigation_previous_item); + prevButton.setOnClickListener(this); + prevButton.setEnabled(!first); + footer.addView(prevButton); + footer.addView(new HorizontalSpace(this)); + + nextButton = new ImageButton(this); + nextButton.setBackgroundResource(0); + nextButton.setImageResource(R.drawable.navigation_next_item); + nextButton.setOnClickListener(this); + nextButton.setEnabled(!last); + footer.addView(nextButton); + footer.addView(new HorizontalSpace(this)); + + replyButton = new ImageButton(this); + replyButton.setBackgroundResource(0); + replyButton.setImageResource(R.drawable.social_reply_all); + replyButton.setOnClickListener(this); + footer.addView(replyButton); + layout.addView(footer); + + setContentView(layout); + + // Bind to the service so we can wait for the DB to be opened + bindService(new Intent(BriarService.class.getName()), + serviceConnection, 0); + } + + private void setReadInDatabase(final boolean read) { + final DatabaseComponent db = this.db; + final MessageId messageId = this.messageId; + dbExecutor.execute(new Runnable() { + public void run() { + try { + serviceConnection.waitForStartup(); + db.setReadFlag(messageId, read); + setReadInUi(read); + } 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 setReadInUi(final boolean read) { + runOnUiThread(new Runnable() { + public void run() { + ReadGroupMessageActivity.this.read = read; + if(read) readButton.setImageResource(R.drawable.content_unread); + else readButton.setImageResource(R.drawable.content_read); + } + }); + } + + private void loadMessageBody() { + final DatabaseComponent db = this.db; + final MessageId messageId = this.messageId; + dbExecutor.execute(new Runnable() { + public void run() { + try { + serviceConnection.waitForStartup(); + byte[] body = db.getMessageBody(messageId); + final String text = new String(body, "UTF-8"); + runOnUiThread(new Runnable() { + public void run() { + content.setText(text); + } + }); + } 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(); + } catch(UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + }); + } + + @Override + public void onSaveInstanceState(Bundle state) { + state.putBoolean("net.sf.briar.READ", read); + bundleEncrypter.encrypt(state); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unbindService(serviceConnection); + } + + public void onClick(View view) { + if(view == readButton) { + setReadInDatabase(!read); + } else if(view == prevButton) { + setResult(RESULT_PREV); + finish(); + } else if(view == nextButton) { + setResult(RESULT_NEXT); + finish(); + } else if(view == replyButton) { + Intent i = new Intent(this, WriteGroupMessageActivity.class); + i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes()); + i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes()); + startActivity(i); + setResult(RESULT_REPLY); + finish(); + } + } +} diff --git a/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java new file mode 100644 index 0000000000..7a1a93c06f --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java @@ -0,0 +1,217 @@ +package net.sf.briar.android.groups; + +import static android.view.Gravity.CENTER_VERTICAL; +import static android.widget.LinearLayout.HORIZONTAL; +import static android.widget.LinearLayout.VERTICAL; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.util.Collection; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import net.sf.briar.R; +import net.sf.briar.android.BriarActivity; +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.HorizontalSpace; +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.messaging.Group; +import net.sf.briar.api.messaging.GroupId; +import net.sf.briar.api.messaging.Message; +import net.sf.briar.api.messaging.MessageFactory; +import net.sf.briar.api.messaging.MessageId; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcelable; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; + +import com.google.inject.Inject; + +public class WriteGroupMessageActivity extends BriarActivity +implements OnClickListener, OnItemSelectedListener { + + private static final Logger LOG = + Logger.getLogger(WriteGroupMessageActivity.class.getName()); + + private final BriarServiceConnection serviceConnection = + new BriarServiceConnection(); + + @Inject private BundleEncrypter bundleEncrypter; + @Inject private DatabaseComponent db; + @Inject @DatabaseExecutor private Executor dbExecutor; + @Inject private MessageFactory messageFactory; + + private Group group = null; + private GroupId groupId = null; + private MessageId parentId = null; + private GroupNameSpinnerAdapter adapter = null; + private Spinner spinner = null; + private ImageButton sendButton = null; + private EditText content = null; + + @Override + public void onCreate(Bundle state) { + super.onCreate(null); + + Intent i = getIntent(); + byte[] id = i.getByteArrayExtra("net.sf.briar.GROUP_ID"); + if(id != null) groupId = new GroupId(id); + id = i.getByteArrayExtra("net.sf.briar.PARENT_ID"); + if(id != null) parentId = new MessageId(id); + + LinearLayout layout = new LinearLayout(this); + layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP); + layout.setOrientation(VERTICAL); + + LinearLayout actionBar = new LinearLayout(this); + actionBar.setLayoutParams(CommonLayoutParams.MATCH_WRAP); + actionBar.setOrientation(HORIZONTAL); + actionBar.setGravity(CENTER_VERTICAL); + + TextView to = new TextView(this); + to.setTextSize(18); + to.setPadding(10, 10, 10, 10); + to.setText(R.string.to); + actionBar.addView(to); + + adapter = new GroupNameSpinnerAdapter(this); + spinner = new Spinner(this); + spinner.setAdapter(adapter); + spinner.setOnItemSelectedListener(this); + loadContactNames(); + actionBar.addView(spinner); + + actionBar.addView(new HorizontalSpace(this)); + + sendButton = new ImageButton(this); + sendButton.setBackgroundResource(0); + sendButton.setImageResource(R.drawable.social_send_now); + sendButton.setEnabled(false); + sendButton.setOnClickListener(this); + actionBar.addView(sendButton); + layout.addView(actionBar); + + content = new EditText(this); + content.setPadding(10, 10, 10, 10); + if(state != null && bundleEncrypter.decrypt(state)) { + Parcelable p = state.getParcelable("net.sf.briar.CONTENT"); + if(p != null) content.onRestoreInstanceState(p); + } + layout.addView(content); + + setContentView(layout); + + // Bind to the service so we can wait for the DB to be opened + bindService(new Intent(BriarService.class.getName()), + serviceConnection, 0); + } + + private void loadContactNames() { + final DatabaseComponent db = this.db; + dbExecutor.execute(new Runnable() { + public void run() { + try { + serviceConnection.waitForStartup(); + final Collection<Group> groups = db.getSubscriptions(); + runOnUiThread(new Runnable() { + public void run() { + for(Group g : groups) { + if(g.getId().equals(groupId)) { + group = g; + spinner.setSelection(adapter.getCount()); + } + adapter.add(g); + } + } + }); + } catch(DbException e) { + if(LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } catch(InterruptedException e) { + LOG.info("Interrupted while waiting for service"); + Thread.currentThread().interrupt(); + } + } + }); + } + + @Override + public void onSaveInstanceState(Bundle state) { + Parcelable p = content.onSaveInstanceState(); + state.putParcelable("net.sf.briar.CONTENT", p); + bundleEncrypter.encrypt(state); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unbindService(serviceConnection); + } + + public void onClick(View view) { + if(group == null) throw new IllegalStateException(); + try { + storeMessage(content.getText().toString().getBytes("UTF-8")); + } catch(UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + finish(); + } + + private void storeMessage(final byte[] body) { + final DatabaseComponent db = this.db; + final MessageFactory messageFactory = this.messageFactory; + final Group group = this.group; + final MessageId parentId = this.parentId; + dbExecutor.execute(new Runnable() { + public void run() { + try { + serviceConnection.waitForStartup(); + Message m = messageFactory.createAnonymousMessage(parentId, + group, "text/plain", body); + db.addLocalGroupMessage(m); + } catch(DbException e) { + if(LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } catch(GeneralSecurityException e) { + throw new RuntimeException(e); + } catch(InterruptedException e) { + if(LOG.isLoggable(INFO)) + LOG.info("Interrupted while waiting for service"); + Thread.currentThread().interrupt(); + } catch(IOException e) { + throw new RuntimeException(e); + } + } + }); + } + + public void onItemSelected(AdapterView<?> parent, View view, int position, + long id) { + group = adapter.getItem(position); + groupId = group.getId(); + sendButton.setEnabled(true); + } + + public void onNothingSelected(AdapterView<?> parent) { + group = null; + groupId = null; + sendButton.setEnabled(false); + } +} diff --git a/briar-android/src/net/sf/briar/android/invitation/ConnectionView.java b/briar-android/src/net/sf/briar/android/invitation/ConnectionView.java index 90be870c03..9daf3287f5 100644 --- a/briar-android/src/net/sf/briar/android/invitation/ConnectionView.java +++ b/briar-android/src/net/sf/briar/android/invitation/ConnectionView.java @@ -4,7 +4,6 @@ import static android.view.Gravity.CENTER; import static android.view.Gravity.CENTER_HORIZONTAL; import net.sf.briar.R; import android.content.Context; -import android.content.res.Resources; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; @@ -45,9 +44,9 @@ public class ConnectionView extends AddContactView { innerLayout.addView(progress); TextView connecting = new TextView(ctx); - Resources res = getResources(); - String connectingVia = res.getString(R.string.connecting_wifi); - connecting.setText(String.format(connectingVia, networkName)); + String format = getResources().getString( + R.string.format_connecting_wifi); + connecting.setText(String.format(format, networkName)); innerLayout.addView(connecting); addView(innerLayout); diff --git a/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java b/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java index 1305acc566..8cb63950fe 100644 --- a/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java +++ b/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java @@ -7,7 +7,6 @@ import net.sf.briar.R; import net.sf.briar.android.widgets.CommonLayoutParams; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.view.View; @@ -70,9 +69,9 @@ public class WifiWidget extends LinearLayout implements OnClickListener { ok.setImageResource(R.drawable.navigation_accept); ok.setPadding(10, 10, 10, 10); addView(ok); - Resources res = getResources(); - String connected = res.getString(R.string.wifi_connected); - status.setText(String.format(connected, networkName)); + String format = getResources().getString( + R.string.format_wifi_connected); + status.setText(String.format(format, networkName)); addView(status); ImageButton settings = new ImageButton(ctx); settings.setImageResource(R.drawable.action_settings); diff --git a/briar-android/src/net/sf/briar/android/messages/ContactNameSpinnerAdapter.java b/briar-android/src/net/sf/briar/android/messages/ContactNameSpinnerAdapter.java index 168d7aa81e..18c4d00e7d 100644 --- a/briar-android/src/net/sf/briar/android/messages/ContactNameSpinnerAdapter.java +++ b/briar-android/src/net/sf/briar/android/messages/ContactNameSpinnerAdapter.java @@ -22,6 +22,7 @@ implements SpinnerAdapter { public View getView(int position, View convertView, ViewGroup parent) { TextView name = new TextView(getContext()); name.setTextSize(18); + name.setMaxLines(1); name.setPadding(10, 10, 10, 10); name.setText(getItem(position).getName()); return name; 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 60d3869012..b382e920ee 100644 --- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java +++ b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java @@ -67,6 +67,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { contactId = new ContactId(id); contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME"); if(contactName == null) throw new IllegalStateException(); + setTitle(contactName); LinearLayout layout = new LinearLayout(this); layout.setLayoutParams(CommonLayoutParams.MATCH_MATCH); @@ -104,23 +105,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { reloadMessageHeaders(); } - @Override - public void onDestroy() { - super.onDestroy(); - db.removeListener(this); - unbindService(serviceConnection); - } - - public void eventOccurred(DatabaseEvent e) { - if(e instanceof MessageAddedEvent) { - if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading"); - reloadMessageHeaders(); - } else if(e instanceof MessageExpiredEvent) { - if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading"); - reloadMessageHeaders(); - } - } - private void reloadMessageHeaders() { final DatabaseComponent db = this.db; final ContactId contactId = this.contactId; @@ -168,8 +152,38 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { }); } + @Override + public void onActivityResult(int request, int result, Intent data) { + if(result == ReadPrivateMessageActivity.RESULT_PREV) { + int position = request - 1; + if(position >= 0 && position < adapter.getCount()) + showMessage(position); + } else if(result == ReadPrivateMessageActivity.RESULT_NEXT) { + int position = request + 1; + if(position >= 0 && position < adapter.getCount()) + showMessage(position); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + db.removeListener(this); + unbindService(serviceConnection); + } + + public void eventOccurred(DatabaseEvent e) { + if(e instanceof MessageAddedEvent) { + if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading"); + reloadMessageHeaders(); + } else if(e instanceof MessageExpiredEvent) { + if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading"); + reloadMessageHeaders(); + } + } + public void onClick(View view) { - Intent i = new Intent(this, WriteMessageActivity.class); + Intent i = new Intent(this, WritePrivateMessageActivity.class); i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt()); startActivity(i); } @@ -181,28 +195,15 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { private void showMessage(int position) { PrivateMessageHeader item = adapter.getItem(position); - Intent i = new Intent(this, ReadMessageActivity.class); + Intent i = new Intent(this, ReadPrivateMessageActivity.class); i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt()); i.putExtra("net.sf.briar.CONTACT_NAME", contactName); i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes()); i.putExtra("net.sf.briar.CONTENT_TYPE", item.getContentType()); i.putExtra("net.sf.briar.TIMESTAMP", item.getTimestamp()); + i.putExtra("net.sf.briar.INCOMING", item.isIncoming()); i.putExtra("net.sf.briar.FIRST", position == 0); i.putExtra("net.sf.briar.LAST", position == adapter.getCount() - 1); - i.putExtra("net.sf.briar.STARRED", item.isStarred()); startActivityForResult(i, position); } - - @Override - public void onActivityResult(int request, int result, Intent data) { - if(result == ReadMessageActivity.RESULT_PREV) { - int position = request - 1; - if(position >= 0 && position < adapter.getCount()) - showMessage(position); - } else if(result == ReadMessageActivity.RESULT_NEXT) { - int position = request + 1; - if(position >= 0 && position < adapter.getCount()) - showMessage(position); - } - } } diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java b/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java index 056935ad82..40e9626dbc 100644 --- a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java +++ b/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java @@ -9,6 +9,7 @@ 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.PrivateMessageHeader; import android.content.Context; import android.text.format.DateUtils; @@ -34,23 +35,24 @@ class ConversationAdapter extends ArrayAdapter<PrivateMessageHeader> { layout.setOrientation(HORIZONTAL); layout.setGravity(CENTER_VERTICAL); - if(!item.getContentType().equals("text/plain")) { + if(item.getContentType().equals("text/plain")) { + TextView subject = new TextView(ctx); + // Give me all the unused width + subject.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1); + subject.setTextSize(14); + subject.setMaxLines(2); + subject.setPadding(10, 10, 10, 10); + if(!item.isRead()) subject.setTypeface(null, BOLD); + subject.setText(item.getSubject()); + layout.addView(subject); + } else { ImageView attachment = new ImageView(ctx); attachment.setPadding(10, 10, 10, 10); attachment.setImageResource(R.drawable.content_attachment); layout.addView(attachment); + layout.addView(new HorizontalSpace(ctx)); } - TextView subject = new TextView(ctx); - // Give me all the unused width - subject.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1); - subject.setTextSize(14); - subject.setMaxLines(2); - subject.setPadding(10, 10, 10, 10); - if(!item.isRead()) subject.setTypeface(null, BOLD); - subject.setText(item.getSubject()); - layout.addView(subject); - TextView date = new TextView(ctx); date.setTextSize(14); date.setPadding(0, 10, 10, 10); diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java index 5618057483..5eafcf3367 100644 --- a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java +++ b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java @@ -238,7 +238,7 @@ implements OnClickListener, DatabaseListener { } public void onClick(View view) { - startActivity(new Intent(this, WriteMessageActivity.class)); + startActivity(new Intent(this, WritePrivateMessageActivity.class)); } public void eventOccurred(DatabaseEvent e) { diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java b/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java index 542a14d52e..19066131bc 100644 --- a/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java +++ b/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java @@ -46,10 +46,12 @@ implements OnItemClickListener { TextView name = new TextView(ctx); name.setTextSize(18); + name.setMaxLines(1); name.setPadding(10, 10, 10, 10); int unread = item.getUnreadCount(); - if(unread > 0) name.setText(item.getName() + " (" + unread + ")"); - else name.setText(item.getName()); + String contactName = item.getContactName(); + if(unread > 0) name.setText(contactName + " (" + unread + ")"); + else name.setText(contactName); innerLayout.addView(name); if(!StringUtils.isNullOrEmpty(item.getSubject())) { @@ -78,7 +80,7 @@ implements OnItemClickListener { ConversationListItem item = getItem(position); Intent i = new Intent(getContext(), ConversationActivity.class); i.putExtra("net.sf.briar.CONTACT_ID", item.getContactId().getInt()); - i.putExtra("net.sf.briar.CONTACT_NAME", item.getName()); + i.putExtra("net.sf.briar.CONTACT_NAME", item.getContactName()); getContext().startActivity(i); } } diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java b/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java index b168f7a9bc..eec30a91c8 100644 --- a/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java +++ b/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java @@ -30,7 +30,7 @@ class ConversationListItem { return contact.getId(); } - String getName() { + String getContactName() { return contact.getName(); } diff --git a/briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java similarity index 90% rename from briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java rename to briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java index 04185e78f8..afe0944578 100644 --- a/briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java +++ b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java @@ -37,7 +37,7 @@ import android.widget.TextView; import com.google.inject.Inject; -public class ReadMessageActivity extends BriarActivity +public class ReadPrivateMessageActivity extends BriarActivity implements OnClickListener { static final int RESULT_REPLY = RESULT_FIRST_USER; @@ -45,7 +45,7 @@ implements OnClickListener { static final int RESULT_NEXT = RESULT_FIRST_USER + 2; private static final Logger LOG = - Logger.getLogger(ReadMessageActivity.class.getName()); + Logger.getLogger(ReadPrivateMessageActivity.class.getName()); private final BriarServiceConnection serviceConnection = new BriarServiceConnection(); @@ -55,11 +55,9 @@ implements OnClickListener { @Inject @DatabaseExecutor private Executor dbExecutor; private ContactId contactId = null; - private String contactName = null; private MessageId messageId = null; - private boolean first, last, starred, read; - private ImageButton readButton = null; - private ImageButton prevButton = null, nextButton = null; + private boolean read; + private ImageButton readButton = null, prevButton = null, nextButton = null; private ImageButton replyButton = null; private TextView content = null; @@ -71,8 +69,9 @@ implements OnClickListener { int cid = i.getIntExtra("net.sf.briar.CONTACT_ID", -1); if(cid == -1) throw new IllegalStateException(); contactId = new ContactId(cid); - contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME"); + String contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME"); if(contactName == null) throw new IllegalStateException(); + setTitle(contactName); byte[] mid = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID"); if(mid == null) throw new IllegalStateException(); messageId = new MessageId(mid); @@ -80,14 +79,13 @@ implements OnClickListener { if(contentType == null) throw new IllegalStateException(); long timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1); if(timestamp == -1) throw new IllegalStateException(); - first = i.getBooleanExtra("net.sf.briar.FIRST", false); - last = i.getBooleanExtra("net.sf.briar.LAST", false); + boolean incoming = i.getBooleanExtra("net.sf.briar.INCOMING", false); + boolean first = i.getBooleanExtra("net.sf.briar.FIRST", false); + boolean last = i.getBooleanExtra("net.sf.briar.LAST", false); if(state != null && bundleEncrypter.decrypt(state)) { - starred = state.getBoolean("net.sf.briar.STARRED"); read = state.getBoolean("net.sf.briar.READ"); } else { - starred = i.getBooleanExtra("net.sf.briar.STARRED", false); read = false; setReadInDatabase(true); } @@ -112,8 +110,11 @@ implements OnClickListener { // Give me all the unused width name.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1); name.setTextSize(18); + name.setMaxLines(1); name.setPadding(10, 10, 10, 10); - String format = getResources().getString(R.string.message_from); + String format; + if(incoming) format = getResources().getString(R.string.format_from); + else format = getResources().getString(R.string.format_to); name.setText(String.format(format, contactName)); header.addView(name); @@ -204,7 +205,7 @@ implements OnClickListener { private void setReadInUi(final boolean read) { runOnUiThread(new Runnable() { public void run() { - ReadMessageActivity.this.read = read; + ReadPrivateMessageActivity.this.read = read; if(read) readButton.setImageResource(R.drawable.content_unread); else readButton.setImageResource(R.drawable.content_read); } @@ -241,7 +242,6 @@ implements OnClickListener { @Override public void onSaveInstanceState(Bundle state) { - state.putBoolean("net.sf.briar.STARRED", starred); state.putBoolean("net.sf.briar.READ", read); bundleEncrypter.encrypt(state); } @@ -262,7 +262,7 @@ implements OnClickListener { setResult(RESULT_NEXT); finish(); } else if(view == replyButton) { - Intent i = new Intent(this, WriteMessageActivity.class); + Intent i = new Intent(this, WritePrivateMessageActivity.class); i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt()); i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes()); startActivity(i); diff --git a/briar-android/src/net/sf/briar/android/messages/WriteMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java similarity index 89% rename from briar-android/src/net/sf/briar/android/messages/WriteMessageActivity.java rename to briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java index 4673d8180e..ced62326ac 100644 --- a/briar-android/src/net/sf/briar/android/messages/WriteMessageActivity.java +++ b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java @@ -7,6 +7,7 @@ import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.util.Collection; import java.util.concurrent.Executor; @@ -42,11 +43,11 @@ import android.widget.TextView; import com.google.inject.Inject; -public class WriteMessageActivity extends BriarActivity +public class WritePrivateMessageActivity extends BriarActivity implements OnClickListener, OnItemSelectedListener { private static final Logger LOG = - Logger.getLogger(WriteMessageActivity.class.getName()); + Logger.getLogger(WritePrivateMessageActivity.class.getName()); private final BriarServiceConnection serviceConnection = new BriarServiceConnection(); @@ -85,7 +86,7 @@ implements OnClickListener, OnItemSelectedListener { TextView to = new TextView(this); to.setTextSize(18); to.setPadding(10, 10, 10, 10); - to.setText(R.string.message_to); + to.setText(R.string.to); actionBar.addView(to); adapter = new ContactNameSpinnerAdapter(this); @@ -163,32 +164,36 @@ implements OnClickListener, OnItemSelectedListener { public void onClick(View view) { if(contactId == null) throw new IllegalStateException(); try { - byte[] body = content.getText().toString().getBytes("UTF-8"); - storeMessage(messageFactory.createPrivateMessage(parentId, - "text/plain", body)); - } catch(IOException e) { - throw new RuntimeException(e); - } catch(GeneralSecurityException e) { + storeMessage(content.getText().toString().getBytes("UTF-8")); + } catch(UnsupportedEncodingException e) { throw new RuntimeException(e); } finish(); } - private void storeMessage(final Message m) { + private void storeMessage(final byte[] body) { final DatabaseComponent db = this.db; + final MessageFactory messageFactory = this.messageFactory; final ContactId contactId = this.contactId; + final MessageId parentId = this.parentId; dbExecutor.execute(new Runnable() { public void run() { try { serviceConnection.waitForStartup(); + Message m = messageFactory.createPrivateMessage(parentId, + "text/plain", body); db.addLocalPrivateMessage(m, contactId); } catch(DbException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } catch(GeneralSecurityException e) { + throw new RuntimeException(e); } catch(InterruptedException e) { if(LOG.isLoggable(INFO)) LOG.info("Interrupted while waiting for service"); Thread.currentThread().interrupt(); + } catch(IOException e) { + throw new RuntimeException(e); } } }); diff --git a/briar-android/src/net/sf/briar/android/widgets/HorizontalBorder.java b/briar-android/src/net/sf/briar/android/widgets/HorizontalBorder.java index 958092f40c..c708f335ca 100644 --- a/briar-android/src/net/sf/briar/android/widgets/HorizontalBorder.java +++ b/briar-android/src/net/sf/briar/android/widgets/HorizontalBorder.java @@ -13,6 +13,6 @@ public class HorizontalBorder extends View { public HorizontalBorder(Context ctx) { super(ctx); setLayoutParams(new LayoutParams(MATCH_PARENT, LINE_WIDTH)); - setBackgroundColor(getResources().getColor(R.color.HorizontalBorder)); + setBackgroundColor(getResources().getColor(R.color.horizontal_border)); } } -- GitLab