From a2d099ea17cca15974c19f91022dc65288fc4891 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Fri, 28 Feb 2014 23:44:35 +0000
Subject: [PATCH] Added debugging screen to alpha and beta builds. Dev task
 #73.

---
 briar-android/AndroidManifest.xml             |   8 +
 .../res/drawable-hdpi/action_help.png         | Bin 0 -> 1544 bytes
 .../res/drawable-hdpi/social_share.png        | Bin 0 -> 1695 bytes
 .../res/drawable-mdpi/action_help.png         | Bin 0 -> 1318 bytes
 .../res/drawable-mdpi/social_share.png        | Bin 0 -> 1394 bytes
 .../res/drawable-xhdpi/action_help.png        | Bin 0 -> 1796 bytes
 .../res/drawable-xhdpi/social_share.png       | Bin 0 -> 1989 bytes
 briar-android/res/values/color.xml            |   1 +
 briar-android/res/values/strings.xml          |   2 +-
 .../android/DashboardActivity.java            |  25 +-
 .../briarproject/android/TestingActivity.java | 347 ++++++++++++++++++
 .../plugins/droidtooth/DroidtoothPlugin.java  |  15 +-
 .../briarproject/plugins/tor/TorPlugin.java   |   7 +-
 .../org/briarproject/api/plugins/Plugin.java  |   3 +
 .../api/plugins/PluginManager.java            |   7 +
 .../plugins/PluginManagerImpl.java            |  18 +-
 .../briarproject/plugins/file/FilePlugin.java |   4 +
 .../briarproject/plugins/tcp/TcpPlugin.java   |   4 +
 .../plugins/bluetooth/BluetoothPlugin.java    |   4 +
 .../plugins/modem/ModemPlugin.java            |  38 +-
 20 files changed, 440 insertions(+), 43 deletions(-)
 create mode 100644 briar-android/res/drawable-hdpi/action_help.png
 create mode 100644 briar-android/res/drawable-hdpi/social_share.png
 create mode 100644 briar-android/res/drawable-mdpi/action_help.png
 create mode 100644 briar-android/res/drawable-mdpi/social_share.png
 create mode 100644 briar-android/res/drawable-xhdpi/action_help.png
 create mode 100644 briar-android/res/drawable-xhdpi/social_share.png
 create mode 100644 briar-android/src/org/briarproject/android/TestingActivity.java

diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index ab787cf379..b9119cccb8 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -12,6 +12,9 @@
 	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 	<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 	<uses-permission android:name="android.permission.VIBRATE" />
+	<!-- FIXME: Only needed for alpha and beta builds -->
+	<uses-permission android:name="android.permission.READ_LOGS" />
+	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
 	<application
 		android:theme="@style/LightTheme"
@@ -54,6 +57,11 @@
 				<category android:name="android.intent.category.LAUNCHER" />
 			</intent-filter>
 		</activity>
+		<activity
+			android:name=".android.TestingActivity"
+			android:logo="@drawable/logo"
+			android:label="@string/app_name" >
+		</activity>
 		<activity
 		    android:name=".android.contact.ContactListActivity"
 			android:logo="@drawable/logo"
diff --git a/briar-android/res/drawable-hdpi/action_help.png b/briar-android/res/drawable-hdpi/action_help.png
new file mode 100644
index 0000000000000000000000000000000000000000..459bed76c5b9f546541383271a6c9b9ff9943093
GIT binary patch
literal 1544
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(#^lGsVipz|h&;z|zRn$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19ytk=TG!rZ_Nr(RHE$SnZc?2=lPS(cjOR+OKs0QR(1
zCT_PF;4}}aHwCL(!2U4AsaGH97=2LGB1JV!2$+6AOnAZta^OinH4m8Hi+~AxLcu##
z1_q`oPZ!6Kid%1H-uD&>6ggg=7Jk~~VBDl$IW3hy&m~7E)o6b3f3ekp>yY^WNpe+u
z@<-AqPMF8_Wug28X_qI?ES+j=1dfWmj9P14$6q6^#~yoW=FOSj-w(B?zQ1?Z@_zNX
zozKs#5pPR8_w1o1qhEvY0~QO$m7E$=y{cAm$1u4TusyMiXOBN9b<=o%h~D)6TxZ2L
z-wy{3Bv_|Anp-fQnfviaop?^u^9P|aavfR^q-HRP|A?-4WGLhNY;oO=;cr6U{mw1b
zfyXQ)5<K2~+8XI|D!qQkH?<wI?Y|fIq%SBtptrqo@&e%!nFlZ3Ebb=VSSseU`hoD_
zg?@*RZYcdEJ7?MSt~-B^O$e}NUDw>7$vI&{BwM(H=$Zy|o6J|otUTZRPVnfQVEX*>
zmxiA52Zj~=`(!sgQ?EEx@Uh2cBJT^&=!TXvm+aU4J}A8WuD}CL|E)Hi{YG6rZvtZX
zXEir4^`G4%r0_X@lb%D*p3^M<)~<`~j+)5LDXstaf~>)z7{lz=mdi>%=7cgnKOoY7
za+cA)=HqXjZtV>)e!To(Wa^&tZuy#hb{d+DNe@(BgdQ_rXUaeC$jpR7{>%@*rhKe?
z_f4ll_W99AcC9sPISZ2PJP!R(VcB9_@+vFOl=Jaq4#j@U2hyyo53DmuZ*Gw34xL?i
z|MlzY2RhsnlD-C1I_%k$+s$CO>(Z&?T5I39tv$(jbBg0*zij534XzC)8Q%|tKHk2x
zVK1xks)eUhIPNkeuVJ=M=`T(;dvd^4N@m4!en$JvN}Y$*T=hOPY3U^0D#(0hmMid~
zsn%aSKdQy=+}<q@MJB29^v^i6ch2oaANNlZc<^swA=4ZN^#hzg_Org>Heh(<y|q2M
T-w{}9Gcb6%`njxgN@xNAO}|8u

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-hdpi/social_share.png b/briar-android/res/drawable-hdpi/social_share.png
new file mode 100644
index 0000000000000000000000000000000000000000..47ae186749df92bbbe77f860ce657d3edce27765
GIT binary patch
literal 1695
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(#^lGsVi((a6=+z{J?p$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19yq1ObbUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnqfX
zG!Lpb1-Dy_aO%|uIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1z&RAG31<Wsko-U3d
z6}R5Z^v)0t6lqh>Ob*hqyVd$&@e0?F6+aZ5Vmma%%tHl4yS%3><;9deKG$0k{QVu*
z(Om)}@(UIugmMIWEbQv{SR1OdNObi{laoK>f3UYLG%oKs`Q+HS#{Nku9`Ebl?|lB}
zyINZ0w2Nm1&1cytF-~k)s<+_k`@<zxvd1K*HRf%YwLbk|^@ard2`5ul2@5!_VR1hw
zH{ntB{sX}U(r5l(=DK2Ob+GbW9pepN&#*@pH@Mmk^2dHZkjWR9wv$I|2}@nm+XpHc
zmje%|U0|;JZKFK@Evt2@=!8c-)vZfHgmSJ0HiXQJXn39^vT$yLMRBaElh?~0zt9Zj
z7wpy|FO%0#`qIoP%d&>m-9bO%+@EP}SI-$v5tH1*xvsgtSL6aCUo&$~l+gVH$_3M>
zz7VNyn(HUpCC#|g&D3b-)8-W3`;%||W4XgT|HnOx3f8j1sVS+;4seGuuXc0be!=wm
z=P%<EyQkSRervWnUhF!7b^DCV?|yK-yMFYHWs<B!Lbbnz))8w1FU7rQKIer=o_y$8
zFuDG4`C^`#C%MmDZn~_x>F#{y_ycBL!qFdQ8PDSl$bF@iAo|y1x%+MXg5t6Q;W~!$
z1CL#`yq#IzJkt@cou^s+fq$l->s-H-*fl4^8aiiiR)6q*<YRL*Ytbhi)oUM@<T1_L
z%%Udx*Kyx-fgLB--Cot*@IImE?6O|%hb}gMWhzeAZepCn*j%J8;MT=9b7f}1&BYUa
zt4ucOKFE>i)p=IF^YyN<mDjvxaA_~>iHco4#qz<PM+~1BY>H>@thHg@adu9v^+S{5
z%Xg}ax0)5#3xD>RTR460yT|uFY`QG=a{1hI*Nj?Ul`hEqXmLU3L!8F*=}w8JMlUag
zA5^`!>*U+pT<-jZ?Vf__FIu*~JhG{CeS<XPzwIZdzP@<TD`dz25IJo}!CRaL%slt@
z-O+lbp>Te)ON^n}P3`;=QG>)E@pqO*xE1fZmAUeBeq>m#v&8dz8O#4l82{04kpA_%
zLE?yFL67?WWxpE_FNoctR(AZu&(4*v{QlU_IRQ$N9u3bvx;HQ~DDj*tE_ssV52`Xe
MUHx3vIVCg!0FRcDBme*a

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-mdpi/action_help.png b/briar-android/res/drawable-mdpi/action_help.png
new file mode 100644
index 0000000000000000000000000000000000000000..72edd5a761481a3598974dac6d741b31ca953fb7
GIT binary patch
literal 1318
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(#^lGsVipz{1GF+`!V*$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19yq1O?oUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnv1@
zG!Lpb1-Dx)aO%|uIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1zo?(~5$H2fi!_&nv
zq~g|_>HGCr9A%D{n^(6#?s)NqOVMeKi-rhS;7e62srnwLDK1-7vU<H-Hgl<julSQy
z&??`~k+*mH9$zzYAxrDcXX_^WKP$`6-=Cj$?@al*-bE))8ri5nKEaxzci{i)W=8=P
z*Y90r4&wIB+6D6VB5jGgm;~-7@bqX`N*8>Ub-i+c^9CFL%6^6<!JdZl4-2maf3nhO
z((H_8^v|q*AX1|hxw<NT@{C3AKJVf`Gee@0dGCx)zJroGSW^v_zwC5RV|cSf_krk|
zrd3r^?7Ta;+rHRwy)iLl>gkSuCoWW%@H?$qeg><qP{4&rCPqxXj>hMjJ<sl4zw*{*
z#fE3mY|UP$=G8a)7Gy>-)Nh~SaW3@-huIb@nafOW2RBUm9o3ip;8>X9tMJ!ZH*-xV
z|FLH}Wt8A~z5LkfMCUinT~+7b{ye&^p=(3A+z-Wwl3fS3`egVr^loQZ_I2jr{d|47
zGdHmNtoT%T{{02Z1Gi>g@Sf8&$Axi|+JaS!!#xXr+irT<xa*ls$lc|f=VyyPcvgS5
fh%u7;hgbrGXm?!3!@v9YgGw$>S3j3^P6<r_yYJL&

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-mdpi/social_share.png b/briar-android/res/drawable-mdpi/social_share.png
new file mode 100644
index 0000000000000000000000000000000000000000..8aa52bc7d8a74739dc5e0ad9c35a006bdcbda182
GIT binary patch
literal 1394
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((b2-i#nH^v$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19yq1POzUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dR<{_r
zx*C|6;xrGcHwCL(z<P1}Lm%iEeNfaQMKw$an0`P^c)|s8;7LC<518JIfC*cxe$saa
z2F53zE{-7;x86)Yt?d#hbNo49+s9LGfyqKQE0$!YDXtfq_=A7V8y%gTRMqT_$w%AP
zw))(haHGP<%SWtPK#=>X?XsPZt6!~N_cHET={uFrtNkzkS!VgX`uUvq-{%A>P3%ye
zo4R$$P9vsGUuVhntKTf-c4XerEM1^8%Wl5D8KY;Is07o~8hN(c34Apt;+kv>I8GhV
z7M#4TInMZ-gK9y8xv&3^Ke9W{2q+v2=CM0HQFj5`mu8*0g~bOm&**h*mYK6-YPR0C
zDqYzHpHAu@^jgI_rAh9>NzZK-Crwu{>u!0>8o|#0k!wSv+5;B<gz5A2*kTt*Pie5-
zz{51_{V&fX#}+N7!!<%vvu`n$x%f456>y{-a6C0x;41s8gPWIyAJ`XhKC`V`OSS9g
zlY=YfeJJM8Iri)Gzn7OU_`lEcewZiEaCW6ozDb9;OyS+PHMNQH9MX)*3j(jn32d8f
zvSDF(^5H;cX*Jyghk|p0m;dLOCSeor_~e&jM@#lKA<jkjU&-k-@VXUB7Zm@`SbW2G
z*@3w{;xUbp27G=JAz^YS9n(4|D=occa6pygHOmx%pj<b;6+Ak7xn?RpnB$$)e~P~_
z^tQQk!;Y%6&CwJ570q^KhF)t`*>&y!3;*W)>qUEmjQ;cf-`n8N$i^W2yU>O2ZR}N0
ODd_3y=d#Wzp$PyYS@Qk>

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-xhdpi/action_help.png b/briar-android/res/drawable-xhdpi/action_help.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e67d7c12130fd86e9c7191c236b31aebdfe890e
GIT binary patch
literal 1796
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=GfuXaxfrYE7lYxt&p{t>#iJ_&diIcOV
zo2j9>iIX8ruS<S%X>Mv>2~2MaLa!T6y`aR9TL84#CABECEH%ZgC_h&L>}jh^+-`Bk
zX&zK>3U0T!;MA)Rbc{YIYLTKECIn1BASOKF0y*%cpPC0u??u3b{ao69Eik{7dAc};
zRNQ(qJGP%YQ0AEb?cCi{mTKQf5L7z2sUy#2NAdGhtsNcPyT3BlaF*LOa&xW|i0Kp%
za%9>2X;RLHkLQjpGP!Z-ZQt|xc4y7bp4rm-*5~|7%ai)I-)=Fko@e?0y0LVjjC<*)
zj#Yf}VGP<0q6fGVST`_5Fj|$Doc+RSJhSKiLrVqLOEs}`+x{`!ca;0{)ymOqc5Xgn
z&AQDJYgqp#*m+#3S;k=3JUxNs&y^z9D?L*a1acT_75tPEP92qbx>xB3>$_&_2aEHV
zPCU8RESsP_<GANki-S(g_r;BG@@{KB{a~9PlMI{n&LgRhgnjGoE3$T#cRb&<&|74}
z?m16by#80N>oUIM9el~DAzFbwsdQRp%k}kpzDcQa?P5Q3&9TA%f#%%Uw@W4;S!vX-
z)UWz#O-pEk1G~I}T7hiNTA>|}_~RO$Crr5)8|I}S!CTh&G~v=={SJ@U2b5(GJ&->3
zNm0$5{~)v9zy8x*f^yA|dFJMIPT<(=vfSo@)Q@9V4Xzz{uFAZJv+qNmwchFl;p&WT
z9>$MteV3j-%(Kf^zipD8pyR)5g_l_)f<;yDIHli9VSLt<Yj<3A%|4^h29~+^jea%U
z5=<`r;j%qz!^De+YwAxd4-jg6w=IdaMfU(#!Gvw`A0mHEGKsyoQT~CsS>xiKw{_)l
z)Bb6yg&gD9Ajz|Nhvtu#BZYo-A=mpR+?IIoB>2?s@9LcAvkPX~Pt|1dP%hbj{)x8e
z;u5>_+p=!9Yc_<`{MBQfxJs*YeN(sb1)of7#-J^=>)AZ#Oy2)sB4?I<!<M7sl`2++
z)6P6^F7t3qYqIqzUmo*r-;d_%#FoVhtR0%~?GiOP+QqkVhS=0xeEK&cqSSK&)04<E
zK07!9g1(yjY!P>mTBKfMQ**L}QKp|m;ME3xkCI&sj>`{C`oOd4pZ{gWeW~x}o5wO#
zYCM{u_5PiU<gaBf{-kLC>CwvXlgMNediCw&JO51*zs_Dz->=WY9>Q&AcgwZGgnMCA
z(fyATzb{qXbufhSSB36=Eq|ZH#+6J*q|OL0TE=B?&7oJH&oX@#!zS-PeMcvRSNvHu
z>%PxbH-?Sxi&x#bKi#ij`>eXC6tVvJAl*6@y*D-vLh~kP{qmg9^~2K4d0wfL+7H*M
z@=Xz^QuXT`tNzO$@@iH7w*Od;X$CmKuVDbE{{_y+?HSn^+&p)g-|%3a462trUHx3v
IIVCg!0CTakTmS$7

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-xhdpi/social_share.png b/briar-android/res/drawable-xhdpi/social_share.png
new file mode 100644
index 0000000000000000000000000000000000000000..cdafd8abca1912ff47f812c38fd29994da1d7867
GIT binary patch
literal 1989
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

literal 0
HcmV?d00001

diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml
index b25cc18435..8b0ec7923c 100644
--- a/briar-android/res/values/color.xml
+++ b/briar-android/res/values/color.xml
@@ -4,6 +4,7 @@
     <color name="action_bar_text">#FFFFFF</color>
     <color name="action_bar_background">#2D3E50</color>
     <color name="button_bar_background">#FFFFFF</color>
+    <color name="dashboard_background">#FFFFFF</color>
     <color name="private_message_background">#FFFFFF</color>
 	<color name="private_message_date">#AAAAAA</color>
     <color name="unread_background">#FFFFFF</color>
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 338a010865..b452b23ea3 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -16,7 +16,7 @@
     <string name="expiry_warning">This software has expired.\nPlease install a newer version.</string>
     <string name="contact_list_button">Contacts</string>
     <string name="forums_button">Forums</string>
-    <string name="synchronize_button">Synchronize</string>
+    <string name="testing_button">Testing</string>
     <string name="sign_out_button">Sign Out</string>
     <string name="contact_list_title">Contacts</string>
     <string name="no_contacts">No contacts</string>
diff --git a/briar-android/src/org/briarproject/android/DashboardActivity.java b/briar-android/src/org/briarproject/android/DashboardActivity.java
index f1f604f1e7..956254c4f1 100644
--- a/briar-android/src/org/briarproject/android/DashboardActivity.java
+++ b/briar-android/src/org/briarproject/android/DashboardActivity.java
@@ -2,7 +2,6 @@ package org.briarproject.android;
 
 import static android.view.Gravity.CENTER;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.widget.Toast.LENGTH_SHORT;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
@@ -37,7 +36,6 @@ import android.widget.GridView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.ProgressBar;
-import android.widget.Toast;
 
 public class DashboardActivity extends BriarActivity {
 
@@ -121,20 +119,19 @@ public class DashboardActivity extends BriarActivity {
 		});
 		buttons.add(forumsButton);
 
-		Button syncButton = new Button(this);
-		syncButton.setLayoutParams(matchMatch);
-		syncButton.setBackgroundResource(0);
-		syncButton.setCompoundDrawablesWithIntrinsicBounds(0,
-				R.drawable.navigation_refresh, 0, 0);
-		syncButton.setText(R.string.synchronize_button);
-		syncButton.setOnClickListener(new OnClickListener() {
+		Button testingButton = new Button(this);
+		testingButton.setLayoutParams(matchMatch);
+		testingButton.setBackgroundResource(0);
+		testingButton.setCompoundDrawablesWithIntrinsicBounds(0,
+				R.drawable.action_help, 0, 0);
+		testingButton.setText(R.string.testing_button);
+		testingButton.setOnClickListener(new OnClickListener() {
 			public void onClick(View view) {
-				// FIXME: Hook this button up to an activity
-				Toast.makeText(DashboardActivity.this,
-						R.string.not_implemented_toast, LENGTH_SHORT).show();
+				startActivity(new Intent(DashboardActivity.this,
+						TestingActivity.class));
 			}
 		});
-		buttons.add(syncButton);
+		buttons.add(testingButton);
 
 		Button signOutButton = new Button(this);
 		signOutButton.setLayoutParams(matchMatch);
@@ -157,7 +154,7 @@ public class DashboardActivity extends BriarActivity {
 		grid.setGravity(CENTER);
 		grid.setPadding(pad, pad, pad, pad);
 		Resources res = getResources();
-		grid.setBackgroundColor(res.getColor(R.color.button_bar_background));
+		grid.setBackgroundColor(res.getColor(R.color.dashboard_background));
 		grid.setNumColumns(2);
 		grid.setAdapter(new BaseAdapter() {
 
diff --git a/briar-android/src/org/briarproject/android/TestingActivity.java b/briar-android/src/org/briarproject/android/TestingActivity.java
new file mode 100644
index 0000000000..0ff9381ce9
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/TestingActivity.java
@@ -0,0 +1,347 @@
+package org.briarproject.android;
+
+import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
+import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
+import static android.content.Intent.ACTION_SEND;
+import static android.content.Intent.EXTRA_EMAIL;
+import static android.content.Intent.EXTRA_STREAM;
+import static android.content.Intent.EXTRA_SUBJECT;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
+import static android.view.Gravity.CENTER;
+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 org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
+import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
+import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Scanner;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import org.briarproject.R;
+import org.briarproject.android.util.ElasticHorizontalSpace;
+import org.briarproject.android.util.HorizontalBorder;
+import org.briarproject.android.util.LayoutUtils;
+import org.briarproject.api.TransportId;
+import org.briarproject.api.android.AndroidExecutor;
+import org.briarproject.api.plugins.Plugin;
+import org.briarproject.api.plugins.PluginManager;
+import org.briarproject.util.StringUtils;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Environment;
+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;
+
+public class TestingActivity extends BriarActivity implements OnClickListener {
+
+	private static final Logger LOG =
+			Logger.getLogger(TestingActivity.class.getName());
+
+	@Inject private AndroidExecutor androidExecutor;
+	@Inject private PluginManager pluginManager;
+	private ScrollView scroll = null;
+	private LinearLayout status = null;
+	private ImageButton refresh = null, share = null;
+	private File temp = null;
+
+	@Override
+	public void onCreate(Bundle state) {
+		super.onCreate(state);
+
+		LinearLayout layout = new LinearLayout(this);
+		layout.setLayoutParams(MATCH_MATCH);
+		layout.setOrientation(VERTICAL);
+		layout.setGravity(CENTER_HORIZONTAL);
+
+		scroll = new ScrollView(this);
+		scroll.setLayoutParams(MATCH_WRAP_1);
+		status = new LinearLayout(this);
+		status.setOrientation(VERTICAL);
+		status.setGravity(CENTER_HORIZONTAL);
+		int pad = LayoutUtils.getPadding(this);
+		status.setPadding(pad, pad, pad, pad);
+		scroll.addView(status);
+		layout.addView(scroll);
+
+		layout.addView(new HorizontalBorder(this));
+
+		LinearLayout footer = new LinearLayout(this);
+		footer.setLayoutParams(MATCH_WRAP);
+		footer.setGravity(CENTER);
+		Resources res = getResources();
+		footer.setBackgroundColor(res.getColor(R.color.button_bar_background));
+		footer.addView(new ElasticHorizontalSpace(this));
+
+		refresh = new ImageButton(this);
+		refresh.setBackgroundResource(0);
+		refresh.setImageResource(R.drawable.navigation_refresh);
+		refresh.setOnClickListener(this);
+		footer.addView(refresh);
+		footer.addView(new ElasticHorizontalSpace(this));
+
+		share = new ImageButton(this);
+		share.setBackgroundResource(0);
+		share.setImageResource(R.drawable.social_share);
+		share.setOnClickListener(this);
+		footer.addView(share);
+		footer.addView(new ElasticHorizontalSpace(this));
+		layout.addView(footer);
+
+		setContentView(layout);
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		refresh();
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		if(temp != null) temp.delete();
+	}
+
+	public void onClick(View view) {
+		if(view == refresh) refresh();
+		else if(view == share) share();
+	}
+
+	private void refresh() {
+		status.removeAllViews();
+		new AsyncTask<Void, Void, Map<String, String>>() {
+
+			protected Map<String, String> doInBackground(Void... args) {
+				return getStatusMap();
+			}
+
+			protected void onPostExecute(Map<String, String> result) {
+				int pad = LayoutUtils.getPadding(TestingActivity.this);
+				for(Entry<String, String> e : result.entrySet()) {
+					TextView title = new TextView(TestingActivity.this);
+					title.setTextSize(18);
+					title.setText(e.getKey());
+					status.addView(title);
+					TextView content = new TextView(TestingActivity.this);
+					content.setPadding(0, 0, 0, pad);
+					content.setText(e.getValue());
+					status.addView(content);
+				}
+				scroll.scrollTo(0, 0);
+			}
+		}.execute();
+	}
+
+	private Map<String, String> getStatusMap() {
+		Map<String, String> statusMap = new LinkedHashMap<String, String>();
+		// Is mobile data available?
+		Object o = getSystemService(CONNECTIVITY_SERVICE);
+		ConnectivityManager cm = (ConnectivityManager) o;
+		NetworkInfo mobile = cm.getNetworkInfo(TYPE_MOBILE);
+		boolean mobileAvailable = mobile != null && mobile.isAvailable();
+		// Is mobile data enabled?
+		boolean mobileEnabled = false;
+		try {
+			Class<?> clazz = Class.forName(cm.getClass().getName());
+			Method method = clazz.getDeclaredMethod("getMobileDataEnabled");
+			method.setAccessible(true);
+			mobileEnabled = (Boolean) method.invoke(cm);
+		} catch(ClassNotFoundException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} catch(NoSuchMethodException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} catch(IllegalAccessException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} catch(IllegalArgumentException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} catch(InvocationTargetException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+		// Is mobile data connected ?
+		boolean mobileConnected = mobile != null && mobile.isConnected();
+
+		// Strings aren't loaded from resources as this activity is temporary
+		String mobileStatus;
+		if(mobileAvailable) mobileStatus = "Available, ";
+		else mobileStatus = "Not available, ";
+		if(mobileEnabled) mobileStatus += "enabled, ";
+		else mobileStatus += "not enabled, ";
+		if(mobileConnected) mobileStatus += "connected";
+		else mobileStatus += "not connected";
+		statusMap.put("Mobile data:", mobileStatus);
+
+		// Is wifi available?
+		NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI);
+		boolean wifiAvailable = wifi != null && wifi.isAvailable();
+		// Is wifi enabled?
+		WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
+		boolean wifiEnabled = wm != null &&
+				wm.getWifiState() == WIFI_STATE_ENABLED;
+		// Is wifi connected?
+		boolean wifiConnected = wifi != null && wifi.isConnected();
+
+		String wifiStatus;
+		if(wifiAvailable) wifiStatus = "Available, ";
+		else wifiStatus = "Not available, ";
+		if(wifiEnabled) wifiStatus += "enabled, ";
+		else wifiStatus += "not enabled, ";
+		if(wifiConnected) wifiStatus += "connected";
+		else wifiStatus += "not connected";
+		statusMap.put("Wi-Fi:", wifiStatus);
+
+		// Is Bluetooth available?
+		BluetoothAdapter bt = null;
+		try {
+			bt = androidExecutor.call(new Callable<BluetoothAdapter>() {
+				public BluetoothAdapter call() throws Exception {
+					return BluetoothAdapter.getDefaultAdapter();
+				}
+			});
+		} catch(InterruptedException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} catch(ExecutionException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+		boolean btAvailable = bt != null;
+		// Is Bluetooth enabled?
+		boolean btEnabled = bt != null && bt.isEnabled() &&
+				!StringUtils.isNullOrEmpty(bt.getAddress());
+		// Is Bluetooth connectable?
+		boolean btConnectable = bt != null &&
+				(bt.getScanMode() == SCAN_MODE_CONNECTABLE ||
+				bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+		// Is Bluetooth discoverable?
+		boolean btDiscoverable = bt != null &&
+				bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
+
+		String btStatus;
+		if(btAvailable) btStatus = "Available, ";
+		else btStatus = "Not available, ";
+		if(btEnabled) btStatus += "enabled, ";
+		else btStatus += "not enabled, ";
+		if(btConnectable) btStatus += "connectable, ";
+		else btStatus += "not connectable, ";
+		if(btDiscoverable) btStatus += "discoverable";
+		else btStatus += "not discoverable";
+		statusMap.put("Bluetooth:", btStatus);
+
+		Plugin torPlugin = pluginManager.getPlugin(new TransportId("tor"));
+		boolean torPluginEnabled = torPlugin != null;
+		boolean torPluginRunning = torPlugin != null && torPlugin.isRunning();
+
+		String torPluginStatus;
+		if(torPluginEnabled) torPluginStatus = "Enabled, ";
+		else torPluginStatus = "Not enabled, ";
+		if(torPluginRunning) torPluginStatus += "running";
+		else torPluginStatus += "not running";
+		statusMap.put("Tor plugin:", torPluginStatus);
+
+		Plugin lanPlugin = pluginManager.getPlugin(new TransportId("lan"));
+		boolean lanPluginEnabled = lanPlugin != null;
+		boolean lanPluginRunning = lanPlugin != null && lanPlugin.isRunning();
+
+		String lanPluginStatus;
+		if(lanPluginEnabled) lanPluginStatus = "Enabled, ";
+		else lanPluginStatus = "Not enabled, ";
+		if(lanPluginRunning) lanPluginStatus += "running";
+		else lanPluginStatus += "not running";
+		statusMap.put("LAN plugin:", lanPluginStatus);
+
+		Plugin btPlugin = pluginManager.getPlugin(new TransportId("bt"));
+		boolean btPluginEnabled = btPlugin != null;
+		boolean btPluginRunning = btPlugin != null && btPlugin.isRunning();
+
+		String btPluginStatus;
+		if(btPluginEnabled) btPluginStatus = "Enabled, ";
+		else btPluginStatus = "Not enabled, ";
+		if(btPluginRunning) btPluginStatus += "running";
+		else btPluginStatus += "not running";
+		statusMap.put("Bluetooth plugin:", btPluginStatus);
+
+		StringBuilder log = new StringBuilder();
+		try {
+			Runtime runtime = Runtime.getRuntime();
+			Process process = runtime.exec("logcat -d -s TorPlugin");
+			Scanner scanner = new Scanner(process.getInputStream());
+			while(scanner.hasNextLine()) {
+				log.append(scanner.nextLine());
+				log.append('\n');
+			}
+			scanner.close();
+		} catch(IOException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+		statusMap.put("Tor log:", log.toString());
+
+		return Collections.unmodifiableMap(statusMap);
+	}
+
+	private void share() {
+		new AsyncTask<Void, Void, Map<String, String>>() {
+
+			protected Map<String, String> doInBackground(Void... args) {
+				return getStatusMap();
+			}
+
+			protected void onPostExecute(Map<String, String> result) {
+				try {
+					File shared = Environment.getExternalStorageDirectory();
+					temp = File.createTempFile("debug", "txt", shared);
+					if(LOG.isLoggable(INFO))
+						LOG.info("Writing to " + temp.getPath());
+					PrintStream p = new PrintStream(new FileOutputStream(temp));
+					for(Entry<String, String> e : result.entrySet()) {
+						p.println(e.getKey());
+						p.println(e.getValue());
+						p.println();
+					}
+					p.flush();
+					p.close();
+					sendEmail(Uri.fromFile(temp));
+				} catch(IOException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
+			}
+		}.execute();
+	}
+
+	private void sendEmail(Uri attachment) {
+		Intent i = new Intent(ACTION_SEND);
+		i.setType("message/rfc822");
+		i.putExtra(EXTRA_EMAIL, new String[] { "debug@briarproject.org" });
+		i.putExtra(EXTRA_SUBJECT, "Debugging information");
+		i.putExtra(EXTRA_STREAM, attachment);
+		startActivity(Intent.createChooser(i, "Send to developers"));
+	}
+}
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
index b2609f435d..4a875f5fb6 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
@@ -68,6 +68,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 
 	private volatile boolean running = false;
 	private volatile boolean wasEnabled = false, isEnabled = false;
+	private volatile BluetoothServerSocket socket = null;
 
 	// Non-null if running has ever been true
 	private volatile BluetoothAdapter adapter = null;
@@ -149,7 +150,8 @@ class DroidtoothPlugin implements DuplexPlugin {
 			tryToClose(ss);
 			return;
 		}
-		acceptContactConnections(ss);
+		socket = ss;
+		acceptContactConnections();
 	}
 
 	private boolean enableBluetooth() {
@@ -198,15 +200,15 @@ class DroidtoothPlugin implements DuplexPlugin {
 		}
 	}
 
-	private void acceptContactConnections(BluetoothServerSocket ss) {
+	private void acceptContactConnections() {
 		while(true) {
 			BluetoothSocket s;
 			try {
-				s = ss.accept();
+				s = socket.accept();
 			} catch(IOException e) {
 				// This is expected when the socket is closed
 				if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
-				tryToClose(ss);
+				tryToClose(socket);
 				return;
 			}
 			callback.incomingConnectionCreated(wrapSocket(s));
@@ -220,6 +222,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 
 	public void stop() {
 		running = false;
+		if(socket != null) tryToClose(socket);
 		// Disable Bluetooth if we enabled it at startup
 		if(isEnabled && !wasEnabled) disableBluetooth();
 	}
@@ -246,6 +249,10 @@ class DroidtoothPlugin implements DuplexPlugin {
 		}
 	}
 
+	public boolean isRunning() {
+		return running && socket != null;
+	}
+
 	public boolean shouldPoll() {
 		return true;
 	}
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
index ed526aee2d..80ac29e959 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
@@ -72,7 +72,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 	private final File torDirectory, torFile, geoIpFile, configFile, doneFile;
 	private final File cookieFile, pidFile, hostnameFile;
 
-	private volatile boolean running = false;
+	private volatile boolean running = false, networkEnabled = false;
 	private volatile Process tor = null;
 	private volatile int pid = -1;
 	private volatile ServerSocket socket = null;
@@ -509,6 +509,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 		if(!running) return;
 		if(LOG.isLoggable(INFO)) LOG.info("Enabling network: " + enable);
 		controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
+		networkEnabled = enable;
 	}
 
 	public void stop() throws IOException {
@@ -534,6 +535,10 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 		}
 	}
 
+	public boolean isRunning() {
+		return running && networkEnabled;
+	}
+
 	public boolean shouldPoll() {
 		return true;
 	}
diff --git a/briar-api/src/org/briarproject/api/plugins/Plugin.java b/briar-api/src/org/briarproject/api/plugins/Plugin.java
index 0eb66620ee..3389a73577 100644
--- a/briar-api/src/org/briarproject/api/plugins/Plugin.java
+++ b/briar-api/src/org/briarproject/api/plugins/Plugin.java
@@ -23,6 +23,9 @@ public interface Plugin {
 	/** Stops the plugin. */
 	void stop() throws IOException;
 
+	/** Returns true if the plugin is running. */
+	boolean isRunning();
+
 	/**
 	 * Returns true if the plugin's {@link #poll(Collection)} method should be
 	 * called periodically to attempt to establish connections.
diff --git a/briar-api/src/org/briarproject/api/plugins/PluginManager.java b/briar-api/src/org/briarproject/api/plugins/PluginManager.java
index 330de8669b..efc336cf02 100644
--- a/briar-api/src/org/briarproject/api/plugins/PluginManager.java
+++ b/briar-api/src/org/briarproject/api/plugins/PluginManager.java
@@ -2,6 +2,7 @@ package org.briarproject.api.plugins;
 
 import java.util.Collection;
 
+import org.briarproject.api.TransportId;
 import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 
@@ -11,6 +12,12 @@ import org.briarproject.api.plugins.duplex.DuplexPlugin;
  */
 public interface PluginManager extends Service {
 
+	/**
+	 * Returns the plugin for the given transport, or null if no such plugin
+	 * is running.
+	 */
+	Plugin getPlugin(TransportId t);
+
 	/** Returns any running duplex plugins that support invitations. */
 	Collection<DuplexPlugin> getInvitationPlugins();
 }
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index b78aba1bb2..6dee08bb2c 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -9,6 +9,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -56,6 +57,7 @@ class PluginManagerImpl implements PluginManager {
 	private final Poller poller;
 	private final ConnectionDispatcher dispatcher;
 	private final UiCallback uiCallback;
+	private final Map<TransportId, Plugin> plugins;
 	private final List<SimplexPlugin> simplexPlugins;
 	private final List<DuplexPlugin> duplexPlugins;
 
@@ -73,6 +75,7 @@ class PluginManagerImpl implements PluginManager {
 		this.poller = poller;
 		this.dispatcher = dispatcher;
 		this.uiCallback = uiCallback;
+		plugins = new ConcurrentHashMap<TransportId, Plugin>();
 		simplexPlugins = new CopyOnWriteArrayList<SimplexPlugin>();
 		duplexPlugins = new CopyOnWriteArrayList<DuplexPlugin>();
 	}
@@ -104,10 +107,7 @@ class PluginManagerImpl implements PluginManager {
 		}
 		// Start the poller
 		if(LOG.isLoggable(INFO)) LOG.info("Starting poller");
-		List<Plugin> plugins = new ArrayList<Plugin>();
-		plugins.addAll(simplexPlugins);
-		plugins.addAll(duplexPlugins);
-		poller.start(Collections.unmodifiableList(plugins));
+		poller.start(plugins.values());
 		return true;
 	}
 
@@ -115,8 +115,7 @@ class PluginManagerImpl implements PluginManager {
 		// Stop the poller
 		if(LOG.isLoggable(INFO)) LOG.info("Stopping poller");
 		poller.stop();
-		int plugins = simplexPlugins.size() + duplexPlugins.size();
-		final CountDownLatch latch = new CountDownLatch(plugins);
+		final CountDownLatch latch = new CountDownLatch(plugins.size());
 		// Stop the simplex plugins
 		if(LOG.isLoggable(INFO)) LOG.info("Stopping simplex plugins");
 		for(SimplexPlugin plugin : simplexPlugins)
@@ -125,6 +124,7 @@ class PluginManagerImpl implements PluginManager {
 		if(LOG.isLoggable(INFO)) LOG.info("Stopping duplex plugins");
 		for(DuplexPlugin plugin : duplexPlugins)
 			pluginExecutor.execute(new PluginStopper(plugin, latch));
+		plugins.clear();
 		simplexPlugins.clear();
 		duplexPlugins.clear();
 		// Wait for all the plugins to stop
@@ -139,6 +139,10 @@ class PluginManagerImpl implements PluginManager {
 		return true;
 	}
 
+	public Plugin getPlugin(TransportId t) {
+		return plugins.get(t);
+	}
+
 	public Collection<DuplexPlugin> getInvitationPlugins() {
 		List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
 		for(DuplexPlugin d : duplexPlugins)
@@ -185,6 +189,7 @@ class PluginManagerImpl implements PluginManager {
 					boolean started = plugin.start();
 					long duration = clock.currentTimeMillis() - start;
 					if(started) {
+						plugins.put(id, plugin);
 						simplexPlugins.add(plugin);
 						if(LOG.isLoggable(INFO)) {
 							String name = plugin.getClass().getSimpleName();
@@ -246,6 +251,7 @@ class PluginManagerImpl implements PluginManager {
 					boolean started = plugin.start();
 					long duration = clock.currentTimeMillis() - start;
 					if(started) {
+						plugins.put(id, plugin);
 						duplexPlugins.add(plugin);
 						if(LOG.isLoggable(INFO)) {
 							String name = plugin.getClass().getSimpleName();
diff --git a/briar-core/src/org/briarproject/plugins/file/FilePlugin.java b/briar-core/src/org/briarproject/plugins/file/FilePlugin.java
index 6b286559aa..77ed42e42c 100644
--- a/briar-core/src/org/briarproject/plugins/file/FilePlugin.java
+++ b/briar-core/src/org/briarproject/plugins/file/FilePlugin.java
@@ -55,6 +55,10 @@ public abstract class FilePlugin implements SimplexPlugin {
 		return maxLatency;
 	}
 
+	public boolean isRunning() {
+		return running;
+	}
+
 	public SimplexTransportReader createReader(ContactId c) {
 		return null;
 	}
diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
index 4f483dc89e..13401c290c 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
@@ -146,6 +146,10 @@ abstract class TcpPlugin implements DuplexPlugin {
 		if(socket != null) tryToClose(socket);
 	}
 
+	public boolean isRunning() {
+		return running && socket != null && socket.isBound();
+	}
+
 	public boolean shouldPoll() {
 		return true;
 	}
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
index 3bc65ce0f7..4572df83e1 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
@@ -172,6 +172,10 @@ class BluetoothPlugin implements DuplexPlugin {
 		tryToClose(socket);
 	}
 
+	public boolean isRunning() {
+		return running;
+	}
+
 	public boolean shouldPoll() {
 		return true;
 	}
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
index 58f1c421e3..4913cd82db 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
@@ -98,23 +98,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 		}
 	}
 
-	private boolean resetModem() {
-		if(!running) return false;
-		for(String portName : serialPortList.getPortNames()) {
-			if(LOG.isLoggable(INFO))
-				LOG.info("Trying to initialise modem on " + portName);
-			modem = modemFactory.createModem(this, portName);
-			try {
-				if(!modem.start()) continue;
-				if(LOG.isLoggable(INFO))
-					LOG.info("Initialised modem on " + portName);
-				return true;
-			} catch(IOException e) {
-				if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			}
-		}
-		running = false;
-		return false;
+	public boolean isRunning() {
+		return running;
 	}
 
 	public boolean shouldPoll() {
@@ -180,6 +165,25 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 		}
 	}
 
+	private boolean resetModem() {
+		if(!running) return false;
+		for(String portName : serialPortList.getPortNames()) {
+			if(LOG.isLoggable(INFO))
+				LOG.info("Trying to initialise modem on " + portName);
+			modem = modemFactory.createModem(this, portName);
+			try {
+				if(!modem.start()) continue;
+				if(LOG.isLoggable(INFO))
+					LOG.info("Initialised modem on " + portName);
+				return true;
+			} catch(IOException e) {
+				if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			}
+		}
+		running = false;
+		return false;
+	}
+
 	public DuplexTransportConnection createConnection(ContactId c) {
 		if(!running) return null;
 		// Get the ISO 3166 code for the caller's country
-- 
GitLab