Compare commits
730 Commits
android-0.
...
android-0.
Author | SHA1 | Date | |
---|---|---|---|
234bc6e5a0 | |||
b0131843ae | |||
c32bda66b5 | |||
6d726df1dc | |||
5b5a99f512 | |||
a11dd1e4e6 | |||
2190c59d73 | |||
c6e06e25a8 | |||
a559eb4fab | |||
d1110aefc4 | |||
9a5c63f620 | |||
d4e9195a6c | |||
e9d2b9f53c | |||
40f3d91ebb | |||
fae4b7e42d | |||
eb700e34ba | |||
6828d985d2 | |||
05a2132295 | |||
dbee390bba | |||
0b5ae4edf8 | |||
bd741cd500 | |||
4d7bc0f92b | |||
d2959ddc3f | |||
ecb071ee88 | |||
624aa27e31 | |||
2407e9be46 | |||
50973b5c06 | |||
8165e49300 | |||
77749dd7f9 | |||
ef7e4cf610 | |||
a7b2bf148b | |||
a2278179f9 | |||
3b3bcb30da | |||
d78b68d285 | |||
00de9e98d2 | |||
a543280a56 | |||
f036544744 | |||
8aa9ce9303 | |||
1c605c16cf | |||
fc7f703658 | |||
163ef0512b | |||
6709bebc6f | |||
c7fad6940a | |||
4b1ee639b7 | |||
d2fa17fa66 | |||
87e12846b3 | |||
97d1367180 | |||
a0419c9eb7 | |||
5191118b87 | |||
f5214e4b99 | |||
9564855cce | |||
17ab043a4b | |||
32b2b0ce75 | |||
b77e2ebbe5 | |||
9eeab68cdb | |||
96257015a9 | |||
d7f6e3688c | |||
5ef434e29f | |||
852d695dac | |||
96cb8ab410 | |||
cd158cca84 | |||
b71a0a27d3 | |||
64268c7af8 | |||
95749f032e | |||
0ac1ae56b0 | |||
c52bc45910 | |||
064ebc6857 | |||
6352cd9412 | |||
db6c74b4b8 | |||
a8d699bea2 | |||
5d9cb0029f | |||
51f7fca1ea | |||
c61ccd32ba | |||
1debd64bc3 | |||
561bcfe3fa | |||
1c1f77f5c5 | |||
420526a7c4 | |||
99496be412 | |||
b6f1cdc769 | |||
75e4153f4e | |||
eefa5b8064 | |||
bae8c7ec00 | |||
9d32e44547 | |||
80c8069769 | |||
42a0d552c7 | |||
65848dd22b | |||
610de188a4 | |||
f1cd3032c5 | |||
1a922ba04a | |||
bdd59734ec | |||
fe162e4f5c | |||
69e30e97b8 | |||
5b4b151079 | |||
513fbe0c9f | |||
de23a76e6b | |||
c9936894d8 | |||
cb6efd9ed8 | |||
ad245003bf | |||
9f27aedc49 | |||
d8f883dce8 | |||
7db1fbac94 | |||
19464124d6 | |||
5ba616facc | |||
590a8183aa | |||
9a45bbd18c | |||
c5eedc0a5e | |||
715302c813 | |||
3327ed722a | |||
40afd69a54 | |||
241381c7fa | |||
7a7ba093db | |||
f0e6744760 | |||
99002c1c5c | |||
605a6c1cf4 | |||
954a9cc46b | |||
1ef838b966 | |||
c672ca05f5 | |||
c493e73889 | |||
2b7c280f5b | |||
23eab8a90a | |||
c59103eb76 | |||
f00a35ee09 | |||
af93725c01 | |||
3953301c57 | |||
2dab9d5d4f | |||
b77666fa26 | |||
eca931c1b5 | |||
86ae77701f | |||
c1ee0c4d9e | |||
e632b35862 | |||
20d2dcd891 | |||
61d5ba5a7c | |||
339f688b7c | |||
fed11e703a | |||
438df8142a | |||
7b3730be24 | |||
d6c20bafb3 | |||
b8998db3a3 | |||
9ab1c84878 | |||
2f3335d361 | |||
0e8d900ed4 | |||
2c7ce0b7c6 | |||
3e2bdacf89 | |||
64c32076a1 | |||
4d75ce7de1 | |||
269ae2f569 | |||
a42a4b2c99 | |||
96f5c2b488 | |||
09ab9779aa | |||
97ed0a3a8f | |||
ec6d225dc6 | |||
800a332691 | |||
45eae17561 | |||
092365cab2 | |||
c98c2f101d | |||
8e86634a41 | |||
7424e5b707 | |||
5175c937a9 | |||
2692a567ab | |||
2de971fb52 | |||
403b2e8cd9 | |||
22141e723a | |||
419758125e | |||
6db307c681 | |||
cdec6d2f98 | |||
18a6dc9719 | |||
fb92d7858a | |||
b8e005bdd5 | |||
6568489b27 | |||
3ff3e14fc2 | |||
092591f4ec | |||
895a3a1dcc | |||
dc4ce3f8c7 | |||
7e3b9d5065 | |||
e58ffc9fd4 | |||
0d029988c3 | |||
6f01989a4b | |||
19aeaec406 | |||
b4d1241a9e | |||
fef4aa0e86 | |||
766266b147 | |||
93410c07bb | |||
dc27a782b0 | |||
b633df73c0 | |||
41d1200df7 | |||
c9a62fba9a | |||
c9598fa831 | |||
9965c31b2d | |||
43de6425b2 | |||
b98cdf3e0b | |||
26c11ebaed | |||
c9c3de1d04 | |||
9b29b56982 | |||
8956fd7ce0 | |||
f1f94ea3e0 | |||
94b433aead | |||
5623d54114 | |||
9133a2f848 | |||
70324c3ecc | |||
5613d21324 | |||
9036bf3f6b | |||
94991d2df3 | |||
76f23946f0 | |||
862e856913 | |||
cf3de34cb6 | |||
81de79c725 | |||
228b6f1baa | |||
8e395cfd4e | |||
6d6123df9b | |||
321a49156c | |||
e8a47e17fb | |||
9df27ea168 | |||
cb6b7c4f48 | |||
ca623e6b18 | |||
8b6e02136e | |||
6a0493a578 | |||
bf2a437a82 | |||
ac949e3f5e | |||
7483251393 | |||
d690b7d861 | |||
829695d690 | |||
05c2dbd388 | |||
c8e1643326 | |||
d72c936a0e | |||
06d4d7d10d | |||
b506b5e740 | |||
2d65bd373c | |||
7c869adf58 | |||
61a7566007 | |||
9d42901079 | |||
fb31818a3c | |||
6355214b5f | |||
d5c0704477 | |||
411131b8a6 | |||
10ed266d2c | |||
bccfe03b5d | |||
a44ac8a45c | |||
5610752c6d | |||
7047913b45 | |||
a41aa79920 | |||
4fcc1121b7 | |||
514aa51224 | |||
0c46dc9bd0 | |||
4b7f951e32 | |||
a58a9d7540 | |||
d3eaebd324 | |||
37c366a528 | |||
8f6289984b | |||
7629bb54ce | |||
ee7d227990 | |||
4cc940c995 | |||
2336eebdd0 | |||
62035050c5 | |||
6775d57c22 | |||
d3a1910b2e | |||
aa43d960dc | |||
2e3047274e | |||
a3cef11e08 | |||
543fb51d76 | |||
4328db1908 | |||
69fbb5dc92 | |||
0c5d8f8e9e | |||
b88e150803 | |||
35fe44fc59 | |||
464adb9e71 | |||
66d370abeb | |||
11aded07ca | |||
5d0861e22e | |||
5778eb9d1c | |||
0e47bc5042 | |||
8f9a6922ad | |||
05cc0634b7 | |||
583666695c | |||
e67ba59e51 | |||
ab619f904d | |||
f2f7418c8b | |||
23c55d50fb | |||
e0acb322a5 | |||
2a1427054d | |||
d878d2d8a4 | |||
5386829edf | |||
5d74e7ffef | |||
332ec1e0ad | |||
060262ee52 | |||
c75fe55e56 | |||
bccf5e0965 | |||
6bd905a027 | |||
fc0b393b14 | |||
f2acde73fe | |||
77a7f5f603 | |||
d235da093f | |||
795d3ab314 | |||
dd40931a23 | |||
8b71e4fc2e | |||
ed61f0414e | |||
06ef95c7ac | |||
2936bfc2b7 | |||
9c655ffebf | |||
9d8fb684d2 | |||
0d744e269c | |||
36ffb6eda4 | |||
df7ee4bd05 | |||
d98d6abff3 | |||
260cc8a5a2 | |||
a0a1df8093 | |||
e4110eb894 | |||
d0264bf475 | |||
7ec8b0a592 | |||
e3ecac8fec | |||
4fdc7940dd | |||
9920ad34cd | |||
8a7025038a | |||
a47c80df8c | |||
a1a5aeaf6c | |||
3a8eeabe3e | |||
3e34bac295 | |||
66d0dce40c | |||
c8d3ee7aac | |||
959537adc2 | |||
7ca050fdf5 | |||
07130abf23 | |||
ba82d59b89 | |||
8819dc5f30 | |||
a034b78dfd | |||
2dc56d57d4 | |||
3aff1c4f75 | |||
55509adda6 | |||
19def413c1 | |||
63a0e2117f | |||
21e0b2a667 | |||
6ce15e27de | |||
7a0a56373d | |||
37da05ca98 | |||
f003bbbfa4 | |||
a6978bb161 | |||
b1ec76de5a | |||
dc58796c97 | |||
c7075c3fc4 | |||
8d4f1b174d | |||
6f29991829 | |||
a414b10ce8 | |||
030fc60445 | |||
9d215353e8 | |||
f0a2166ae0 | |||
af097474de | |||
5a1fc32da4 | |||
7218b79643 | |||
ccd5ae45df | |||
3584890277 | |||
4e17010f59 | |||
aa8009cb70 | |||
30d1816c43 | |||
02c25ba174 | |||
8f8bbcb19f | |||
696ae2bfc0 | |||
0ea468ea71 | |||
a2d9adb071 | |||
b8cc64d4ea | |||
6e3e99c62f | |||
84a63cc911 | |||
b8c3b7e3df | |||
04e190d2d0 | |||
ce2a762db2 | |||
76014f2081 | |||
6d570646f1 | |||
104c17cb9c | |||
0860ee83b0 | |||
707c0e9aa8 | |||
163bc2ce58 | |||
cec1f8fe52 | |||
97f52f8139 | |||
0094cc5637 | |||
01c994e7b2 | |||
b60ae00fd7 | |||
2340500083 | |||
a91261f5ca | |||
a145729353 | |||
0270444a94 | |||
01e22f4fb5 | |||
2e892841cb | |||
e271dc90ae | |||
77ced0bd1f | |||
ac67533ef2 | |||
305c834aa1 | |||
cbb2973b36 | |||
f360ab4d5d | |||
296d21d1d0 | |||
a5dd751227 | |||
d47bdf85d2 | |||
c2a33541b3 | |||
3e46d98481 | |||
124b1499b4 | |||
2d8529e691 | |||
4a135023b9 | |||
e9fbe8c2ef | |||
228d0204fc | |||
0ebecd9b64 | |||
c062a0f803 | |||
4f24517de9 | |||
d8636ff563 | |||
4e57f78931 | |||
ccbbc3c368 | |||
b9374b5ead | |||
ceaff935d6 | |||
255702b6bb | |||
2e68aa900b | |||
67eddba621 | |||
29fb1f0689 | |||
de605a1d5b | |||
41b49b7bc8 | |||
c691a11c3d | |||
2f6b1189ae | |||
a7a328238b | |||
c9c31ccb76 | |||
8457f279f2 | |||
fbf353858e | |||
99ef07d1fc | |||
a5f5e97e37 | |||
165bcc1c9d | |||
45efd6670f | |||
b601722b31 | |||
14e1a2dca1 | |||
d2385166cb | |||
ed17d59896 | |||
55cfd455ca | |||
f207cf3116 | |||
5825d1d2a5 | |||
077f9902a9 | |||
d77cbde3b3 | |||
62ff63665f | |||
1377aceb18 | |||
c7617ba856 | |||
08c6018483 | |||
f9f283409d | |||
4ef42cb462 | |||
65428dda8e | |||
6311ab4b67 | |||
0c20a45207 | |||
6712148010 | |||
07e6e293bc | |||
2dd1655e1e | |||
97037fe1d8 | |||
d79f797558 | |||
42649e02ea | |||
c6aeb79944 | |||
fb66ec62d6 | |||
15f1e18da1 | |||
15caf8a97c | |||
44989a42f2 | |||
3867eb6fda | |||
0755e79b1a | |||
3a1e43b322 | |||
6a2d494921 | |||
be14d65899 | |||
c42dc725d6 | |||
83ab1d09ae | |||
af30dc8e24 | |||
0268ce13ad | |||
5b897bc993 | |||
1fbf6b1b72 | |||
8095eed241 | |||
8916c123ef | |||
b742dd8ee8 | |||
b02b446e46 | |||
01b07fed5b | |||
2fa205daec | |||
3644d738ee | |||
c044c4de4d | |||
c705527113 | |||
8b79af434e | |||
2c8e9d62be | |||
9832779a50 | |||
9f535a3260 | |||
0beaec366f | |||
8c288ad559 | |||
df5d5ad38e | |||
8b1648c37b | |||
cea42e9ec4 | |||
64c44838a8 | |||
76f9259ee7 | |||
d437f45132 | |||
fc618ad9e5 | |||
15275680e8 | |||
7848a81110 | |||
207f9837d0 | |||
cda09ea4f5 | |||
2a15994a76 | |||
e954953130 | |||
8b51c26a6b | |||
d40f806be6 | |||
bf5b29da76 | |||
18c4276ba0 | |||
a4107e974a | |||
8dd35f6c0f | |||
5a4be4ae86 | |||
9527725760 | |||
b6a8fc02f5 | |||
7794a7db5b | |||
c03debf332 | |||
fbc56d4eb9 | |||
2a050b3ca1 | |||
48c8d84d2f | |||
b6d6258e95 | |||
47215495ed | |||
9e45f1998b | |||
788445f6ce | |||
a057e4a512 | |||
7a09670097 | |||
c92d881a51 | |||
8c2ba03880 | |||
9c9f871667 | |||
7f9758197d | |||
f6d1c093e4 | |||
5e045bc23b | |||
f667a81c6d | |||
a13a405b49 | |||
383ece497f | |||
075c7c09d7 | |||
7cec48e55f | |||
7ddd3c69c8 | |||
9d965a5504 | |||
abd4a99654 | |||
92c734624d | |||
81d0441d2b | |||
9c7b2142cf | |||
3da41888b7 | |||
bf47b901b7 | |||
54a446ebdb | |||
0eb0c67616 | |||
409d823dec | |||
0208e58a3b | |||
6d18e50a3a | |||
c748610280 | |||
7768c624f9 | |||
72ed6bd170 | |||
3d5b9938fd | |||
03b5927447 | |||
b7a6b4acd5 | |||
e27e1e55bd | |||
0b0511dbce | |||
02c370a04a | |||
4810c9e990 | |||
cc801de79d | |||
93fd4f7e0c | |||
abd9908a21 | |||
52d278134c | |||
c60e4f6b3e | |||
5b7452ff90 | |||
014fc9b79b | |||
9ca0ce3192 | |||
99d720c685 | |||
c46ba4f24b | |||
522178598b | |||
5b6658531e | |||
333455b738 | |||
5ebdeedb2b | |||
0af8d2145f | |||
5ac6d51289 | |||
238ab91092 | |||
0af8ed90f7 | |||
c761287a8a | |||
a77674603a | |||
9a2382d886 | |||
0d58d81bce | |||
1e4300cb83 | |||
28b0950990 | |||
536102658a | |||
6754f6b5b1 | |||
06d1903184 | |||
4d13e8adfd | |||
145b249394 | |||
a00c08bb49 | |||
449627be3d | |||
27be4aacb2 | |||
f4f849182d | |||
32d8a7112c | |||
4dbfff292b | |||
228d27d82b | |||
9d0858ad17 | |||
480dacb7f2 | |||
84edc743f5 | |||
b7dfc45b1e | |||
bd0ebc8852 | |||
8c0e2228a5 | |||
4dafc3e5af | |||
291294435a | |||
99588c3cb1 | |||
f7904e0c7e | |||
ae2fa4dce7 | |||
bdbc777a52 | |||
320e8d5153 | |||
c53b98d2b9 | |||
e1236d2824 | |||
d7ac916eeb | |||
5b9203f77d | |||
9757d6e396 | |||
c2ff90af91 | |||
c1e8719d0e | |||
56198bf771 | |||
73286f43f6 | |||
cd3157038c | |||
9359c7a726 | |||
058f41ec73 | |||
e4c9095626 | |||
dd90ea9874 | |||
53c7770e4e | |||
21274f0335 | |||
4ccf3e713d | |||
2ef4d71cdb | |||
c2bc999847 | |||
d3f37a21a5 | |||
658d2a68e2 | |||
490148cb5b | |||
430d56b681 | |||
405bb3317e | |||
bbb41c9c54 | |||
5c9c438e28 | |||
27239cf09d | |||
b853c3af39 | |||
48f84f1a1b | |||
eb4ae2c66d | |||
17be8fb3f7 | |||
bf5accb121 | |||
4749e470b5 | |||
0553815777 | |||
57d81fb14d | |||
ee97af6e4f | |||
6397a93cac | |||
03465185f9 | |||
ddd9a195e6 | |||
664985461a | |||
6d340dc056 | |||
f312b7c6f1 | |||
a42bd73de3 | |||
4424392bdc | |||
bfe71213f8 | |||
cd2a12c8ed | |||
4ca2ed756f | |||
a4abfd8fb8 | |||
9d754c29ae | |||
0101e63bce | |||
75fe8bfbe0 | |||
806f6edbf1 | |||
767ff4f3d2 | |||
07fafb03b6 | |||
e98f86b29a | |||
18bc4f141d | |||
a3792bad7a | |||
745bd3fa94 | |||
97692a4635 | |||
3fe7575dab | |||
a7fc5090f3 | |||
6154f64120 | |||
0e5ca23732 | |||
c47e7dab31 | |||
472fa6d49e | |||
60ece9d4d2 | |||
afd656c6b4 | |||
78e250a207 | |||
134db2ecd3 | |||
53caad9f2a | |||
742df967e2 | |||
5d0c5c30eb | |||
cb9924a0bf | |||
3ceed9a6b3 | |||
c3d95a608d | |||
1b004a628e | |||
b080bd387a | |||
7a429674a7 | |||
45a1511cab | |||
6c7be97ed5 | |||
3244509cab | |||
43a20d18c5 | |||
e2332543ec | |||
4d68da45b4 | |||
084ed85467 | |||
1d060fd419 | |||
a95ca82a3e | |||
c13101d535 | |||
7dc7697c14 | |||
91fb0a2248 | |||
2663cc7d57 | |||
f9fce317d3 | |||
f87a3eb03c | |||
36a0f2c678 | |||
f8fe3f082a | |||
6e130185de | |||
006fc1dc51 | |||
87836ddab6 | |||
ff7154b525 | |||
7a829236b9 | |||
d3edd31155 | |||
455726f05a | |||
5dc9d729f4 | |||
6d42b93de4 | |||
39b54c41ab | |||
f8920298ee | |||
cc3b37c4c0 | |||
0cdd5a5d88 | |||
b61bc8f5ef | |||
2a6a2ef7a7 | |||
245f69f2d3 | |||
4c43b6f5d5 | |||
28230c914d | |||
f7e1acdb68 | |||
995cd7f327 | |||
1e024c22da | |||
4e853a753f | |||
01547e9a20 | |||
09b2af14fd | |||
f5d06470e4 | |||
abe20c95d0 | |||
c9abfa80f0 | |||
f34ef46dbf | |||
0c30296888 | |||
3fd373ad8c | |||
41091dce25 | |||
0fbaeb0ea4 | |||
19791a5965 | |||
9d4c0aa839 | |||
0b12a952cb | |||
a95aa6e89b | |||
f13c772509 | |||
0f28c4d807 | |||
a649d434c7 | |||
23c5710d42 | |||
52a60b37e2 | |||
adb5c56f26 | |||
177ac2e3a1 | |||
1219725e6a |
27
.mtn-ignore
27
.mtn-ignore
@ -22,3 +22,30 @@ _jsp\.java$
|
||||
~$
|
||||
/build/
|
||||
/classes/
|
||||
|
||||
# Android-specific ignores
|
||||
^routerjars/libs
|
||||
local.properties
|
||||
signing.properties
|
||||
|
||||
#IntelliJ IDEA
|
||||
^.idea
|
||||
.*.iml
|
||||
.*.ipr
|
||||
.*.iws
|
||||
|
||||
#Gradle
|
||||
^.gradle
|
||||
build
|
||||
|
||||
# I2P-specific ignores
|
||||
^app/src/main/res/drawable/i2plogo.png
|
||||
^app/src/main/res/raw/blocklist_txt
|
||||
^app/src/main/res/raw/hosts_txt
|
||||
^app/src/main/res/raw/license_
|
||||
^app/src/main/res/raw/certificates_zip
|
||||
^app/src/main/assets/themes/console/images
|
||||
^app/src/main/assets/themes/console/light/console.css
|
||||
^app/src/main/assets/themes/console/light/images/header.png
|
||||
^scripts/build.number
|
||||
^scripts/version.properties
|
||||
|
18
.tx/config
Normal file
18
.tx/config
Normal file
@ -0,0 +1,18 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA: uk, zh_CN: zh
|
||||
|
||||
[I2P.android]
|
||||
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = app/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 50
|
||||
|
||||
[I2P.android_lib_client]
|
||||
file_filter = client/src/main/res/values-<lang>/strings.xml
|
||||
source_file = client/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 50
|
||||
|
@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.i2p.android.router"
|
||||
android.versionCode="4208704"
|
||||
android.versionName="0.8.7-4_b1-API8"
|
||||
android:installLocation="preferExternal"
|
||||
>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<uses-sdk android:minSdkVersion="8" />
|
||||
|
||||
<application android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_launcher_itoopie" >
|
||||
<service android:name=".service.RouterService"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_launcher_itoopie" />
|
||||
<provider android:name=".provider.CacheProvider"
|
||||
android:authorities="net.i2p.android.router" />
|
||||
<activity android:name=".activity.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_launcher_itoopie"
|
||||
android.theme="@android:style/Theme.NoTitleBar"
|
||||
android:launchMode="singleTop" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".activity.NewsActivity"
|
||||
android:label="I2P News"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android.theme="@android:style/Theme.NoTitleBar" >
|
||||
</activity>
|
||||
<activity android:name=".activity.TextResourceActivity"
|
||||
android:label="I2P Information"
|
||||
android.theme="@android:style/Theme.NoTitleBar" >
|
||||
</activity>
|
||||
<activity android:name=".activity.LicenseActivity"
|
||||
android:label="I2P License Information"
|
||||
android.theme="@android:style/Theme.NoTitleBar" >
|
||||
</activity>
|
||||
<activity android:name=".activity.WebActivity"
|
||||
android:label="I2P Web Browser"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android.theme="@android:style/Theme.NoTitleBar" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:host="*.i2p" android:scheme="http" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".activity.SettingsActivity"
|
||||
android:label="I2P Settings"
|
||||
android.theme="@android:style/Theme.NoTitleBar"
|
||||
android:launchMode="singleTop" >
|
||||
</activity>
|
||||
<activity android:name=".activity.AddressbookActivity"
|
||||
android:label="I2P Address Book"
|
||||
android.theme="@android:style/Theme.NoTitleBar"
|
||||
android:launchMode="singleTop" >
|
||||
</activity>
|
||||
<activity android:name=".activity.LogActivity"
|
||||
android:label="I2P Logs"
|
||||
android.theme="@android:style/Theme.NoTitleBar" >
|
||||
</activity>
|
||||
<activity android:name=".activity.PeersActivity"
|
||||
android:label="I2P Peers and Transport Status"
|
||||
android.theme="@android:style/Theme.NoTitleBar"
|
||||
android:launchMode="singleTop" >
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
33
CHANGELOG
Normal file
33
CHANGELOG
Normal file
@ -0,0 +1,33 @@
|
||||
0.9.19
|
||||
* Made internal state handling more stable
|
||||
* Added graceful shutdown support
|
||||
* Improved logging
|
||||
* Bug fixes and translation updates
|
||||
|
||||
0.9.18 / 2015-03-04 / c2f4831a1617f4ce716a08640446fdd992c751ff
|
||||
* I2P can start automatically when phone boots (configure in Setting)
|
||||
* Updated browser configuration guides for Orfox and Firefox
|
||||
* Tunnels for postman's mail server added to defaults (for new installs)
|
||||
* Settings options for configuring UDP and TCP ports
|
||||
* Bug fixes and translation updates
|
||||
|
||||
0.9.17.1 / 2014-12-14 / cd8bb5e3ac4238efac12179c78c4fa517fcaabec
|
||||
* Fixed crashes in addressbook and netDb status page
|
||||
* Fixed crash when opening an IRC client tunnel
|
||||
* Updated translations
|
||||
|
||||
0.9.17 / 2014-12-01 / bcf947f433876f643e0f6dff81aac88848b797d3
|
||||
* Migrated to the new Material design from Android Lollipop
|
||||
* Added a browser configuration guide
|
||||
* Improved the help screen
|
||||
* Upgraded the I2P router to 0.9.17
|
||||
* Various bug fixes and translation updates
|
||||
|
||||
0.9.15.1 / 2014-10-16 / 9cc4e80134cf9becb3838ed3cc78e0bed1363165
|
||||
* Fixed a configuration bug
|
||||
|
||||
0.9.15 / 2014-10-16 / 9b51f78b791c28a580d57f1a5019c94493de6231
|
||||
* Upgraded the I2P router to 0.9.15
|
||||
* Added a help screen with some basic information
|
||||
* Logs can now be copied to the clipboard
|
||||
* Various user interface improvements and fixes
|
151
README.md
Normal file
151
README.md
Normal file
@ -0,0 +1,151 @@
|
||||
# I2P Android
|
||||
|
||||
## Build process
|
||||
|
||||
### Dependencies:
|
||||
|
||||
- Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
||||
- Apache Ant 1.8.0 or higher
|
||||
- I2P source
|
||||
- Android SDK for API 21
|
||||
- Android Build Tools 21.1.2
|
||||
- Android Support Repository
|
||||
- Gradle 2.2.1
|
||||
|
||||
### Gradle
|
||||
|
||||
The build system is based on Gradle. There are several methods for setting Gradle up:
|
||||
|
||||
* It can be downloaded from [the Gradle website](http://www.gradle.org/downloads).
|
||||
|
||||
* Most distributions will have Gradle packages. Be careful to check the
|
||||
provided version; Debian and Ubuntu have old versions in their main
|
||||
repositories. There is a [PPA](https://launchpad.net/~cwchien/+archive/gradle)
|
||||
for Ubuntu with the latest version of Gradle.
|
||||
|
||||
* A Gradle wrapper is provided in the codebase. It takes all the same commands
|
||||
as the regular `gradle` command. The first time that any command is run, it
|
||||
will automatically download, cache and use the correct version of Gradle.
|
||||
This is the simplest way to get started with the codebase. To use it, replace
|
||||
`gradle` with `./gradlew` (or `./gradlew.bat` on Windows) in the commands
|
||||
below.
|
||||
|
||||
Gradle will pull dependencies over the clearnet by default. To send all Gradle
|
||||
connections from your user over Tor, create a `gradle.properties` file in
|
||||
`~/.gradle/` containing:
|
||||
|
||||
```
|
||||
systemProp.socksProxyHost=localhost
|
||||
systemProp.socksProxyPort=9150
|
||||
```
|
||||
|
||||
### Preparation
|
||||
|
||||
1. Download the Android SDK. The simplest method is to download [Android Studio](https://developer.android.com/sdk/installing/studio.html).
|
||||
|
||||
* If you are using an existing Android SDK, install the Android Support
|
||||
Repository via the SDK Manager.
|
||||
|
||||
2. Check out the [`i2p.i2p`](https://github.com/i2p/i2p.i2p) repository.
|
||||
|
||||
3. Create a `local.properties` file in `i2p.android.base/routerjars` containing:
|
||||
|
||||
```
|
||||
i2psrc=/path/to/i2p.i2p
|
||||
```
|
||||
|
||||
### Building from the command line
|
||||
|
||||
1. Create a `local.properties` file in `i2p.android.base` containing:
|
||||
|
||||
```
|
||||
sdk.dir=/path/to/android-studio/sdk
|
||||
```
|
||||
|
||||
2. `gradle assembleDebug`
|
||||
|
||||
3. The APK will be placed in `i2p.android.base/app/build/outputs/apk`.
|
||||
|
||||
### Building with Android Studio
|
||||
|
||||
1. Import `i2p.android.base` into Android Studio. (This creates the `local.properties` file automatically).
|
||||
|
||||
2. Build and run the app (`Shift+F10`).
|
||||
|
||||
### Signing release builds
|
||||
|
||||
1. Create a `signing.properties` file in `i2p.android.base` containing:
|
||||
|
||||
```
|
||||
STORE_FILE=/path/to/android.keystore
|
||||
STORE_PASSWORD=store.password
|
||||
KEY_ALIAS=key.alias
|
||||
KEY_PASSWORD=key.password
|
||||
```
|
||||
|
||||
2. `gradle assembleRelease`
|
||||
|
||||
## Client library
|
||||
|
||||
### "Uploading" to the local Maven repository (to use a local build of the library in a project)
|
||||
|
||||
1. `gradle :client:installArchives`
|
||||
|
||||
2. Add the local Maven repository to your project. For Gradle projects, add the following above any existing repositories (so it is checked first):
|
||||
|
||||
```
|
||||
repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
```
|
||||
|
||||
### Uploading to Maven Central via Sonatype OSSRH
|
||||
|
||||
1. Add the following lines to your `~/.gradle/gradle.properties` (filling in the blanks):
|
||||
|
||||
```
|
||||
signing.keyId=
|
||||
signing.password=
|
||||
signing.secretKeyRingFile=/path/to/secring.gpg
|
||||
ossrhUsername=
|
||||
ossrhPassword=
|
||||
```
|
||||
|
||||
2. `gradle :client:uploadArchives`
|
||||
|
||||
### Commands from the old build instructions that might be useful
|
||||
|
||||
```
|
||||
# Create the android 4.4 (API 19) virtual device
|
||||
# (don't make a custom hardware profile)
|
||||
../android-sdk-linux/tools/android create avd --name i2p --target android-19
|
||||
|
||||
# then run the emulator:
|
||||
# This may take a LONG time the first time (half an hour or more)...
|
||||
# Run the debugger to ensure it is making progress
|
||||
# -no-boot-anim for faster boot
|
||||
# -dns-server 8.8.8.8 if the router can't reseed
|
||||
# ../android-sdk-linux/tools/emulator -avd i2p -no-boot-anim -dns-server 8.8.8.8 &
|
||||
../android-sdk-linux/tools/emulator -avd i2p &
|
||||
|
||||
# or to talk to a real device in debug mode:
|
||||
# You have to do this if you get a permission error -
|
||||
# Stop ddms, unplug the device, do the following,
|
||||
# then plug in the device, then start ddms
|
||||
adb kill-server
|
||||
sudo adb start-server
|
||||
adb devices
|
||||
|
||||
# Anyway, with I2P installed, click on the I2P icon on your device and enjoy!
|
||||
|
||||
#other helpful commands
|
||||
../android-sdk-linux/platform-tools/adb shell
|
||||
../android-sdk-linux/platform-tools/adb pull /some/file/on/emulator some-local-dir/
|
||||
|
||||
# copy the Dev Tools app from the emulator to your device
|
||||
adb -e pull /system/app/Development.apk ./Development.apk
|
||||
adb -d install Development.apk
|
||||
|
||||
# reinstall an existing apk onto the emulator
|
||||
adb -e install -r bin/I2PAndroid-debug.apk
|
||||
```
|
75
README.txt
75
README.txt
@ -1,75 +0,0 @@
|
||||
These instructions are for a recent Android SDK (1.6 or later).
|
||||
These instructions were last updated for SDK Tools Version 11 with
|
||||
SDK Platform-tools Version 5, June 2011.
|
||||
|
||||
The i2p source must be installed in ../i2p.i2p,
|
||||
or else add i2psrc=/path/to/source in the local.properties file.
|
||||
|
||||
=====================
|
||||
|
||||
#Download the SDK from http://developer.android.com/sdk/index.html
|
||||
#Unzip the android SDK in ../
|
||||
#So then the android tools will be in ../android-sdk-linux_86/tools/
|
||||
#
|
||||
# Run the GUI updater, which you must do to get an SDK Platform:
|
||||
../android-sdk-linux_86/tools/android &
|
||||
|
||||
# now go to the available packages tab, check the box and click refresh,
|
||||
# and download an SDK Platform
|
||||
# Since I2P is configured to run on 1.1 or higher
|
||||
# (API 2) download that one. Otherwise you must change the
|
||||
# target in default.properties from android-2 to andriod-x
|
||||
# where x is the API version.
|
||||
|
||||
# To run the debugger (ddms) you also need to download the
|
||||
# "Android SDK Platform-Tools" package from the GUI updater.
|
||||
|
||||
# create a file local.properties with the following line (without the leading # of course):
|
||||
# sdk.dir=/path/to/your/android-sdk-linux_86
|
||||
# The old property was sdk-location=/path/to/your/android-sdk-linux_86
|
||||
# but it changed in more recent tools.
|
||||
|
||||
# DO NOT create a new project or anything. It's all set up right here for you.
|
||||
|
||||
# Create the android 2.2 (API 8) virtual device
|
||||
# (don't make a custom hardware profile)
|
||||
../android-sdk-linux_86/tools/android create avd --name i2p --target 8
|
||||
|
||||
#then run the emulator:
|
||||
# This may take a LONG time the first time (half an hour or more)...
|
||||
# Run the debugger to ensure it is making progress
|
||||
# -no-boot-anim for faster boot
|
||||
# -dns-server 8.8.8.8 if the router can't reseed
|
||||
#../android-sdk-linux_86/tools/emulator -avd i2p -no-boot-anim -dns-server 8.8.8.8 &
|
||||
../android-sdk-linux_86/tools/emulator -avd i2p &
|
||||
|
||||
# or to talk to a real phone in debug mode:
|
||||
# You have to do this if you get a permission error -
|
||||
# Stop ddms, unplug the phone, do the following,
|
||||
# then plug in the phone, then start ddms
|
||||
adb kill-server
|
||||
sudo adb start-server
|
||||
adb devices
|
||||
|
||||
#then wait a couple minutes until the emulator is up
|
||||
#then install the I2P app
|
||||
ant install
|
||||
|
||||
#then run the debugger
|
||||
../android-sdk-linux_86/tools/ddms &
|
||||
|
||||
#to rebuild and reinstall to emulator or phone:
|
||||
ant reinstall
|
||||
|
||||
# Now click on the I2P icon on your phone!
|
||||
|
||||
#other helpful commands
|
||||
../android-sdk-linux_86/platform-tools/adb shell
|
||||
../android-sdk-linux_86/platform-tools/adb pull /some/file/on/emulator some-local-dir/
|
||||
|
||||
# copy the Dev Tools app from the emulator to your phone
|
||||
adb -e pull /system/app/Development.apk ./Development.apk
|
||||
adb -d install Development.apk
|
||||
|
||||
# reinstall an existing apk onto the emulator
|
||||
adb -e install -r bin/I2PAndroid-debug.apk
|
96
TODO
Normal file
96
TODO
Normal file
@ -0,0 +1,96 @@
|
||||
# Fixes
|
||||
|
||||
- Better twopane column widths
|
||||
<zzz> on the i2ptunnel and addressbook pages on the tablet, the columns are too skinny, they aren't as wide as the tab
|
||||
<zzz> only a few addressbook entries wrap but on i2ptunnel everything is wrapped and most of the screen is empty
|
||||
- Create tunnel wizard
|
||||
<zzz> hmm would be nice if they could be shared-client or have an option
|
||||
<zzz> was setting up email tunnels
|
||||
- Browser
|
||||
<zzzccc> Bug report: i2p browser treats 302 as an error
|
||||
<zzzccc> Bug 2: rotate screen in i2p browser seems to go back one page
|
||||
|
||||
# Short-term
|
||||
|
||||
- Disable uPnP when on cell networks
|
||||
<zzz> spewing UPnP out into cell networks is a waste of time at best and a security risk at worst, but you really want it for wifi
|
||||
- I2PTunnel
|
||||
- Show all messages somewhere
|
||||
- Improve detail page, expose advanced settings
|
||||
- Add edit page
|
||||
- Progress feedback for addressbook subscriptions reload
|
||||
- Display release notes directly on new router version
|
||||
- Fill out help pages
|
||||
- Rewrite release notes to be release-specific
|
||||
- Fix release notes UI, either make back button use clear or add buttons
|
||||
- NetDB tablet view fixes
|
||||
- Refresh detail fragment when changing tab
|
||||
- Move list to correct item when changing tab
|
||||
- Create nav history when viewing RI from LS
|
||||
- Include GeoIP db for country info
|
||||
- Maybe change router-off mechanic for various pages? Enable as they become available?
|
||||
|
||||
# Medium-term
|
||||
|
||||
- Network profiles
|
||||
- User selects profile in settings
|
||||
- Change network participation etc. based on profile
|
||||
- Also look at connection type: Connectivity.isConnectionFast()
|
||||
- Expose log level overrides
|
||||
- Improve graphs
|
||||
- Show time on bottom axis
|
||||
- Show fixed x range, not only available data
|
||||
- Think about pan/zoom
|
||||
- How to persist data across restarts?
|
||||
|
||||
# Silent Store approval checks to confirm/implement
|
||||
|
||||
- Known Vulnerabilities
|
||||
- Apps will be tested to ensure that they are not susceptible to known
|
||||
publicly disclosed vulnerabilities. For example:
|
||||
- Heartbleed
|
||||
- Poodle
|
||||
- MasterKey
|
||||
- Common Path Traversal attacks
|
||||
- Common SQL Injection attacks
|
||||
- Network Security Protocols
|
||||
- All Apps that require transmission of data from the App to a system that
|
||||
does not exist on the device must use, at a minimum, TLS1.1 standards.
|
||||
However, Blackphone would prefer the usage of TLS1.2.
|
||||
- Apps must not use algorithms for cryptographic purposes that are considered
|
||||
obsolete or outdated i.e. MD5, SHA1, RC4, DES, or any encryption algorithm
|
||||
that is weaker than AES128.
|
||||
- Transport Layer Protection
|
||||
- All network communication should be encrypted
|
||||
- Not vulnerable to SSl Strip
|
||||
- Data Leakage
|
||||
- No storage of sensitive data outside of application sandbox
|
||||
- Files should not be created with MODE_WORLD_READABLE or MODE_WORLD_WRITABLE
|
||||
- Copy & Paste will be evaluated on a case by case basis
|
||||
- App logs should not contain sensitive information
|
||||
- Authentication and Authorization
|
||||
- Validate that authentication credentials are not stored on the device
|
||||
- Must use an approved password-based key derivation function ie. PBKDF2, scrypt
|
||||
- Data-at-rest Encryption
|
||||
- Must use at a minimum AES128 with modes CCM or GCM
|
||||
- Should not store the encryption key on the file system
|
||||
- Permission Checks
|
||||
- The App must function with all permissions disabled
|
||||
- Apps must not hard crash if a permission is disabled
|
||||
- Apps should ask users to enable permissions that are disabled if needed to
|
||||
function properly and explain why the permission is necessary
|
||||
- Privacy Policy
|
||||
- Apps must have a privacy policy that details how customer data is used,
|
||||
stored, shared, etc...
|
||||
- Apps must be configured with the customer opted out by default
|
||||
- App logs should not contain PII
|
||||
- Error Handling
|
||||
- Apps should follow best-practices for error handling and logging
|
||||
|
||||
# Long-term
|
||||
|
||||
- Remote router support
|
||||
- Implement a "router wrapper" that can represent a local or remote router
|
||||
- Implement/use client APIs to talk to remote router
|
||||
- I2CP
|
||||
- I2PControl
|
206
app/build.gradle
Normal file
206
app/build.gradle
Normal file
@ -0,0 +1,206 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'witness'
|
||||
|
||||
android {
|
||||
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
|
||||
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
|
||||
defaultConfig {
|
||||
versionCode 4745227
|
||||
versionName '0.9.19'
|
||||
minSdkVersion 9
|
||||
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
|
||||
|
||||
// For Espresso
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
signingConfigs {
|
||||
release
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix '.debug'
|
||||
versionNameSuffix '-DEBUG'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
packagingOptions {
|
||||
exclude 'LICENSE.txt'
|
||||
}
|
||||
productFlavors {
|
||||
free {
|
||||
applicationId 'net.i2p.android'
|
||||
}
|
||||
donate {
|
||||
applicationId 'net.i2p.android.donate'
|
||||
}
|
||||
legacy {
|
||||
applicationId 'net.i2p.android.router'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Local dependencies
|
||||
compile project(':routerjars')
|
||||
compile project(':client')
|
||||
|
||||
// Android Support Repository dependencies
|
||||
compile 'com.android.support:support-v4:21.0.3'
|
||||
compile 'com.android.support:appcompat-v7:21.0.3'
|
||||
compile 'com.android.support:recyclerview-v7:21.0.3'
|
||||
|
||||
// Remote dependencies
|
||||
compile 'net.i2p.android.ext:floatingactionbutton:1.8.0'
|
||||
compile files('libs/androidplot-core-0.6.1.jar')
|
||||
|
||||
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
|
||||
// Testing-only dependencies
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
|
||||
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
|
||||
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
|
||||
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
|
||||
'net.i2p.android.ext:floatingactionbutton:a20d1f0cae15f8965b81486ba31245937968ae6ee5fa6e8a3ea21d7f6c6243ab',
|
||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
||||
]
|
||||
}
|
||||
|
||||
project.ext.i2pbase = '../i2p.i2p'
|
||||
def Properties props = new Properties()
|
||||
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
|
||||
|
||||
if (propFile.canRead()) {
|
||||
props.load(new FileInputStream(propFile))
|
||||
|
||||
if (props != null &&
|
||||
props.containsKey('i2psrc')) {
|
||||
i2pbase = props['i2psrc']
|
||||
} else {
|
||||
println 'local.properties found but some entries are missing'
|
||||
}
|
||||
} else {
|
||||
println 'local.properties not found'
|
||||
}
|
||||
|
||||
task certificatesZip(type: Zip) {
|
||||
archiveName = 'certificates_zip'
|
||||
from files('' + i2pbase + '/installer/resources/certificates')
|
||||
}
|
||||
task copyI2PResources(type: Copy) {
|
||||
// Force this to always run: Copy only detects source changes, not if missing in destination
|
||||
outputs.upToDateWhen { false }
|
||||
into 'src/main/res'
|
||||
into('drawable') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
||||
}
|
||||
into('raw') {
|
||||
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
|
||||
from(i2pbase + '/installer/resources/hosts.txt') { rename { 'hosts_txt' } }
|
||||
from('../LICENSE.txt') { rename { 'license_app_txt' } }
|
||||
from('../licenses/LICENSE-Apache2.0.txt') { rename { 'license_apache20_txt' } }
|
||||
from(i2pbase + '/licenses') {
|
||||
include { elem ->
|
||||
elem.name in [
|
||||
'LICENSE-ElGamalDSA.txt',
|
||||
'LICENSE-SHA256.txt',
|
||||
'LICENSE-BSD.txt',
|
||||
'LICENSE-SNTP.txt',
|
||||
'LICENSE-LGPLv2.1.txt',
|
||||
'LICENSE-InstallCert.txt',
|
||||
'LICENSE-BlockFile.txt',
|
||||
'LICENSE-GPLv2.txt',
|
||||
'LICENSE-GPLv3.txt',
|
||||
'LICENSE-LGPLv3.txt',
|
||||
'LICENSE-FatCowIcons.txt',
|
||||
'LICENSE-Addressbook.txt',
|
||||
]
|
||||
}
|
||||
rename { String name ->
|
||||
String part = name.substring(8, name.lastIndexOf('.txt'))
|
||||
String.format('license_%s_txt',
|
||||
part.toLowerCase(Locale.US).replace('.', '_'))
|
||||
}
|
||||
}
|
||||
from certificatesZip
|
||||
}
|
||||
}
|
||||
// For peers WebView
|
||||
task copyI2PAssets(type: Copy) {
|
||||
// Force this to always run: Copy only detects source changes, not if missing in destination
|
||||
outputs.upToDateWhen { false }
|
||||
into 'src/main/assets/themes/console'
|
||||
into('images') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/inbound.png')
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/outbound.png')
|
||||
}
|
||||
into('light') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/light/console.css')
|
||||
}
|
||||
into('light/images') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/light/images/header.png')
|
||||
}
|
||||
}
|
||||
|
||||
preBuild.dependsOn copyI2PResources
|
||||
preBuild.dependsOn copyI2PAssets
|
||||
|
||||
task cleanI2PResources(type: Delete) {
|
||||
delete file('src/main/res/drawable/i2plogo.png')
|
||||
delete fileTree('src/main/res/raw') {
|
||||
include 'blocklist_txt'
|
||||
include 'hosts_txt'
|
||||
include 'license_*'
|
||||
include 'certificates_zip'
|
||||
}
|
||||
}
|
||||
task cleanI2PAssets(type: Delete) {
|
||||
delete fileTree('src/main/assets/themes/console/images')
|
||||
delete file('src/main/assets/themes/console/light/console.css')
|
||||
delete file('src/main/assets/themes/console/light/images/header.png')
|
||||
}
|
||||
|
||||
clean.dependsOn cleanI2PResources
|
||||
clean.dependsOn cleanI2PAssets
|
||||
|
||||
props = new Properties()
|
||||
propFile = new File(project.rootDir, 'signing.properties')
|
||||
|
||||
if (propFile.canRead()) {
|
||||
props.load(new FileInputStream(propFile))
|
||||
|
||||
if (props != null &&
|
||||
props.containsKey('STORE_FILE') &&
|
||||
props.containsKey('STORE_PASSWORD') &&
|
||||
props.containsKey('KEY_ALIAS') &&
|
||||
props.containsKey('KEY_PASSWORD')) {
|
||||
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
|
||||
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
|
||||
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
|
||||
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
|
||||
} else {
|
||||
println 'signing.properties found but some entries are missing'
|
||||
android.buildTypes.release.signingConfig = null
|
||||
}
|
||||
} else {
|
||||
println 'signing.properties not found'
|
||||
android.buildTypes.release.signingConfig = null
|
||||
}
|
BIN
app/libs/androidplot-core-0.6.1.jar
Normal file
BIN
app/libs/androidplot-core-0.6.1.jar
Normal file
Binary file not shown.
4
app/src/debug/res/values/strings.xml
Normal file
4
app/src/debug/res/values/strings.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">I2P DEBUG</string>
|
||||
</resources>
|
37
app/src/debug/res/xml/settings_headers.xml
Normal file
37
app/src/debug/res/xml/settings_headers.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_bandwidth_net">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="net" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/label_graphs">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="graphs" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_logging">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="logging" />
|
||||
</header>
|
||||
<header
|
||||
android:title="@string/label_addressbook">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_advanced">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="advanced" />
|
||||
</header>
|
||||
</preference-headers>
|
32
app/src/debug/res/xml/settings_headers_legacy.xml
Normal file
32
app/src/debug/res/xml/settings_headers_legacy.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<Preference android:title="@string/settings_label_bandwidth_net">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_NET" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/label_graphs">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_GRAPHS" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/settings_label_logging">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_LOGGING" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/label_addressbook">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/settings_label_advanced">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_ADVANCED" />
|
||||
</Preference>
|
||||
</PreferenceScreen>
|
37
app/src/donate/res/xml/settings_headers.xml
Normal file
37
app/src/donate/res/xml/settings_headers.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_bandwidth_net">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="net" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/label_graphs">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="graphs" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_logging">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="logging" />
|
||||
</header>
|
||||
<header
|
||||
android:title="@string/label_addressbook">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.donate"
|
||||
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_advanced">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="advanced" />
|
||||
</header>
|
||||
</preference-headers>
|
32
app/src/donate/res/xml/settings_headers_legacy.xml
Normal file
32
app/src/donate/res/xml/settings_headers_legacy.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<Preference android:title="@string/settings_label_bandwidth_net">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.donate"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_NET" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/label_graphs">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.donate"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_GRAPHS" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/settings_label_logging">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.donate"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_LOGGING" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/label_addressbook">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.donate"
|
||||
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/settings_label_advanced">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.donate"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_ADVANCED" />
|
||||
</Preference>
|
||||
</PreferenceScreen>
|
37
app/src/legacy/res/xml/settings_headers.xml
Normal file
37
app/src/legacy/res/xml/settings_headers.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_bandwidth_net">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="net" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/label_graphs">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="graphs" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_logging">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="logging" />
|
||||
</header>
|
||||
<header
|
||||
android:title="@string/label_addressbook">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.router"
|
||||
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_advanced">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="advanced" />
|
||||
</header>
|
||||
</preference-headers>
|
32
app/src/legacy/res/xml/settings_headers_legacy.xml
Normal file
32
app/src/legacy/res/xml/settings_headers_legacy.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<Preference android:title="@string/settings_label_bandwidth_net">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.router"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_NET" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/label_graphs">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.router"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_GRAPHS" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/settings_label_logging">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.router"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_LOGGING" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/label_addressbook">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.router"
|
||||
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/settings_label_advanced">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.router"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_ADVANCED" />
|
||||
</Preference>
|
||||
</PreferenceScreen>
|
220
app/src/main/AndroidManifest.xml
Normal file
220
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,220 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.i2p.android.router"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:icon="@drawable/ic_launcher_itoopie"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.I2P">
|
||||
<service
|
||||
android:name=".service.RouterService"
|
||||
android:icon="@drawable/ic_launcher_itoopie"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="net.i2p.android.router.service.IRouterState" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<provider
|
||||
android:name=".provider.CacheProvider"
|
||||
android:authorities="${applicationId}.provider" />
|
||||
<receiver android:name=".receiver.OnBootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:icon="@drawable/ic_launcher_itoopie"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="net.i2p.android.router.START_I2P" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".NewsActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/label_news"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.i2p.android.help.HelpActivity"
|
||||
android:label="@string/menu_help"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.i2p.android.help.BrowserConfigActivity"
|
||||
android:label="@string/label_browser_configuration"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".LicenseActivity"
|
||||
android:label="@string/label_licenses"
|
||||
android:parentActivityName="net.i2p.android.help.HelpActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.help.HelpActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".web.WebActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="I2P Web Browser">
|
||||
<!-- Disabled, this browser is not very secure
|
||||
Temporarily enabled until an alternative browser is ready -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="*.i2p"
|
||||
android:scheme="http" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/menu_settings"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".addressbook.AddressbookSettingsActivity"
|
||||
android:label="Addressbook Settings"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".addressbook.AddressbookActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".addressbook.AddressbookActivity"
|
||||
android:label="@string/label_addressbook"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PICK" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable_addressbook" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".addressbook.AddressbookAddWizardActivity"
|
||||
android:label="Add new Destination"
|
||||
android:parentActivityName=".addressbook.AddressbookActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.i2p.android.i2ptunnel.TunnelListActivity"
|
||||
android:label="@string/label_i2ptunnel"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity"
|
||||
android:label="I2PTunnel"
|
||||
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.i2ptunnel.TunnelListActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.i2p.android.i2ptunnel.EditTunnelActivity"
|
||||
android:label="@string/edit_tunnel"
|
||||
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelDetailActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.i2ptunnel.TunnelDetailActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
|
||||
android:label="Tunnel Creation Wizard"
|
||||
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".log.LogActivity"
|
||||
android:label="I2P Logs"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".log.LogDetailActivity"
|
||||
android:label="Log Entry"
|
||||
android:parentActivityName=".log.LogActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.log.LogActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".stats.RateGraphActivity"
|
||||
android:label="Rate Graph"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".stats.PeersActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="I2P Peers and Transport Status"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".netdb.NetDbActivity"
|
||||
android:label="NetDB"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".netdb.NetDbDetailActivity"
|
||||
android:label="NetDB Detail"
|
||||
android:parentActivityName=".netdb.NetDbActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.netdb.NetDbActivity" />
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* The following code was written by Matthew Wiggins
|
||||
* and is released under the APACHE 2.0 license
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Slight modifications and bugfixes by Sponge <sponge@mail.i2p>
|
||||
* These modifications are released under the WTFPL (any version)
|
||||
*
|
||||
* We don't need negative numbers yet, and may never need to.
|
||||
*
|
||||
* XML Usage example:
|
||||
*
|
||||
* <com.hlidskialf.android.preference.SeekBarPreference android:key="duration"
|
||||
* android:title="Duration of something"
|
||||
* android:summary="How long something will last"
|
||||
* android:dialogMessage="Something duration"
|
||||
* android:defaultValue="5"
|
||||
* android:text=" minutes"
|
||||
* android:max="60"
|
||||
* />
|
||||
*
|
||||
*/
|
||||
package com.hlidskialf.android.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SeekBarPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener {
|
||||
|
||||
private static final String androidns = "http://schemas.android.com/apk/res/android";
|
||||
private SeekBar mSeekBar;
|
||||
private TextView mSplashText;
|
||||
private TextView mValueText;
|
||||
private Context mContext;
|
||||
private String mDialogMessage, mSuffix;
|
||||
private String mDefault = "0";
|
||||
private int mMax = 0;
|
||||
private int mValue = 0;
|
||||
private int mDirection = LinearLayout.HORIZONTAL;
|
||||
|
||||
|
||||
public SeekBarPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
int dialogMessageR = attrs.getAttributeResourceValue(androidns, "dialogMessage", 0);
|
||||
mDialogMessage = (dialogMessageR == 0)
|
||||
? attrs.getAttributeValue(androidns, "dialogMessage")
|
||||
: context.getResources().getString(dialogMessageR);
|
||||
int textR = attrs.getAttributeResourceValue(androidns, "text", 0);
|
||||
mSuffix = (textR == 0)
|
||||
? attrs.getAttributeValue(androidns, "text")
|
||||
: context.getResources().getString(textR);
|
||||
mDefault = attrs.getAttributeValue(androidns, "defaultValue");
|
||||
mMax = Integer.parseInt(attrs.getAttributeValue(androidns, "max"));
|
||||
if (attrs.getAttributeValue(androidns, "direction") != null) {
|
||||
mDirection = Integer.parseInt(attrs.getAttributeValue(androidns, "direction"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateDialogView() {
|
||||
LinearLayout.LayoutParams params;
|
||||
LinearLayout layout = new LinearLayout(mContext);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(6, 6, 6, 10);
|
||||
|
||||
// Set the width so that it is as usable as possible.
|
||||
// We multiplymMax so that the smaller ranges will get a bigger area.
|
||||
|
||||
if (mDirection == LinearLayout.HORIZONTAL) {
|
||||
layout.setMinimumWidth(mMax*5);
|
||||
} else {
|
||||
layout.setMinimumHeight(mMax*5);
|
||||
}
|
||||
|
||||
mSplashText = new TextView(mContext);
|
||||
if (mDialogMessage != null) {
|
||||
mSplashText.setText(mDialogMessage);
|
||||
}
|
||||
layout.addView(mSplashText);
|
||||
|
||||
mValueText = new TextView(mContext);
|
||||
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
|
||||
mValueText.setTextSize(32);
|
||||
params = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
layout.addView(mValueText, params);
|
||||
|
||||
mSeekBar = new SeekBar(mContext);
|
||||
mSeekBar.setOnSeekBarChangeListener(this);
|
||||
// Move the bar away from the changing text, so you can see it, and
|
||||
// move it away from the edges to improve usability for the end-ranges.
|
||||
mSeekBar.setPadding(6, 30, 6, 6);
|
||||
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
if (shouldPersist()) {
|
||||
mValue = Integer.parseInt(getPersistedString(mDefault));
|
||||
}
|
||||
mSeekBar.setMax(mMax);
|
||||
mSeekBar.setProgress(mValue);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View v) {
|
||||
super.onBindDialogView(v);
|
||||
mSeekBar.setMax(mMax);
|
||||
mSeekBar.setProgress(mValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restore, Object defaultValue) {
|
||||
super.onSetInitialValue(restore, defaultValue);
|
||||
if (restore) {
|
||||
mValue = shouldPersist() ? Integer.parseInt(getPersistedString(mDefault)) : 0;
|
||||
} else {
|
||||
mValue = (Integer) defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
|
||||
String t = String.valueOf(value);
|
||||
mValueText.setText(mSuffix == null ? t : t.concat(mSuffix));
|
||||
if (shouldPersist()) {
|
||||
persistString(t);
|
||||
}
|
||||
callChangeListener(Integer.valueOf(value));
|
||||
}
|
||||
|
||||
public void onStartTrackingTouch(SeekBar seek) {
|
||||
}
|
||||
|
||||
public void onStopTrackingTouch(SeekBar seek) {
|
||||
}
|
||||
|
||||
public void setMax(int max) {
|
||||
mMax = max;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return mMax;
|
||||
}
|
||||
|
||||
public void setProgress(int progress) {
|
||||
mValue = progress;
|
||||
if (mSeekBar != null) {
|
||||
mSeekBar.setProgress(progress);
|
||||
}
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
return mValue;
|
||||
}
|
||||
}
|
@ -2,10 +2,9 @@ package net.i2p.android.apps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EepGet;
|
||||
@ -47,7 +46,7 @@ public class EepGetFetcher implements EepGet.StatusListener {
|
||||
_eepget.setWriteErrorToOutput();
|
||||
//_eepget.addStatusListener(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes to output stream
|
||||
*/
|
||||
@ -62,7 +61,7 @@ public class EepGetFetcher implements EepGet.StatusListener {
|
||||
if (writeErrorToStream)
|
||||
_eepget.setWriteErrorToOutput();
|
||||
}
|
||||
|
||||
|
||||
public void addStatusListener(EepGet.StatusListener l) {
|
||||
_eepget.addStatusListener(l);
|
||||
}
|
||||
@ -149,7 +148,7 @@ public class EepGetFetcher implements EepGet.StatusListener {
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
|
||||
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {}
|
||||
|
||||
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {}
|
||||
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
@ -1,14 +1,10 @@
|
||||
package net.i2p.android.apps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.android.router.NewsActivity;
|
||||
import net.i2p.android.router.util.Notifications;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.util.EepGet;
|
||||
@ -22,6 +18,7 @@ import net.i2p.util.Translate;
|
||||
*/
|
||||
public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
private final RouterContext _context;
|
||||
private final Notifications _notif;
|
||||
private final Log _log;
|
||||
private long _lastFetch;
|
||||
private long _lastUpdated;
|
||||
@ -30,31 +27,43 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
private File _newsFile;
|
||||
private File _tempFile;
|
||||
private static NewsFetcher _instance;
|
||||
private volatile boolean _isRunning = true;
|
||||
private Thread _thread;
|
||||
|
||||
public static final NewsFetcher getInstance() {
|
||||
public static /*final */ NewsFetcher getInstance() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
public static final synchronized NewsFetcher getInstance(RouterContext ctx) {
|
||||
public static /* final */ synchronized NewsFetcher getInstance(
|
||||
RouterContext ctx, Notifications notif) {
|
||||
if (_instance != null)
|
||||
return _instance;
|
||||
_instance = new NewsFetcher(ctx);
|
||||
_instance = new NewsFetcher(ctx, notif);
|
||||
return _instance;
|
||||
}
|
||||
|
||||
private static final String NEWS_DIR = "docs";
|
||||
private static final String NEWS_FILE = "news.xml";
|
||||
private static final String TEMP_NEWS_FILE = "news.xml.temp";
|
||||
/** @since 0.7.14 not configurable */
|
||||
private static final String BACKUP_NEWS_URL = "http://www.i2p2.i2p/_static/news/news.xml";
|
||||
|
||||
/**
|
||||
* Changed in 0.9.11 to the b32 for psi.i2p, run by psi.
|
||||
* We may be able to change it to psi.i2p in a future release after
|
||||
* the hostname propagates.
|
||||
*
|
||||
* @since 0.7.14 not configurable
|
||||
*/
|
||||
private static final String BACKUP_NEWS_URL = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.xml";
|
||||
private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
|
||||
private static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
|
||||
private static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + "";
|
||||
private static final String PROP_NEWS_URL = "router.newsURL";
|
||||
private static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml";
|
||||
|
||||
private NewsFetcher(RouterContext ctx) {
|
||||
|
||||
private NewsFetcher(RouterContext ctx, Notifications notif) {
|
||||
_context = ctx;
|
||||
_notif = notif;
|
||||
_context.addShutdownTask(new Shutdown());
|
||||
_log = ctx.logManager().getLog(NewsFetcher.class);
|
||||
try {
|
||||
String last = ctx.getProperty(PROP_LAST_CHECKED);
|
||||
@ -68,7 +77,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
_tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
|
||||
updateLastFetched();
|
||||
}
|
||||
|
||||
|
||||
private void updateLastFetched() {
|
||||
if (_newsFile.exists()) {
|
||||
if (_lastUpdated == 0)
|
||||
@ -83,7 +92,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
_lastModified = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String status() {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
long now = _context.clock().now();
|
||||
@ -100,17 +109,18 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
private static final long INITIAL_DELAY = 5*60*1000;
|
||||
private static final long RUN_DELAY = 30*60*1000;
|
||||
|
||||
public void run() {
|
||||
_thread = Thread.currentThread();
|
||||
try {
|
||||
Thread.sleep(INITIAL_DELAY);
|
||||
} catch (InterruptedException ie) {
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
while (_isRunning && _context.router().isAlive()) {
|
||||
if (shouldFetchNews()) {
|
||||
fetchNews();
|
||||
}
|
||||
@ -121,7 +131,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean shouldFetchNews() {
|
||||
if (_invalidated)
|
||||
return true;
|
||||
@ -132,7 +142,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
long ms = Long.parseLong(freq);
|
||||
if (ms <= 0)
|
||||
return false;
|
||||
|
||||
|
||||
if (_lastFetch + ms < _context.clock().now()) {
|
||||
return true;
|
||||
} else {
|
||||
@ -162,10 +172,10 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
int proxyPort = 4444;
|
||||
if (_tempFile.exists())
|
||||
_tempFile.delete();
|
||||
|
||||
|
||||
try {
|
||||
EepGet get = null;
|
||||
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||
// EepGet get = null;
|
||||
EepGet get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||
get.addStatusListener(this);
|
||||
if (get.fetch()) {
|
||||
_lastModified = get.getLastModified();
|
||||
@ -182,7 +192,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
_log.error("Error fetching the news", t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||
// ignore
|
||||
}
|
||||
@ -192,14 +202,17 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
|
||||
|
||||
|
||||
long now = _context.clock().now();
|
||||
if (_tempFile.exists()) {
|
||||
boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true);
|
||||
if (copied) {
|
||||
_lastUpdated = now;
|
||||
_tempFile.delete();
|
||||
// notify somebody?
|
||||
|
||||
// Notify user
|
||||
_notif.notify("News Updated", "Touch to view latest I2P news",
|
||||
NewsActivity.class);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Failed to copy the news file!");
|
||||
@ -212,7 +225,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
_context.router().setConfigSetting(PROP_LAST_CHECKED, "" + now);
|
||||
_context.router().saveConfig();
|
||||
}
|
||||
|
||||
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Failed to fetch the news from " + url);
|
||||
@ -220,4 +233,12 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
}
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
public void attempting(String url) {}
|
||||
|
||||
private class Shutdown implements Runnable {
|
||||
public void run() {
|
||||
_isRunning = false;
|
||||
if (_thread != null)
|
||||
_thread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
83
app/src/main/java/net/i2p/android/help/Browser.java
Normal file
83
app/src/main/java/net/i2p/android/help/Browser.java
Normal file
@ -0,0 +1,83 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class Browser implements Comparable<Browser> {
|
||||
public final String packageName;
|
||||
public final CharSequence label;
|
||||
public final Drawable icon;
|
||||
public final boolean isInstalled;
|
||||
public final boolean isKnown;
|
||||
public final boolean isSupported;
|
||||
public final boolean isRecommended;
|
||||
|
||||
/**
|
||||
* A browser that we don't know about.
|
||||
*
|
||||
* @param pm the PackageManager used to find the browser
|
||||
* @param browser the browser
|
||||
*/
|
||||
public Browser(PackageManager pm, ResolveInfo browser) {
|
||||
this(
|
||||
browser.activityInfo.packageName,
|
||||
browser.loadLabel(pm),
|
||||
browser.loadIcon(pm),
|
||||
true, false, false, false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A browser that we know about.
|
||||
*
|
||||
* @param pm the PackageManager used to find the browser
|
||||
* @param browser the browser
|
||||
* @param supported can this browser be used with I2P?
|
||||
*/
|
||||
public Browser(PackageManager pm, ResolveInfo browser, boolean supported, boolean recommended) {
|
||||
this(
|
||||
browser.activityInfo.packageName,
|
||||
browser.loadLabel(pm),
|
||||
browser.loadIcon(pm),
|
||||
true, true, supported, recommended
|
||||
);
|
||||
}
|
||||
|
||||
public Browser(String pn, CharSequence l, Drawable ic, boolean i, boolean k, boolean s, boolean r) {
|
||||
packageName = pn;
|
||||
label = l;
|
||||
icon = ic;
|
||||
isInstalled = i;
|
||||
isKnown = k;
|
||||
isSupported = s;
|
||||
isRecommended = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull Browser browser) {
|
||||
// Sort order: supported -> unknown -> unsupported
|
||||
int a = getOrder(this);
|
||||
int b = getOrder(browser);
|
||||
|
||||
if (a < b)
|
||||
return -1;
|
||||
else if (a > b)
|
||||
return 1;
|
||||
|
||||
return label.toString().compareTo(browser.label.toString());
|
||||
}
|
||||
|
||||
private static int getOrder(Browser browser) {
|
||||
if (browser.isKnown) {
|
||||
if (browser.isRecommended)
|
||||
return 0;
|
||||
else if (browser.isSupported)
|
||||
return 1;
|
||||
else
|
||||
return 3;
|
||||
} else
|
||||
return 2;
|
||||
}
|
||||
}
|
117
app/src/main/java/net/i2p/android/help/BrowserAdapter.java
Normal file
117
app/src/main/java/net/i2p/android/help/BrowserAdapter.java
Normal file
@ -0,0 +1,117 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.net.Uri;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHolder> {
|
||||
private Context mCtx;
|
||||
private Browser[] mBrowsers;
|
||||
private OnBrowserSelectedListener mListener;
|
||||
|
||||
// Provide a reference to the views for each data item
|
||||
// Complex data items may need more than one view per item, and
|
||||
// you provide access to all the views for a data item in a view holder
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public ImageView mIcon;
|
||||
public TextView mLabel;
|
||||
public ImageView mStatus;
|
||||
|
||||
public ViewHolder(View v) {
|
||||
super(v);
|
||||
mIcon = (ImageView) v.findViewById(R.id.browser_icon);
|
||||
mLabel = (TextView) v.findViewById(R.id.browser_label);
|
||||
mStatus = (ImageView) v.findViewById(R.id.browser_status_icon);
|
||||
}
|
||||
}
|
||||
|
||||
public static interface OnBrowserSelectedListener {
|
||||
public void onBrowserSelected(Browser browser);
|
||||
}
|
||||
|
||||
public BrowserAdapter(Context ctx, OnBrowserSelectedListener listener) {
|
||||
mCtx = ctx;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void setBrowsers(Browser[] browsers) {
|
||||
mBrowsers = browsers;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mBrowsers = null;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// Create new views (invoked by the layout manager)
|
||||
@Override
|
||||
public BrowserAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
|
||||
int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.listitem_browser, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
// Replace the contents of a view (invoked by the layout manager)
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
final Browser browser = mBrowsers[position];
|
||||
holder.mIcon.setImageDrawable(browser.icon);
|
||||
holder.mLabel.setText(browser.label);
|
||||
|
||||
if (browser.isKnown) {
|
||||
if (browser.isRecommended && browser.isInstalled) {
|
||||
holder.mStatus.setImageDrawable(
|
||||
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
|
||||
holder.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (browser.isSupported && !browser.isInstalled) {
|
||||
holder.mStatus.setImageDrawable(
|
||||
mCtx.getResources().getDrawable(R.drawable.ic_shop_white_24dp));
|
||||
holder.mStatus.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String uriMarket = "market://search?q=pname:" + browser.packageName;
|
||||
Uri uri = Uri.parse(uriMarket);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
mCtx.startActivity(intent);
|
||||
}
|
||||
});
|
||||
holder.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (!browser.isSupported) {
|
||||
// Make the icon gray-scale to show it is unsupported
|
||||
ColorMatrix matrix = new ColorMatrix();
|
||||
matrix.setSaturation(0);
|
||||
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
|
||||
holder.mIcon.setColorFilter(filter);
|
||||
holder.mLabel.setTextColor(
|
||||
mCtx.getResources().getColor(R.color.primary_text_disabled_material_dark));
|
||||
}
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mListener.onBrowserSelected(browser);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Return the size of the dataset (invoked by the layout manager)
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (mBrowsers != null)
|
||||
return mBrowsers.length;
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class BrowserConfigActivity extends ActionBarActivity implements
|
||||
BrowserAdapter.OnBrowserSelectedListener {
|
||||
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_help);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, new BrowserListFragment())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
// BrowserAdapter.OnBrowserSelected
|
||||
|
||||
@Override
|
||||
public void onBrowserSelected(Browser browser) {
|
||||
int file;
|
||||
if (browser.isKnown) {
|
||||
if (browser.isSupported) {
|
||||
// Check for embedded browser
|
||||
if (browser.packageName.startsWith("net.i2p.android"))
|
||||
file = R.raw.help_embedded_browser;
|
||||
else {
|
||||
// Load the configuration guide for this browser
|
||||
try {
|
||||
String name = "help_" + browser.packageName.replace('.', '_');
|
||||
Class res = R.raw.class;
|
||||
Field field = res.getField(name);
|
||||
file = field.getInt(null);
|
||||
} catch (Exception e) {
|
||||
file = R.raw.help_unknown_browser;
|
||||
}
|
||||
}
|
||||
} else
|
||||
file = R.raw.help_unsupported_browser;
|
||||
} else
|
||||
file = R.raw.help_unknown_browser;
|
||||
HelpHtmlFragment configFrag = HelpHtmlFragment.newInstance(file);
|
||||
if (mTwoPane) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, configFrag)
|
||||
.commit();
|
||||
} else {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, configFrag)
|
||||
.addToBackStack("config" + browser.packageName)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
}
|
187
app/src/main/java/net/i2p/android/help/BrowserListFragment.java
Normal file
187
app/src/main/java/net/i2p/android/help/BrowserListFragment.java
Normal file
@ -0,0 +1,187 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.BetterAsyncTaskLoader;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BrowserListFragment extends Fragment implements
|
||||
LoaderManager.LoaderCallbacks<List<Browser>> {
|
||||
private static final int BROWSER_LOADER_ID = 1;
|
||||
|
||||
private BrowserAdapter.OnBrowserSelectedListener mCallback;
|
||||
private BrowserAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mCallback = (BrowserAdapter.OnBrowserSelectedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnBrowserSelectedListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_help_browsers, container, false);
|
||||
RecyclerView mRecyclerView = (RecyclerView) v.findViewById(R.id.browser_list);
|
||||
|
||||
// use a linear layout manager
|
||||
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
|
||||
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||
|
||||
mAdapter = new BrowserAdapter(getActivity(), mCallback);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
getLoaderManager().initLoader(BROWSER_LOADER_ID, null, this);
|
||||
}
|
||||
|
||||
// LoaderManager.LoaderCallbacks<List<Browser>>
|
||||
|
||||
@Override
|
||||
public Loader<List<Browser>> onCreateLoader(int id, Bundle args) {
|
||||
return new BrowserLoader(getActivity());
|
||||
}
|
||||
|
||||
public static class BrowserLoader extends BetterAsyncTaskLoader<List<Browser>> {
|
||||
private List<String> recommended;
|
||||
private List<String> recommendedLabels;
|
||||
private List<String> supported;
|
||||
private List<String> supportedLabels;
|
||||
private List<String> unsupported;
|
||||
|
||||
public BrowserLoader(Context context) {
|
||||
super(context);
|
||||
recommended = Arrays.asList(
|
||||
getContext().getResources().getStringArray(R.array.recommended_browsers));
|
||||
recommendedLabels = Arrays.asList(
|
||||
getContext().getResources().getStringArray(R.array.recommended_browser_labels));
|
||||
supported = Arrays.asList(
|
||||
getContext().getResources().getStringArray(R.array.supported_browsers));
|
||||
supportedLabels = Arrays.asList(
|
||||
getContext().getResources().getStringArray(R.array.supported_browser_labels));
|
||||
unsupported = Arrays.asList(
|
||||
context.getResources().getStringArray(R.array.unsupported_browsers));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Browser> loadInBackground() {
|
||||
List<Browser> browsers = new ArrayList<>();
|
||||
Map<String, String> recommendedMap = new HashMap<>();
|
||||
for (int i = 0; i < recommended.size(); i++) {
|
||||
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
|
||||
}
|
||||
Map<String, String> supportedMap = new HashMap<>();
|
||||
for (int i = 0; i < supported.size(); i++) {
|
||||
supportedMap.put(supported.get(i), supportedLabels.get(i));
|
||||
}
|
||||
|
||||
// Find all installed browsers that listen for ".i2p"
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("http://stats.i2p"));
|
||||
|
||||
final PackageManager pm = getContext().getPackageManager();
|
||||
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
|
||||
|
||||
for (ResolveInfo browser : installedBrowsers) {
|
||||
if (recommended.contains(browser.activityInfo.packageName)) {
|
||||
browsers.add(new Browser(pm, browser, true, true));
|
||||
recommendedMap.remove(browser.activityInfo.packageName);
|
||||
} else if (supported.contains(browser.activityInfo.packageName) ||
|
||||
browser.activityInfo.packageName.startsWith("net.i2p.android")) {
|
||||
browsers.add(new Browser(pm, browser, true, false));
|
||||
supportedMap.remove(browser.activityInfo.packageName);
|
||||
} else if (unsupported.contains(browser.activityInfo.packageName))
|
||||
browsers.add(new Browser(pm, browser, false, false));
|
||||
else
|
||||
browsers.add(new Browser(pm, browser));
|
||||
}
|
||||
|
||||
// Now add the remaining recommended and supported browsers
|
||||
for (Map.Entry<String, String> browser : recommendedMap.entrySet()) {
|
||||
browsers.add(new Browser(browser.getKey(), browser.getValue(),
|
||||
getDrawableForPackage(browser.getKey()),
|
||||
false, true, true, true));
|
||||
}
|
||||
for (Map.Entry<String, String> browser : supportedMap.entrySet()) {
|
||||
browsers.add(new Browser(browser.getKey(), browser.getValue(),
|
||||
getDrawableForPackage(browser.getKey()),
|
||||
false, true, true, false));
|
||||
}
|
||||
|
||||
Collections.sort(browsers);
|
||||
return browsers;
|
||||
}
|
||||
private Drawable getDrawableForPackage(String packageName) {
|
||||
try {
|
||||
String name = "icon_" + packageName.replace('.', '_');
|
||||
Class res = R.drawable.class;
|
||||
Field field = res.getField(name);
|
||||
int drawable = field.getInt(null);
|
||||
return getContext().getResources().getDrawable(drawable);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartMonitoring() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopMonitoring() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseResources(List<Browser> data) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<List<Browser>> listLoader, List<Browser> browsers) {
|
||||
if (listLoader.getId() == BROWSER_LOADER_ID)
|
||||
mAdapter.setBrowsers(browsers.toArray(new Browser[browsers.size()]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<List<Browser>> listLoader) {
|
||||
if (listLoader.getId() == BROWSER_LOADER_ID)
|
||||
mAdapter.clear();
|
||||
}
|
||||
}
|
155
app/src/main/java/net/i2p/android/help/HelpActivity.java
Normal file
155
app/src/main/java/net/i2p/android/help/HelpActivity.java
Normal file
@ -0,0 +1,155 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import net.i2p.android.router.LicenseActivity;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||
|
||||
public class HelpActivity extends ActionBarActivity implements
|
||||
HelpListFragment.OnEntrySelectedListener {
|
||||
public static final String CATEGORY = "help_category";
|
||||
public static final int CAT_MAIN = 0;
|
||||
public static final int CAT_CONFIGURE_BROWSER = 1;
|
||||
public static final int CAT_ADDRESSBOOK = 2;
|
||||
public static final int CAT_I2PTUNNEL = 3;
|
||||
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
private int mCategory;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_help);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, new HelpListFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
mCategory = getIntent().getIntExtra(CATEGORY, -1);
|
||||
if (mCategory >= 0) {
|
||||
showCategory(mCategory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_help_actions, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
if (mCategory >= 0) {
|
||||
onBackPressed();
|
||||
} else {
|
||||
Intent upIntent = NavUtils.getParentActivityIntent(this);
|
||||
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
|
||||
// This activity is NOT part of this app's task, so create a new task
|
||||
// when navigating up, with a synthesized back stack.
|
||||
TaskStackBuilder.create(this)
|
||||
// Add all of this activity's parents to the back stack
|
||||
.addNextIntentWithParentStack(upIntent)
|
||||
// Navigate up to the closest parent
|
||||
.startActivities();
|
||||
} else {
|
||||
// This activity is part of this app's task, so simply
|
||||
// navigate up to the logical parent activity.
|
||||
NavUtils.navigateUpTo(this, upIntent);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.menu_help_licenses:
|
||||
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
|
||||
startActivity(lic);
|
||||
return true;
|
||||
case R.id.menu_help_release_notes:
|
||||
TextResourceDialog dialog = new TextResourceDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
|
||||
getResources().getString(R.string.label_release_notes));
|
||||
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getSupportFragmentManager(), "release_notes");
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
if (mCategory >= 0)
|
||||
mCategory = -1;
|
||||
}
|
||||
|
||||
// HelpListFragment.OnEntrySelectedListener
|
||||
|
||||
@Override
|
||||
public void onEntrySelected(int entry) {
|
||||
if (entry == CAT_CONFIGURE_BROWSER) {
|
||||
Intent i = new Intent(this, BrowserConfigActivity.class);
|
||||
startActivity(i);
|
||||
} else {
|
||||
mCategory = entry;
|
||||
showCategory(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void showCategory(int category) {
|
||||
int file;
|
||||
switch (category) {
|
||||
case CAT_ADDRESSBOOK:
|
||||
file = R.raw.help_addressbook;
|
||||
break;
|
||||
|
||||
case CAT_I2PTUNNEL:
|
||||
file = R.raw.help_i2ptunnel;
|
||||
break;
|
||||
|
||||
case CAT_MAIN:
|
||||
default:
|
||||
file = R.raw.help_main;
|
||||
break;
|
||||
}
|
||||
HelpHtmlFragment f = HelpHtmlFragment.newInstance(file);
|
||||
if (mTwoPane) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, f).commit();
|
||||
} else {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f)
|
||||
.addToBackStack("help" + category)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
}
|
36
app/src/main/java/net/i2p/android/help/HelpHtmlFragment.java
Normal file
36
app/src/main/java/net/i2p/android/help/HelpHtmlFragment.java
Normal file
@ -0,0 +1,36 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
|
||||
public class HelpHtmlFragment extends Fragment {
|
||||
public static final String ARG_HTML_FILE = "htmlFile";
|
||||
|
||||
static HelpHtmlFragment newInstance(int htmlFile) {
|
||||
HelpHtmlFragment f = new HelpHtmlFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_HTML_FILE, htmlFile);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
ScrollView scroller = new ScrollView(getActivity());
|
||||
HtmlTextView text = new HtmlTextView(getActivity());
|
||||
scroller.addView(text);
|
||||
int padH = getResources().getDimensionPixelOffset(R.dimen.activity_horizontal_margin);
|
||||
int padV = getResources().getDimensionPixelOffset(R.dimen.activity_vertical_margin);
|
||||
text.setPadding(padH, padV, padH, padV);
|
||||
text.setHtmlFromRawResource(getActivity(), getArguments().getInt(ARG_HTML_FILE), true);
|
||||
return scroller;
|
||||
}
|
||||
}
|
47
app/src/main/java/net/i2p/android/help/HelpListFragment.java
Normal file
47
app/src/main/java/net/i2p/android/help/HelpListFragment.java
Normal file
@ -0,0 +1,47 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class HelpListFragment extends ListFragment {
|
||||
OnEntrySelectedListener mEntrySelectedCallback;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnEntrySelectedListener {
|
||||
public void onEntrySelected(int entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnEntrySelectedListener");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setListAdapter(ArrayAdapter.createFromResource(getActivity(),
|
||||
R.array.help_categories, R.layout.listitem_text));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||
super.onListItemClick(parent, view, pos, id);
|
||||
mEntrySelectedCallback.onEntrySelected(pos);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class EditTunnelActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_help_onepane);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
|
||||
EditTunnelFragment editFrag = EditTunnelFragment.newInstance(tunnelId);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, editFrag).commit();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,272 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelLogic;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||
|
||||
public class EditTunnelFragment extends PreferenceFragment {
|
||||
private static final String ARG_TUNNEL_ID = "tunnelId";
|
||||
|
||||
private TunnelControllerGroup mGroup;
|
||||
private int mTunnelId;
|
||||
|
||||
public static EditTunnelFragment newInstance(int tunnelId) {
|
||||
EditTunnelFragment f = new EditTunnelFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_TUNNEL_ID, tunnelId);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
|
||||
String error;
|
||||
try {
|
||||
mGroup = TunnelControllerGroup.getInstance();
|
||||
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
mGroup = null;
|
||||
error = iae.toString();
|
||||
}
|
||||
|
||||
if (mGroup == null) {
|
||||
// TODO Show error
|
||||
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
|
||||
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
|
||||
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
|
||||
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
|
||||
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
|
||||
loadPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// Pre-Honeycomb: onPause() is the last method guaranteed to be called.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
|
||||
saveTunnel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// Honeycomb and above: onStop() is the last method guaranteed to be called.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
|
||||
saveTunnel();
|
||||
}
|
||||
|
||||
private void saveTunnel() {
|
||||
if (mGroup != null) {
|
||||
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
|
||||
TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, cfg);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPreferences() {
|
||||
String type = TunnelUtil.getController(mGroup, mTunnelId).getType();
|
||||
new TunnelPreferences(type).runLogic();
|
||||
}
|
||||
|
||||
class TunnelPreferences extends TunnelLogic {
|
||||
PreferenceScreen ps;
|
||||
PreferenceCategory generalCategory;
|
||||
PreferenceCategory portCategory;
|
||||
PreferenceScreen advanced;
|
||||
PreferenceCategory tunParamCategory;
|
||||
|
||||
public TunnelPreferences(String type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void general() {
|
||||
addPreferencesFromResource(R.xml.tunnel_gen);
|
||||
ps = getPreferenceScreen();
|
||||
generalCategory = (PreferenceCategory) ps.findPreference(
|
||||
getString(R.string.TUNNEL_CAT_GENERAL));
|
||||
portCategory = (PreferenceCategory) ps.findPreference(
|
||||
getString(R.string.TUNNEL_CAT_PORT));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClient() {
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_client, generalCategory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientStreamr(boolean isStreamr) {
|
||||
if (isStreamr) {
|
||||
generalCategory.removePreference(generalCategory.findPreference(getString(R.string.TUNNEL_SHARED_CLIENT)));
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_server_port, portCategory);
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_PORT)));
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientPort() {
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_client_port, portCategory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientPortStreamr(boolean isStreamr) {
|
||||
if (isStreamr)
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_INTERFACE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientProxy(boolean isProxy) {
|
||||
if (isProxy) {
|
||||
generalCategory.removePreference(generalCategory.findPreference(getString(R.string.TUNNEL_DEST)));
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_client_proxy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientProxyHttp(boolean isHttp) {
|
||||
if (!isHttp)
|
||||
ps.removePreference(ps.findPreference(getString(R.string.TUNNEL_HTTPCLIENT_SSL_OUTPROXIES)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
|
||||
if (!isStandardOrIrc)
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientIrc() {
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_client_irc);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerHttp() {
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_server_http, generalCategory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_client_port, portCategory);
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
if (isStreamr)
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_LISTEN_PORT)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerPort() {
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_server_port, portCategory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerPortStreamr(boolean isStreamr) {
|
||||
if (isStreamr) {
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_HOST)));
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advanced() {
|
||||
addPreferencesFromResource(R.xml.tunnel_adv);
|
||||
advanced = (PreferenceScreen) ps.findPreference(
|
||||
getString(R.string.TUNNEL_CAT_ADVANCED));
|
||||
tunParamCategory = (PreferenceCategory) ps.findPreference(
|
||||
getString(R.string.TUNNEL_CAT_TUNNEL_PARAMS));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedStreamr(boolean isStreamr) {
|
||||
if (isStreamr)
|
||||
tunParamCategory.removePreference(tunParamCategory.findPreference(getString(R.string.TUNNEL_OPT_PROFILE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||
if (isServerOrStreamrClient)
|
||||
tunParamCategory.removePreference(tunParamCategory.findPreference(getString(R.string.TUNNEL_OPT_DELAY_CONNECT)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServer() {
|
||||
addPreferencesFromResource(R.xml.tunnel_adv_server, advanced);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServerHttp(boolean isHttp) {
|
||||
if (isHttp)
|
||||
addPreferencesFromResource(R.xml.tunnel_adv_server_http, advanced);
|
||||
else {
|
||||
PreferenceCategory accessCtlCategory = (PreferenceCategory) ps.findPreference(
|
||||
getString(R.string.TUNNEL_CAT_ACCESS_CONTROL));
|
||||
accessCtlCategory.removePreference(accessCtlCategory.findPreference(getString(R.string.TUNNEL_OPT_REJECT_INPROXY)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedIdle() {
|
||||
addPreferencesFromResource(R.xml.tunnel_adv_idle, advanced);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||
if (isServerOrStreamrClient)
|
||||
advanced.removePreference(advanced.findPreference(getString(R.string.TUNNEL_OPT_DELAY_OPEN)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClient() {
|
||||
PreferenceCategory idleCategory = (PreferenceCategory) ps.findPreference(
|
||||
getString(R.string.TUNNEL_CAT_IDLE)
|
||||
);
|
||||
addPreferencesFromResource(R.xml.tunnel_adv_idle_client, idleCategory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClientHttp() {
|
||||
addPreferencesFromResource(R.xml.tunnel_adv_client_http, advanced);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClientProxy() {
|
||||
addPreferencesFromResource(R.xml.tunnel_adv_client_proxy, advanced);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedOther() {
|
||||
addPreferencesFromResource(R.xml.tunnel_adv_other, advanced);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/a/20806812
|
||||
*
|
||||
* @param id the Preferences XML to load
|
||||
* @param newParent the parent PreferenceGroup to add the new Preferences to.
|
||||
*/
|
||||
private void addPreferencesFromResource (int id, PreferenceGroup newParent) {
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
int last = screen.getPreferenceCount();
|
||||
addPreferencesFromResource(id);
|
||||
while (screen.getPreferenceCount () > last) {
|
||||
Preference p = screen.getPreference (last);
|
||||
screen.removePreference(p); // decreases the preference count
|
||||
newParent.addPreference(p);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class TunnelDetailActivity extends I2PActivityBase implements
|
||||
TunnelDetailFragment.TunnelDetailListener {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
|
||||
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, detailFrag).commit();
|
||||
}
|
||||
}
|
||||
|
||||
// TunnelDetailFragment.TunnelDetailListener
|
||||
|
||||
@Override
|
||||
public void onEditTunnel(int tunnelId) {
|
||||
Intent editIntent = new Intent(this, EditTunnelActivity.class);
|
||||
editIntent.putExtra(TunnelDetailFragment.TUNNEL_ID, tunnelId);
|
||||
startActivity(editIntent);
|
||||
}
|
||||
|
||||
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft) {
|
||||
finish();
|
||||
}
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TunnelDetailFragment extends Fragment {
|
||||
public static final String TUNNEL_ID = "tunnel_id";
|
||||
|
||||
TunnelDetailListener mCallback;
|
||||
private TunnelControllerGroup mGroup;
|
||||
private TunnelEntry mTunnel;
|
||||
|
||||
public static TunnelDetailFragment newInstance(int tunnelId) {
|
||||
TunnelDetailFragment f = new TunnelDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(TUNNEL_ID, tunnelId);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface TunnelDetailListener {
|
||||
public void onEditTunnel(int tunnelId);
|
||||
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mCallback = (TunnelDetailListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnTunnelDeletedListener");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
String error;
|
||||
try {
|
||||
mGroup = TunnelControllerGroup.getInstance();
|
||||
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
mGroup = null;
|
||||
error = iae.toString();
|
||||
}
|
||||
|
||||
if (mGroup == null) {
|
||||
// Show error
|
||||
} else if (getArguments().containsKey(TUNNEL_ID)) {
|
||||
int tunnelId = getArguments().getInt(TUNNEL_ID);
|
||||
mTunnel = new TunnelEntry(getActivity(),
|
||||
mGroup.getControllers().get(tunnelId),
|
||||
tunnelId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_i2ptunnel_detail, container, false);
|
||||
|
||||
if (mTunnel != null) {
|
||||
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
||||
name.setText(mTunnel.getName());
|
||||
|
||||
TextView type = (TextView) v.findViewById(R.id.tunnel_type);
|
||||
type.setText(mTunnel.getType());
|
||||
|
||||
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
|
||||
description.setText(mTunnel.getDescription());
|
||||
|
||||
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
|
||||
details.setText(mTunnel.getDetails());
|
||||
|
||||
View accessIfacePortLabel = v.findViewById(R.id.tunnel_access_interface_port_label);
|
||||
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
|
||||
View targetIfacePortLabel = v.findViewById(R.id.tunnel_target_interface_port_label);
|
||||
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
|
||||
switch (mTunnel.getInternalType()) {
|
||||
case "httpbidirserver":
|
||||
accessIfacePort.setText(mTunnel.getClientLink(false));
|
||||
targetIfacePort.setText(mTunnel.getServerLink(false));
|
||||
break;
|
||||
case "streamrserver":
|
||||
accessIfacePort.setText(mTunnel.getServerLink(false));
|
||||
targetIfacePortLabel.setVisibility(View.GONE);
|
||||
targetIfacePort.setVisibility(View.GONE);
|
||||
break;
|
||||
case "streamrclient":
|
||||
accessIfacePortLabel.setVisibility(View.GONE);
|
||||
accessIfacePort.setVisibility(View.GONE);
|
||||
targetIfacePort.setText(mTunnel.getClientLink(false));
|
||||
break;
|
||||
default:
|
||||
if (mTunnel.isClient()) {
|
||||
accessIfacePort.setText(mTunnel.getClientLink(false));
|
||||
targetIfacePortLabel.setVisibility(View.GONE);
|
||||
targetIfacePort.setVisibility(View.GONE);
|
||||
} else {
|
||||
accessIfacePortLabel.setVisibility(View.GONE);
|
||||
accessIfacePort.setVisibility(View.GONE);
|
||||
targetIfacePort.setText(mTunnel.getServerLink(false));
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox autoStart = (CheckBox) v.findViewById(R.id.tunnel_autostart);
|
||||
autoStart.setChecked(mTunnel.startAutomatically());
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_i2ptunnel_detail_actions, menu);
|
||||
// Disable until ticket #815 is closed
|
||||
menu.findItem(R.id.action_edit_tunnel).setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
MenuItem start = menu.findItem(R.id.action_start_tunnel);
|
||||
MenuItem stop = menu.findItem(R.id.action_stop_tunnel);
|
||||
|
||||
if (mTunnel != null) {
|
||||
boolean isStopped = mTunnel.getStatus() == TunnelEntry.NOT_RUNNING;
|
||||
|
||||
start.setVisible(isStopped);
|
||||
start.setEnabled(isStopped);
|
||||
|
||||
stop.setVisible(!isStopped);
|
||||
stop.setEnabled(!isStopped);
|
||||
} else {
|
||||
start.setVisible(false);
|
||||
start.setEnabled(false);
|
||||
|
||||
stop.setVisible(false);
|
||||
stop.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (mTunnel == null)
|
||||
return false;
|
||||
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_start_tunnel:
|
||||
mTunnel.getController().startTunnelBackground();
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
getResources().getString(R.string.i2ptunnel_msg_tunnel_starting)
|
||||
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||
// Reload the action bar to change the start/stop action
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
return true;
|
||||
case R.id.action_stop_tunnel:
|
||||
mTunnel.getController().stopTunnel();
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
getResources().getString(R.string.i2ptunnel_msg_tunnel_stopping)
|
||||
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||
// Reload the action bar to change the start/stop action
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
return true;
|
||||
case R.id.action_edit_tunnel:
|
||||
mCallback.onEditTunnel(mTunnel.getId());
|
||||
return true;
|
||||
case R.id.action_delete_tunnel:
|
||||
DialogFragment dg = new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.i2ptunnel_delete_confirm_message)
|
||||
.setPositiveButton(R.string.i2ptunnel_delete_confirm_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
List<String> msgs = TunnelUtil.deleteTunnel(
|
||||
I2PAppContext.getGlobalContext(),
|
||||
mGroup, mTunnel.getId(), null);
|
||||
dialog.dismiss();
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||
mCallback.onTunnelDeleted(mTunnel.getId(),
|
||||
mGroup.getControllers().size());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
}
|
||||
};
|
||||
dg.show(getFragmentManager(), "delete_tunnel_dialog");
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
287
app/src/main/java/net/i2p/android/i2ptunnel/TunnelEntry.java
Normal file
287
app/src/main/java/net/i2p/android/i2ptunnel/TunnelEntry.java
Normal file
@ -0,0 +1,287 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TunnelEntry {
|
||||
public static final int RUNNING = 1;
|
||||
public static final int STARTING = 2;
|
||||
public static final int NOT_RUNNING = 3;
|
||||
public static final int STANDBY = 4;
|
||||
|
||||
private final Context mContext;
|
||||
private final TunnelController mController;
|
||||
private final int mId;
|
||||
|
||||
public static TunnelEntry createNewTunnel(
|
||||
Context ctx,
|
||||
TunnelControllerGroup tcg,
|
||||
TunnelConfig cfg) {
|
||||
int tunnelId = tcg.getControllers().size();
|
||||
List<String> msgs = TunnelUtil.saveTunnel(
|
||||
I2PAppContext.getGlobalContext(), tcg, -1, cfg);
|
||||
// TODO: Do something else with the other messages.
|
||||
Toast.makeText(ctx.getApplicationContext(),
|
||||
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
|
||||
return new TunnelEntry(ctx, cur, tunnelId);
|
||||
}
|
||||
|
||||
public TunnelEntry(Context context, TunnelController controller, int id) {
|
||||
mContext = context;
|
||||
mController = controller;
|
||||
mId = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public TunnelController getController() {
|
||||
return mController;
|
||||
}
|
||||
|
||||
/* General tunnel data for any type */
|
||||
|
||||
public String getName() {
|
||||
if (mController.getName() != null)
|
||||
return mController.getName();
|
||||
else
|
||||
return mContext.getResources()
|
||||
.getString(R.string.i2ptunnel_new_tunnel);
|
||||
}
|
||||
|
||||
public String getInternalType() {
|
||||
return mController.getType();
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return TunnelUtil.getTypeName(mController.getType(), mContext);
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
String rv = mController.getDescription();
|
||||
if (rv != null)
|
||||
return rv;
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean startAutomatically() {
|
||||
return mController.getStartOnLoad();
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
if (mController.getIsRunning()) {
|
||||
if (isClient() && mController.getIsStandby())
|
||||
return STANDBY;
|
||||
else
|
||||
return RUNNING;
|
||||
} else if (mController.getIsStarting()) return STARTING;
|
||||
else return NOT_RUNNING;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
switch (getStatus()) {
|
||||
case STANDBY:
|
||||
case RUNNING:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClient() {
|
||||
return TunnelUtil.isClient(mController.getType());
|
||||
}
|
||||
|
||||
/* Client tunnel data */
|
||||
|
||||
public boolean isSharedClient() {
|
||||
return Boolean.parseBoolean(mController.getSharedClient());
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to see if it is okay to linkify getClientLink()
|
||||
* @return true if getClientLink() can be linkified, false otherwise.
|
||||
*/
|
||||
public boolean isClientLinkValid() {
|
||||
return ("ircclient".equals(mController.getType())) &&
|
||||
mController.getListenOnInterface() != null &&
|
||||
mController.getListenPort() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return valid host:port only if isClientLinkValid() is true
|
||||
*/
|
||||
public String getClientLink(boolean linkify) {
|
||||
String host = getClientInterface();
|
||||
String port = getClientPort();
|
||||
String link = host + ":" + port;
|
||||
if (linkify) {
|
||||
if ("ircclient".equals(mController.getType()))
|
||||
link = "irc://" + link;
|
||||
}
|
||||
return link;
|
||||
}
|
||||
|
||||
public String getClientInterface() {
|
||||
String rv;
|
||||
if ("streamrclient".equals(mController.getType()))
|
||||
rv = mController.getTargetHost();
|
||||
else
|
||||
rv = mController.getListenOnInterface();
|
||||
return rv != null ? rv : "";
|
||||
}
|
||||
|
||||
public String getClientPort() {
|
||||
String rv = mController.getListenPort();
|
||||
return rv != null ? rv : "";
|
||||
}
|
||||
|
||||
public String getClientDestination() {
|
||||
String rv;
|
||||
if ("client".equals(getInternalType()) ||
|
||||
"ircclient".equals(getInternalType()) ||
|
||||
"streamrclient".equals(getInternalType()))
|
||||
rv = mController.getTargetDestination();
|
||||
else
|
||||
rv = mController.getProxyList();
|
||||
return rv != null ? rv : "";
|
||||
}
|
||||
|
||||
/* Server tunnel data */
|
||||
|
||||
/**
|
||||
* Call this to see if it is okay to linkify getServerLink()
|
||||
* @return true if getServerLink() can be linkified, false otherwise.
|
||||
*/
|
||||
public boolean isServerLinkValid() {
|
||||
return ("httpserver".equals(mController.getType()) ||
|
||||
"httpbidirserver".equals(mController.getType())) &&
|
||||
mController.getTargetHost() != null &&
|
||||
mController.getTargetPort() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return valid host:port only if isServerLinkValid() is true
|
||||
*/
|
||||
public String getServerLink(boolean linkify) {
|
||||
String host;
|
||||
if ("streamrserver".equals(getInternalType()))
|
||||
host = mController.getListenOnInterface();
|
||||
else
|
||||
host = mController.getTargetHost();
|
||||
String port = mController.getTargetPort();
|
||||
if (host == null) host = "";
|
||||
if (port == null) port = "";
|
||||
if (host.indexOf(':') >= 0)
|
||||
host = '[' + host + ']';
|
||||
String link = host + ":" + port;
|
||||
if (linkify) {
|
||||
if ("httpserver".equals(mController.getType()) ||
|
||||
"httpbidirserver".equals(mController.getType()))
|
||||
link = "http://" + link;
|
||||
}
|
||||
return link;
|
||||
}
|
||||
|
||||
public String getDestinationBase64() {
|
||||
String rv = mController.getMyDestination();
|
||||
if (rv != null)
|
||||
return rv;
|
||||
// if not running, do this the hard way
|
||||
String keyFile = mController.getPrivKeyFile();
|
||||
if (keyFile != null && keyFile.trim().length() > 0) {
|
||||
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
|
||||
try {
|
||||
Destination d = pkf.getDestination();
|
||||
if (d != null)
|
||||
return d.toBase64();
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getDestHashBase32() {
|
||||
String rv = mController.getMyDestHashBase32();
|
||||
if (rv != null)
|
||||
return rv;
|
||||
return "";
|
||||
}
|
||||
|
||||
/* Data for some client and server tunnels */
|
||||
|
||||
/* Other output formats */
|
||||
|
||||
public boolean isTunnelLinkValid() {
|
||||
if (isClient()) return isClientLinkValid();
|
||||
else return isServerLinkValid();
|
||||
}
|
||||
|
||||
public String getTunnelLink(boolean linkify) {
|
||||
if (isClient()) return getClientLink(linkify);
|
||||
else return getServerLink(linkify);
|
||||
}
|
||||
|
||||
public Uri getRecommendedAppForTunnel() {
|
||||
int resId = 0;
|
||||
if ("ircclient".equals(mController.getType()))
|
||||
resId = R.string.market_irc;
|
||||
|
||||
if (resId > 0)
|
||||
return Uri.parse(mContext.getString(resId));
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
String details;
|
||||
if (isClient())
|
||||
details = getClientDestination();
|
||||
else
|
||||
details = "";
|
||||
return details;
|
||||
}
|
||||
|
||||
public Drawable getStatusIcon() {
|
||||
switch (getStatus()) {
|
||||
case STANDBY:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.ic_schedule_black_24dp);
|
||||
case STARTING:
|
||||
case RUNNING:
|
||||
case NOT_RUNNING:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable getStatusBackground() {
|
||||
switch (getStatus()) {
|
||||
case STANDBY:
|
||||
case STARTING:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.tunnel_yellow);
|
||||
case RUNNING:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.tunnel_green);
|
||||
case NOT_RUNNING:
|
||||
default:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.tunnel_red);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TunnelEntryAdapter extends ArrayAdapter<TunnelEntry> {
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
public TunnelEntryAdapter(Context context) {
|
||||
super(context, android.R.layout.simple_list_item_2);
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
public void setData(List<TunnelEntry> tunnels) {
|
||||
clear();
|
||||
if (tunnels != null) {
|
||||
for (TunnelEntry tunnel : tunnels) {
|
||||
add(tunnel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = mInflater.inflate(R.layout.listitem_i2ptunnel, parent, false);
|
||||
final TunnelEntry tunnel = getItem(position);
|
||||
|
||||
ImageView status = (ImageView) v.findViewById(R.id.tunnel_status);
|
||||
status.setImageDrawable(tunnel.getStatusIcon());
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||
status.setBackgroundDrawable(tunnel.getStatusBackground());
|
||||
else
|
||||
status.setBackground(tunnel.getStatusBackground());
|
||||
|
||||
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
||||
name.setText(tunnel.getName());
|
||||
|
||||
TextView type = (TextView) v.findViewById(R.id.tunnel_description);
|
||||
type.setText(tunnel.getDescription());
|
||||
|
||||
TextView ifacePort = (TextView) v.findViewById(R.id.tunnel_interface_port);
|
||||
ifacePort.setText(tunnel.getTunnelLink(false));
|
||||
|
||||
if (tunnel.isRunning() && tunnel.isTunnelLinkValid()) {
|
||||
View open = v.findViewById(R.id.tunnel_open);
|
||||
open.setVisibility(View.VISIBLE);
|
||||
open.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(tunnel.getTunnelLink(true)));
|
||||
try {
|
||||
getContext().startActivity(i);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle(R.string.install_recommended_app)
|
||||
.setMessage(R.string.app_needed_for_this_tunnel_type)
|
||||
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Uri uri = tunnel.getRecommendedAppForTunnel();
|
||||
if (uri != null) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(net.i2p.android.lib.client.R.string.no, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
|
||||
private TunnelControllerGroup mGroup;
|
||||
private boolean mClientTunnels;
|
||||
private List<TunnelEntry> mData;
|
||||
private Handler mHandler;
|
||||
private TunnelControllerMonitor mMonitor;
|
||||
|
||||
public TunnelEntryLoader(Context context, TunnelControllerGroup tcg, boolean clientTunnels) {
|
||||
super(context);
|
||||
mGroup = tcg;
|
||||
mClientTunnels = clientTunnels;
|
||||
mHandler = new Handler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TunnelEntry> loadInBackground() {
|
||||
List<TunnelEntry> ret = new ArrayList<>();
|
||||
List<TunnelController> controllers = mGroup.getControllers();
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelEntry tunnel = new TunnelEntry(getContext(), controllers.get(i), i);
|
||||
if ( (mClientTunnels && tunnel.isClient()) ||
|
||||
(!mClientTunnels && !tunnel.isClient()) )
|
||||
ret.add(tunnel);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(List<TunnelEntry> data) {
|
||||
if (isReset()) {
|
||||
// The Loader has been reset; ignore the result and invalidate the data.
|
||||
if (data != null) {
|
||||
releaseResources(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||
// We must protect it until the new data has been delivered.
|
||||
List<TunnelEntry> oldData = mData;
|
||||
mData = data;
|
||||
|
||||
if (isStarted()) {
|
||||
// If the Loader is in a started state, have the superclass deliver the
|
||||
// results to the client.
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
// Invalidate the old data as we don't need it any more.
|
||||
if (oldData != null && oldData != data) {
|
||||
releaseResources(oldData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mData != null) {
|
||||
// Deliver any previously loaded data immediately.
|
||||
deliverResult(mData);
|
||||
}
|
||||
|
||||
// Begin monitoring the underlying data source.
|
||||
mMonitor = new TunnelControllerMonitor();
|
||||
mHandler.postDelayed(mMonitor, 50);
|
||||
|
||||
if (takeContentChanged() || mData == null) {
|
||||
// When the observer detects a change, it should call onContentChanged()
|
||||
// on the Loader, which will cause the next call to takeContentChanged()
|
||||
// to return true. If this is ever the case (or if the current data is
|
||||
// null), we force a new load.
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||
// current load (if there is one).
|
||||
cancelLoad();
|
||||
|
||||
// Note that we leave the observer as is. Loaders in a stopped state
|
||||
// should still monitor the data source for changes so that the Loader
|
||||
// will know to force a new load if it is ever started again.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
// Ensure the loader has been stopped.
|
||||
onStopLoading();
|
||||
|
||||
// At this point we can release the resources associated with 'mData'.
|
||||
if (mData != null) {
|
||||
releaseResources(mData);
|
||||
mData = null;
|
||||
}
|
||||
|
||||
// The Loader is being reset, so we should stop monitoring for changes.
|
||||
if (mMonitor != null) {
|
||||
mHandler.removeCallbacks(mMonitor);
|
||||
mMonitor = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(List<TunnelEntry> data) {
|
||||
// Attempt to cancel the current asynchronous load.
|
||||
super.onCanceled(data);
|
||||
|
||||
// The load has been canceled, so we should release the resources
|
||||
// associated with 'data'.
|
||||
releaseResources(data);
|
||||
}
|
||||
|
||||
private void releaseResources(List<TunnelEntry> data) {
|
||||
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||
// would close it in this method. All resources associated with the Loader
|
||||
// should be released here.
|
||||
}
|
||||
|
||||
private class TunnelControllerMonitor implements Runnable {
|
||||
public void run() {
|
||||
// There is no way (yet) to monitor for changes to the list of
|
||||
// TunnelControllers, so just force a refresh every 10 seconds.
|
||||
onContentChanged();
|
||||
mHandler.postDelayed(this, 10 * 1000);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class TunnelListActivity extends I2PActivityBase implements
|
||||
TunnelListFragment.OnTunnelSelectedListener,
|
||||
TunnelDetailFragment.TunnelDetailListener {
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
private static final String SELECTED_PAGE = "selected_page";
|
||||
private static final int PAGE_CLIENT = 0;
|
||||
|
||||
private Spinner mSpinner;
|
||||
|
||||
@Override
|
||||
protected boolean canUseTwoPanes() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
|
||||
R.array.i2ptunnel_pages, android.R.layout.simple_spinner_dropdown_item));
|
||||
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectPage(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_PAGE);
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectPage(PAGE_CLIENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_PAGE, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
private void selectPage(int page) {
|
||||
TunnelListFragment f = new TunnelListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(TunnelListFragment.SHOW_CLIENT_TUNNELS, page == PAGE_CLIENT);
|
||||
f.setArguments(args);
|
||||
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
if (mTwoPane)
|
||||
f.setActivateOnItemClick(true);
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f).commit();
|
||||
}
|
||||
|
||||
// TunnelListFragment.OnTunnelSelectedListener
|
||||
|
||||
public void onTunnelSelected(int tunnelId) {
|
||||
if (mTwoPane) {
|
||||
// In two-pane mode, show the detail view in this activity by
|
||||
// adding or replacing the detail fragment using a
|
||||
// fragment transaction.
|
||||
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
} else {
|
||||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
Intent detailIntent = new Intent(this, TunnelDetailActivity.class);
|
||||
detailIntent.putExtra(TunnelDetailFragment.TUNNEL_ID, tunnelId);
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
}
|
||||
|
||||
// TunnelDetailFragment.TunnelDetailListener
|
||||
|
||||
@Override
|
||||
public void onEditTunnel(int tunnelId) {
|
||||
EditTunnelFragment editFrag = EditTunnelFragment.newInstance(tunnelId);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, editFrag)
|
||||
.addToBackStack("")
|
||||
.commit();
|
||||
}
|
||||
|
||||
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft) {
|
||||
// Should only get here in two-pane mode, but just to be safe:
|
||||
if (mTwoPane) {
|
||||
if (numTunnelsLeft > 0) {
|
||||
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(
|
||||
(tunnelId > 0 ? tunnelId - 1 : 0));
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
} else {
|
||||
TunnelDetailFragment detailFrag = (TunnelDetailFragment) getSupportFragmentManager().findFragmentById(R.id.detail_fragment);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.remove(detailFrag).commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,298 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.help.HelpActivity;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TunnelListFragment extends ListFragment implements
|
||||
I2PFragmentBase.RouterContextUser,
|
||||
LoaderManager.LoaderCallbacks<List<TunnelEntry>> {
|
||||
public static final String SHOW_CLIENT_TUNNELS = "show_client_tunnels";
|
||||
public static final String TUNNEL_WIZARD_DATA = "tunnel_wizard_data";
|
||||
|
||||
static final int TUNNEL_WIZARD_REQUEST = 1;
|
||||
|
||||
private static final int CLIENT_LOADER_ID = 1;
|
||||
private static final int SERVER_LOADER_ID = 2;
|
||||
/**
|
||||
* The serialization (saved instance state) Bundle key representing the
|
||||
* activated item position. Only used on tablets.
|
||||
*/
|
||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||
|
||||
private boolean mOnActivityCreated;
|
||||
RouterContextProvider mRouterContextProvider;
|
||||
OnTunnelSelectedListener mCallback;
|
||||
private TunnelControllerGroup mGroup;
|
||||
private TunnelEntryAdapter mAdapter;
|
||||
private boolean mClientTunnels;
|
||||
/**
|
||||
* The current activated item position. Only used on tablets.
|
||||
*/
|
||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||
private boolean mActivateOnItemClick = false;
|
||||
|
||||
private ImageButton mNewTunnel;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnTunnelSelectedListener {
|
||||
public void onTunnelSelected(int tunnelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mRouterContextProvider = (RouterContextProvider) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement RouterContextProvider");
|
||||
}
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mCallback = (OnTunnelSelectedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnTunnelSelectedListener");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// Create the list fragment's content view by calling the super method
|
||||
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
|
||||
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
|
||||
listContainer.addView(listFragmentView);
|
||||
|
||||
mNewTunnel = (ImageButton) v.findViewById(R.id.promoted_action);
|
||||
mNewTunnel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
|
||||
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// Restore the previously serialized activated item position.
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||
setActivatedPosition(savedInstanceState
|
||||
.getInt(STATE_ACTIVATED_POSITION));
|
||||
}
|
||||
|
||||
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||
// give items the 'activated' state when touched.
|
||||
getListView().setChoiceMode(
|
||||
mActivateOnItemClick ? ListView.CHOICE_MODE_SINGLE
|
||||
: ListView.CHOICE_MODE_NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mAdapter = new TunnelEntryAdapter(getActivity());
|
||||
mClientTunnels = getArguments().getBoolean(SHOW_CLIENT_TUNNELS);
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
mOnActivityCreated = true;
|
||||
if (getRouterContext() != null)
|
||||
onRouterConnectionReady();
|
||||
else
|
||||
setEmptyText(getResources().getString(
|
||||
R.string.router_not_running));
|
||||
}
|
||||
|
||||
public void onRouterConnectionReady() {
|
||||
String error;
|
||||
try {
|
||||
mGroup = TunnelControllerGroup.getInstance();
|
||||
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
mGroup = null;
|
||||
error = iae.toString();
|
||||
}
|
||||
|
||||
if (mGroup == null) {
|
||||
setEmptyText(error);
|
||||
} else {
|
||||
if (mClientTunnels)
|
||||
setEmptyText("No configured client tunnels.");
|
||||
else
|
||||
setEmptyText("No configured server tunnels.");
|
||||
|
||||
setListShown(false);
|
||||
getLoaderManager().initLoader(mClientTunnels ? CLIENT_LOADER_ID
|
||||
: SERVER_LOADER_ID, null, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||
super.onListItemClick(parent, view, pos, id);
|
||||
mCallback.onTunnelSelected(mAdapter.getItem(pos).getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||
// Serialize and persist the activated item position.
|
||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_i2ptunnel_list_actions, menu);
|
||||
if (getRouterContext() == null) {
|
||||
mNewTunnel.setVisibility(View.GONE);
|
||||
menu.findItem(R.id.action_start_all_tunnels).setVisible(false);
|
||||
menu.findItem(R.id.action_stop_all_tunnels).setVisible(false);
|
||||
menu.findItem(R.id.action_restart_all_tunnels).setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
List<String> msgs;
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_start_all_tunnels:
|
||||
msgs = mGroup.startAllControllers();
|
||||
break;
|
||||
case R.id.action_stop_all_tunnels:
|
||||
msgs = mGroup.stopAllControllers();
|
||||
break;
|
||||
case R.id.action_restart_all_tunnels:
|
||||
msgs = mGroup.restartAllControllers();
|
||||
break;
|
||||
case R.id.action_i2ptunnel_help:
|
||||
Intent hi = new Intent(getActivity(), HelpActivity.class);
|
||||
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_I2PTUNNEL);
|
||||
startActivity(hi);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
// TODO: Do something with the other messages
|
||||
if (msgs.size() > 0)
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == TUNNEL_WIZARD_REQUEST) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Bundle tunnelData = data.getExtras().getBundle(TUNNEL_WIZARD_DATA);
|
||||
TunnelConfig cfg = TunnelUtil.createConfigFromWizard(getActivity(), mGroup, tunnelData);
|
||||
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), mGroup, cfg);
|
||||
mAdapter.add(tunnel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||
* given the 'activated' state when touched.
|
||||
*/
|
||||
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
||||
mActivateOnItemClick = activateOnItemClick;
|
||||
}
|
||||
|
||||
private void setActivatedPosition(int position) {
|
||||
if (position == ListView.INVALID_POSITION) {
|
||||
getListView().setItemChecked(mActivatedPosition, false);
|
||||
} else {
|
||||
getListView().setItemChecked(position, true);
|
||||
}
|
||||
|
||||
mActivatedPosition = position;
|
||||
}
|
||||
|
||||
// Duplicated from I2PFragmentBase because this extends ListFragment
|
||||
private RouterContext getRouterContext() {
|
||||
return mRouterContextProvider.getRouterContext();
|
||||
}
|
||||
|
||||
// I2PFragmentBase.RouterContextUser
|
||||
|
||||
public void onRouterBind() {
|
||||
if (mOnActivityCreated)
|
||||
onRouterConnectionReady();
|
||||
}
|
||||
|
||||
// LoaderManager.LoaderCallbacks<List<TunnelEntry>>
|
||||
|
||||
public Loader<List<TunnelEntry>> onCreateLoader(int id, Bundle args) {
|
||||
return new TunnelEntryLoader(getActivity(), mGroup, mClientTunnels);
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<List<TunnelEntry>> loader,
|
||||
List<TunnelEntry> data) {
|
||||
if (loader.getId() == (mClientTunnels ?
|
||||
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
|
||||
mAdapter.setData(data);
|
||||
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<List<TunnelEntry>> loader) {
|
||||
if (loader.getId() == (mClientTunnels ?
|
||||
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
|
||||
mAdapter.setData(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
||||
|
||||
public class TunnelWizardActivity extends AbstractWizardActivity {
|
||||
@Override
|
||||
protected AbstractWizardModel onCreateModel() {
|
||||
return new TunnelWizardModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DialogFragment onGetFinishWizardDialog() {
|
||||
return new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.i2ptunnel_wizard_submit_confirm_message)
|
||||
.setPositiveButton(R.string.i2ptunnel_wizard_submit_confirm_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(TunnelListFragment.TUNNEL_WIZARD_DATA, mWizardModel.save());
|
||||
setResult(Activity.RESULT_OK, result);
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||
import net.i2p.android.wizard.model.BranchPage;
|
||||
import net.i2p.android.wizard.model.Conditional;
|
||||
import net.i2p.android.wizard.model.I2PDestinationPage;
|
||||
import net.i2p.android.wizard.model.PageList;
|
||||
import net.i2p.android.wizard.model.SingleFixedBooleanPage;
|
||||
import net.i2p.android.wizard.model.SingleFixedChoicePage;
|
||||
import net.i2p.android.wizard.model.SingleTextFieldPage;
|
||||
|
||||
public class TunnelWizardModel extends AbstractWizardModel {
|
||||
public TunnelWizardModel(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PageList onNewRootPageList() {
|
||||
Resources res = mContext.getResources();
|
||||
Conditional cTunnelType = new Conditional();
|
||||
Conditional cClientType = new Conditional();
|
||||
Conditional cServerType = new Conditional();
|
||||
|
||||
return new PageList(
|
||||
new BranchPage(this, res.getString(R.string.i2ptunnel_wizard_k_client_server))
|
||||
.addBranch(res.getString(R.string.i2ptunnel_wizard_v_client),
|
||||
new SingleFixedChoicePage(this, res.getString(R.string.i2ptunnel_wizard_k_type))
|
||||
.setChoices(
|
||||
res.getString(R.string.i2ptunnel_type_client),
|
||||
res.getString(R.string.i2ptunnel_type_httpclient),
|
||||
res.getString(R.string.i2ptunnel_type_ircclient),
|
||||
res.getString(R.string.i2ptunnel_type_sockstunnel),
|
||||
res.getString(R.string.i2ptunnel_type_socksirctunnel),
|
||||
res.getString(R.string.i2ptunnel_type_connectclient),
|
||||
res.getString(R.string.i2ptunnel_type_streamrclient))
|
||||
.setRequired(true)
|
||||
.makeConditional(cClientType))
|
||||
.addBranch(res.getString(R.string.i2ptunnel_wizard_v_server),
|
||||
new SingleFixedChoicePage(this, res.getString(R.string.i2ptunnel_wizard_k_type))
|
||||
.setChoices(
|
||||
res.getString(R.string.i2ptunnel_type_server),
|
||||
res.getString(R.string.i2ptunnel_type_httpserver),
|
||||
res.getString(R.string.i2ptunnel_type_httpbidirserver),
|
||||
res.getString(R.string.i2ptunnel_type_ircserver),
|
||||
res.getString(R.string.i2ptunnel_type_streamrserver))
|
||||
.setRequired(true)
|
||||
.makeConditional(cServerType))
|
||||
.setRequired(true)
|
||||
.makeConditional(cTunnelType),
|
||||
|
||||
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_name))
|
||||
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_name))
|
||||
.setRequired(true),
|
||||
|
||||
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_desc))
|
||||
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_desc)),
|
||||
|
||||
new I2PDestinationPage(this, res.getString(R.string.i2ptunnel_wizard_k_dest))
|
||||
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_dest))
|
||||
.setRequired(true)
|
||||
.setEqualAnyCondition(cClientType,
|
||||
res.getString(R.string.i2ptunnel_type_client),
|
||||
res.getString(R.string.i2ptunnel_type_ircclient),
|
||||
res.getString(R.string.i2ptunnel_type_streamrclient)),
|
||||
|
||||
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_outproxies))
|
||||
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_outproxies))
|
||||
.setEqualAnyCondition(cClientType,
|
||||
res.getString(R.string.i2ptunnel_type_httpclient),
|
||||
res.getString(R.string.i2ptunnel_type_connectclient),
|
||||
res.getString(R.string.i2ptunnel_type_sockstunnel),
|
||||
res.getString(R.string.i2ptunnel_type_socksirctunnel)),
|
||||
|
||||
// Not set required because a default is specified.
|
||||
// Otherwise user would need to edit the field to
|
||||
// enable the Next button.
|
||||
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_target_host))
|
||||
.setDefault("127.0.0.1")
|
||||
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_target_host))
|
||||
.setEqualCondition(cClientType,
|
||||
res.getString(R.string.i2ptunnel_type_streamrclient))
|
||||
.setEqualAnyCondition(cServerType,
|
||||
res.getString(R.string.i2ptunnel_type_server),
|
||||
res.getString(R.string.i2ptunnel_type_httpserver),
|
||||
res.getString(R.string.i2ptunnel_type_httpbidirserver),
|
||||
res.getString(R.string.i2ptunnel_type_ircserver)),
|
||||
|
||||
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_target_port))
|
||||
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_target_port))
|
||||
.setNumeric(true)
|
||||
.setRequired(true)
|
||||
.setEqualCondition(cTunnelType, res.getString(R.string.i2ptunnel_wizard_v_server)),
|
||||
|
||||
// Not set required because a default is specified.
|
||||
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_reachable_on))
|
||||
.setDefault("127.0.0.1")
|
||||
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_reachable_on))
|
||||
.setEqualAnyCondition(cClientType,
|
||||
res.getString(R.string.i2ptunnel_type_client),
|
||||
res.getString(R.string.i2ptunnel_type_httpclient),
|
||||
res.getString(R.string.i2ptunnel_type_ircclient),
|
||||
res.getString(R.string.i2ptunnel_type_sockstunnel),
|
||||
res.getString(R.string.i2ptunnel_type_socksirctunnel),
|
||||
res.getString(R.string.i2ptunnel_type_connectclient))
|
||||
.setEqualAnyCondition(cServerType,
|
||||
res.getString(R.string.i2ptunnel_type_httpbidirserver),
|
||||
res.getString(R.string.i2ptunnel_type_streamrserver)),
|
||||
|
||||
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_binding_port))
|
||||
.setDescription(res.getString(R.string.i2ptunnel_wizard_k_binding_port))
|
||||
.setNumeric(true)
|
||||
.setRequired(true)
|
||||
.setEqualCondition(cTunnelType, res.getString(R.string.i2ptunnel_wizard_v_client))
|
||||
.setEqualCondition(cServerType, res.getString(R.string.i2ptunnel_type_httpbidirserver)),
|
||||
|
||||
new SingleFixedBooleanPage(this, res.getString(R.string.i2ptunnel_wizard_k_auto_start))
|
||||
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_auto_start))
|
||||
.setRequired(true)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package net.i2p.android.i2ptunnel.util;
|
||||
|
||||
/**
|
||||
* Generic class for handling the composition of tunnel properties.
|
||||
* <p/>
|
||||
* See I2PTunnel's editClient.jsp and editServer.jsp for composition logic.
|
||||
* <p/>
|
||||
* Some of the abstract methods have boolean parameters. These are the methods
|
||||
* where the corresponding tunnel properties may or may not exist, depending on
|
||||
* the value of the boolean. In all other abstract methods, all corresponding
|
||||
* tunnel properties always exist.
|
||||
*/
|
||||
public abstract class TunnelLogic {
|
||||
protected String mType;
|
||||
|
||||
public TunnelLogic(String type) {
|
||||
mType = type;
|
||||
}
|
||||
|
||||
public void runLogic() {
|
||||
boolean isProxy = "httpclient".equals(mType) ||
|
||||
"connectclient".equals(mType) ||
|
||||
"sockstunnel".equals(mType) ||
|
||||
"socksirctunnel".equals(mType);
|
||||
|
||||
general();
|
||||
|
||||
if (TunnelUtil.isClient(mType)) {
|
||||
generalClient();
|
||||
generalClientStreamr("streamrclient".equals(mType));
|
||||
|
||||
generalClientPort();
|
||||
generalClientPortStreamr("streamrclient".equals(mType));
|
||||
|
||||
generalClientProxy(isProxy);
|
||||
if (isProxy)
|
||||
generalClientProxyHttp("httpclient".equals(mType));
|
||||
|
||||
generalClientStandardOrIrc("client".equals(mType) || "ircclient".equals(mType));
|
||||
if ("ircclient".equals(mType))
|
||||
generalClientIrc();
|
||||
} else {
|
||||
if ("httpserver".equals(mType) || "httpbidirserver".equals(mType))
|
||||
generalServerHttp();
|
||||
if ("httpbidirserver".equals(mType) || "streamrserver".equals(mType))
|
||||
generalServerHttpBidirOrStreamr("streamrserver".equals(mType));
|
||||
|
||||
generalServerPort();
|
||||
generalServerPortStreamr("streamrserver".equals(mType));
|
||||
}
|
||||
|
||||
advanced();
|
||||
advancedStreamr("streamrclient".equals(mType) || "streamrserver".equals(mType));
|
||||
advancedServerOrStreamrClient(!TunnelUtil.isClient(mType) || "streamrclient".equals(mType));
|
||||
|
||||
if (!TunnelUtil.isClient(mType)) {
|
||||
advancedServer();
|
||||
advancedServerHttp("httpserver".equals(mType) || "httpbidirserver".equals(mType));
|
||||
}
|
||||
|
||||
advancedIdle();
|
||||
// streamr client sends pings so it will never be idle
|
||||
advancedIdleServerOrStreamrClient(!TunnelUtil.isClient(mType) || "streamrclient".equals(mType));
|
||||
|
||||
if (TunnelUtil.isClient(mType)) {
|
||||
advancedClient();
|
||||
if ("httpclient".equals(mType))
|
||||
advancedClientHttp();
|
||||
if (isProxy)
|
||||
advancedClientProxy();
|
||||
}
|
||||
|
||||
advancedOther();
|
||||
}
|
||||
|
||||
protected abstract void general();
|
||||
protected abstract void generalClient();
|
||||
protected abstract void generalClientStreamr(boolean isStreamr);
|
||||
protected abstract void generalClientPort();
|
||||
protected abstract void generalClientPortStreamr(boolean isStreamr);
|
||||
protected abstract void generalClientProxy(boolean isProxy);
|
||||
protected abstract void generalClientProxyHttp(boolean isHttp);
|
||||
protected abstract void generalClientStandardOrIrc(boolean isStandardOrIrc);
|
||||
protected abstract void generalClientIrc();
|
||||
protected abstract void generalServerHttp();
|
||||
protected abstract void generalServerHttpBidirOrStreamr(boolean isStreamr);
|
||||
protected abstract void generalServerPort();
|
||||
protected abstract void generalServerPortStreamr(boolean isStreamr);
|
||||
|
||||
protected abstract void advanced();
|
||||
protected abstract void advancedStreamr(boolean isStreamr);
|
||||
protected abstract void advancedServerOrStreamrClient(boolean isServerOrStreamrClient);
|
||||
protected abstract void advancedServer();
|
||||
protected abstract void advancedServerHttp(boolean isHttp);
|
||||
protected abstract void advancedIdle();
|
||||
protected abstract void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient);
|
||||
protected abstract void advancedClient();
|
||||
protected abstract void advancedClientHttp();
|
||||
protected abstract void advancedClientProxy();
|
||||
protected abstract void advancedOther();
|
||||
}
|
782
app/src/main/java/net/i2p/android/i2ptunnel/util/TunnelUtil.java
Normal file
782
app/src/main/java/net/i2p/android/i2ptunnel/util/TunnelUtil.java
Normal file
@ -0,0 +1,782 @@
|
||||
package net.i2p.android.i2ptunnel.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.wizard.model.Page;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.i2ptunnel.ui.GeneralHelper;
|
||||
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||
|
||||
public class TunnelUtil extends GeneralHelper {
|
||||
public static final String PREFERENCES_FILENAME_PREFIX = "tunnel.";
|
||||
|
||||
public TunnelUtil(I2PAppContext context, TunnelControllerGroup tcg) {
|
||||
super(context, tcg);
|
||||
}
|
||||
|
||||
public TunnelUtil(TunnelControllerGroup tcg) {
|
||||
super(tcg);
|
||||
}
|
||||
|
||||
/* General tunnel data for any type */
|
||||
|
||||
public static String getTypeFromName(String typeName, Context ctx) {
|
||||
Resources res = ctx.getResources();
|
||||
if (res.getString(R.string.i2ptunnel_type_client).equals(typeName))
|
||||
return "client";
|
||||
else if (res.getString(R.string.i2ptunnel_type_httpclient).equals(typeName))
|
||||
return "httpclient";
|
||||
else if (res.getString(R.string.i2ptunnel_type_ircclient).equals(typeName))
|
||||
return "ircclient";
|
||||
else if (res.getString(R.string.i2ptunnel_type_server).equals(typeName))
|
||||
return "server";
|
||||
else if (res.getString(R.string.i2ptunnel_type_httpserver).equals(typeName))
|
||||
return "httpserver";
|
||||
else if (res.getString(R.string.i2ptunnel_type_sockstunnel).equals(typeName))
|
||||
return "sockstunnel";
|
||||
else if (res.getString(R.string.i2ptunnel_type_socksirctunnel).equals(typeName))
|
||||
return "socksirctunnel";
|
||||
else if (res.getString(R.string.i2ptunnel_type_connectclient).equals(typeName))
|
||||
return "connectclient";
|
||||
else if (res.getString(R.string.i2ptunnel_type_ircserver).equals(typeName))
|
||||
return "ircserver";
|
||||
else if (res.getString(R.string.i2ptunnel_type_streamrclient).equals(typeName))
|
||||
return "streamrclient";
|
||||
else if (res.getString(R.string.i2ptunnel_type_streamrserver).equals(typeName))
|
||||
return "streamrserver";
|
||||
else if (res.getString(R.string.i2ptunnel_type_httpbidirserver).equals(typeName))
|
||||
return "httpbidirserver";
|
||||
else
|
||||
return typeName;
|
||||
}
|
||||
|
||||
public static String getTypeName(String type, Context context) {
|
||||
Resources res = context.getResources();
|
||||
switch (type) {
|
||||
case "client":
|
||||
return res.getString(R.string.i2ptunnel_type_client);
|
||||
case "httpclient":
|
||||
return res.getString(R.string.i2ptunnel_type_httpclient);
|
||||
case "ircclient":
|
||||
return res.getString(R.string.i2ptunnel_type_ircclient);
|
||||
case "server":
|
||||
return res.getString(R.string.i2ptunnel_type_server);
|
||||
case "httpserver":
|
||||
return res.getString(R.string.i2ptunnel_type_httpserver);
|
||||
case "sockstunnel":
|
||||
return res.getString(R.string.i2ptunnel_type_sockstunnel);
|
||||
case "socksirctunnel":
|
||||
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
|
||||
case "connectclient":
|
||||
return res.getString(R.string.i2ptunnel_type_connectclient);
|
||||
case "ircserver":
|
||||
return res.getString(R.string.i2ptunnel_type_ircserver);
|
||||
case "streamrclient":
|
||||
return res.getString(R.string.i2ptunnel_type_streamrclient);
|
||||
case "streamrserver":
|
||||
return res.getString(R.string.i2ptunnel_type_streamrserver);
|
||||
case "httpbidirserver":
|
||||
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isClient(String type) {
|
||||
return TunnelController.isClient(type);
|
||||
}
|
||||
|
||||
public static String getPreferencesFilename(int tunnel) {
|
||||
return PREFERENCES_FILENAME_PREFIX + tunnel;
|
||||
}
|
||||
|
||||
public static void writeTunnelToPreferences(Context ctx, TunnelControllerGroup tcg, int tunnel) {
|
||||
new TunnelUtil(tcg).writeTunnelToPreferences(ctx, tunnel);
|
||||
}
|
||||
public void writeTunnelToPreferences(Context ctx, int tunnel) {
|
||||
Resources res = ctx.getResources();
|
||||
|
||||
if (getController(tunnel) == null)
|
||||
throw new IllegalArgumentException("Cannot write non-existent tunnel to Preferences");
|
||||
|
||||
// Get the current preferences for this tunnel
|
||||
SharedPreferences preferences = ctx.getSharedPreferences(
|
||||
getPreferencesFilename(tunnel), Context.MODE_PRIVATE);
|
||||
|
||||
// Clear all previous values
|
||||
SharedPreferences.Editor ed = preferences.edit().clear();
|
||||
|
||||
// Load the tunnel config into the preferences
|
||||
String type = getTunnelType(tunnel);
|
||||
ed.putString(res.getString(R.string.TUNNEL_TYPE), type);
|
||||
|
||||
new TunnelToPreferences(ed, res, tunnel, type).runLogic();
|
||||
|
||||
ed.apply();
|
||||
}
|
||||
|
||||
class TunnelToPreferences extends TunnelLogic {
|
||||
SharedPreferences.Editor ed;
|
||||
Resources res;
|
||||
int tunnel;
|
||||
|
||||
public TunnelToPreferences(SharedPreferences.Editor ed, Resources res, int tunnel, String type) {
|
||||
super(type);
|
||||
this.ed = ed;
|
||||
this.res = res;
|
||||
this.tunnel = tunnel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void general() {
|
||||
ed.putString(res.getString(R.string.TUNNEL_NAME), getTunnelName(tunnel));
|
||||
ed.putString(res.getString(R.string.TUNNEL_DESCRIPTION), getTunnelDescription(tunnel));
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_START_ON_LOAD), shouldStartAutomatically(tunnel));
|
||||
if (!isClient(mType) || getPersistentClientKey(tunnel))
|
||||
ed.putString(res.getString(R.string.TUNNEL_PRIV_KEY_FILE), getPrivateKeyFile(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClient() {
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_PERSISTENT_KEY), getPersistentClientKey(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientStreamr(boolean isStreamr) {
|
||||
if (isStreamr)
|
||||
ed.putString(res.getString(R.string.TUNNEL_TARGET_HOST), getTargetHost(tunnel));
|
||||
else
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_SHARED_CLIENT), isSharedClient(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientPort() {
|
||||
ed.putInt(res.getString(R.string.TUNNEL_LISTEN_PORT), getClientPort(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientPortStreamr(boolean isStreamr) {
|
||||
if (!isStreamr)
|
||||
ed.putString(res.getString(R.string.TUNNEL_INTERFACE), getClientInterface(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientProxy(boolean isProxy) {
|
||||
if (isProxy)
|
||||
ed.putString(res.getString(R.string.TUNNEL_PROXIES), getClientDestination(tunnel));
|
||||
else
|
||||
ed.putString(res.getString(R.string.TUNNEL_DEST), getClientDestination(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientProxyHttp(boolean isHttp) {
|
||||
if (isHttp)
|
||||
ed.putString(res.getString(R.string.TUNNEL_HTTPCLIENT_SSL_OUTPROXIES), getSslProxies(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
|
||||
if (isStandardOrIrc)
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_USE_SSL), isSSLEnabled(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientIrc() {
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_IRCCLIENT_ENABLE_DCC), getDCC(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerHttp() {
|
||||
ed.putString(res.getString(R.string.TUNNEL_SPOOFED_HOST), getSpoofedHost(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
|
||||
ed.putString(res.getString(R.string.TUNNEL_INTERFACE), getClientInterface(tunnel));
|
||||
if (!isStreamr)
|
||||
ed.putInt(res.getString(R.string.TUNNEL_LISTEN_PORT), getClientPort(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerPort() {
|
||||
ed.putInt(res.getString(R.string.TUNNEL_TARGET_PORT), getTargetPort(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerPortStreamr(boolean isStreamr) {
|
||||
if (!isStreamr) {
|
||||
ed.putString(res.getString(R.string.TUNNEL_TARGET_HOST), getTargetHost(tunnel));
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_USE_SSL), isSSLEnabled(tunnel));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advanced() {
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_LENGTH),
|
||||
getTunnelDepth(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_LENGTH)));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_VARIANCE),
|
||||
getTunnelVariance(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_VARIANCE)));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_QUANTITY),
|
||||
getTunnelQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY)));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_BACKUP_QUANTITY),
|
||||
getTunnelQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedStreamr(boolean isStreamr) {
|
||||
if (!isStreamr)
|
||||
ed.putString(res.getString(R.string.TUNNEL_OPT_PROFILE),
|
||||
isInteractive(tunnel) ? "interactive" : "bulk");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||
if (!isServerOrStreamrClient)
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_CONNECT),
|
||||
shouldDelayConnect(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServer() {
|
||||
//ed.putBoolean(res.getString(R.string.TUNNEL_OPT_ENCRYPT), getEncrypt(tunnel));
|
||||
//ed.putString(res.getString(R.string.TUNNEL_OPT_ENCRYPT_KEY), getEncryptKey(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_ACCESS_MODE), getAccessMode(tunnel));
|
||||
ed.putString(res.getString(R.string.TUNNEL_OPT_ACCESS_LIST), getAccessList(tunnel));
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_UNIQUE_LOCAL), getUniqueLocal(tunnel));
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_MULTIHOME), getMultihome(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_LIMIT_MINUTE), getLimitMinute(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_LIMIT_HOUR), getLimitHour(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_LIMIT_DAY), getLimitDay(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_TOTAL_MINUTE), getTotalMinute(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_TOTAL_HOUR), getTotalHour(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_TOTAL_DAY), getTotalDay(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_MAX_STREAMS), getMaxStreams(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServerHttp(boolean isHttp) {
|
||||
if (isHttp) {
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_REJECT_INPROXY), getRejectInproxy(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_CHECK_TIME), getPostCheckTime(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_MAX), getPostMax(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_BAN_TIME), getPostBanTime(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_MAX), getPostTotalMax(tunnel));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_BAN_TIME), getPostTotalBanTime(tunnel));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedIdle() {
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_REDUCE_IDLE),
|
||||
getReduceOnIdle(tunnel, res.getBoolean(R.bool.DEFAULT_REDUCE_ON_IDLE)));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_REDUCE_QUANTITY),
|
||||
getReduceCount(tunnel, res.getInteger(R.integer.DEFAULT_REDUCE_COUNT)));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_REDUCE_TIME),
|
||||
getReduceTime(tunnel, res.getInteger(R.integer.DEFAULT_REDUCE_TIME)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||
if (!isServerOrStreamrClient)
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_OPEN), getDelayOpen(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClient() {
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_CLOSE_IDLE),
|
||||
getCloseOnIdle(tunnel, res.getBoolean(R.bool.DEFAULT_CLOSE_ON_IDLE)));
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_CLOSE_TIME),
|
||||
getCloseTime(tunnel, res.getInteger(R.integer.DEFAULT_CLOSE_TIME)));
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OTP_NEW_KEYS), getNewDest(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClientHttp() {
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_UA), getAllowUserAgent(tunnel));
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_REFERER), getAllowReferer(tunnel));
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_ACCEPT), getAllowAccept(tunnel));
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_ALLOW_SSL), getAllowInternalSSL(tunnel));
|
||||
ed.putString(res.getString(R.string.TUNNEL_OPT_JUMP_LIST), getJumpList(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClientProxy() {
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_LOCAL_AUTH), !"false".equals(getProxyAuth(tunnel)));
|
||||
ed.putString(res.getString(R.string.TUNNEL_OPT_LOCAL_USERNAME), "");
|
||||
ed.putString(res.getString(R.string.TUNNEL_OPT_LOCAL_PASSWORD), "");
|
||||
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_OUTPROXY_AUTH), getOutproxyAuth(tunnel));
|
||||
ed.putString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_USERNAME), getOutproxyUsername(tunnel));
|
||||
ed.putString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_PASSWORD), getOutproxyPassword(tunnel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedOther() {
|
||||
ed.putInt(res.getString(R.string.TUNNEL_OPT_SIGTYPE), getSigType(tunnel, mType));
|
||||
ed.putString(res.getString(R.string.TUNNEL_OPT_CUSTOM_OPTIONS), getCustomOptionsString(tunnel));
|
||||
}
|
||||
}
|
||||
|
||||
public static TunnelConfig createConfigFromPreferences(Context ctx, TunnelControllerGroup tcg, int tunnel) {
|
||||
return new TunnelUtil(tcg).createConfigFromPreferences(ctx, tunnel);
|
||||
}
|
||||
public TunnelConfig createConfigFromPreferences(Context ctx, int tunnel) {
|
||||
Resources res = ctx.getResources();
|
||||
|
||||
// Get the current preferences for this tunnel
|
||||
SharedPreferences prefs = ctx.getSharedPreferences(
|
||||
getPreferencesFilename(tunnel), Context.MODE_PRIVATE);
|
||||
|
||||
// Create the TunnelConfig
|
||||
TunnelConfig cfg = new TunnelConfig();
|
||||
|
||||
// Update the TunnelConfig from the preferences
|
||||
cfg.setType(prefs.getString(res.getString(R.string.TUNNEL_TYPE), null));
|
||||
String type = cfg.getType();
|
||||
|
||||
new TunnelConfigFromPreferences(cfg, prefs, res, _group, tunnel, type).runLogic();
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
class TunnelConfigFromPreferences extends TunnelLogic {
|
||||
TunnelConfig cfg;
|
||||
SharedPreferences prefs;
|
||||
Resources res;
|
||||
TunnelControllerGroup tcg;
|
||||
int tunnel;
|
||||
|
||||
public TunnelConfigFromPreferences(TunnelConfig cfg, SharedPreferences prefs, Resources res,
|
||||
TunnelControllerGroup tcg, int tunnel, String type) {
|
||||
super(type);
|
||||
this.cfg = cfg;
|
||||
this.prefs = prefs;
|
||||
this.res = res;
|
||||
this.tcg = tcg;
|
||||
this.tunnel = tunnel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void general() {
|
||||
cfg.setName(prefs.getString(res.getString(R.string.TUNNEL_NAME), null));
|
||||
cfg.setDescription(prefs.getString(res.getString(R.string.TUNNEL_DESCRIPTION), null));
|
||||
cfg.setStartOnLoad(prefs.getBoolean(res.getString(R.string.TUNNEL_START_ON_LOAD),
|
||||
res.getBoolean(R.bool.DEFAULT_START_ON_LOAD)));
|
||||
if (!isClient(mType) || prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
|
||||
res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY)))
|
||||
cfg.setPrivKeyFile(prefs.getString(res.getString(R.string.TUNNEL_PRIV_KEY_FILE),
|
||||
getPrivateKeyFile(tcg, tunnel)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClient() {
|
||||
// See advancedClient() for persistent key handling
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientStreamr(boolean isStreamr) {
|
||||
if (isStreamr)
|
||||
cfg.setTargetHost(prefs.getString(res.getString(R.string.TUNNEL_TARGET_HOST), null));
|
||||
else
|
||||
cfg.setShared(prefs.getBoolean(res.getString(R.string.TUNNEL_SHARED_CLIENT),
|
||||
res.getBoolean(R.bool.DEFAULT_SHARED_CLIENTS)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientPort() {
|
||||
cfg.setPort(prefs.getInt(res.getString(R.string.TUNNEL_LISTEN_PORT), -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientPortStreamr(boolean isStreamr) {
|
||||
if (!isStreamr)
|
||||
cfg.setReachableBy(prefs.getString(res.getString(R.string.TUNNEL_INTERFACE), "127.0.0.1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientProxy(boolean isProxy) {
|
||||
if (isProxy)
|
||||
cfg.setProxyList(prefs.getString(res.getString(R.string.TUNNEL_PROXIES), null));
|
||||
else
|
||||
cfg.setTargetDestination(prefs.getString(res.getString(R.string.TUNNEL_DEST), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientProxyHttp(boolean isHttp) {
|
||||
if (isHttp)
|
||||
cfg.setSslProxies(prefs.getString(res.getString(R.string.TUNNEL_HTTPCLIENT_SSL_OUTPROXIES), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
|
||||
if (isStandardOrIrc)
|
||||
cfg.setUseSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_USE_SSL), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientIrc() {
|
||||
cfg.setDCC(prefs.getBoolean(res.getString(R.string.TUNNEL_IRCCLIENT_ENABLE_DCC), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerHttp() {
|
||||
cfg.setSpoofedHost(prefs.getString(res.getString(R.string.TUNNEL_SPOOFED_HOST), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
|
||||
cfg.setReachableBy(prefs.getString(res.getString(R.string.TUNNEL_INTERFACE), "127.0.0.1"));
|
||||
if (!isStreamr)
|
||||
cfg.setPort(prefs.getInt(res.getString(R.string.TUNNEL_LISTEN_PORT), -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerPort() {
|
||||
cfg.setTargetPort(prefs.getInt(res.getString(R.string.TUNNEL_TARGET_PORT), -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerPortStreamr(boolean isStreamr) {
|
||||
if (!isStreamr) {
|
||||
cfg.setTargetHost(prefs.getString(res.getString(R.string.TUNNEL_TARGET_HOST), "127.0.0.1"));
|
||||
cfg.setUseSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_USE_SSL), false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advanced() {
|
||||
cfg.setTunnelDepth(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LENGTH),
|
||||
res.getInteger(R.integer.DEFAULT_TUNNEL_LENGTH)));
|
||||
cfg.setTunnelVariance(prefs.getInt(res.getString(R.string.TUNNEL_OPT_VARIANCE),
|
||||
res.getInteger(R.integer.DEFAULT_TUNNEL_VARIANCE)));
|
||||
cfg.setTunnelQuantity(prefs.getInt(res.getString(R.string.TUNNEL_OPT_QUANTITY),
|
||||
res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY)));
|
||||
cfg.setTunnelBackupQuantity(prefs.getInt(res.getString(R.string.TUNNEL_OPT_BACKUP_QUANTITY),
|
||||
res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedStreamr(boolean isStreamr) {
|
||||
if (!isStreamr)
|
||||
cfg.setProfile(prefs.getString(res.getString(R.string.TUNNEL_OPT_PROFILE), "bulk"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||
if (!isServerOrStreamrClient)
|
||||
cfg.setConnectDelay(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_CONNECT), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServer() {
|
||||
//cfg.setEncrypt(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_ENCRYPT), false));
|
||||
//cfg.setEncryptKey(prefs.getString(res.getString(R.string.TUNNEL_OPT_ENCRYPT_KEY), ""));
|
||||
cfg.setAccessMode(prefs.getInt(res.getString(R.string.TUNNEL_OPT_ACCESS_MODE), 0));
|
||||
cfg.setAccessList(prefs.getString(res.getString(R.string.TUNNEL_OPT_ACCESS_LIST), ""));
|
||||
cfg.setUniqueLocal(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_UNIQUE_LOCAL), false));
|
||||
cfg.setMultihome(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_MULTIHOME), false));
|
||||
cfg.setLimitMinute(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LIMIT_MINUTE), 0));
|
||||
cfg.setLimitHour(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LIMIT_HOUR), 0));
|
||||
cfg.setLimitDay(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LIMIT_DAY), 0));
|
||||
cfg.setTotalMinute(prefs.getInt(res.getString(R.string.TUNNEL_OPT_TOTAL_MINUTE), 0));
|
||||
cfg.setTotalHour(prefs.getInt(res.getString(R.string.TUNNEL_OPT_TOTAL_HOUR), 0));
|
||||
cfg.setTotalDay(prefs.getInt(res.getString(R.string.TUNNEL_OPT_TOTAL_DAY), 0));
|
||||
cfg.setMaxStreams(prefs.getInt(res.getString(R.string.TUNNEL_OPT_MAX_STREAMS), 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServerHttp(boolean isHttp) {
|
||||
if (isHttp) {
|
||||
cfg.setRejectInproxy(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_REJECT_INPROXY), false));
|
||||
cfg.setPostCheckTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_CHECK_TIME), 0));
|
||||
cfg.setPostMax(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_MAX), 0));
|
||||
cfg.setPostBanTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_BAN_TIME), 0));
|
||||
cfg.setPostTotalMax(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_MAX), 0));
|
||||
cfg.setPostTotalBanTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_BAN_TIME), 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedIdle() {
|
||||
cfg.setReduce(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_REDUCE_IDLE),
|
||||
res.getBoolean(R.bool.DEFAULT_REDUCE_ON_IDLE)));
|
||||
cfg.setReduceCount(prefs.getInt(res.getString(R.string.TUNNEL_OPT_REDUCE_QUANTITY),
|
||||
res.getInteger(R.integer.DEFAULT_REDUCE_COUNT)));
|
||||
cfg.setReduceTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_REDUCE_TIME),
|
||||
res.getInteger(R.integer.DEFAULT_REDUCE_TIME)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||
if (!isServerOrStreamrClient)
|
||||
cfg.setDelayOpen(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_OPEN),
|
||||
res.getBoolean(R.bool.DEFAULT_DELAY_OPEN)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClient() {
|
||||
cfg.setClose(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_CLOSE_IDLE),
|
||||
res.getBoolean(R.bool.DEFAULT_CLOSE_ON_IDLE)));
|
||||
cfg.setCloseTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_CLOSE_TIME),
|
||||
res.getInteger(R.integer.DEFAULT_CLOSE_TIME)));
|
||||
cfg.setNewDest(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
|
||||
res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY)) ? 2 :
|
||||
prefs.getBoolean(res.getString(R.string.TUNNEL_OTP_NEW_KEYS), res.getBoolean(R.bool.DEFAULT_NEW_KEYS)) ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClientHttp() {
|
||||
cfg.setAllowUserAgent(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_UA), false));
|
||||
cfg.setAllowReferer(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_REFERER), false));
|
||||
cfg.setAllowAccept(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_ACCEPT), false));
|
||||
cfg.setAllowInternalSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_ALLOW_SSL), false));
|
||||
cfg.setJumpList(prefs.getString(res.getString(R.string.TUNNEL_OPT_JUMP_LIST),
|
||||
res.getString(R.string.DEFAULT_JUMP_LIST)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClientProxy() {
|
||||
cfg.setProxyAuth(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_LOCAL_AUTH), false) ? "digest" : "false");
|
||||
String username = prefs.getString(res.getString(R.string.TUNNEL_OPT_LOCAL_USERNAME), "");
|
||||
if (!username.isEmpty()) {
|
||||
cfg.setProxyUsername(username);
|
||||
cfg.setProxyPassword(prefs.getString(res.getString(R.string.TUNNEL_OPT_LOCAL_PASSWORD), ""));
|
||||
}
|
||||
cfg.setOutproxyAuth(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_OUTPROXY_AUTH), false));
|
||||
cfg.setOutproxyUsername(prefs.getString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_USERNAME), ""));
|
||||
cfg.setOutproxyPassword(prefs.getString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_PASSWORD), ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedOther() {
|
||||
cfg.setSigType(Integer.toString(prefs.getInt(res.getString(R.string.TUNNEL_OPT_SIGTYPE),
|
||||
res.getInteger(R.integer.DEFAULT_SIGTYPE))));
|
||||
cfg.setCustomOptions(prefs.getString(res.getString(R.string.TUNNEL_OPT_CUSTOM_OPTIONS), null));
|
||||
}
|
||||
}
|
||||
|
||||
public static TunnelConfig createConfigFromWizard(
|
||||
Context ctx, TunnelControllerGroup tcg, Bundle data) {
|
||||
return new TunnelUtil(tcg).createConfigFromWizard(ctx, data);
|
||||
}
|
||||
public TunnelConfig createConfigFromWizard(Context ctx, Bundle data) {
|
||||
// Get the Bundle keys
|
||||
Resources res = ctx.getResources();
|
||||
|
||||
// Create the TunnelConfig
|
||||
TunnelConfig cfg = new TunnelConfig();
|
||||
|
||||
// Update the TunnelConfig from the tunnel wizard settings
|
||||
String kClientServer = res.getString(R.string.i2ptunnel_wizard_k_client_server);
|
||||
String kType = res.getString(R.string.i2ptunnel_wizard_k_type);
|
||||
String clientServer = data.getBundle(kClientServer).getString(Page.SIMPLE_DATA_KEY);
|
||||
String typeName = data.getBundle(clientServer + ":" + kType).getString(Page.SIMPLE_DATA_KEY);
|
||||
String type = getTypeFromName(typeName, ctx);
|
||||
cfg.setType(type);
|
||||
|
||||
new TunnelConfigFromWizard(cfg, data, res, _group, type).runLogic();
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
class TunnelConfigFromWizard extends TunnelLogic {
|
||||
TunnelConfig cfg;
|
||||
Bundle data;
|
||||
Resources res;
|
||||
TunnelControllerGroup tcg;
|
||||
|
||||
public TunnelConfigFromWizard(TunnelConfig cfg, Bundle data, Resources res,
|
||||
TunnelControllerGroup tcg, String type) {
|
||||
super(type);
|
||||
this.cfg = cfg;
|
||||
this.data = data;
|
||||
this.res = res;
|
||||
this.tcg = tcg;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void general() {
|
||||
cfg.setName(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_name)).getString(Page.SIMPLE_DATA_KEY));
|
||||
cfg.setDescription(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_desc)).getString(Page.SIMPLE_DATA_KEY));
|
||||
cfg.setStartOnLoad(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_auto_start)).getBoolean(Page.SIMPLE_DATA_KEY));
|
||||
|
||||
if (!isClient(mType) || res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY))
|
||||
cfg.setPrivKeyFile(getPrivateKeyFile(tcg, -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClient() {
|
||||
// See advancedClient() for persistent key handling
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientStreamr(boolean isStreamr) {
|
||||
if (isStreamr)
|
||||
cfg.setTargetHost(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_target_host)).getString(Page.SIMPLE_DATA_KEY));
|
||||
else
|
||||
cfg.setShared(res.getBoolean(R.bool.DEFAULT_SHARED_CLIENTS));
|
||||
|
||||
// Only set default tunnel parameters if this is not going to be a shared tunnel
|
||||
if (isStreamr || res.getBoolean(R.bool.DEFAULT_SHARED_CLIENTS)) {
|
||||
cfg.setTunnelDepth(res.getInteger(R.integer.DEFAULT_TUNNEL_LENGTH));
|
||||
cfg.setTunnelVariance(res.getInteger(R.integer.DEFAULT_TUNNEL_VARIANCE));
|
||||
cfg.setTunnelQuantity(res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY));
|
||||
cfg.setTunnelBackupQuantity(res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientPort() {
|
||||
cfg.setPort(Integer.parseInt(data.getBundle(
|
||||
res.getString(R.string.i2ptunnel_wizard_k_binding_port)).getString(Page.SIMPLE_DATA_KEY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientPortStreamr(boolean isStreamr) {
|
||||
if (!isStreamr)
|
||||
cfg.setReachableBy(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_reachable_on)).getString(Page.SIMPLE_DATA_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientProxy(boolean isProxy) {
|
||||
if (isProxy)
|
||||
cfg.setProxyList(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_outproxies)).getString(Page.SIMPLE_DATA_KEY));
|
||||
else
|
||||
cfg.setTargetDestination(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_dest)).getString(Page.SIMPLE_DATA_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientProxyHttp(boolean isHttp) {
|
||||
if (isHttp)
|
||||
cfg.setSslProxies(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
|
||||
if (isStandardOrIrc)
|
||||
cfg.setUseSSL(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalClientIrc() {
|
||||
cfg.setDCC(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerHttp() {
|
||||
cfg.setSpoofedHost(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
|
||||
cfg.setReachableBy(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_reachable_on)).getString(Page.SIMPLE_DATA_KEY));
|
||||
if (!isStreamr)
|
||||
cfg.setPort(Integer.parseInt(data.getBundle(
|
||||
res.getString(R.string.i2ptunnel_wizard_k_binding_port)).getString(Page.SIMPLE_DATA_KEY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerPort() {
|
||||
cfg.setTargetPort(Integer.parseInt(data.getBundle(
|
||||
res.getString(R.string.i2ptunnel_wizard_k_target_port)).getString(Page.SIMPLE_DATA_KEY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generalServerPortStreamr(boolean isStreamr) {
|
||||
if (!isStreamr) {
|
||||
cfg.setTargetHost(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_target_host)).getString(Page.SIMPLE_DATA_KEY));
|
||||
cfg.setUseSSL(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advanced() {
|
||||
// Tunnel parameters handled in generalClientStreamr()
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedStreamr(boolean isStreamr) {
|
||||
if (!isStreamr)
|
||||
cfg.setProfile("bulk");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||
if (!isServerOrStreamrClient)
|
||||
cfg.setConnectDelay(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServer() {
|
||||
cfg.setEncrypt(false);
|
||||
cfg.setAccessMode(0);
|
||||
cfg.setUniqueLocal(false);
|
||||
cfg.setMultihome(false);
|
||||
cfg.setLimitMinute(0);
|
||||
cfg.setLimitHour(0);
|
||||
cfg.setLimitDay(0);
|
||||
cfg.setTotalMinute(0);
|
||||
cfg.setTotalHour(0);
|
||||
cfg.setTotalDay(0);
|
||||
cfg.setMaxStreams(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedServerHttp(boolean isHttp) {
|
||||
if (isHttp) {
|
||||
cfg.setRejectInproxy(false);
|
||||
cfg.setPostCheckTime(0);
|
||||
cfg.setPostMax(0);
|
||||
cfg.setPostBanTime(0);
|
||||
cfg.setPostTotalMax(0);
|
||||
cfg.setPostTotalBanTime(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedIdle() {
|
||||
cfg.setReduce(res.getBoolean(R.bool.DEFAULT_REDUCE_ON_IDLE));
|
||||
cfg.setReduceCount(res.getInteger(R.integer.DEFAULT_REDUCE_COUNT));
|
||||
cfg.setReduceTime(res.getInteger(R.integer.DEFAULT_REDUCE_TIME));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||
if (!isServerOrStreamrClient)
|
||||
cfg.setDelayOpen(res.getBoolean(R.bool.DEFAULT_DELAY_OPEN));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClient() {
|
||||
cfg.setClose(res.getBoolean(R.bool.DEFAULT_CLOSE_ON_IDLE));
|
||||
cfg.setCloseTime(res.getInteger(R.integer.DEFAULT_CLOSE_TIME));
|
||||
cfg.setNewDest(res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY) ? 2 :
|
||||
res.getBoolean(R.bool.DEFAULT_NEW_KEYS) ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClientHttp() {
|
||||
cfg.setAllowUserAgent(false);
|
||||
cfg.setAllowReferer(false);
|
||||
cfg.setAllowAccept(false);
|
||||
cfg.setAllowInternalSSL(false);
|
||||
cfg.setJumpList(res.getString(R.string.DEFAULT_JUMP_LIST));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedClientProxy() {
|
||||
cfg.setProxyAuth("false");
|
||||
cfg.setOutproxyAuth(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void advancedOther() {
|
||||
cfg.setSigType(Integer.toString(res.getInteger(R.integer.DEFAULT_SIGTYPE)));
|
||||
}
|
||||
}
|
||||
}
|
477
app/src/main/java/net/i2p/android/router/I2PActivityBase.java
Normal file
477
app/src/main/java/net/i2p/android/router/I2PActivityBase.java
Normal file
@ -0,0 +1,477 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBar.Tab;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import net.i2p.android.i2ptunnel.TunnelListActivity;
|
||||
import net.i2p.android.router.addressbook.AddressbookActivity;
|
||||
import net.i2p.android.router.log.LogActivity;
|
||||
import net.i2p.android.router.netdb.NetDbActivity;
|
||||
import net.i2p.android.router.service.RouterBinder;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
import net.i2p.android.router.stats.PeersActivity;
|
||||
import net.i2p.android.router.stats.RateGraphActivity;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.android.router.web.WebActivity;
|
||||
import net.i2p.android.router.web.WebFragment;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
I2PFragmentBase.RouterContextProvider {
|
||||
/**
|
||||
* Navigation drawer variables
|
||||
*/
|
||||
protected DrawerLayout mDrawerLayout;
|
||||
protected ListView mDrawerList;
|
||||
protected ActionBarDrawerToggle mDrawerToggle;
|
||||
|
||||
private CharSequence mDrawerTitle;
|
||||
private CharSequence mTitle;
|
||||
|
||||
/**
|
||||
* Router variables
|
||||
*/
|
||||
protected boolean _isBound;
|
||||
protected boolean _triedBind;
|
||||
protected ServiceConnection _connection;
|
||||
protected RouterService _routerService;
|
||||
private SharedPreferences _sharedPrefs;
|
||||
|
||||
private static final String SHARED_PREFS = "net.i2p.android.router";
|
||||
protected static final String PREF_AUTO_START = "autoStart";
|
||||
/**
|
||||
* true leads to a poor install experience, very slow to paint the screen
|
||||
*/
|
||||
protected static final boolean DEFAULT_AUTO_START = false;
|
||||
protected static final String PREF_NAV_DRAWER_OPENED = "navDrawerOpened";
|
||||
|
||||
/**
|
||||
* Override this in subclasses that need a ViewPager, such as a
|
||||
* category view.
|
||||
*
|
||||
* @return whether this Activity needs a ViewPager.
|
||||
*/
|
||||
protected boolean useViewPager() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this in subclasses that can use two panes, such as a
|
||||
* list/detail class.
|
||||
*
|
||||
* @return whether this Activity can use a two-pane layout.
|
||||
*/
|
||||
protected boolean canUseTwoPanes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Util.d(this + " onCreate called");
|
||||
super.onCreate(savedInstanceState);
|
||||
_sharedPrefs = getSharedPreferences(SHARED_PREFS, 0);
|
||||
|
||||
// If the Activity wants to use a ViewPager, provide it.
|
||||
// If the Activity can make use of two panes (if available),
|
||||
// load the layout that will enable them. Otherwise, load the
|
||||
// layout that will only ever have a single pane.
|
||||
if (useViewPager())
|
||||
setContentView(R.layout.activity_navdrawer_viewpager);
|
||||
else if (canUseTwoPanes())
|
||||
setContentView(R.layout.activity_navdrawer);
|
||||
else
|
||||
setContentView(R.layout.activity_navdrawer_onepane);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
mTitle = mDrawerTitle = getTitle();
|
||||
String[] activityTitles = getResources().getStringArray(R.array.navdrawer_activity_titles);
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("i2pandroid.main.showStats", false)) {
|
||||
String[] advActivityTitles = getResources().getStringArray(R.array.navdrawer_activity_titles_advanced);
|
||||
String[] allTitles = new String[activityTitles.length + advActivityTitles.length];
|
||||
System.arraycopy(activityTitles, 0, allTitles, 0, activityTitles.length);
|
||||
System.arraycopy(advActivityTitles, 0, allTitles, activityTitles.length, advActivityTitles.length);
|
||||
activityTitles = allTitles;
|
||||
}
|
||||
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
mDrawerList = (ListView) findViewById(R.id.drawer);
|
||||
|
||||
// Set a custom shadow that overlays the main content when the drawer opens
|
||||
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
||||
mDrawerList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
// Set the adapter for the list view
|
||||
mDrawerList.setAdapter(new ArrayAdapter<>(this,
|
||||
R.layout.listitem_navdrawer, activityTitles));
|
||||
// Set the list's click listener
|
||||
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
|
||||
|
||||
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
|
||||
R.string.drawer_open, R.string.drawer_close) {
|
||||
private boolean wasDragged = false;
|
||||
|
||||
/** Called when a drawer has settled in a completely closed state. */
|
||||
public void onDrawerClosed(View view) {
|
||||
// Don't mark as opened if the user closed by dragging
|
||||
// but uses the action bar icon to open
|
||||
wasDragged = false;
|
||||
getSupportActionBar().setTitle(mTitle);
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
/** Called when a drawer has settled in a completely open state. */
|
||||
public void onDrawerOpened(View view) {
|
||||
if (wasDragged && !getPref(PREF_NAV_DRAWER_OPENED, false))
|
||||
setPref(PREF_NAV_DRAWER_OPENED, true);
|
||||
getSupportActionBar().setTitle(mDrawerTitle);
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
/** Called when the drawer motion state changes. */
|
||||
public void onDrawerStateChanged(int newState) {
|
||||
if (newState == DrawerLayout.STATE_DRAGGING)
|
||||
wasDragged = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Set the drawer toggle as the DrawerListener
|
||||
mDrawerLayout.setDrawerListener(mDrawerToggle);
|
||||
}
|
||||
|
||||
private class DrawerItemClickListener implements ListView.OnItemClickListener {
|
||||
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
|
||||
selectItem(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectItem(int pos) {
|
||||
switch (pos) {
|
||||
case 1:
|
||||
if (!(this instanceof NewsActivity)) {
|
||||
Intent news = new Intent(I2PActivityBase.this, NewsActivity.class);
|
||||
startActivity(news);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
Intent ab = new Intent(I2PActivityBase.this, AddressbookActivity.class);
|
||||
startActivity(ab);
|
||||
break;
|
||||
case 3:
|
||||
Intent itb = new Intent(I2PActivityBase.this, TunnelListActivity.class);
|
||||
startActivity(itb);
|
||||
break;
|
||||
case 4:
|
||||
if (!(this instanceof LogActivity)) {
|
||||
Intent log = new Intent(I2PActivityBase.this, LogActivity.class);
|
||||
startActivity(log);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
Intent wp = new Intent(I2PActivityBase.this, WebActivity.class);
|
||||
wp.putExtra(WebFragment.HTML_RESOURCE_ID, R.raw.welcome_html);
|
||||
startActivity(wp);
|
||||
break;
|
||||
case 6:
|
||||
if (!(this instanceof RateGraphActivity)) {
|
||||
Intent active = new Intent(I2PActivityBase.this, RateGraphActivity.class);
|
||||
startActivity(active);
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
Intent peers = new Intent(I2PActivityBase.this, PeersActivity.class);
|
||||
startActivity(peers);
|
||||
break;
|
||||
case 8:
|
||||
if (!(this instanceof NetDbActivity)) {
|
||||
Intent netdb = new Intent(I2PActivityBase.this, NetDbActivity.class);
|
||||
startActivity(netdb);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Intent main = new Intent(I2PActivityBase.this, MainActivity.class);
|
||||
startActivity(main);
|
||||
break;
|
||||
}
|
||||
mDrawerLayout.closeDrawer(mDrawerList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestart() {
|
||||
Util.d(this + " onRestart called");
|
||||
super.onRestart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
Util.d(this + " onStart called");
|
||||
super.onStart();
|
||||
if (_sharedPrefs.getBoolean(PREF_AUTO_START, DEFAULT_AUTO_START))
|
||||
startRouter();
|
||||
else
|
||||
bindRouter(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param def default
|
||||
*/
|
||||
public boolean getPref(String pref, boolean def) {
|
||||
return _sharedPrefs.getBoolean(pref, def);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param def default
|
||||
*/
|
||||
public String getPref(String pref, String def) {
|
||||
return _sharedPrefs.getString(pref, def);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success
|
||||
*/
|
||||
public boolean setPref(String pref, boolean val) {
|
||||
SharedPreferences.Editor edit = _sharedPrefs.edit();
|
||||
edit.putBoolean(pref, val);
|
||||
return edit.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success
|
||||
*/
|
||||
public boolean setPref(String pref, String val) {
|
||||
SharedPreferences.Editor edit = _sharedPrefs.edit();
|
||||
edit.putString(pref, val);
|
||||
return edit.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
Util.d(this + " onResume called");
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
Util.d(this + " onPause called");
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
Util.d(this + " onSaveInstanceState called");
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
Util.d(this + " onStop called");
|
||||
unbindRouter();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Util.d(this + " onDestroy called");
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever we call invalidateOptionsMenu()
|
||||
*/
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
// If the nav drawer is open, hide action items related to the content view
|
||||
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
|
||||
onDrawerChange(drawerOpen);
|
||||
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override in subclass with e.g.
|
||||
* menu.findItem(R.id.action_add_to_addressbook).setVisible(!drawerOpen);
|
||||
*
|
||||
* @param drawerOpen true if the drawer is open
|
||||
*/
|
||||
protected void onDrawerChange(boolean drawerOpen) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// The action bar home/up action should open or close the drawer.
|
||||
// ActionBarDrawerToggle will take care of this.
|
||||
if (mDrawerToggle.onOptionsItemSelected(item))
|
||||
return true;
|
||||
else if (item.getItemId() == android.R.id.home) {
|
||||
// This happens when mDrawerToggle.setDrawerIndicatorEnabled(false)
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
// Handle action buttons and overflow
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(CharSequence title) {
|
||||
mTitle = title;
|
||||
getSupportActionBar().setTitle(mTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
// Sync the toggle state after onRestoreInstanceState has occurred.
|
||||
mDrawerToggle.syncState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// Pass any configuration change to the drawer toggle
|
||||
mDrawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
public static class TabListener implements ActionBar.TabListener {
|
||||
protected Fragment mFragment;
|
||||
|
||||
public TabListener(Fragment fragment) {
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
public void onTabSelected(Tab tab, FragmentTransaction ft) {
|
||||
ft.replace(R.id.main_fragment, mFragment);
|
||||
}
|
||||
|
||||
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
|
||||
ft.remove(mFragment);
|
||||
}
|
||||
|
||||
public void onTabReselected(Tab tab, FragmentTransaction ft) {
|
||||
// User selected the already selected tab.
|
||||
}
|
||||
}
|
||||
|
||||
////// Service stuff
|
||||
|
||||
/**
|
||||
* Start the service and bind to it
|
||||
*/
|
||||
protected boolean startRouter() {
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName(this, "net.i2p.android.router.service.RouterService");
|
||||
Util.d(this + " calling startService");
|
||||
ComponentName name = startService(intent);
|
||||
if (name == null)
|
||||
Util.d(this + " XXXXXXXXXXXXXXXXXXXX got null from startService!");
|
||||
Util.d(this + " got from startService: " + name);
|
||||
boolean success = bindRouter(true);
|
||||
if (!success)
|
||||
Util.d(this + " Bind router failed");
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind only
|
||||
*/
|
||||
protected boolean bindRouter(boolean autoCreate) {
|
||||
Intent intent = new Intent(RouterBinder.class.getName());
|
||||
intent.setClassName(this, "net.i2p.android.router.service.RouterService");
|
||||
Util.d(this + " calling bindService");
|
||||
_connection = new RouterConnection();
|
||||
_triedBind = bindService(intent, _connection, autoCreate ? BIND_AUTO_CREATE : 0);
|
||||
Util.d(this + " bindService: auto create? " + autoCreate + " success? " + _triedBind);
|
||||
return _triedBind;
|
||||
}
|
||||
|
||||
protected void unbindRouter() {
|
||||
Util.d(this + " unbindRouter called with _isBound:" + _isBound + " _connection:" + _connection + " _triedBind:" + _triedBind);
|
||||
if (_triedBind && _connection != null)
|
||||
unbindService(_connection);
|
||||
|
||||
_triedBind = false;
|
||||
_connection = null;
|
||||
_routerService = null;
|
||||
_isBound = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for interacting with the main interface of the RouterService.
|
||||
*/
|
||||
protected class RouterConnection implements ServiceConnection {
|
||||
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
Util.d(this + " connected to router service");
|
||||
RouterBinder binder = (RouterBinder) service;
|
||||
RouterService svc = binder.getService();
|
||||
_routerService = svc;
|
||||
_isBound = true;
|
||||
onRouterBind(svc);
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Util.d(this + " disconnected from router service!!!!!!!");
|
||||
// save memory
|
||||
_routerService = null;
|
||||
_isBound = false;
|
||||
onRouterUnbind();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* callback from ServiceConnection, override as necessary
|
||||
*/
|
||||
protected void onRouterBind(RouterService svc) {
|
||||
Fragment f = getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||
if (f instanceof I2PFragmentBase)
|
||||
((I2PFragmentBase) f).onRouterBind();
|
||||
else if (f instanceof I2PFragmentBase.RouterContextUser)
|
||||
((I2PFragmentBase.RouterContextUser) f).onRouterBind();
|
||||
|
||||
if (canUseTwoPanes()) {
|
||||
f = getSupportFragmentManager().findFragmentById(R.id.detail_fragment);
|
||||
if (f instanceof I2PFragmentBase)
|
||||
((I2PFragmentBase) f).onRouterBind();
|
||||
else if (f instanceof I2PFragmentBase.RouterContextUser)
|
||||
((I2PFragmentBase.RouterContextUser) f).onRouterBind();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* callback from ServiceConnection, override as necessary
|
||||
*/
|
||||
protected void onRouterUnbind() {
|
||||
}
|
||||
|
||||
// I2PFragmentBase.RouterContextProvider
|
||||
|
||||
public RouterContext getRouterContext() {
|
||||
RouterService svc = _routerService;
|
||||
if (svc == null || !_isBound)
|
||||
return null;
|
||||
return svc.getRouterContext();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
public interface I2PConstants {
|
||||
public static final String ANDROID_PREF_PREFIX = "i2pandroid.";
|
||||
}
|
118
app/src/main/java/net/i2p/android/router/I2PFragmentBase.java
Normal file
118
app/src/main/java/net/i2p/android/router/I2PFragmentBase.java
Normal file
@ -0,0 +1,118 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.NetworkDatabaseFacade;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelManagerFacade;
|
||||
import net.i2p.router.peermanager.ProfileOrganizer;
|
||||
import net.i2p.router.transport.FIFOBandwidthLimiter;
|
||||
import net.i2p.stat.StatManager;
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
public class I2PFragmentBase extends Fragment {
|
||||
private boolean mOnActivityCreated;
|
||||
RouterContextProvider mCallback;
|
||||
|
||||
public static final String PREF_INSTALLED_VERSION = "app.version";
|
||||
|
||||
public interface RouterContextUser {
|
||||
public void onRouterBind();
|
||||
}
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface RouterContextProvider {
|
||||
public RouterContext getRouterContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mCallback = (RouterContextProvider) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement RouterContextProvider");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mOnActivityCreated = true;
|
||||
if (getRouterContext() != null)
|
||||
onRouterConnectionReady();
|
||||
else
|
||||
onRouterConnectionNotReady();
|
||||
}
|
||||
|
||||
public void onRouterBind() {
|
||||
if (mOnActivityCreated)
|
||||
onRouterConnectionReady();
|
||||
}
|
||||
|
||||
/** callback from I2PFragmentBase, override as necessary */
|
||||
public void onRouterConnectionReady() {}
|
||||
|
||||
/** callback from I2PFragmentBase, override as necessary */
|
||||
public void onRouterConnectionNotReady() {}
|
||||
|
||||
protected RouterContext getRouterContext() {
|
||||
return mCallback.getRouterContext();
|
||||
}
|
||||
|
||||
protected Router getRouter() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx == null)
|
||||
return null;
|
||||
return ctx.router();
|
||||
}
|
||||
|
||||
protected NetworkDatabaseFacade getNetDb() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx == null)
|
||||
return null;
|
||||
return ctx.netDb();
|
||||
}
|
||||
|
||||
protected ProfileOrganizer getProfileOrganizer() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx == null)
|
||||
return null;
|
||||
return ctx.profileOrganizer();
|
||||
}
|
||||
|
||||
protected TunnelManagerFacade getTunnelManager() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx == null)
|
||||
return null;
|
||||
return ctx.tunnelManager();
|
||||
}
|
||||
|
||||
protected CommSystemFacade getCommSystem() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx == null)
|
||||
return null;
|
||||
return ctx.commSystem();
|
||||
}
|
||||
|
||||
protected FIFOBandwidthLimiter getBandwidthLimiter() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx == null)
|
||||
return null;
|
||||
return ctx.bandwidthLimiter();
|
||||
}
|
||||
|
||||
protected StatManager getStatManager() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx == null)
|
||||
return null;
|
||||
return ctx.statManager();
|
||||
}
|
||||
}
|
271
app/src/main/java/net/i2p/android/router/InitActivities.java
Normal file
271
app/src/main/java/net/i2p/android/router/InitActivities.java
Normal file
@ -0,0 +1,271 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
// Wouldn't this be better as a private class in MainActivity?
|
||||
|
||||
class InitActivities {
|
||||
|
||||
private final Context ctx;
|
||||
private final String myDir;
|
||||
private final String _ourVersion;
|
||||
|
||||
private static final String CONFIG_FILE = "android.config";
|
||||
private static final String PROP_NEW_INSTALL = "i2p.newInstall";
|
||||
private static final String PROP_NEW_VERSION = "i2p.newVersion";
|
||||
private static final String PROP_INSTALLED_VERSION = "i2p.version";
|
||||
|
||||
public InitActivities(Context c) {
|
||||
ctx = c;
|
||||
myDir = Util.getFileDir(c);
|
||||
_ourVersion = Util.getOurVersion(c);
|
||||
}
|
||||
|
||||
void debugStuff() {
|
||||
Util.d("java.io.tmpdir" + ": " + System.getProperty("java.io.tmpdir"));
|
||||
Util.d("java.vendor" + ": " + System.getProperty("java.vendor"));
|
||||
Util.d("java.version" + ": " + System.getProperty("java.version"));
|
||||
Util.d("os.arch" + ": " + System.getProperty("os.arch"));
|
||||
Util.d("os.name" + ": " + System.getProperty("os.name"));
|
||||
Util.d("os.version" + ": " + System.getProperty("os.version"));
|
||||
Util.d("user.dir" + ": " + System.getProperty("user.dir"));
|
||||
Util.d("user.home" + ": " + System.getProperty("user.home"));
|
||||
Util.d("user.name" + ": " + System.getProperty("user.name"));
|
||||
Util.d("getFilesDir()" + ": " + myDir);
|
||||
Util.d("max mem" + ": " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()));
|
||||
Util.d("Package" + ": " + ctx.getPackageName());
|
||||
Util.d("Version" + ": " + _ourVersion);
|
||||
Util.d("MODEL" + ": " + Build.MODEL);
|
||||
Util.d("DISPLAY" + ": " + Build.DISPLAY);
|
||||
Util.d("VERSION" + ": " + Build.VERSION.RELEASE);
|
||||
Util.d("SDK" + ": " + Build.VERSION.SDK_INT);
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
|
||||
if (checkNewVersion()) {
|
||||
List<Properties> lProps = Util.getPropertiesFromPreferences(ctx);
|
||||
Properties props = lProps.get(0);
|
||||
|
||||
props.setProperty("i2p.dir.temp", myDir + "/tmp");
|
||||
props.setProperty("i2p.dir.pid", myDir + "/tmp");
|
||||
// Time disabled in default router.config
|
||||
// But lots of time problems on Android, not all carriers support NITZ
|
||||
// and there was no NTP before 3.0. Tablets should be fine?
|
||||
// Phones in airplane mode with wifi enabled still a problem.
|
||||
// Deactivated phones in airplane mode definitely won't have correct time.
|
||||
if (Build.VERSION.SDK_INT < 11) // Honeycomb 3.0
|
||||
props.setProperty("time.disabled", "false");
|
||||
mergeResourceToFile(R.raw.router_config, "router.config", props);
|
||||
mergeResourceToFile(R.raw.logger_config, "logger.config", lProps.get(1));
|
||||
// This is not needed for now, i2ptunnel.config only contains tunnel
|
||||
// settings, which can now be configured manually. We don't want to
|
||||
// overwrite the user's tunnels.
|
||||
//mergeResourceToFile(R.raw.i2ptunnel_config, "i2ptunnel.config", null);
|
||||
copyResourceToFileIfAbsent(R.raw.i2ptunnel_config, "i2ptunnel.config");
|
||||
// FIXME this is a memory hog to merge this way
|
||||
mergeResourceToFile(R.raw.hosts_txt, "hosts.txt", null);
|
||||
mergeResourceToFile(R.raw.more_hosts_txt, "hosts.txt", null);
|
||||
copyResourceToFile(R.raw.blocklist_txt, "blocklist.txt");
|
||||
|
||||
File abDir = new File(myDir, "addressbook");
|
||||
abDir.mkdir();
|
||||
copyResourceToFile(R.raw.subscriptions_txt, "addressbook/subscriptions.txt");
|
||||
mergeResourceToFile(R.raw.addressbook_config_txt, "addressbook/config.txt", null);
|
||||
|
||||
File docsDir = new File(myDir, "docs");
|
||||
docsDir.mkdir();
|
||||
copyResourceToFile(R.raw.ahelper_conflict_header_ht, "docs/ahelper-conflict-header.ht");
|
||||
copyResourceToFile(R.raw.ahelper_new_header_ht, "docs/ahelper-new-header.ht");
|
||||
copyResourceToFile(R.raw.auth_header_ht, "docs/auth-header.ht");
|
||||
copyResourceToFile(R.raw.denied_header_ht, "docs/denied-header.ht");
|
||||
copyResourceToFile(R.raw.dnf_header_ht, "docs/dnf-header.ht");
|
||||
copyResourceToFile(R.raw.dnfb_header_ht, "docs/dnfb-header.ht");
|
||||
copyResourceToFile(R.raw.dnfh_header_ht, "docs/dnfh-header.ht");
|
||||
copyResourceToFile(R.raw.dnfp_header_ht, "docs/dnfp-header.ht");
|
||||
copyResourceToFile(R.raw.localhost_header_ht, "docs/localhost-header.ht");
|
||||
copyResourceToFile(R.raw.noproxy_header_ht, "docs/noproxy-header.ht");
|
||||
copyResourceToFile(R.raw.protocol_header_ht, "docs/protocol-header.ht");
|
||||
|
||||
File cssDir = new File(docsDir, "themes/console/light");
|
||||
cssDir.mkdirs();
|
||||
//copyResourceToFile(R.raw.console_css, "docs/themes/console/light/console.css");
|
||||
//copyResourceToFile(R.raw.android_css, "docs/themes/console/light/android.css");
|
||||
|
||||
File imgDir = new File(docsDir, "themes/console/images");
|
||||
imgDir.mkdir();
|
||||
copyResourceToFile(R.drawable.i2plogo, "docs/themes/console/images/i2plogo.png");
|
||||
copyResourceToFile(R.drawable.itoopie_sm, "docs/themes/console/images/itoopie_sm.png");
|
||||
//copyResourceToFile(R.drawable.outbound, "docs/themes/console/images/outbound.png");
|
||||
//copyResourceToFile(R.drawable.inbound, "docs/themes/console/images/inbound.png");
|
||||
|
||||
File img2Dir = new File(cssDir, "images");
|
||||
img2Dir.mkdir();
|
||||
//copyResourceToFile(R.drawable.header, "docs/themes/console/light/images/header.png");
|
||||
|
||||
File certDir = new File(myDir, "certificates");
|
||||
certDir.mkdir();
|
||||
File certificates = new File(myDir, "certificates");
|
||||
File[] allcertificates = certificates.listFiles();
|
||||
if ( allcertificates != null) {
|
||||
for (File f : allcertificates) {
|
||||
Util.d("Deleting old certificate file/dir " + f);
|
||||
FileUtil.rmdir(f, false);
|
||||
}
|
||||
}
|
||||
unzipResourceToDir(R.raw.certificates_zip, "certificates");
|
||||
//File netDBDir = new File(myDir, "netDB");
|
||||
//netDBDir.mkdir();
|
||||
//unzipResourceToDir(R.raw.netdb_zip, "netDB");
|
||||
}
|
||||
|
||||
// Set up the locations so settings can find them
|
||||
System.setProperty("i2p.dir.base", myDir);
|
||||
System.setProperty("i2p.dir.config", myDir);
|
||||
System.setProperty("wrapper.logfile", myDir + "/wrapper.log");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param f relative to base dir
|
||||
*/
|
||||
private void copyResourceToFileIfAbsent(int resID, String f) {
|
||||
File file = new File(myDir, f);
|
||||
if (!file.exists())
|
||||
copyResourceToFile(resID, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param f relative to base dir
|
||||
*/
|
||||
private void copyResourceToFile(int resID, String f) {
|
||||
InputStream in = null;
|
||||
FileOutputStream out = null;
|
||||
|
||||
Util.d("Creating file " + f + " from resource");
|
||||
byte buf[] = new byte[4096];
|
||||
try {
|
||||
// Context methods
|
||||
in = ctx.getResources().openRawResource(resID);
|
||||
out = new FileOutputStream(new File(myDir, f));
|
||||
|
||||
int read;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param folder relative to base dir
|
||||
*/
|
||||
private void unzipResourceToDir(int resID, String folder) {
|
||||
InputStream in = null;
|
||||
FileOutputStream out = null;
|
||||
ZipInputStream zis = null;
|
||||
|
||||
Util.d("Creating files in '" + myDir + "/" + folder + "/' from resource");
|
||||
try {
|
||||
// Context methods
|
||||
in = ctx.getResources().openRawResource(resID);
|
||||
zis = new ZipInputStream((in));
|
||||
ZipEntry ze;
|
||||
while ((ze = zis.getNextEntry()) != null) {
|
||||
out = null;
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[1024];
|
||||
int count;
|
||||
while ((count = zis.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, count);
|
||||
}
|
||||
String name = ze.getName();
|
||||
File f = new File(myDir + "/" + folder +"/" + name);
|
||||
if (ze.isDirectory()) {
|
||||
Util.d("Creating directory " + myDir + "/" + folder +"/" + name + " from resource");
|
||||
f.mkdir();
|
||||
} else {
|
||||
Util.d("Creating file " + myDir + "/" + folder +"/" + name + " from resource");
|
||||
byte[] bytes = baos.toByteArray();
|
||||
out = new FileOutputStream(f);
|
||||
out.write(bytes);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
} finally {
|
||||
if (out != null) { try { out.close(); } catch (IOException ioe) {} out = null; }
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
if (zis != null) try { zis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load defaults from resource,
|
||||
* then add props from settings,
|
||||
* and write back.
|
||||
*
|
||||
* @param f relative to base dir
|
||||
* @param overrides local overrides or null
|
||||
*/
|
||||
private void mergeResourceToFile(int resID, String f, Properties overrides) {
|
||||
Util.mergeResourceToFile(ctx, myDir, f, resID, overrides, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for new version.
|
||||
* FIXME we could just use shared prefs for this instead of storing in a file
|
||||
* @return true if new version
|
||||
*/
|
||||
private boolean checkNewVersion() {
|
||||
Properties props = new Properties();
|
||||
|
||||
InputStream fin = null;
|
||||
try {
|
||||
fin = ctx.openFileInput(CONFIG_FILE);
|
||||
DataHelper.loadProps(props, fin);
|
||||
} catch (IOException ioe) {
|
||||
Util.d("Looks like a new install");
|
||||
} finally {
|
||||
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
String oldVersion = props.getProperty(PROP_INSTALLED_VERSION);
|
||||
boolean newInstall = oldVersion == null;
|
||||
boolean newVersion = !_ourVersion.equals(oldVersion);
|
||||
|
||||
if (newVersion) {
|
||||
Util.d("New version " + _ourVersion);
|
||||
props.setProperty(PROP_INSTALLED_VERSION, _ourVersion);
|
||||
try {
|
||||
DataHelper.storeProps(props, ctx.getFileStreamPath(CONFIG_FILE));
|
||||
} catch (IOException ioe) {
|
||||
Util.d("Failed to write " + CONFIG_FILE);
|
||||
}
|
||||
}
|
||||
return newVersion;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public class LicenseActivity extends I2PActivityBase {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
// Start with the base view
|
||||
if (savedInstanceState == null) {
|
||||
LicenseFragment f = new LicenseFragment();
|
||||
f.setArguments(getIntent().getExtras());
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, f).commit();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||
|
||||
public class LicenseFragment extends ListFragment {
|
||||
|
||||
private static final String[] names = {
|
||||
"Android Application License", "Apache 2.0",
|
||||
"Router License Overview", "Blockfile", "Crypto Filters", "ElGamal / DSA",
|
||||
"GPLv2", "LGPLv2.1", "GPLv3", "LGPLv3", "FatCowIcons",
|
||||
"Ministreaming",
|
||||
"InstallCert", "SHA-256", "SNTP", "Addressbook"};
|
||||
|
||||
private static final int[] files = {
|
||||
R.raw.license_app_txt, R.raw.license_apache20_txt,
|
||||
R.raw.licenses_txt, R.raw.license_blockfile_txt, R.raw.license_bsd_txt, R.raw.license_elgamaldsa_txt,
|
||||
R.raw.license_gplv2_txt, R.raw.license_lgplv2_1_txt, R.raw.license_gplv3_txt, R.raw.license_lgplv3_txt,
|
||||
R.raw.license_fatcowicons_txt, R.raw.license_bsd_txt,
|
||||
R.raw.license_installcert_txt, R.raw.license_sha256_txt, R.raw.license_sntp_txt, R.raw.license_addressbook_txt};
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, names));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||
TextResourceDialog dialog = new TextResourceDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE, names[pos]);
|
||||
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, files[pos]);
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "license");
|
||||
}
|
||||
}
|
243
app/src/main/java/net/i2p/android/router/MainActivity.java
Normal file
243
app/src/main/java/net/i2p/android/router/MainActivity.java
Normal file
@ -0,0 +1,243 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import net.i2p.android.help.HelpActivity;
|
||||
import net.i2p.android.router.dialog.AboutDialog;
|
||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
import net.i2p.android.router.service.State;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class MainActivity extends I2PActivityBase implements
|
||||
MainFragment.RouterControlListener {
|
||||
MainFragment mMainFragment = null;
|
||||
private boolean mAutoStartFromIntent = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Start with the home view
|
||||
if (savedInstanceState == null) {
|
||||
mMainFragment = new MainFragment();
|
||||
mMainFragment.setArguments(getIntent().getExtras());
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, mMainFragment).commit();
|
||||
}
|
||||
|
||||
// Open nav drawer if the user has never opened it themselves
|
||||
if (!getPref(PREF_NAV_DRAWER_OPENED, false))
|
||||
mDrawerLayout.openDrawer(mDrawerList);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
Util.d("Initializing...");
|
||||
InitActivities init = new InitActivities(this);
|
||||
init.debugStuff();
|
||||
init.initialize();
|
||||
super.onPostCreate(savedInstanceState);
|
||||
handleIntents();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntents();
|
||||
}
|
||||
|
||||
private void handleIntents() {
|
||||
if (getIntent() == null)
|
||||
return;
|
||||
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
|
||||
if (action == null)
|
||||
return;
|
||||
|
||||
if (action.equals("net.i2p.android.router.START_I2P")) {
|
||||
autoStart();
|
||||
}
|
||||
}
|
||||
|
||||
private void autoStart() {
|
||||
if (canStart()) {
|
||||
if (Connectivity.isConnected(this)) {
|
||||
mAutoStartFromIntent = true;
|
||||
onStartRouterClicked();
|
||||
} else {
|
||||
// Not connected to a network
|
||||
// TODO: Notify user
|
||||
}
|
||||
} else {
|
||||
// TODO: Notify user
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
|
||||
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
|
||||
lbm.registerReceiver(onStateChange, filter);
|
||||
|
||||
lbm.sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
|
||||
}
|
||||
|
||||
private State lastRouterState = null;
|
||||
private BroadcastReceiver onStateChange = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
State state = intent.getParcelableExtra(RouterService.LOCAL_BROADCAST_EXTRA_STATE);
|
||||
if (lastRouterState == null || lastRouterState != state) {
|
||||
if (mMainFragment == null)
|
||||
mMainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||
if (mMainFragment != null) {
|
||||
mMainFragment.updateState(state);
|
||||
lastRouterState = state;
|
||||
}
|
||||
|
||||
if (state == State.RUNNING && mAutoStartFromIntent) {
|
||||
MainActivity.this.setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.activity_main_actions, menu);
|
||||
inflater.inflate(R.menu.activity_base_actions, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_settings:
|
||||
Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
case R.id.menu_about:
|
||||
AboutDialog dialog = new AboutDialog();
|
||||
dialog.show(getSupportFragmentManager(), "about");
|
||||
return true;
|
||||
|
||||
case R.id.menu_help:
|
||||
Intent hi = new Intent(MainActivity.this, HelpActivity.class);
|
||||
startActivity(hi);
|
||||
return true;
|
||||
|
||||
case R.id.menu_help_release_notes:
|
||||
TextResourceDialog rDdialog = new TextResourceDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
|
||||
getResources().getString(R.string.label_release_notes));
|
||||
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||
rDdialog.setArguments(args);
|
||||
rDdialog.show(getSupportFragmentManager(), "release_notes");
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(onStateChange);
|
||||
}
|
||||
|
||||
private boolean canStart() {
|
||||
RouterService svc = _routerService;
|
||||
return (svc == null) || (!_isBound) || svc.canManualStart();
|
||||
}
|
||||
|
||||
private boolean canStop() {
|
||||
RouterService svc = _routerService;
|
||||
return svc != null && _isBound && svc.canManualStop();
|
||||
}
|
||||
|
||||
// MainFragment.RouterControlListener
|
||||
|
||||
public boolean shouldShowOnOff() {
|
||||
return (canStart() && Connectivity.isConnected(this)) || (canStop() && !isGracefulShutdownInProgress());
|
||||
}
|
||||
|
||||
public boolean shouldBeOn() {
|
||||
String action = getIntent().getAction();
|
||||
return (canStop()) ||
|
||||
(action != null && action.equals("net.i2p.android.router.START_I2P"));
|
||||
}
|
||||
|
||||
public void onStartRouterClicked() {
|
||||
RouterService svc = _routerService;
|
||||
if(svc != null && _isBound) {
|
||||
setPref(PREF_AUTO_START, true);
|
||||
svc.manualStart();
|
||||
} else {
|
||||
(new File(Util.getFileDir(this), "wrapper.log")).delete();
|
||||
startRouter();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onStopRouterClicked() {
|
||||
RouterService svc = _routerService;
|
||||
if(svc != null && _isBound) {
|
||||
setPref(PREF_AUTO_START, false);
|
||||
svc.manualQuit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @since 0.9.19 */
|
||||
public boolean isGracefulShutdownInProgress() {
|
||||
RouterService svc = _routerService;
|
||||
return svc != null && svc.isGracefulShutdownInProgress();
|
||||
}
|
||||
|
||||
/** @since 0.9.19 */
|
||||
public boolean onGracefulShutdownClicked() {
|
||||
RouterService svc = _routerService;
|
||||
if(svc != null && _isBound) {
|
||||
setPref(PREF_AUTO_START, false);
|
||||
svc.gracefulShutdown();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @since 0.9.19 */
|
||||
public boolean onCancelGracefulShutdownClicked() {
|
||||
RouterService svc = _routerService;
|
||||
if(svc != null && _isBound) {
|
||||
setPref(PREF_AUTO_START, false);
|
||||
svc.cancelGracefulShutdown();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
572
app/src/main/java/net/i2p/android/router/MainFragment.java
Normal file
572
app/src/main/java/net/i2p/android/router/MainFragment.java
Normal file
@ -0,0 +1,572 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import net.i2p.android.router.dialog.ConfigureBrowserDialog;
|
||||
import net.i2p.android.router.dialog.FirstStartDialog;
|
||||
import net.i2p.android.router.service.State;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.LongToggleButton;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
import net.i2p.util.Translate;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class MainFragment extends I2PFragmentBase {
|
||||
|
||||
private Handler _handler;
|
||||
private Runnable _updater;
|
||||
private Runnable _oneShotUpdate;
|
||||
private String _savedStatus;
|
||||
private boolean _keep = true;
|
||||
private boolean _startPressed = false;
|
||||
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
|
||||
private static final String PREF_FIRST_START = "app.router.firstStart";
|
||||
private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats";
|
||||
protected static final String PROP_NEW_INSTALL = "i2p.newInstall";
|
||||
protected static final String PROP_NEW_VERSION = "i2p.newVersion";
|
||||
RouterControlListener mCallback;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface RouterControlListener {
|
||||
public boolean shouldShowOnOff();
|
||||
public boolean shouldBeOn();
|
||||
public void onStartRouterClicked();
|
||||
public boolean onStopRouterClicked();
|
||||
/** @since 0.9.19 */
|
||||
public boolean isGracefulShutdownInProgress();
|
||||
/** @since 0.9.19 */
|
||||
public boolean onGracefulShutdownClicked();
|
||||
/** @since 0.9.19 */
|
||||
public boolean onCancelGracefulShutdownClicked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mCallback = (RouterControlListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement RouterControlListener");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the fragment is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Init stuff here so settings work.
|
||||
if(savedInstanceState != null) {
|
||||
String saved = savedInstanceState.getString("status");
|
||||
if(saved != null) {
|
||||
_savedStatus = saved;
|
||||
}
|
||||
}
|
||||
|
||||
_keep = true;
|
||||
|
||||
_handler = new Handler();
|
||||
_updater = new Updater();
|
||||
_oneShotUpdate = new OneShotUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_main, container, false);
|
||||
|
||||
final ImageView lightImage = (ImageView) v.findViewById(R.id.main_lights);
|
||||
lightImage.setImageResource(R.drawable.routerlogo_0);
|
||||
|
||||
LongToggleButton b = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
|
||||
b.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
|
||||
public boolean onLongClick(View view) {
|
||||
boolean on = ((ToggleButton) view).isChecked();
|
||||
if (on) {
|
||||
_startPressed = true;
|
||||
mCallback.onStartRouterClicked();
|
||||
updateOneShot();
|
||||
checkFirstStart();
|
||||
} else if(mCallback.onGracefulShutdownClicked())
|
||||
updateOneShot();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Button gb = (Button) v.findViewById(R.id.button_shutdown_now);
|
||||
gb.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
if (mCallback.isGracefulShutdownInProgress())
|
||||
if(mCallback.onStopRouterClicked())
|
||||
updateOneShot();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
gb = (Button) v.findViewById(R.id.button_cancel_graceful);
|
||||
gb.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
if (mCallback.isGracefulShutdownInProgress())
|
||||
if(mCallback.onCancelGracefulShutdownClicked())
|
||||
updateOneShot();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
_handler.removeCallbacks(_updater);
|
||||
_handler.removeCallbacks(_oneShotUpdate);
|
||||
if(_savedStatus != null) {
|
||||
TextView tv = (TextView) getActivity().findViewById(R.id.main_status_text);
|
||||
tv.setText(_savedStatus);
|
||||
}
|
||||
checkDialog();
|
||||
_handler.postDelayed(_updater, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
_handler.removeCallbacks(_updater);
|
||||
_handler.removeCallbacks(_oneShotUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
updateOneShot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
if(_savedStatus != null) {
|
||||
outState.putString("status", _savedStatus);
|
||||
}
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
private void updateOneShot() {
|
||||
_handler.postDelayed(_oneShotUpdate, 10);
|
||||
}
|
||||
|
||||
private class OneShotUpdate implements Runnable {
|
||||
|
||||
public void run() {
|
||||
updateVisibility();
|
||||
updateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private class Updater implements Runnable {
|
||||
|
||||
private int counter;
|
||||
private final int delay = 1000;
|
||||
private final int toloop = delay / 500;
|
||||
public void run() {
|
||||
updateVisibility();
|
||||
if(counter++ % toloop == 0) {
|
||||
updateStatus();
|
||||
}
|
||||
//_handler.postDelayed(this, 2500);
|
||||
_handler.postDelayed(this, delay);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVisibility() {
|
||||
boolean showOnOff = mCallback.shouldShowOnOff();
|
||||
ToggleButton b = (ToggleButton) getActivity().findViewById(R.id.router_onoff_button);
|
||||
b.setVisibility(showOnOff ? View.VISIBLE : View.GONE);
|
||||
|
||||
boolean isOn = mCallback.shouldBeOn();
|
||||
b.setChecked(isOn);
|
||||
|
||||
boolean isGraceful = mCallback.isGracefulShutdownInProgress();
|
||||
LinearLayout gv = (LinearLayout) getActivity().findViewById(R.id.router_graceful_buttons);
|
||||
gv.setVisibility(isGraceful ? View.VISIBLE : View.GONE);
|
||||
if (isOn && isGraceful) {
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx != null) {
|
||||
TextView tv = (TextView) gv.findViewById(R.id.router_graceful_status);
|
||||
long ms = ctx.router().getShutdownTimeRemaining();
|
||||
if (ms > 1000) {
|
||||
tv.setText(getActivity().getResources().getString(R.string.button_router_graceful,
|
||||
DataHelper.formatDuration(ms)));
|
||||
} else {
|
||||
tv.setText("Stopping I2P");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showOnOff && !isOn) {
|
||||
// Sometimes the final state message from the RouterService
|
||||
// is not received. Ensure that the state image is correct.
|
||||
// TODO: Fix the race between RouterService shutdown and
|
||||
// IRouterState unbinding.
|
||||
updateState(State.INIT);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
// RouterService svc = _routerService; Which is better to use?!
|
||||
_keep = Connectivity.isConnected(getActivity()) && (ctx != null || _startPressed);
|
||||
Util.d("*********************************************************");
|
||||
Util.d("Back pressed, Keep? " + _keep);
|
||||
Util.d("*********************************************************");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if(!_keep) {
|
||||
Thread t = new Thread(new KillMe());
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
private class KillMe implements Runnable {
|
||||
|
||||
public void run() {
|
||||
Util.d("*********************************************************");
|
||||
Util.d("KillMe started!");
|
||||
Util.d("*********************************************************");
|
||||
try {
|
||||
Thread.sleep(500); // is 500ms long enough?
|
||||
} catch(InterruptedException ex) {
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateState(State newState) {
|
||||
final ImageView lightImage = (ImageView) getView().findViewById(R.id.main_lights);
|
||||
if (newState == State.INIT ||
|
||||
newState == State.STOPPED ||
|
||||
newState == State.MANUAL_STOPPED ||
|
||||
newState == State.MANUAL_QUITTED ||
|
||||
newState == State.NETWORK_STOPPED) {
|
||||
lightImage.setImageResource(R.drawable.routerlogo_0);
|
||||
} else if (newState == State.STARTING ||
|
||||
newState == State.GRACEFUL_SHUTDOWN ||
|
||||
newState == State.STOPPING ||
|
||||
newState == State.MANUAL_STOPPING ||
|
||||
newState == State.MANUAL_QUITTING ||
|
||||
newState == State.NETWORK_STOPPING) {
|
||||
lightImage.setImageResource(R.drawable.routerlogo_1);
|
||||
} else if (newState == State.RUNNING) {
|
||||
lightImage.setImageResource(R.drawable.routerlogo_2);
|
||||
} else if (newState == State.ACTIVE) {
|
||||
lightImage.setImageResource(R.drawable.routerlogo_3);
|
||||
} else if (newState == State.WAITING) {
|
||||
lightImage.setImageResource(R.drawable.routerlogo_4);
|
||||
} // Ignore unknown states.
|
||||
}
|
||||
|
||||
private void updateStatus() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
ScrollView sv = (ScrollView) getActivity().findViewById(R.id.main_scrollview);
|
||||
LinearLayout vStatus = (LinearLayout) getActivity().findViewById(R.id.main_status);
|
||||
TextView vStatusText = (TextView) getActivity().findViewById(R.id.main_status_text);
|
||||
|
||||
if(!Connectivity.isConnected(getActivity())) {
|
||||
// Manually set state, RouterService won't be running
|
||||
updateState(State.WAITING);
|
||||
vStatusText.setText("No Internet connection is available");
|
||||
vStatus.setVisibility(View.VISIBLE);
|
||||
sv.setVisibility(View.VISIBLE);
|
||||
} else if(ctx != null) {
|
||||
if(_startPressed) {
|
||||
_startPressed = false;
|
||||
}
|
||||
|
||||
// Load running tunnels
|
||||
loadDestinations(ctx);
|
||||
|
||||
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(PREF_SHOW_STATS, false)) {
|
||||
short reach = ctx.commSystem().getReachabilityStatus();
|
||||
int active = ctx.commSystem().countActivePeers();
|
||||
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
|
||||
int inEx = ctx.tunnelManager().getFreeTunnelCount();
|
||||
int outEx = ctx.tunnelManager().getOutboundTunnelCount();
|
||||
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
|
||||
int outCl = ctx.tunnelManager().getOutboundClientTunnelCount();
|
||||
int part = ctx.tunnelManager().getParticipatingCount();
|
||||
double dLag = ctx.statManager().getRate("jobQueue.jobLag").getRate(60000).getAverageValue();
|
||||
String jobLag = DataHelper.formatDuration((long) dLag);
|
||||
String msgDelay = DataHelper.formatDuration(ctx.throttle().getMessageDelay());
|
||||
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
|
||||
|
||||
String netstatus;
|
||||
if (reach == net.i2p.router.CommSystemFacade.STATUS_DIFFERENT) {
|
||||
netstatus = "Symmetric NAT";
|
||||
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_HOSED) {
|
||||
netstatus = "Port Failure";
|
||||
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_OK) {
|
||||
netstatus = "OK";
|
||||
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_REJECT_UNSOLICITED) {
|
||||
netstatus = "Firewalled";
|
||||
} else {
|
||||
netstatus = "Unknown";
|
||||
}
|
||||
String tunnelStatus = ctx.throttle().getTunnelStatus();
|
||||
//ctx.commSystem().getReachabilityStatus();
|
||||
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
|
||||
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
|
||||
|
||||
// control total width
|
||||
DecimalFormat fmt;
|
||||
if(inBW >= 1000 || outBW >= 1000) {
|
||||
fmt = new DecimalFormat("#0");
|
||||
} else if(inBW >= 100 || outBW >= 100) {
|
||||
fmt = new DecimalFormat("#0.0");
|
||||
} else {
|
||||
fmt = new DecimalFormat("#0.00");
|
||||
}
|
||||
|
||||
double kBytesIn = ctx.bandwidthLimiter().getTotalAllocatedInboundBytes() / 1024;
|
||||
double kBytesOut = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes() / 1024;
|
||||
|
||||
// control total width
|
||||
DecimalFormat kBfmt;
|
||||
if(kBytesIn >= 1000 || kBytesOut >= 1000) {
|
||||
kBfmt = new DecimalFormat("#0");
|
||||
} else if(kBytesIn >= 100 || kBytesOut >= 100) {
|
||||
kBfmt = new DecimalFormat("#0.0");
|
||||
} else {
|
||||
kBfmt = new DecimalFormat("#0.00");
|
||||
}
|
||||
|
||||
String status =
|
||||
"Network: " + netstatus
|
||||
+ "\nPeers active/known: " + active + " / " + known
|
||||
+ "\nExploratory Tunnels in/out: " + inEx + " / " + outEx
|
||||
+ "\nClient Tunnels in/out: " + inCl + " / " + outCl;
|
||||
|
||||
|
||||
// Need to see if we have the participation option set to on.
|
||||
// I thought there was a router method for that? I guess not! WHY NOT?
|
||||
// It would be easier if we had a number to test status.
|
||||
String participate = "\nParticipation: " + tunnelStatus +" (" + part + ")";
|
||||
|
||||
String details =
|
||||
"\nBandwidth in/out: " + fmt.format(inBW) + " / " + fmt.format(outBW) + " KBps"
|
||||
+ "\nData usage in/out: " + kBfmt.format(kBytesIn) + " / " + kBfmt.format(kBytesOut) + " KB"
|
||||
+ "\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
|
||||
+ "B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B'
|
||||
+ "\nJob Lag: " + jobLag
|
||||
+ "\nMsg Delay: " + msgDelay
|
||||
+ "\nUptime: " + uptime;
|
||||
|
||||
_savedStatus = status + participate + details;
|
||||
vStatusText.setText(_savedStatus);
|
||||
vStatus.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
vStatus.setVisibility(View.GONE);
|
||||
}
|
||||
sv.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
// network but no router context
|
||||
vStatusText.setText("Not running");
|
||||
sv.setVisibility(View.INVISIBLE);
|
||||
/**
|
||||
* **
|
||||
* RouterService svc = _routerService; String status = "connected? "
|
||||
* + Util.isConnected(this) + "\nMemory: " +
|
||||
* DataHelper.formatSize(Runtime.getRuntime().totalMemory() -
|
||||
* Runtime.getRuntime().freeMemory()) + "B / " +
|
||||
* DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B' +
|
||||
* "\nhave ctx? " + (ctx != null) + "\nhave svc? " + (svc != null) +
|
||||
* "\nis bound? " + _isBound + "\nsvc state: " + (svc == null ?
|
||||
* "null" : svc.getState()) + "\ncan start? " + (svc == null ?
|
||||
* "null" : svc.canManualStart()) + "\ncan stop? " + (svc == null ?
|
||||
* "null" : svc.canManualStop()); tv.setText(status);
|
||||
* tv.setVisibility(View.VISIBLE);
|
||||
***
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on net.i2p.router.web.SummaryHelper.getDestinations()
|
||||
* @param ctx The RouterContext
|
||||
*/
|
||||
private void loadDestinations(RouterContext ctx) {
|
||||
TableLayout dests = (TableLayout) getView().findViewById(R.id.main_tunnels);
|
||||
dests.removeAllViews();
|
||||
|
||||
List<Destination> clients = new ArrayList<Destination>(ctx.clientManager().listClients());
|
||||
if (!clients.isEmpty()) {
|
||||
Collections.sort(clients, new AlphaComparator(ctx));
|
||||
for (Destination client : clients) {
|
||||
String name = getName(ctx, client);
|
||||
Hash h = client.calculateHash();
|
||||
TableRow dest = new TableRow(getActivity());
|
||||
dest.setPadding(16, 4, 0, 4);
|
||||
|
||||
// Client or server
|
||||
TextView type = new TextView(getActivity());
|
||||
type.setTextColor(getResources().getColor(android.R.color.primary_text_light));
|
||||
type.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
type.setGravity(Gravity.CENTER);
|
||||
if (ctx.clientManager().shouldPublishLeaseSet(h))
|
||||
type.setText(R.string.char_server_tunnel);
|
||||
else
|
||||
type.setText(R.string.char_client_tunnel);
|
||||
dest.addView(type);
|
||||
|
||||
// Name
|
||||
TextView destName = new TextView(getActivity());
|
||||
destName.setPadding(16, 0, 0, 0);
|
||||
destName.setGravity(Gravity.CENTER_VERTICAL);
|
||||
destName.setText(name);
|
||||
dest.addView(destName);
|
||||
|
||||
// Status
|
||||
LeaseSet ls = ctx.netDb().lookupLeaseSetLocally(h);
|
||||
if (ls != null && ctx.tunnelManager().getOutboundClientTunnelCount(h) > 0) {
|
||||
long timeToExpire = ls.getEarliestLeaseDate() - ctx.clock().now();
|
||||
if (timeToExpire < 0) {
|
||||
// red or yellow light
|
||||
type.setBackgroundResource(R.drawable.tunnel_yellow);
|
||||
} else {
|
||||
// green light
|
||||
type.setBackgroundResource(R.drawable.tunnel_green);
|
||||
}
|
||||
} else {
|
||||
// yellow light
|
||||
type.setBackgroundResource(R.drawable.tunnel_yellow);
|
||||
}
|
||||
|
||||
dests.addView(dest);
|
||||
}
|
||||
} else {
|
||||
TableRow empty = new TableRow(getActivity());
|
||||
TextView emptyText = new TextView(getActivity());
|
||||
emptyText.setText(R.string.no_client_tunnels_running);
|
||||
empty.addView(emptyText);
|
||||
dests.addView(empty);
|
||||
}
|
||||
}
|
||||
|
||||
/** compare translated nicknames - put "shared clients" first in the sort */
|
||||
private class AlphaComparator implements Comparator<Destination> {
|
||||
private String xsc;
|
||||
private RouterContext _ctx;
|
||||
|
||||
public AlphaComparator(RouterContext ctx) {
|
||||
_ctx = ctx;
|
||||
xsc = _(ctx, "shared clients");
|
||||
}
|
||||
|
||||
public int compare(Destination lhs, Destination rhs) {
|
||||
String lname = getName(_ctx, lhs);
|
||||
String rname = getName(_ctx, rhs);
|
||||
if (lname.equals(xsc))
|
||||
return -1;
|
||||
if (rname.equals(xsc))
|
||||
return 1;
|
||||
return Collator.getInstance().compare(lname, rname);
|
||||
}
|
||||
}
|
||||
|
||||
/** translate here so collation works above */
|
||||
private String getName(RouterContext ctx, Destination d) {
|
||||
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(d.calculateHash());
|
||||
String name = (in != null ? in.getDestinationNickname() : null);
|
||||
if (name == null) {
|
||||
TunnelPoolSettings out = ctx.tunnelManager().getOutboundSettings(d.calculateHash());
|
||||
name = (out != null ? out.getDestinationNickname() : null);
|
||||
if (name == null)
|
||||
name = d.calculateHash().toBase64().substring(0,6);
|
||||
else
|
||||
name = _(ctx, name);
|
||||
} else {
|
||||
name = _(ctx, name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String _(RouterContext ctx, String s) {
|
||||
return Translate.getString(s, ctx, "net.i2p.router.web.messages");
|
||||
}
|
||||
|
||||
private void checkDialog() {
|
||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||
boolean configureBrowser = ab.getPref(PREF_CONFIGURE_BROWSER, true);
|
||||
if (configureBrowser) {
|
||||
ConfigureBrowserDialog dialog = new ConfigureBrowserDialog();
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "configurebrowser");
|
||||
ab.setPref(PREF_CONFIGURE_BROWSER, false);
|
||||
}
|
||||
/*VersionDialog dialog = new VersionDialog();
|
||||
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");
|
||||
if(oldVersion.equals("??")) {
|
||||
// TODO Don't show this dialog until it is reworked
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(VersionDialog.DIALOG_TYPE, VersionDialog.DIALOG_NEW_INSTALL);
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "newinstall");
|
||||
} else {
|
||||
// TODO Don't show dialog on new version until we have something new to tell them
|
||||
String currentVersion = Util.getOurVersion(getActivity());
|
||||
if(!oldVersion.equals(currentVersion)) {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(VersionDialog.DIALOG_TYPE, VersionDialog.DIALOG_NEW_VERSION);
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "newversion");
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private void checkFirstStart() {
|
||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||
boolean firstStart = ab.getPref(PREF_FIRST_START, true);
|
||||
if (firstStart) {
|
||||
FirstStartDialog dialog = new FirstStartDialog();
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "firststart");
|
||||
ab.setPref(PREF_FIRST_START, false);
|
||||
}
|
||||
}
|
||||
}
|
18
app/src/main/java/net/i2p/android/router/NewsActivity.java
Normal file
18
app/src/main/java/net/i2p/android/router/NewsActivity.java
Normal file
@ -0,0 +1,18 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public class NewsActivity extends I2PActivityBase {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
// Start with the base view
|
||||
if (savedInstanceState == null) {
|
||||
NewsFragment f = new NewsFragment();
|
||||
f.setArguments(getIntent().getExtras());
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, f).commit();
|
||||
}
|
||||
}
|
||||
}
|
95
app/src/main/java/net/i2p/android/router/NewsFragment.java
Normal file
95
app/src/main/java/net/i2p/android/router/NewsFragment.java
Normal file
@ -0,0 +1,95 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.apps.NewsFetcher;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class NewsFragment extends I2PFragmentBase {
|
||||
private long _lastChanged;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_news, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
NewsFetcher nf = NewsFetcher.getInstance();
|
||||
if (nf != null) {
|
||||
// Always update the status
|
||||
TextView tv = (TextView) getActivity().findViewById(R.id.news_status);
|
||||
tv.setText(nf.status().replace(" ", " "));
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// Only update the content if we need to
|
||||
File newsFile = new File(Util.getFileDir(getActivity()), "docs/news.xml");
|
||||
boolean newsExists = newsFile.exists();
|
||||
if (_lastChanged > 0 && ((!newsExists) || newsFile.lastModified() < _lastChanged))
|
||||
return;
|
||||
_lastChanged = System.currentTimeMillis();
|
||||
|
||||
InputStream in = null;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
|
||||
byte buf[] = new byte[1024];
|
||||
try {
|
||||
if (newsExists) {
|
||||
in = new FileInputStream(newsFile);
|
||||
} else {
|
||||
in = getResources().openRawResource(R.raw.initialnews_html);
|
||||
}
|
||||
|
||||
int read;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("news error " + ioe);
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
String news = "";
|
||||
try {
|
||||
news = out.toString("UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
}
|
||||
|
||||
// Get SpannableStringBuilder object from HTML code
|
||||
CharSequence sequence = Html.fromHtml(news);
|
||||
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
|
||||
|
||||
// Get an array of URLSpan from SpannableStringBuilder object
|
||||
URLSpan[] urlSpans = strBuilder.getSpans(0, strBuilder.length(), URLSpan.class);
|
||||
|
||||
// Remove URLSpans with relative paths, which can't be clicked on
|
||||
for (final URLSpan span : urlSpans) {
|
||||
if (span.getURL().startsWith("/"))
|
||||
strBuilder.removeSpan(span);
|
||||
}
|
||||
|
||||
TextView tv = (TextView) getActivity().findViewById(R.id.news_content);
|
||||
tv.setText(strBuilder);
|
||||
tv.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
}
|
370
app/src/main/java/net/i2p/android/router/SettingsActivity.java
Normal file
370
app/src/main/java/net/i2p/android/router/SettingsActivity.java
Normal file
@ -0,0 +1,370 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.router.service.StatSummarizer;
|
||||
import net.i2p.android.router.util.PortPreference;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.stat.FrequencyStat;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.util.LogManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
public class SettingsActivity extends PreferenceActivity {
|
||||
// Actions for legacy settings
|
||||
private static final String ACTION_PREFS_NET = "net.i2p.android.router.PREFS_NET";
|
||||
public static final String ACTION_PREFS_GRAPHS = "net.i2p.android.router.PREFS_GRAPHS";
|
||||
private static final String ACTION_PREFS_LOGGING = "net.i2p.android.router.PREFS_LOGGING";
|
||||
private static final String ACTION_PREFS_ADVANCED = "net.i2p.android.router.PREFS_ADVANCED";
|
||||
|
||||
private Toolbar mToolbar;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String action = getIntent().getAction();
|
||||
if (action != null) {
|
||||
switch (action) {
|
||||
case ACTION_PREFS_NET:
|
||||
addPreferencesFromResource(R.xml.settings_net);
|
||||
break;
|
||||
case ACTION_PREFS_GRAPHS:
|
||||
addPreferencesFromResource(R.xml.settings_graphs);
|
||||
setupGraphSettings(this, getPreferenceScreen(), Util.getRouterContext());
|
||||
break;
|
||||
case ACTION_PREFS_LOGGING:
|
||||
addPreferencesFromResource(R.xml.settings_logging);
|
||||
setupLoggingSettings(this, getPreferenceScreen(), Util.getRouterContext());
|
||||
break;
|
||||
case ACTION_PREFS_ADVANCED:
|
||||
addPreferencesFromResource(R.xml.settings_advanced);
|
||||
setupAdvancedSettings(this, getPreferenceScreen());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Load any properties that the router might have changed on us.
|
||||
setupPreferences(this, Util.getRouterContext());
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Load the legacy preferences headers
|
||||
addPreferencesFromResource(R.xml.settings_headers_legacy);
|
||||
}
|
||||
}
|
||||
|
||||
mToolbar.setTitle(getTitle());
|
||||
}
|
||||
|
||||
protected static void setupPreferences(Context context, RouterContext ctx) {
|
||||
if (ctx != null) {
|
||||
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
|
||||
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
|
||||
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
|
||||
|
||||
int udpPort = ctx.getProperty(udpPortKey, -1);
|
||||
int ntcpPort = ctx.getProperty(ntcpPortKey, -1);
|
||||
boolean ntcpAutoPort = ctx.getBooleanPropertyDefaultTrue(ntcpAutoPortKey);
|
||||
if (ntcpPort < 0 && ntcpAutoPort)
|
||||
ntcpPort = udpPort;
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (prefs.getInt(udpPortKey, -1) != udpPort ||
|
||||
prefs.getInt(ntcpPortKey, -1) != ntcpPort) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(udpPortKey, udpPort);
|
||||
editor.putInt(ntcpPortKey, ntcpPort);
|
||||
// commit() instead of apply() because this needs to happen
|
||||
// before SettingsActivity loads its Preferences.
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void setupGraphSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
|
||||
if (ctx == null) {
|
||||
PreferenceCategory noRouter = new PreferenceCategory(context);
|
||||
noRouter.setTitle(R.string.router_not_running);
|
||||
ps.addPreference(noRouter);
|
||||
} else if (StatSummarizer.instance() == null) {
|
||||
PreferenceCategory noStats = new PreferenceCategory(context);
|
||||
noStats.setTitle(R.string.stats_not_ready);
|
||||
ps.addPreference(noStats);
|
||||
} else {
|
||||
StatManager mgr = ctx.statManager();
|
||||
Map<String, SortedSet<String>> all = mgr.getStatsByGroup();
|
||||
for (String group : all.keySet()) {
|
||||
SortedSet<String> stats = all.get(group);
|
||||
if (stats.size() == 0) continue;
|
||||
PreferenceCategory groupPrefs = new PreferenceCategory(context);
|
||||
groupPrefs.setKey("stat.groups." + group);
|
||||
groupPrefs.setTitle(group);
|
||||
ps.addPreference(groupPrefs);
|
||||
for (String stat : stats) {
|
||||
String key;
|
||||
String description;
|
||||
boolean canBeGraphed = false;
|
||||
boolean currentIsGraphed = false;
|
||||
RateStat rs = mgr.getRate(stat);
|
||||
if (rs != null) {
|
||||
description = rs.getDescription();
|
||||
long period = rs.getPeriods()[0]; // should be the minimum
|
||||
key = stat + "." + period;
|
||||
if (period <= 10*60*1000) {
|
||||
Rate r = rs.getRate(period);
|
||||
canBeGraphed = r != null;
|
||||
if (canBeGraphed) {
|
||||
currentIsGraphed = r.getSummaryListener() != null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FrequencyStat fs = mgr.getFrequency(stat);
|
||||
if (fs != null) {
|
||||
key = stat;
|
||||
description = fs.getDescription();
|
||||
// FrequencyStats cannot be graphed, but can be logged.
|
||||
// XXX: Should log settings be here as well, or in a
|
||||
// separate settings menu?
|
||||
} else {
|
||||
Util.e("Stat does not exist?! [" + stat + "]");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
CheckBoxPreference statPref = new CheckBoxPreference(context);
|
||||
statPref.setKey("stat.summaries." + key);
|
||||
statPref.setTitle(stat);
|
||||
statPref.setSummary(description);
|
||||
statPref.setEnabled(canBeGraphed);
|
||||
statPref.setChecked(currentIsGraphed);
|
||||
groupPrefs.addPreference(statPref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void setupLoggingSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
|
||||
if (ctx != null) {
|
||||
LogManager mgr = ctx.logManager();
|
||||
// Log level overrides
|
||||
/*
|
||||
StringBuilder buf = new StringBuilder(32*1024);
|
||||
Properties limits = mgr.getLimits();
|
||||
TreeSet<String> sortedLogs = new TreeSet<String>();
|
||||
for (Iterator iter = limits.keySet().iterator(); iter.hasNext(); ) {
|
||||
String prefix = (String)iter.next();
|
||||
sortedLogs.add(prefix);
|
||||
}
|
||||
for (Iterator iter = sortedLogs.iterator(); iter.hasNext(); ) {
|
||||
String prefix = (String)iter.next();
|
||||
String level = limits.getProperty(prefix);
|
||||
buf.append(prefix).append('=').append(level).append('\n');
|
||||
}
|
||||
*/
|
||||
/* Don't show, there are no settings that require the router
|
||||
} else {
|
||||
PreferenceCategory noRouter = new PreferenceCategory(context);
|
||||
noRouter.setTitle(R.string.router_not_running);
|
||||
ps.addPreference(noRouter);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
protected static void setupAdvancedSettings(final Context context, PreferenceScreen ps) {
|
||||
final String udpEnableKey = context.getString(R.string.PROP_ENABLE_UDP);
|
||||
final String ntcpEnableKey = context.getString(R.string.PROP_ENABLE_NTCP);
|
||||
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
|
||||
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
|
||||
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
|
||||
|
||||
final CheckBoxPreference udpEnable = (CheckBoxPreference) ps.findPreference(udpEnableKey);
|
||||
final CheckBoxPreference ntcpEnable = (CheckBoxPreference) ps.findPreference(ntcpEnableKey);
|
||||
final PortPreference udpPort = (PortPreference) ps.findPreference(udpPortKey);
|
||||
final PortPreference ntcpPort = (PortPreference) ps.findPreference(ntcpPortKey);
|
||||
final CheckBoxPreference ntcpAutoPort = (CheckBoxPreference) ps.findPreference(ntcpAutoPortKey);
|
||||
|
||||
udpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final Boolean checked = (Boolean) newValue;
|
||||
if (checked || ntcpEnable.isChecked())
|
||||
return true;
|
||||
else {
|
||||
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ntcpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final Boolean checked = (Boolean) newValue;
|
||||
if (checked || udpEnable.isChecked())
|
||||
return true;
|
||||
else {
|
||||
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
udpPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
if (ntcpAutoPort.isChecked())
|
||||
ntcpPort.setText((String) newValue);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
ntcpAutoPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final Boolean checked = (Boolean) newValue;
|
||||
if (checked)
|
||||
ntcpPort.setText(udpPort.getText());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
@Override
|
||||
public void onBuildHeaders(List<Header> target) {
|
||||
// The resource com.android.internal.R.bool.preferences_prefer_dual_pane
|
||||
// has different definitions based upon screen size. At present, it will
|
||||
// be true for -sw720dp devices, false otherwise. For your curiosity, in
|
||||
// Nexus 7 it is false.
|
||||
loadHeadersFromResource(R.xml.settings_headers, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentView(int layoutResID) {
|
||||
ViewGroup contentView = (ViewGroup) LayoutInflater.from(this).inflate(
|
||||
R.layout.activity_settings,
|
||||
(ViewGroup) getWindow().getDecorView().getRootView(), false);
|
||||
|
||||
mToolbar = (Toolbar) contentView.findViewById(R.id.main_toolbar);
|
||||
mToolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onBackPressed();
|
||||
}
|
||||
});
|
||||
|
||||
ViewGroup contentWrapper = (ViewGroup) contentView.findViewById(R.id.content_wrapper);
|
||||
LayoutInflater.from(this).inflate(layoutResID, contentWrapper, true);
|
||||
|
||||
getWindow().setContentView(contentView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
List<Properties> lProps = Util.getPropertiesFromPreferences(this);
|
||||
Properties props = lProps.get(0);
|
||||
Properties propsToRemove = lProps.get(1);
|
||||
Properties logSettings = lProps.get(2);
|
||||
|
||||
Set toRemove = propsToRemove.keySet();
|
||||
|
||||
boolean restartRequired = Util.checkAndCorrectRouterConfig(this, props, toRemove);
|
||||
|
||||
// Apply new config if we are running.
|
||||
RouterContext rCtx = Util.getRouterContext();
|
||||
if (rCtx != null) {
|
||||
rCtx.router().saveConfig(props, toRemove);
|
||||
|
||||
// Merge in new log settings
|
||||
saveLoggingChanges(rCtx, logSettings);
|
||||
} else {
|
||||
// Merge in new config settings, write the file.
|
||||
Util.mergeResourceToFile(this, Util.getFileDir(this), "router.config", R.raw.router_config, props, toRemove);
|
||||
|
||||
// Merge in new log settings
|
||||
saveLoggingChanges(I2PAppContext.getGlobalContext(), logSettings);
|
||||
}
|
||||
|
||||
// Store the settings in Android
|
||||
super.onPause();
|
||||
|
||||
if (restartRequired)
|
||||
Toast.makeText(this, R.string.settings_router_restart_required, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void saveLoggingChanges(I2PAppContext ctx, Properties logSettings) {
|
||||
boolean shouldSave = false;
|
||||
|
||||
for (Object key : logSettings.keySet()) {
|
||||
if ("logger.defaultLevel".equals(key)) {
|
||||
String defaultLevel = (String) logSettings.get(key);
|
||||
String oldDefault = ctx.logManager().getDefaultLimit();
|
||||
if (!defaultLevel.equals(oldDefault)) {
|
||||
shouldSave = true;
|
||||
ctx.logManager().setDefaultLimit(defaultLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSave) {
|
||||
ctx.logManager().saveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static class SettingsFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String settings = getArguments().getString("settings");
|
||||
switch (settings) {
|
||||
case "net":
|
||||
addPreferencesFromResource(R.xml.settings_net);
|
||||
break;
|
||||
case "graphs":
|
||||
addPreferencesFromResource(R.xml.settings_graphs);
|
||||
setupGraphSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
|
||||
break;
|
||||
case "logging":
|
||||
addPreferencesFromResource(R.xml.settings_logging);
|
||||
setupLoggingSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
|
||||
break;
|
||||
case "advanced":
|
||||
addPreferencesFromResource(R.xml.settings_advanced);
|
||||
setupAdvancedSettings(getActivity(), getPreferenceScreen());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return SettingsFragment.class.getName().equals(fragmentName);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
public class AddressEntry {
|
||||
private final String mHostName;
|
||||
private final Destination mDest;
|
||||
|
||||
public AddressEntry(String hostName, Destination dest) {
|
||||
mHostName = hostName;
|
||||
mDest = dest;
|
||||
}
|
||||
|
||||
public String getHostName() {
|
||||
return mHostName;
|
||||
}
|
||||
|
||||
public Destination getDestination() {
|
||||
return mDest;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class AddressEntryAdapter extends ArrayAdapter<AddressEntry> {
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
public AddressEntryAdapter(Context context) {
|
||||
super(context, R.layout.listitem_text);
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
public void setData(List<AddressEntry> addresses) {
|
||||
clear();
|
||||
if (addresses != null) {
|
||||
for (AddressEntry address : addresses) {
|
||||
add(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = mInflater.inflate(R.layout.listitem_text, parent, false);
|
||||
AddressEntry address = getItem(position);
|
||||
|
||||
TextView text = (TextView) v.findViewById(R.id.text);
|
||||
text.setText(address.getHostName());
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.util.NamingServiceUtil;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
|
||||
private I2PFragmentBase.RouterContextProvider mRContextProvider;
|
||||
private String mBook;
|
||||
private String mFilter;
|
||||
private List<AddressEntry> mData;
|
||||
|
||||
public AddressEntryLoader(Context context, I2PFragmentBase.RouterContextProvider rContextProvider,
|
||||
String book, String filter) {
|
||||
super(context);
|
||||
mRContextProvider = rContextProvider;
|
||||
mBook = book;
|
||||
mFilter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AddressEntry> loadInBackground() {
|
||||
RouterContext routerContext = mRContextProvider.getRouterContext();
|
||||
if (routerContext == null)
|
||||
return null;
|
||||
|
||||
// get the names
|
||||
NamingService ns = NamingServiceUtil.getNamingService(routerContext, mBook);
|
||||
Util.d("NamingService: " + ns.getName());
|
||||
// After router shutdown we get nothing... why?
|
||||
List<AddressEntry> ret = new ArrayList<>();
|
||||
Map<String, Destination> names = new TreeMap<>();
|
||||
|
||||
Properties searchProps = new Properties();
|
||||
// Needed for HostsTxtNamingService
|
||||
searchProps.setProperty("file", mBook);
|
||||
if (mFilter != null && mFilter.length() > 0)
|
||||
searchProps.setProperty("search", mFilter);
|
||||
|
||||
names.putAll(ns.getEntries(searchProps));
|
||||
for (String hostName : names.keySet())
|
||||
ret.add(new AddressEntry(hostName, names.get(hostName)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(List<AddressEntry> data) {
|
||||
if (isReset()) {
|
||||
// The Loader has been reset; ignore the result and invalidate the data.
|
||||
if (data != null) {
|
||||
releaseResources(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||
// We must protect it until the new data has been delivered.
|
||||
List<AddressEntry> oldData = mData;
|
||||
mData = data;
|
||||
|
||||
if (isStarted()) {
|
||||
// If the Loader is in a started state, have the superclass deliver the
|
||||
// results to the client.
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
// Invalidate the old data as we don't need it any more.
|
||||
if (oldData != null && oldData != data) {
|
||||
releaseResources(oldData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mData != null) {
|
||||
// Deliver any previously loaded data immediately.
|
||||
deliverResult(mData);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mData == null) {
|
||||
// When the observer detects a change, it should call onContentChanged()
|
||||
// on the Loader, which will cause the next call to takeContentChanged()
|
||||
// to return true. If this is ever the case (or if the current data is
|
||||
// null), we force a new load.
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||
// current load (if there is one).
|
||||
cancelLoad();
|
||||
|
||||
// Note that we leave the observer as is. Loaders in a stopped state
|
||||
// should still monitor the data source for changes so that the Loader
|
||||
// will know to force a new load if it is ever started again.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
// Ensure the loader has been stopped.
|
||||
onStopLoading();
|
||||
|
||||
// At this point we can release the resources associated with 'mData'.
|
||||
if (mData != null) {
|
||||
releaseResources(mData);
|
||||
mData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(List<AddressEntry> data) {
|
||||
// Attempt to cancel the current asynchronous load.
|
||||
super.onCanceled(data);
|
||||
|
||||
// The load has been canceled, so we should release the resources
|
||||
// associated with 'data'.
|
||||
releaseResources(data);
|
||||
}
|
||||
|
||||
private void releaseResources(List<AddressEntry> data) {
|
||||
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||
// would close it in this method. All resources associated with the Loader
|
||||
// should be released here.
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class AddressbookActivity extends I2PActivityBase
|
||||
implements AddressbookFragment.OnAddressSelectedListener,
|
||||
SearchView.OnQueryTextListener {
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
private static final String SELECTED_PAGE = "selected_page";
|
||||
private static final int PAGE_ROUTER = 0;
|
||||
|
||||
private Spinner mSpinner;
|
||||
|
||||
@Override
|
||||
protected boolean canUseTwoPanes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
|
||||
R.array.addressbook_pages, android.R.layout.simple_spinner_dropdown_item));
|
||||
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectPage(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_PAGE);
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectPage(PAGE_ROUTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_PAGE, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
private void selectPage(int page) {
|
||||
AddressbookFragment f = new AddressbookFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(AddressbookFragment.BOOK_NAME,
|
||||
page == PAGE_ROUTER ?
|
||||
AddressbookFragment.ROUTER_BOOK :
|
||||
AddressbookFragment.PRIVATE_BOOK);
|
||||
f.setArguments(args);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_addressbook_actions, menu);
|
||||
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search_addressbook);
|
||||
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
|
||||
searchView.setOnQueryTextListener(this);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
// AddressbookFragment.OnAddressSelectedListener
|
||||
|
||||
public void onAddressSelected(CharSequence host) {
|
||||
if (Intent.ACTION_PICK.equals(getIntent().getAction())) {
|
||||
Intent result = new Intent();
|
||||
result.setData(Uri.parse("http://" + host));
|
||||
setResult(Activity.RESULT_OK, result);
|
||||
finish();
|
||||
} else {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse("http://" + host));
|
||||
startActivity(i);
|
||||
}
|
||||
}
|
||||
|
||||
// SearchView.OnQueryTextListener
|
||||
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
filterAddresses(newText);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
filterAddresses(query);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void filterAddresses(String query) {
|
||||
Fragment f = getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||
if (f instanceof AddressbookFragment) {
|
||||
AddressbookFragment af = (AddressbookFragment) f;
|
||||
af.filterAddresses(query);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
||||
|
||||
public class AddressbookAddWizardActivity extends AbstractWizardActivity {
|
||||
@Override
|
||||
protected AbstractWizardModel onCreateModel() {
|
||||
return new AddressbookAddWizardModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DialogFragment onGetFinishWizardDialog() {
|
||||
return new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setMessage("Add to private addressbook?")
|
||||
.setPositiveButton("Add",
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent result = new Intent();
|
||||
setResult(Activity.RESULT_OK, result);
|
||||
result.putExtra(AddressbookFragment.ADD_WIZARD_DATA, mWizardModel.save());
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||
import net.i2p.android.wizard.model.I2PB64DestinationPage;
|
||||
import net.i2p.android.wizard.model.PageList;
|
||||
import net.i2p.android.wizard.model.SingleTextFieldPage;
|
||||
|
||||
public class AddressbookAddWizardModel extends AbstractWizardModel {
|
||||
public AddressbookAddWizardModel(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PageList onNewRootPageList() {
|
||||
Resources res = mContext.getResources();
|
||||
|
||||
return new PageList(
|
||||
new SingleTextFieldPage(this, res.getString(R.string.addressbook_add_wizard_k_name))
|
||||
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_name))
|
||||
.setRequired(true),
|
||||
|
||||
new I2PB64DestinationPage(this, res.getString(R.string.addressbook_add_wizard_k_destination))
|
||||
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_destination))
|
||||
.setRequired(true)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,288 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.addressbook.Daemon;
|
||||
import net.i2p.android.help.HelpActivity;
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.NamingServiceUtil;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AddressbookFragment extends ListFragment implements
|
||||
I2PFragmentBase.RouterContextUser,
|
||||
LoaderManager.LoaderCallbacks<List<AddressEntry>> {
|
||||
public static final String BOOK_NAME = "book_name";
|
||||
public static final String ROUTER_BOOK = "hosts.txt";
|
||||
public static final String PRIVATE_BOOK = "privatehosts.txt";
|
||||
public static final String ADD_WIZARD_DATA = "add_wizard_data";
|
||||
|
||||
private static final int ADD_WIZARD_REQUEST = 1;
|
||||
|
||||
private static final int ROUTER_LOADER_ID = 1;
|
||||
private static final int PRIVATE_LOADER_ID = 2;
|
||||
|
||||
private boolean mOnActivityCreated;
|
||||
private RouterContextProvider mRouterContextProvider;
|
||||
private OnAddressSelectedListener mCallback;
|
||||
private AddressEntryAdapter mAdapter;
|
||||
private String mBook;
|
||||
private String mCurFilter;
|
||||
|
||||
private ImageButton mAddToAddressbook;
|
||||
|
||||
// Set in onActivityResult()
|
||||
private Intent mAddWizardData;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnAddressSelectedListener {
|
||||
public void onAddressSelected(CharSequence host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mRouterContextProvider = (RouterContextProvider) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement RouterContextProvider");
|
||||
}
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mCallback = (OnAddressSelectedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnAddressSelectedListener");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// Create the list fragment's content view by calling the super method
|
||||
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
|
||||
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
|
||||
listContainer.addView(listFragmentView);
|
||||
|
||||
mAddToAddressbook = (ImageButton) v.findViewById(R.id.promoted_action);
|
||||
mAddToAddressbook.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
|
||||
startActivityForResult(wi, ADD_WIZARD_REQUEST);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mAdapter = new AddressEntryAdapter(getActivity());
|
||||
mBook = getArguments().getString(BOOK_NAME);
|
||||
|
||||
// Set adapter to null before setting the header
|
||||
setListAdapter(null);
|
||||
|
||||
TextView v = new TextView(getActivity());
|
||||
v.setTag("addressbook_header");
|
||||
getListView().addHeaderView(v);
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
mOnActivityCreated = true;
|
||||
if (getRouterContext() != null)
|
||||
onRouterConnectionReady();
|
||||
else
|
||||
setEmptyText(getResources().getString(
|
||||
R.string.router_not_running));
|
||||
}
|
||||
|
||||
public void onRouterConnectionReady() {
|
||||
// Show actions
|
||||
if (mSearchAddressbook != null)
|
||||
mSearchAddressbook.setVisible(true);
|
||||
if (mAddToAddressbook != null && mAddToAddressbook.getVisibility() != View.VISIBLE)
|
||||
mAddToAddressbook.setVisibility(View.VISIBLE);
|
||||
|
||||
if (mAddWizardData != null) {
|
||||
// Save the new entry
|
||||
Bundle entryData = mAddWizardData.getExtras().getBundle(ADD_WIZARD_DATA);
|
||||
NamingService ns = NamingServiceUtil.getNamingService(getRouterContext(), mBook);
|
||||
boolean success = NamingServiceUtil.addFromWizard(getActivity(), ns, entryData, false);
|
||||
if (success) {
|
||||
// Reload the list
|
||||
setListShown(false);
|
||||
getLoaderManager().restartLoader(PRIVATE_LOADER_ID, null, this);
|
||||
}
|
||||
} else {
|
||||
setEmptyText("No hosts in address book " + mBook);
|
||||
|
||||
setListShown(false);
|
||||
getLoaderManager().initLoader(PRIVATE_BOOK.equals(mBook) ?
|
||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||
CharSequence host = ((TextView) view).getText();
|
||||
mCallback.onAddressSelected(host);
|
||||
}
|
||||
|
||||
private MenuItem mSearchAddressbook;
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_addressbook_actions, menu);
|
||||
|
||||
mSearchAddressbook = menu.findItem(R.id.action_search_addressbook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
// Hide until needed
|
||||
if (getRouterContext() == null) {
|
||||
mSearchAddressbook.setVisible(false);
|
||||
if (mAddToAddressbook != null)
|
||||
mAddToAddressbook.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Only allow adding to private book
|
||||
if (!PRIVATE_BOOK.equals(mBook) && mAddToAddressbook != null) {
|
||||
mAddToAddressbook.setVisibility(View.GONE);
|
||||
mAddToAddressbook = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_reload_subscriptions:
|
||||
Daemon.wakeup();
|
||||
Toast.makeText(getActivity(), "Reloading subscriptions...",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
case R.id.action_addressbook_settings:
|
||||
Intent si = new Intent(getActivity(), AddressbookSettingsActivity.class);
|
||||
startActivity(si);
|
||||
return true;
|
||||
case R.id.action_addressbook_help:
|
||||
Intent hi = new Intent(getActivity(), HelpActivity.class);
|
||||
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_ADDRESSBOOK);
|
||||
startActivity(hi);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == ADD_WIZARD_REQUEST &&
|
||||
resultCode == Activity.RESULT_OK &&
|
||||
PRIVATE_BOOK.equals(mBook)) {
|
||||
mAddWizardData = data;
|
||||
}
|
||||
}
|
||||
|
||||
public void filterAddresses(String query) {
|
||||
mCurFilter = !TextUtils.isEmpty(query) ? query : null;
|
||||
if (getRouterContext() != null && mAdapter != null) {
|
||||
setListShown(false);
|
||||
getLoaderManager().restartLoader(PRIVATE_BOOK.equals(mBook) ?
|
||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicated from I2PFragmentBase because this extends ListFragment
|
||||
private RouterContext getRouterContext() {
|
||||
return mRouterContextProvider.getRouterContext();
|
||||
}
|
||||
|
||||
// I2PFragmentBase.RouterContextUser
|
||||
|
||||
public void onRouterBind() {
|
||||
if (mOnActivityCreated)
|
||||
onRouterConnectionReady();
|
||||
}
|
||||
|
||||
// LoaderManager.LoaderCallbacks<List<AddressEntry>>
|
||||
|
||||
public Loader<List<AddressEntry>> onCreateLoader(int id, Bundle args) {
|
||||
return new AddressEntryLoader(getActivity(),
|
||||
mRouterContextProvider, mBook, mCurFilter);
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<List<AddressEntry>> loader,
|
||||
List<AddressEntry> data) {
|
||||
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
|
||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
|
||||
if (data == null)
|
||||
setEmptyText(getResources().getString(
|
||||
R.string.router_not_running));
|
||||
else {
|
||||
mAdapter.setData(data);
|
||||
|
||||
TextView v = (TextView) getListView().findViewWithTag("addressbook_header");
|
||||
if (mCurFilter != null)
|
||||
v.setText(getActivity().getResources().getString(
|
||||
R.string.addressbook_search_header,
|
||||
data.size()));
|
||||
else
|
||||
v.setText("");
|
||||
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<List<AddressEntry>> loader) {
|
||||
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
|
||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
|
||||
mAdapter.setData(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AddressbookSettingsActivity extends ActionBarActivity {
|
||||
|
||||
private EditText text_content_subscriptions;
|
||||
private Button btn_save_subscriptions;
|
||||
private String filename = "/addressbook/subscriptions.txt";
|
||||
private File i2pDir;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_addressbook_settings);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
text_content_subscriptions = (EditText) findViewById(R.id.subscriptions_content);
|
||||
btn_save_subscriptions = (Button) findViewById(R.id.button_save_subscriptions);
|
||||
init_actions();
|
||||
i2pDir = new File(getFilesDir(), filename);
|
||||
load();
|
||||
}
|
||||
|
||||
private void init_actions() {
|
||||
btn_save_subscriptions.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
Context context = getApplicationContext();
|
||||
CharSequence text;
|
||||
if (save()) {
|
||||
text = "subscriptions.txt successfully saved!";
|
||||
} else {
|
||||
text = "there was a problem saving subscriptions.txt! Try fix permissions or reinstall i2p.";
|
||||
}
|
||||
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean load() {
|
||||
String res = FileUtil.readTextFile(i2pDir.getAbsolutePath(), -1, true);
|
||||
if (res.length() > 0) {
|
||||
text_content_subscriptions.setText(res);
|
||||
return true;
|
||||
}
|
||||
Context context = getApplicationContext();
|
||||
CharSequence text = "Sorry, could not load subscriptions.txt!";
|
||||
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean save() {
|
||||
//
|
||||
String content = text_content_subscriptions.getText().toString();
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(i2pDir);
|
||||
byte[] contentInBytes = content.getBytes();
|
||||
out.write(contentInBytes);
|
||||
out.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
if (out != null) try {out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package net.i2p.android.router.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.LicenseActivity;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.I2Patterns;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
public class AboutDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||
LayoutInflater li = LayoutInflater.from(getActivity());
|
||||
View view = li.inflate(R.layout.fragment_dialog_about, null);
|
||||
|
||||
final String currentVersion = Util.getOurVersion(getActivity());
|
||||
TextView tv = (TextView)view.findViewById(R.id.about_version);
|
||||
tv.setText(currentVersion);
|
||||
|
||||
tv = (TextView)view.findViewById(R.id.url_project);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
tv = (TextView)view.findViewById(R.id.url_android_bugs);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
tv = (TextView)view.findViewById(R.id.url_android_volunteer);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
tv = (TextView)view.findViewById(R.id.url_donate);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
b.setTitle(R.string.menu_about)
|
||||
.setView(view)
|
||||
.setNeutralButton(R.string.label_licenses, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Intent lic = new Intent(getActivity(), LicenseActivity.class);
|
||||
startActivity(lic);
|
||||
}
|
||||
});
|
||||
return b.create();
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package net.i2p.android.router.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import net.i2p.android.help.BrowserConfigActivity;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class ConfigureBrowserDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
b.setTitle(R.string.configure_browser_title)
|
||||
.setMessage(R.string.configure_browser_for_i2p)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
dialogInterface.dismiss();
|
||||
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
|
||||
startActivity(hi);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
dialogInterface.cancel();
|
||||
}
|
||||
});
|
||||
return b.create();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package net.i2p.android.router.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.I2Patterns;
|
||||
|
||||
public class FirstStartDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
LayoutInflater li = LayoutInflater.from(getActivity());
|
||||
View view = li.inflate(R.layout.fragment_dialog_first_start, null);
|
||||
|
||||
TextView tv = (TextView)view.findViewById(R.id.url_faq);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
tv = (TextView)view.findViewById(R.id.url_irc_i2p);
|
||||
Linkify.addLinks(tv, I2Patterns.IRC_URL, "irc://");
|
||||
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
b.setTitle(R.string.first_start_title)
|
||||
.setView(view);
|
||||
return b.create();
|
||||
}
|
||||
}
|
@ -1,41 +1,46 @@
|
||||
package net.i2p.android.router.activity;
|
||||
package net.i2p.android.router.dialog;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* Display a raw text resource.
|
||||
* The resource ID must be passed as an extra in the intent.
|
||||
*/
|
||||
public class TextResourceActivity extends I2PActivityBase {
|
||||
public class TextResourceDialog extends DialogFragment {
|
||||
|
||||
final static String TEXT_RESOURCE_ID = "text_resource_id";
|
||||
public static final String TEXT_DIALOG_TITLE = "text_title";
|
||||
public final static String TEXT_RESOURCE_ID = "text_resource_id";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.text_resource);
|
||||
TextView tv = (TextView) findViewById(R.id.text_resource_text);
|
||||
tv.setMovementMethod(ScrollingMovementMethod.getInstance());
|
||||
Intent intent = getIntent();
|
||||
int id = intent.getIntExtra(TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||
View v = inflater.inflate(R.layout.fragment_dialog_text_resource, container, false);
|
||||
TextView tv = (TextView) v.findViewById(R.id.text_resource_text);
|
||||
String title = getArguments().getString(TEXT_DIALOG_TITLE);
|
||||
if (title != null)
|
||||
getDialog().setTitle(title);
|
||||
int id = getArguments().getInt(TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||
if (id == R.raw.releasenotes_txt)
|
||||
tv.setText("Release Notes for Release " + Util.getOurVersion(this) + "\n\n" +
|
||||
tv.setText("Release Notes for Release " + Util.getOurVersion(getActivity()) + "\n\n" +
|
||||
getResourceAsString(id));
|
||||
else
|
||||
tv.setText(getResourceAsString(id));
|
||||
return v;
|
||||
}
|
||||
|
||||
private String getResourceAsString(int id) {
|
||||
@ -45,14 +50,12 @@ public class TextResourceActivity extends I2PActivityBase {
|
||||
try {
|
||||
in = getResources().openRawResource(id);
|
||||
|
||||
int read = 0;
|
||||
int read;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("resource error " + ioe);
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
System.err.println("resource error " + nfe);
|
||||
} catch (IOException | Resources.NotFoundException re) {
|
||||
System.err.println("resource error " + re);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package net.i2p.android.router.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.MainFragment;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
public class VersionDialog extends DialogFragment {
|
||||
public static final String DIALOG_TYPE = "dialog_type";
|
||||
public static final int DIALOG_NEW_INSTALL = 0;
|
||||
public static final int DIALOG_NEW_VERSION = 1;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||
final String currentVersion = Util.getOurVersion(getActivity());
|
||||
Dialog rv;
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
int id = getArguments().getInt(DIALOG_TYPE);
|
||||
switch(id) {
|
||||
case DIALOG_NEW_INSTALL:
|
||||
b.setMessage(R.string.welcome_new_install)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Dismiss", new DialogInterface.OnClickListener() {
|
||||
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).setNegativeButton(R.string.label_release_notes, new DialogInterface.OnClickListener() {
|
||||
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
|
||||
dialog.dismiss();
|
||||
TextResourceDialog f = new TextResourceDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||
f.setArguments(args);
|
||||
getActivity().getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.main_fragment, f)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
});
|
||||
rv = b.create();
|
||||
break;
|
||||
|
||||
case DIALOG_NEW_VERSION:
|
||||
default:
|
||||
b.setMessage(getResources().getString(R.string.welcome_new_version) +
|
||||
" " + currentVersion)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton("Dismiss", new DialogInterface.OnClickListener() {
|
||||
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).setNegativeButton(R.string.label_release_notes, new DialogInterface.OnClickListener() {
|
||||
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
|
||||
dialog.dismiss();
|
||||
TextResourceDialog f = new TextResourceDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||
f.setArguments(args);
|
||||
getActivity().getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.main_fragment, f)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
});
|
||||
|
||||
rv = b.create();
|
||||
break;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
141
app/src/main/java/net/i2p/android/router/log/LogActivity.java
Normal file
141
app/src/main/java/net/i2p/android/router/log/LogActivity.java
Normal file
@ -0,0 +1,141 @@
|
||||
package net.i2p.android.router.log;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.SettingsActivity;
|
||||
|
||||
public class LogActivity extends I2PActivityBase implements
|
||||
LogFragment.OnEntrySelectedListener {
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
private static final String SELECTED_LEVEL = "selected_level";
|
||||
|
||||
private String[] mLevels;
|
||||
private Spinner mSpinner;
|
||||
|
||||
@Override
|
||||
protected boolean canUseTwoPanes() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mLevels = getResources().getStringArray(R.array.log_level_list);
|
||||
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
|
||||
R.array.log_level_list, android.R.layout.simple_spinner_dropdown_item));
|
||||
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectLevel(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_LEVEL);
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectLevel(0);
|
||||
}
|
||||
|
||||
private void selectLevel(int i) {
|
||||
String level = mLevels[i];
|
||||
LogFragment f = LogFragment.newInstance(level);
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
if (mTwoPane)
|
||||
f.setActivateOnItemClick(true);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f, level).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.activity_base_actions, menu);
|
||||
// Help menu not needed (yet), hide
|
||||
// TODO: Unhide when Help finished
|
||||
//menu.findItem(R.id.menu_help).setVisible(false);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_settings:
|
||||
Intent intent = new Intent(LogActivity.this, SettingsActivity.class);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
intent.setAction("net.i2p.android.router.PREFS_LOGGING");
|
||||
} else { // TODO: Test if this works, fix if not
|
||||
Bundle args = new Bundle();
|
||||
args.putString("settings", "logging");
|
||||
intent.putExtras(args);
|
||||
}
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_LEVEL, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
// LogFragment.OnEntrySelectedListener
|
||||
|
||||
public void onEntrySelected(String entry) {
|
||||
if (mTwoPane) {
|
||||
// In two-pane mode, show the detail view in this activity by
|
||||
// adding or replacing the detail fragment using a
|
||||
// fragment transaction.
|
||||
LogDetailFragment detailFrag = LogDetailFragment.newInstance(entry);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
} else {
|
||||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
Intent detailIntent = new Intent(this, LogDetailActivity.class);
|
||||
detailIntent.putExtra(LogDetailFragment.LOG_ENTRY, entry);
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
}
|
||||
}
|
24
app/src/main/java/net/i2p/android/router/log/LogAdapter.java
Normal file
24
app/src/main/java/net/i2p/android/router/log/LogAdapter.java
Normal file
@ -0,0 +1,24 @@
|
||||
package net.i2p.android.router.log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
public class LogAdapter extends ArrayAdapter<String> {
|
||||
|
||||
public LogAdapter(Context context) {
|
||||
super(context, R.layout.listitem_logs);
|
||||
}
|
||||
|
||||
public void setData(List<String> entries) {
|
||||
clear();
|
||||
if (entries != null) {
|
||||
for (String entry : entries) {
|
||||
add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package net.i2p.android.router.log;
|
||||
|
||||
import android.os.Bundle;
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class LogDetailActivity extends I2PActivityBase {
|
||||
LogDetailFragment mDetailFrag;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
String entry = getIntent().getStringExtra(LogDetailFragment.LOG_ENTRY);
|
||||
mDetailFrag = LogDetailFragment.newInstance(entry);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, mDetailFrag).commit();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package net.i2p.android.router.log;
|
||||
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class LogDetailFragment extends I2PFragmentBase {
|
||||
public static final String LOG_ENTRY = "log_entry";
|
||||
|
||||
private String mEntry;
|
||||
|
||||
public static LogDetailFragment newInstance (String entry) {
|
||||
LogDetailFragment f = new LogDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(LOG_ENTRY, entry);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_log_entry, container, false);
|
||||
|
||||
mEntry = getArguments().getString(LOG_ENTRY);
|
||||
TextView tv = (TextView) v.findViewById(R.id.log_entry);
|
||||
tv.setMovementMethod(new ScrollingMovementMethod());
|
||||
tv.setText(mEntry);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_log_actions, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_copy_logs:
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(mEntry);
|
||||
} else {
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText(
|
||||
getString(R.string.i2p_android_logs), mEntry);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
|
||||
Toast.makeText(getActivity(), R.string.logs_copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
257
app/src/main/java/net/i2p/android/router/log/LogFragment.java
Normal file
257
app/src/main/java/net/i2p/android/router/log/LogFragment.java
Normal file
@ -0,0 +1,257 @@
|
||||
package net.i2p.android.router.log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LogFragment extends ListFragment implements
|
||||
LoaderManager.LoaderCallbacks<List<String>> {
|
||||
public static final String LOG_LEVEL = "log_level";
|
||||
/**
|
||||
* The serialization (saved instance state) Bundle key representing the
|
||||
* activated item position. Only used on tablets.
|
||||
*/
|
||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||
|
||||
private static final int LEVEL_ERROR = 1;
|
||||
private static final int LEVEL_ALL = 2;
|
||||
|
||||
OnEntrySelectedListener mEntrySelectedCallback;
|
||||
private final List<String> mLogEntries = new ArrayList<>();
|
||||
private LogAdapter mAdapter;
|
||||
private TextView mHeaderView;
|
||||
private String mLogLevel;
|
||||
/**
|
||||
* The current activated item position. Only used on tablets.
|
||||
*/
|
||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||
private boolean mActivateOnItemClick = false;
|
||||
|
||||
private MenuItem mCopyLogs;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnEntrySelectedListener {
|
||||
public void onEntrySelected(String entry);
|
||||
}
|
||||
|
||||
public static LogFragment newInstance(String level) {
|
||||
LogFragment f = new LogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(LOG_LEVEL, level);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnEntrySelectedListener");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// Restore the previously serialized activated item position.
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||
setActivatedPosition(savedInstanceState
|
||||
.getInt(STATE_ACTIVATED_POSITION));
|
||||
}
|
||||
|
||||
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||
// give items the 'activated' state when touched.
|
||||
getListView().setChoiceMode(
|
||||
mActivateOnItemClick ? ListView.CHOICE_MODE_SINGLE
|
||||
: ListView.CHOICE_MODE_NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState)
|
||||
{
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mAdapter = new LogAdapter(getActivity());
|
||||
mLogLevel = getArguments().getString(LOG_LEVEL);
|
||||
|
||||
// set the header
|
||||
mHeaderView = (TextView) getActivity().getLayoutInflater().inflate(R.layout.logs_header, null);
|
||||
getListView().addHeaderView(mHeaderView, "", false);
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
I2PAppContext ctx = I2PAppContext.getCurrentContext();
|
||||
if (ctx != null) {
|
||||
setEmptyText("ERROR".equals(mLogLevel) ?
|
||||
"No error messages" : "No messages");
|
||||
|
||||
setListShown(false);
|
||||
getLoaderManager().initLoader("ERROR".equals(mLogLevel) ?
|
||||
LEVEL_ERROR : LEVEL_ALL, null, this);
|
||||
} else
|
||||
setEmptyText(getResources().getString(
|
||||
R.string.router_not_running));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||
super.onListItemClick(parent, view, pos, id);
|
||||
String entry = mAdapter.getItem(pos - 1);
|
||||
mEntrySelectedCallback.onEntrySelected(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||
// Serialize and persist the activated item position.
|
||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_log_actions, menu);
|
||||
mCopyLogs = menu.findItem(R.id.action_copy_logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
mCopyLogs.setVisible(I2PAppContext.getCurrentContext() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_copy_logs:
|
||||
String logText = "";
|
||||
synchronized (mLogEntries) {
|
||||
for (String logEntry : mLogEntries) {
|
||||
logText += logEntry;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isError = "ERROR".equals(mLogLevel);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(logText);
|
||||
} else {
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText(
|
||||
isError ? getString(R.string.i2p_android_error_logs) : getString(R.string.i2p_android_logs),
|
||||
logText);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
|
||||
int textId;
|
||||
if (isError)
|
||||
textId = R.string.error_logs_copied_to_clipboard;
|
||||
else
|
||||
textId = R.string.logs_copied_to_clipboard;
|
||||
Toast.makeText(getActivity(), textId, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||
* given the 'activated' state when touched.
|
||||
*/
|
||||
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
||||
mActivateOnItemClick = activateOnItemClick;
|
||||
}
|
||||
|
||||
private void setActivatedPosition(int position) {
|
||||
if (position == ListView.INVALID_POSITION) {
|
||||
getListView().setItemChecked(mActivatedPosition, false);
|
||||
} else {
|
||||
getListView().setItemChecked(position, true);
|
||||
}
|
||||
|
||||
mActivatedPosition = position;
|
||||
}
|
||||
|
||||
/** fixme plurals */
|
||||
private static String getHeader(int sz, boolean errorsOnly) {
|
||||
if (errorsOnly) {
|
||||
if (sz == 0)
|
||||
return "No error messages";
|
||||
if (sz == 1)
|
||||
return "1 error message";
|
||||
return sz + " error messages, newest first";
|
||||
}
|
||||
if (sz == 0)
|
||||
return "No messages";
|
||||
if (sz == 1)
|
||||
return "1 message";
|
||||
return sz + " messages, newest first";
|
||||
}
|
||||
|
||||
// LoaderManager.LoaderCallbacks<List<String>>
|
||||
|
||||
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
|
||||
return new LogLoader(getActivity(),
|
||||
I2PAppContext.getCurrentContext(), mLogLevel);
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<List<String>> loader,
|
||||
List<String> data) {
|
||||
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
|
||||
LEVEL_ERROR : LEVEL_ALL)) {
|
||||
synchronized (mLogEntries) {
|
||||
mLogEntries.clear();
|
||||
mLogEntries.addAll(data);
|
||||
}
|
||||
mAdapter.setData(data);
|
||||
String header = getHeader(data.size(), ("ERROR".equals(mLogLevel)));
|
||||
mHeaderView.setText(header);
|
||||
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<List<String>> loader) {
|
||||
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
|
||||
LEVEL_ERROR : LEVEL_ALL)) {
|
||||
mAdapter.setData(null);
|
||||
}
|
||||
}
|
||||
}
|
132
app/src/main/java/net/i2p/android/router/log/LogLoader.java
Normal file
132
app/src/main/java/net/i2p/android/router/log/LogLoader.java
Normal file
@ -0,0 +1,132 @@
|
||||
package net.i2p.android.router.log;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
public class LogLoader extends AsyncTaskLoader<List<String>> {
|
||||
private I2PAppContext mCtx;
|
||||
private String mLogLevel;
|
||||
private List<String> mData;
|
||||
|
||||
private static final int MAX_LOG_LENGTH = 250;
|
||||
|
||||
public LogLoader(Context context, I2PAppContext ctx, String logLevel) {
|
||||
super(context);
|
||||
mCtx = ctx;
|
||||
mLogLevel = logLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> loadInBackground() {
|
||||
List<String> msgs;
|
||||
if ("ERROR".equals(mLogLevel)) {
|
||||
msgs = mCtx.logManager().getBuffer().getMostRecentCriticalMessages();
|
||||
} else {
|
||||
msgs = mCtx.logManager().getBuffer().getMostRecentMessages();
|
||||
}
|
||||
int sz = msgs.size();
|
||||
if (sz > 1)
|
||||
Collections.reverse(msgs);
|
||||
if (sz > 0 && mData != null) {
|
||||
String oldNewest = mData.size() > 0 ? mData.get(0) : null;
|
||||
for (int i = 0; i < sz; i++) {
|
||||
String newItem = msgs.get(i);
|
||||
if (newItem.equals(oldNewest))
|
||||
break;
|
||||
mData.add(i, newItem);
|
||||
}
|
||||
int newSz = mData.size();
|
||||
for (int i = newSz - 1; i > MAX_LOG_LENGTH; i--) {
|
||||
mData.remove(i);
|
||||
}
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(List<String> data) {
|
||||
if (isReset()) {
|
||||
// The Loader has been reset; ignore the result and invalidate the data.
|
||||
if (data != null) {
|
||||
releaseResources(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||
// We must protect it until the new data has been delivered.
|
||||
List<String> oldData = mData;
|
||||
mData = data;
|
||||
|
||||
if (isStarted()) {
|
||||
// If the Loader is in a started state, have the superclass deliver the
|
||||
// results to the client.
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
// Invalidate the old data as we don't need it any more.
|
||||
if (oldData != null && oldData != data) {
|
||||
releaseResources(oldData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mData != null) {
|
||||
// Deliver any previously loaded data immediately.
|
||||
deliverResult(mData);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mData == null) {
|
||||
// When the observer detects a change, it should call onContentChanged()
|
||||
// on the Loader, which will cause the next call to takeContentChanged()
|
||||
// to return true. If this is ever the case (or if the current data is
|
||||
// null), we force a new load.
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||
// current load (if there is one).
|
||||
cancelLoad();
|
||||
|
||||
// Note that we leave the observer as is. Loaders in a stopped state
|
||||
// should still monitor the data source for changes so that the Loader
|
||||
// will know to force a new load if it is ever started again.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
// Ensure the loader has been stopped.
|
||||
onStopLoading();
|
||||
|
||||
// At this point we can release the resources associated with 'mData'.
|
||||
if (mData != null) {
|
||||
releaseResources(mData);
|
||||
mData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(List<String> data) {
|
||||
// Attempt to cancel the current asynchronous load.
|
||||
super.onCanceled(data);
|
||||
|
||||
// The load has been canceled, so we should release the resources
|
||||
// associated with 'data'.
|
||||
releaseResources(data);
|
||||
}
|
||||
|
||||
private void releaseResources(List<String> data) {
|
||||
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||
// would close it in this method. All resources associated with the Loader
|
||||
// should be released here.
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.data.Hash;
|
||||
|
||||
public class NetDbActivity extends I2PActivityBase implements
|
||||
NetDbListFragment.OnEntrySelectedListener {
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
private static final String SELECTED_PAGE = "selected_page";
|
||||
private static final int PAGE_STATS = 0;
|
||||
private static final int PAGE_ROUTERS = 1;
|
||||
|
||||
private Spinner mSpinner;
|
||||
|
||||
@Override
|
||||
protected boolean canUseTwoPanes() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
|
||||
R.array.netdb_pages, android.R.layout.simple_spinner_dropdown_item));
|
||||
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectPage(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_PAGE);
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectPage(PAGE_STATS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_PAGE, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
private void selectPage(int page) {
|
||||
Fragment f;
|
||||
if (page == PAGE_STATS)
|
||||
f = new NetDbSummaryPagerFragment();
|
||||
else {
|
||||
f = new NetDbListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(NetDbListFragment.SHOW_ROUTERS, page == PAGE_ROUTERS);
|
||||
f.setArguments(args);
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
if (mTwoPane)
|
||||
((NetDbListFragment) f).setActivateOnItemClick(true);
|
||||
}
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f).commit();
|
||||
}
|
||||
|
||||
// NetDbListFragment.OnEntrySelectedListener
|
||||
|
||||
public void onEntrySelected(boolean isRouterInfo, Hash entryHash) {
|
||||
if (mTwoPane) {
|
||||
// In two-pane mode, show the detail view in this activity by
|
||||
// adding or replacing the detail fragment using a
|
||||
// fragment transaction.
|
||||
NetDbDetailFragment detailFrag = NetDbDetailFragment.newInstance(
|
||||
isRouterInfo, entryHash);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
|
||||
// If we are coming from a LS to a RI, change the tab
|
||||
int currentTab = mSpinner.getSelectedItemPosition();
|
||||
if (isRouterInfo && currentTab != PAGE_ROUTERS)
|
||||
selectPage(PAGE_ROUTERS);
|
||||
} else {
|
||||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
Intent detailIntent = new Intent(this, NetDbDetailActivity.class);
|
||||
detailIntent.putExtra(NetDbDetailFragment.IS_RI, isRouterInfo);
|
||||
detailIntent.putExtra(NetDbDetailFragment.ENTRY_HASH,
|
||||
entryHash.toBase64());
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Hash;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class NetDbDetailActivity extends I2PActivityBase implements
|
||||
NetDbListFragment.OnEntrySelectedListener {
|
||||
NetDbDetailFragment mDetailFrag;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
boolean isRI = getIntent().getBooleanExtra(NetDbDetailFragment.IS_RI, true);
|
||||
Hash hash = new Hash();
|
||||
try {
|
||||
hash.fromBase64(getIntent().getStringExtra(NetDbDetailFragment.ENTRY_HASH));
|
||||
mDetailFrag = NetDbDetailFragment.newInstance(isRI, hash);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, mDetailFrag).commit();
|
||||
} catch (DataFormatException e) {
|
||||
Util.e(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NetDbListFragment.OnEntrySelectedListener
|
||||
|
||||
public void onEntrySelected(boolean isRouterInfo, Hash entryHash) {
|
||||
// Start the detail activity for the selected item ID.
|
||||
Intent detailIntent = new Intent(this, NetDbDetailActivity.class);
|
||||
detailIntent.putExtra(NetDbDetailFragment.IS_RI, isRouterInfo);
|
||||
detailIntent.putExtra(NetDbDetailFragment.ENTRY_HASH,
|
||||
entryHash.toBase64());
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
}
|
@ -0,0 +1,246 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.netdb.NetDbListFragment.OnEntrySelectedListener;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Lease;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class NetDbDetailFragment extends I2PFragmentBase {
|
||||
public static final String IS_RI = "is_routerinfo";
|
||||
public static final String ENTRY_HASH = "entry_hash";
|
||||
|
||||
OnEntrySelectedListener mEntrySelectedCallback;
|
||||
private NetDbEntry mEntry;
|
||||
|
||||
public static NetDbDetailFragment newInstance(boolean isRI, Hash hash) {
|
||||
NetDbDetailFragment f = new NetDbDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(IS_RI, isRI);
|
||||
args.putString(ENTRY_HASH, hash.toBase64());
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnEntrySelectedListener");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v;
|
||||
if (getArguments().getBoolean(IS_RI)) {
|
||||
v = inflater.inflate(R.layout.fragment_netdb_router_detail, container, false);
|
||||
} else {
|
||||
v = inflater.inflate(R.layout.fragment_netdb_leaseset_detail, container, false);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouterConnectionReady() {
|
||||
if (getRouterContext() != null && mEntry == null)
|
||||
loadEntry();
|
||||
}
|
||||
|
||||
private void loadEntry() {
|
||||
if (getNetDb().isInitialized()) {
|
||||
Hash hash = new Hash();
|
||||
try {
|
||||
hash.fromBase64(getArguments().getString(ENTRY_HASH));
|
||||
if (getArguments().getBoolean(IS_RI)) {
|
||||
// Load RouterInfo
|
||||
RouterInfo ri = getNetDb().lookupRouterInfoLocally(hash);
|
||||
if (ri != null)
|
||||
loadRouterInfo(ri);
|
||||
// TODO: Handle null case in UI
|
||||
} else {
|
||||
// Load LeaseSet
|
||||
LeaseSet ls = getNetDb().lookupLeaseSetLocally(hash);
|
||||
if (ls != null)
|
||||
loadLeaseSet(ls);
|
||||
// TODO: Handle null case in UI
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
Util.e(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadRouterInfo(RouterInfo ri) {
|
||||
mEntry = NetDbEntry.fromRouterInfo(getRouterContext(), ri);
|
||||
|
||||
if (mEntry.isUs())
|
||||
getActivity().setTitle("Our info");
|
||||
else
|
||||
getActivity().setTitle("Peer info");
|
||||
|
||||
TextView entryHash = (TextView) getView().findViewById(R.id.dbentry_hash);
|
||||
entryHash.setText(mEntry.getHash().toBase64());
|
||||
|
||||
if (mEntry.isUs() && getRouter().isHidden()) {
|
||||
TextView pubLabel = (TextView) getView().findViewById(R.id.label_ri_published);
|
||||
pubLabel.setText("Hidden, Updated:");
|
||||
}
|
||||
|
||||
TextView published = (TextView) getView().findViewById(R.id.ri_published);
|
||||
long age = getRouterContext().clock().now() - ri.getPublished();
|
||||
if (age > 0) {
|
||||
published.setText(DataHelper.formatDuration(age) + " ago");
|
||||
} else {
|
||||
// shouldn't happen
|
||||
published.setText(DataHelper.formatDuration(0-age) + " ago???");
|
||||
}
|
||||
|
||||
LinearLayout addresses = (LinearLayout) getView().findViewById(R.id.ri_addresses);
|
||||
for (RouterAddress addr : ri.getAddresses()) {
|
||||
addAddress(addresses, addr);
|
||||
}
|
||||
|
||||
TableLayout stats = (TableLayout) getView().findViewById(R.id.ri_stats);
|
||||
Map<Object, Object> p = ri.getOptionsMap();
|
||||
for (Map.Entry<Object,Object> e : p.entrySet()) {
|
||||
String key = (String)e.getKey();
|
||||
String val = (String)e.getValue();
|
||||
addTableRow(stats, DataHelper.stripHTML(key), DataHelper.stripHTML(val));
|
||||
}
|
||||
}
|
||||
|
||||
private void addAddress(LinearLayout addresses, RouterAddress addr) {
|
||||
TableLayout table = new TableLayout(getActivity());
|
||||
|
||||
String style = addr.getTransportStyle();
|
||||
addTableRow(table, "Style", style);
|
||||
|
||||
int cost = addr.getCost();
|
||||
if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10)))
|
||||
addTableRow(table, "cost", ""+cost);
|
||||
|
||||
Map<Object, Object> p = addr.getOptionsMap();
|
||||
for (Map.Entry<Object,Object> e : p.entrySet()) {
|
||||
String key = (String)e.getKey();
|
||||
String val = (String)e.getValue();
|
||||
addTableRow(table, DataHelper.stripHTML(key), DataHelper.stripHTML(val));
|
||||
}
|
||||
|
||||
addresses.addView(table);
|
||||
}
|
||||
|
||||
private void loadLeaseSet(LeaseSet ls) {
|
||||
mEntry = NetDbEntry.fromLeaseSet(getRouterContext(), ls);
|
||||
|
||||
getActivity().setTitle("LeaseSet");
|
||||
|
||||
TextView nickname = (TextView) getView().findViewById(R.id.ls_nickname);
|
||||
nickname.setText(mEntry.getNickname());
|
||||
|
||||
TextView type = (TextView) getView().findViewById(R.id.ls_type);
|
||||
if (mEntry.isLocal()) {
|
||||
if (mEntry.isUnpublished())
|
||||
type.setText("Local Unpublished Destination");
|
||||
else
|
||||
type.setText("Local Destination");
|
||||
}
|
||||
|
||||
TextView entryHash = (TextView) getView().findViewById(R.id.dbentry_hash);
|
||||
entryHash.setText(mEntry.getHash().toBase64());
|
||||
|
||||
TextView expiry = (TextView) getView().findViewById(R.id.ls_expiry);
|
||||
long exp = ls.getLatestLeaseDate() - getRouterContext().clock().now();
|
||||
if (exp > 0) {
|
||||
expiry.setText(DataHelper.formatDuration(exp));
|
||||
} else {
|
||||
TextView expiryLabel = (TextView) getView().findViewById(R.id.label_ls_expiry);
|
||||
expiryLabel.setText("Expired:");
|
||||
expiry.setText(DataHelper.formatDuration(exp) + " ago");
|
||||
}
|
||||
|
||||
LinearLayout leases = (LinearLayout) getView().findViewById(R.id.ls_leases);
|
||||
for (int i = 0; i < ls.getLeaseCount(); i++) {
|
||||
Lease lease = ls.getLease(i);
|
||||
addLease(leases, lease, i);
|
||||
}
|
||||
}
|
||||
|
||||
private void addLease(LinearLayout leases, Lease lease, int i) {
|
||||
TableLayout table = new TableLayout(getActivity());
|
||||
|
||||
addTableRow(table, "Lease", ""+(i+1));
|
||||
|
||||
TableRow gateway = new TableRow(getActivity());
|
||||
gateway.setPadding(10, 0, 0, 0);
|
||||
|
||||
TextView gatewayLabel = new TextView(getActivity());
|
||||
gatewayLabel.setText("Gateway");
|
||||
|
||||
Button gatewayButton = new Button(getActivity());
|
||||
gatewayButton.setText(lease.getGateway().toBase64().substring(0, 4));
|
||||
final Hash gatewayHash = lease.getGateway();
|
||||
gatewayButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
mEntrySelectedCallback.onEntrySelected(
|
||||
true, gatewayHash);
|
||||
}
|
||||
});
|
||||
|
||||
gateway.addView(gatewayLabel);
|
||||
gateway.addView(gatewayButton);
|
||||
|
||||
table.addView(gateway);
|
||||
|
||||
addTableRow(table, "Tunnel", ""+lease.getTunnelId().getTunnelId());
|
||||
|
||||
leases.addView(table);
|
||||
}
|
||||
|
||||
private void addTableRow(TableLayout table, String key, String val) {
|
||||
TableRow row;
|
||||
TextView tl1, tl2;
|
||||
|
||||
row = new TableRow(getActivity());
|
||||
row.setPadding(10, 0, 0, 0);
|
||||
|
||||
tl1 = new TextView(getActivity());
|
||||
tl2 = new TextView(getActivity());
|
||||
|
||||
tl1.setText(key);
|
||||
tl2.setText(val);
|
||||
|
||||
row.addView(tl1);
|
||||
row.addView(tl2);
|
||||
|
||||
table.addView(row);
|
||||
}
|
||||
}
|
124
app/src/main/java/net/i2p/android/router/netdb/NetDbEntry.java
Normal file
124
app/src/main/java/net/i2p/android/router/netdb/NetDbEntry.java
Normal file
@ -0,0 +1,124 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
|
||||
public class NetDbEntry {
|
||||
private final boolean mIsRI;
|
||||
private final DatabaseEntry mEntry;
|
||||
|
||||
private final boolean mIsUs;
|
||||
private final String mCountry;
|
||||
|
||||
private final String mNick;
|
||||
private final boolean mLocal;
|
||||
private final boolean mUnpublished;
|
||||
|
||||
public static NetDbEntry fromRouterInfo(RouterContext ctx, RouterInfo ri) {
|
||||
Hash us = ctx.routerHash();
|
||||
boolean isUs = ri.getHash().equals(us);
|
||||
// XXX Disabled, no GeoIP file
|
||||
String country = "";//ctx.commSystem().getCountry(ri.getIdentity().getHash());
|
||||
return new NetDbEntry(ri, isUs, country);
|
||||
}
|
||||
|
||||
public static NetDbEntry fromLeaseSet(RouterContext ctx, LeaseSet ls) {
|
||||
String nick;
|
||||
boolean local = false;
|
||||
boolean unpublished = false;
|
||||
Destination dest = ls.getDestination();
|
||||
Hash key = dest.calculateHash();
|
||||
if (ctx.clientManager().isLocal(dest)) {
|
||||
local = true;
|
||||
if (! ctx.clientManager().shouldPublishLeaseSet(key))
|
||||
unpublished = true;
|
||||
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(key);
|
||||
if (in != null && in.getDestinationNickname() != null)
|
||||
nick = in.getDestinationNickname();
|
||||
else
|
||||
nick = dest.toBase64().substring(0, 6);
|
||||
} else {
|
||||
String host = ctx.namingService().reverseLookup(dest);
|
||||
if (host != null)
|
||||
nick = host;
|
||||
else
|
||||
nick = dest.toBase64().substring(0, 6);
|
||||
}
|
||||
return new NetDbEntry(ls, nick, local, unpublished);
|
||||
}
|
||||
|
||||
private NetDbEntry(RouterInfo ri,
|
||||
boolean isUs, String country) {
|
||||
mIsRI = true;
|
||||
mEntry = ri;
|
||||
|
||||
mIsUs = isUs;
|
||||
mCountry = country;
|
||||
|
||||
mNick = "";
|
||||
mLocal = mUnpublished = false;
|
||||
}
|
||||
|
||||
private NetDbEntry(LeaseSet ls,
|
||||
String nick, boolean local, boolean unpublished) {
|
||||
mIsRI = false;
|
||||
mEntry = ls;
|
||||
|
||||
mNick = nick;
|
||||
mLocal = local;
|
||||
mUnpublished = unpublished;
|
||||
|
||||
mIsUs = false;
|
||||
mCountry = "";
|
||||
}
|
||||
|
||||
public boolean isRouterInfo() {
|
||||
return mIsRI;
|
||||
}
|
||||
|
||||
// General methods
|
||||
|
||||
public Hash getHash() {
|
||||
return mEntry.getHash();
|
||||
}
|
||||
|
||||
// RouterInfo-specific methods
|
||||
|
||||
public boolean isUs() {
|
||||
return mIsUs;
|
||||
}
|
||||
|
||||
public int getCountryIcon() {
|
||||
// http://daniel-codes.blogspot.com/2009/12/dynamically-retrieving-resources-in.html
|
||||
try {
|
||||
Class<R.drawable> res = R.drawable.class;
|
||||
Field field = res.getField("flag_" + mCountry);
|
||||
return field.getInt(null);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// LeaseSet-specific methods
|
||||
|
||||
public String getNickname() {
|
||||
return mNick;
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
return mLocal;
|
||||
}
|
||||
|
||||
public boolean isUnpublished() {
|
||||
return mUnpublished;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class NetDbEntryAdapter extends ArrayAdapter<NetDbEntry> {
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
public NetDbEntryAdapter(Context context) {
|
||||
super(context, android.R.layout.simple_list_item_2);
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
public void setData(List<NetDbEntry> entries) {
|
||||
clear();
|
||||
if (entries != null) {
|
||||
for (NetDbEntry entry : entries) {
|
||||
add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v;
|
||||
NetDbEntry entry = getItem(position);
|
||||
|
||||
if (entry.isRouterInfo()) {
|
||||
v = mInflater.inflate(R.layout.listitem_routerinfo, parent, false);
|
||||
|
||||
int countryIcon = entry.getCountryIcon();
|
||||
if (countryIcon > 0) {
|
||||
ImageView country = (ImageView) v.findViewById(R.id.ri_country);
|
||||
country.setImageDrawable(getContext().getResources()
|
||||
.getDrawable(countryIcon));
|
||||
}
|
||||
} else {
|
||||
v = mInflater.inflate(R.layout.listitem_leaseset, parent, false);
|
||||
|
||||
TextView nickname = (TextView) v.findViewById(R.id.ls_nickname);
|
||||
nickname.setText(entry.getNickname());
|
||||
}
|
||||
|
||||
TextView hash = (TextView) v.findViewById(R.id.dbentry_hash);
|
||||
hash.setText(entry.getHash().toBase64());
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
|
||||
private RouterContext mRContext;
|
||||
private boolean mRouters;
|
||||
private List<NetDbEntry> mData;
|
||||
|
||||
public NetDbEntryLoader(Context context, RouterContext rContext, boolean routers) {
|
||||
super(context);
|
||||
mRContext = rContext;
|
||||
mRouters = routers;
|
||||
}
|
||||
|
||||
private static class RouterInfoComparator implements Comparator<RouterInfo> {
|
||||
public int compare(RouterInfo l, RouterInfo r) {
|
||||
return l.getIdentity().getHash().toBase64().compareTo(r.getIdentity().getHash().toBase64());
|
||||
}
|
||||
}
|
||||
|
||||
private class LeaseSetComparator implements Comparator<LeaseSet> {
|
||||
public int compare(LeaseSet l, LeaseSet r) {
|
||||
Destination dl = l.getDestination();
|
||||
Destination dr = r.getDestination();
|
||||
boolean locall = mRContext.clientManager().isLocal(dl);
|
||||
boolean localr = mRContext.clientManager().isLocal(dr);
|
||||
if (locall && !localr) return -1;
|
||||
if (localr && !locall) return 1;
|
||||
return dl.calculateHash().toBase64().compareTo(dr.calculateHash().toBase64());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NetDbEntry> loadInBackground() {
|
||||
List<NetDbEntry> ret = new ArrayList<>();
|
||||
if (mRContext.netDb().isInitialized()) {
|
||||
if (mRouters) {
|
||||
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
|
||||
routers.addAll(mRContext.netDb().getRouters());
|
||||
for (RouterInfo ri : routers) {
|
||||
NetDbEntry entry = NetDbEntry.fromRouterInfo(mRContext, ri);
|
||||
ret.add(entry);
|
||||
}
|
||||
} else {
|
||||
Set<LeaseSet> leases = new TreeSet<>(new LeaseSetComparator());
|
||||
leases.addAll(mRContext.netDb().getLeases());
|
||||
for (LeaseSet ls : leases) {
|
||||
NetDbEntry entry = NetDbEntry.fromLeaseSet(mRContext, ls);
|
||||
ret.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(List<NetDbEntry> data) {
|
||||
if (isReset()) {
|
||||
// The Loader has been reset; ignore the result and invalidate the data.
|
||||
if (data != null) {
|
||||
releaseResources(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||
// We must protect it until the new data has been delivered.
|
||||
List<NetDbEntry> oldData = mData;
|
||||
mData = data;
|
||||
|
||||
if (isStarted()) {
|
||||
// If the Loader is in a started state, have the superclass deliver the
|
||||
// results to the client.
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
// Invalidate the old data as we don't need it any more.
|
||||
if (oldData != null && oldData != data) {
|
||||
releaseResources(oldData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mData != null) {
|
||||
// Deliver any previously loaded data immediately.
|
||||
deliverResult(mData);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mData == null) {
|
||||
// When the observer detects a change, it should call onContentChanged()
|
||||
// on the Loader, which will cause the next call to takeContentChanged()
|
||||
// to return true. If this is ever the case (or if the current data is
|
||||
// null), we force a new load.
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||
// current load (if there is one).
|
||||
cancelLoad();
|
||||
|
||||
// Note that we leave the observer as is. Loaders in a stopped state
|
||||
// should still monitor the data source for changes so that the Loader
|
||||
// will know to force a new load if it is ever started again.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
// Ensure the loader has been stopped.
|
||||
onStopLoading();
|
||||
|
||||
// At this point we can release the resources associated with 'mData'.
|
||||
if (mData != null) {
|
||||
releaseResources(mData);
|
||||
mData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(List<NetDbEntry> data) {
|
||||
// Attempt to cancel the current asynchronous load.
|
||||
super.onCanceled(data);
|
||||
|
||||
// The load has been canceled, so we should release the resources
|
||||
// associated with 'data'.
|
||||
releaseResources(data);
|
||||
}
|
||||
|
||||
private void releaseResources(List<NetDbEntry> data) {
|
||||
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||
// would close it in this method. All resources associated with the Loader
|
||||
// should be released here.
|
||||
}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class NetDbListFragment extends ListFragment implements
|
||||
I2PFragmentBase.RouterContextUser,
|
||||
LoaderManager.LoaderCallbacks<List<NetDbEntry>> {
|
||||
public static final String SHOW_ROUTERS = "show_routers";
|
||||
|
||||
private static final int ROUTER_LOADER_ID = 1;
|
||||
private static final int LEASESET_LOADER_ID = 2;
|
||||
/**
|
||||
* The serialization (saved instance state) Bundle key representing the
|
||||
* activated item position. Only used on tablets.
|
||||
*/
|
||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||
|
||||
private boolean mOnActivityCreated;
|
||||
private RouterContextProvider mRouterContextProvider;
|
||||
private OnEntrySelectedListener mEntrySelectedCallback;
|
||||
private NetDbEntryAdapter mAdapter;
|
||||
private boolean mRouters;
|
||||
/**
|
||||
* The current activated item position. Only used on tablets.
|
||||
*/
|
||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||
private boolean mActivateOnItemClick = false;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnEntrySelectedListener {
|
||||
public void onEntrySelected(boolean isRouterInfo, Hash entryHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mRouterContextProvider = (RouterContextProvider) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement RouterContextProvider");
|
||||
}
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnEntrySelectedListener");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// Restore the previously serialized activated item position.
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||
setActivatedPosition(savedInstanceState
|
||||
.getInt(STATE_ACTIVATED_POSITION));
|
||||
}
|
||||
|
||||
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||
// give items the 'activated' state when touched.
|
||||
getListView().setChoiceMode(
|
||||
mActivateOnItemClick ? ListView.CHOICE_MODE_SINGLE
|
||||
: ListView.CHOICE_MODE_NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mAdapter = new NetDbEntryAdapter(getActivity());
|
||||
mRouters = getArguments().getBoolean(SHOW_ROUTERS);
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
mOnActivityCreated = true;
|
||||
if (getRouterContext() != null)
|
||||
onRouterConnectionReady();
|
||||
else
|
||||
setEmptyText(getResources().getString(
|
||||
R.string.router_not_running));
|
||||
}
|
||||
|
||||
public void onRouterConnectionReady() {
|
||||
setEmptyText(getResources().getString((mRouters ?
|
||||
R.string.netdb_routers_empty :
|
||||
R.string.netdb_leases_empty)));
|
||||
|
||||
setListShown(false);
|
||||
getLoaderManager().initLoader(mRouters ? ROUTER_LOADER_ID
|
||||
: LEASESET_LOADER_ID, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||
super.onListItemClick(parent, view, pos, id);
|
||||
NetDbEntry entry = mAdapter.getItem(pos);
|
||||
mEntrySelectedCallback.onEntrySelected(
|
||||
entry.isRouterInfo(), entry.getHash());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||
// Serialize and persist the activated item position.
|
||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_netdb_list_actions, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_refresh:
|
||||
if (getRouterContext() != null) {
|
||||
setListShown(false);
|
||||
getLoaderManager().restartLoader(mRouters ? ROUTER_LOADER_ID
|
||||
: LEASESET_LOADER_ID, null, this);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||
* given the 'activated' state when touched.
|
||||
*/
|
||||
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
||||
mActivateOnItemClick = activateOnItemClick;
|
||||
}
|
||||
|
||||
private void setActivatedPosition(int position) {
|
||||
if (position == ListView.INVALID_POSITION) {
|
||||
getListView().setItemChecked(mActivatedPosition, false);
|
||||
} else {
|
||||
getListView().setItemChecked(position, true);
|
||||
}
|
||||
|
||||
mActivatedPosition = position;
|
||||
}
|
||||
|
||||
// Duplicated from I2PFragmentBase because this extends ListFragment
|
||||
private RouterContext getRouterContext() {
|
||||
return mRouterContextProvider.getRouterContext();
|
||||
}
|
||||
|
||||
// I2PFragmentBase.RouterContextUser
|
||||
|
||||
public void onRouterBind() {
|
||||
if (mOnActivityCreated)
|
||||
onRouterConnectionReady();
|
||||
}
|
||||
|
||||
// LoaderManager.LoaderCallbacks<List<NetDbEntry>>
|
||||
|
||||
public Loader<List<NetDbEntry>> onCreateLoader(int id, Bundle args) {
|
||||
return new NetDbEntryLoader(getActivity(),
|
||||
getRouterContext(), mRouters);
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<List<NetDbEntry>> loader,
|
||||
List<NetDbEntry> data) {
|
||||
if (loader.getId() == (mRouters ?
|
||||
ROUTER_LOADER_ID : LEASESET_LOADER_ID)) {
|
||||
mAdapter.setData(data);
|
||||
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<List<NetDbEntry>> loader) {
|
||||
if (loader.getId() == (mRouters ?
|
||||
ROUTER_LOADER_ID : LEASESET_LOADER_ID)) {
|
||||
mAdapter.setData(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>>> {
|
||||
private RouterContext mRContext;
|
||||
private List<ObjectCounter<String>> mData;
|
||||
|
||||
public NetDbStatsLoader(Context context, RouterContext rContext) {
|
||||
super(context);
|
||||
mRContext = rContext;
|
||||
}
|
||||
|
||||
private static class RouterInfoComparator implements Comparator<RouterInfo> {
|
||||
public int compare(RouterInfo l, RouterInfo r) {
|
||||
return l.getHash().toBase64().compareTo(r.getHash().toBase64());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectCounter<String>> loadInBackground() {
|
||||
List<ObjectCounter<String>> ret = new ArrayList<>();
|
||||
|
||||
ObjectCounter<String> versions = new ObjectCounter<>();
|
||||
ObjectCounter<String> countries = new ObjectCounter<>();
|
||||
ObjectCounter<String> transports = new ObjectCounter<>();
|
||||
|
||||
if (mRContext != null && mRContext.netDb() != null && mRContext.netDb().isInitialized()) {
|
||||
Hash us = mRContext.routerHash();
|
||||
|
||||
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
|
||||
routers.addAll(mRContext.netDb().getRouters());
|
||||
for (RouterInfo ri : routers) {
|
||||
Hash key = ri.getHash();
|
||||
if (!key.equals(us)) {
|
||||
String routerVersion = ri.getOption("router.version");
|
||||
if (routerVersion != null)
|
||||
versions.increment(routerVersion);
|
||||
// XXX Disabled, no GeoIP file
|
||||
String country = null;//mRContext.commSystem().getCountry(key);
|
||||
if(country != null)
|
||||
countries.increment(country);
|
||||
transports.increment(classifyTransports(ri));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret.add(versions);
|
||||
ret.add(countries);
|
||||
ret.add(transports);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static final int SSU = 1;
|
||||
private static final int SSUI = 2;
|
||||
private static final int NTCP = 4;
|
||||
private static final int IPV6 = 8;
|
||||
private static final String[] TNAMES = { "Hidden or starting up", "SSU", "SSU with introducers", "",
|
||||
"NTCP", "NTCP and SSU", "NTCP and SSU with introducers", "",
|
||||
"", "IPv6 SSU", "IPv6 Only SSU, introducers", "IPv6 SSU, introducers",
|
||||
"IPv6 NTCP", "IPv6 NTCP, SSU", "IPv6 Only NTCP, SSU, introducers", "IPv6 NTCP, SSU, introducers" };
|
||||
/**
|
||||
* what transport types
|
||||
*/
|
||||
private static String classifyTransports(RouterInfo info) {
|
||||
int rv = 0;
|
||||
for (RouterAddress addr : info.getAddresses()) {
|
||||
String style = addr.getTransportStyle();
|
||||
if (style.equals("NTCP")) {
|
||||
rv |= NTCP;
|
||||
} else if (style.equals("SSU")) {
|
||||
if (addr.getOption("iport0") != null)
|
||||
rv |= SSUI;
|
||||
else
|
||||
rv |= SSU;
|
||||
}
|
||||
String host = addr.getHost();
|
||||
if (host != null && host.contains(":"))
|
||||
rv |= IPV6;
|
||||
|
||||
}
|
||||
return TNAMES[rv];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(List<ObjectCounter<String>> data) {
|
||||
if (isReset()) {
|
||||
// The Loader has been reset; ignore the result and invalidate the data.
|
||||
if (data != null) {
|
||||
releaseResources(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||
// We must protect it until the new data has been delivered.
|
||||
List<ObjectCounter<String>> oldData = mData;
|
||||
mData = data;
|
||||
|
||||
if (isStarted()) {
|
||||
// If the Loader is in a started state, have the superclass deliver the
|
||||
// results to the client.
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
// Invalidate the old data as we don't need it any more.
|
||||
if (oldData != null && oldData != data) {
|
||||
releaseResources(oldData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mData != null) {
|
||||
// Deliver any previously loaded data immediately.
|
||||
deliverResult(mData);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mData == null) {
|
||||
// When the observer detects a change, it should call onContentChanged()
|
||||
// on the Loader, which will cause the next call to takeContentChanged()
|
||||
// to return true. If this is ever the case (or if the current data is
|
||||
// null), we force a new load.
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||
// current load (if there is one).
|
||||
cancelLoad();
|
||||
|
||||
// Note that we leave the observer as is. Loaders in a stopped state
|
||||
// should still monitor the data source for changes so that the Loader
|
||||
// will know to force a new load if it is ever started again.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
// Ensure the loader has been stopped.
|
||||
onStopLoading();
|
||||
|
||||
// At this point we can release the resources associated with 'mData'.
|
||||
if (mData != null) {
|
||||
releaseResources(mData);
|
||||
mData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(List<ObjectCounter<String>> data) {
|
||||
// Attempt to cancel the current asynchronous load.
|
||||
super.onCanceled(data);
|
||||
|
||||
// The load has been canceled, so we should release the resources
|
||||
// associated with 'data'.
|
||||
releaseResources(data);
|
||||
}
|
||||
|
||||
private void releaseResources(List<ObjectCounter<String>> data) {
|
||||
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||
// would close it in this method. All resources associated with the Loader
|
||||
// should be released here.
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class NetDbSummaryPagerFragment extends I2PFragmentBase implements
|
||||
LoaderManager.LoaderCallbacks<List<ObjectCounter<String>>> {
|
||||
private NetDbPagerAdapter mNetDbPagerAdapter;
|
||||
ViewPager mViewPager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.parentfragment_viewpager, container, false);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// Set up NetDbPagerAdapter containing the categories
|
||||
mNetDbPagerAdapter = new NetDbPagerAdapter(getChildFragmentManager());
|
||||
|
||||
// Set up ViewPager for swiping between categories
|
||||
mViewPager = (ViewPager) getActivity().findViewById(R.id.pager);
|
||||
mViewPager.setAdapter(mNetDbPagerAdapter);
|
||||
mViewPager.setOnPageChangeListener(
|
||||
new ViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
mViewPager.setCurrentItem(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouterConnectionReady() {
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouterConnectionNotReady() {
|
||||
Util.d("Router not running or not bound to NetDbSummaryPagerFragment");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_netdb_list_actions, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_refresh:
|
||||
if (getRouterContext() != null) {
|
||||
Util.d("Refresh called, restarting Loader");
|
||||
mNetDbPagerAdapter.setData(null);
|
||||
mViewPager.invalidate();
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
public class NetDbPagerAdapter extends FragmentStatePagerAdapter {
|
||||
private List<ObjectCounter<String>> mData;
|
||||
|
||||
public NetDbPagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
||||
public void setData(List<ObjectCounter<String>> data) {
|
||||
mData = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int i) {
|
||||
if (mData == null)
|
||||
return null;
|
||||
|
||||
return NetDbSummaryTableFragment.newInstance(i, mData.get(i));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (mData == null)
|
||||
return 0;
|
||||
else
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int i) {
|
||||
switch (i) {
|
||||
case 1:
|
||||
return "Countries";
|
||||
case 2:
|
||||
return "Transports";
|
||||
default:
|
||||
return "Versions";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LoaderManager.LoaderCallbacks<List<ObjectCounter<String>>>
|
||||
|
||||
public Loader<List<ObjectCounter<String>>> onCreateLoader(int id, Bundle args) {
|
||||
return new NetDbStatsLoader(getActivity(), getRouterContext());
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<List<ObjectCounter<String>>> loader,
|
||||
List<ObjectCounter<String>> data) {
|
||||
mNetDbPagerAdapter.setData(data);
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<List<ObjectCounter<String>>> loader) {
|
||||
mNetDbPagerAdapter.setData(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class NetDbSummaryTableFragment extends Fragment {
|
||||
private static final String CATEGORY = "category";
|
||||
private static final String COUNTS = "counts";
|
||||
|
||||
private int mCategory;
|
||||
private ObjectCounter<String> mCounts;
|
||||
private TableLayout mTable;
|
||||
|
||||
public static NetDbSummaryTableFragment newInstance(int category,
|
||||
ObjectCounter<String> counts) {
|
||||
NetDbSummaryTableFragment f = new NetDbSummaryTableFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(CATEGORY, category);
|
||||
args.putSerializable(COUNTS, counts);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_table, container, false);
|
||||
|
||||
mCategory = getArguments().getInt(CATEGORY);
|
||||
mCounts = (ObjectCounter<String>) getArguments().getSerializable(COUNTS);
|
||||
|
||||
mTable = (TableLayout) v.findViewById(R.id.table);
|
||||
|
||||
List<String> objects = new ArrayList<>(mCounts.objects());
|
||||
if (!objects.isEmpty()) {
|
||||
createTableTitle();
|
||||
|
||||
switch (mCategory) {
|
||||
case 1:
|
||||
case 2:
|
||||
Collections.sort(objects);
|
||||
break;
|
||||
default:
|
||||
Collections.sort(objects,
|
||||
Collections.reverseOrder(new VersionComparator()));
|
||||
break;
|
||||
}
|
||||
|
||||
for (String object : objects) {
|
||||
int num = mCounts.count(object);
|
||||
addTableRow(object, ""+num);
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private void createTableTitle() {
|
||||
TableRow titleRow;
|
||||
TextView tl1, tl2;
|
||||
|
||||
titleRow = new TableRow(getActivity());
|
||||
titleRow.setPadding(10, 0, 0, 0);
|
||||
|
||||
tl1 = new TextView(getActivity());
|
||||
tl1.setTextSize(20);
|
||||
tl2 = new TextView(getActivity());
|
||||
tl2.setTextSize(20);
|
||||
|
||||
switch (mCategory) {
|
||||
case 1:
|
||||
tl1.setText("Transports");
|
||||
break;
|
||||
case 2:
|
||||
tl1.setText("Country");
|
||||
break;
|
||||
default:
|
||||
tl1.setText("Version");
|
||||
break;
|
||||
}
|
||||
tl2.setText("Count");
|
||||
|
||||
titleRow.addView(tl1);
|
||||
titleRow.addView(tl2);
|
||||
|
||||
mTable.addView(titleRow);
|
||||
}
|
||||
|
||||
private void addTableRow(String name, String count) {
|
||||
TableRow row;
|
||||
TextView tl1, tl2;
|
||||
|
||||
row = new TableRow(getActivity());
|
||||
row.setPadding(10, 0, 0, 0);
|
||||
|
||||
tl1 = new TextView(getActivity());
|
||||
tl2 = new TextView(getActivity());
|
||||
|
||||
tl1.setText(name);
|
||||
tl2.setText(count);
|
||||
|
||||
row.addView(tl1);
|
||||
row.addView(tl2);
|
||||
|
||||
mTable.addView(row);
|
||||
}
|
||||
}
|
@ -7,6 +7,11 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import net.i2p.android.apps.EepGetFetcher;
|
||||
import net.i2p.android.router.BuildConfig;
|
||||
import net.i2p.android.router.util.AppCache;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@ -15,10 +20,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.android.apps.EepGetFetcher;
|
||||
import net.i2p.android.router.util.AppCache;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
/**
|
||||
* Usage: content://net.i2p.android.router/NONCE/ENCODED-SCHEME/ENCODED-AUTHORITY/ENCODED_PATH + QUERY_MARKER + ENCODED-QUERY
|
||||
* Where NONCE is set at instantiation
|
||||
@ -47,7 +48,7 @@ public class CacheProvider extends ContentProvider {
|
||||
//private static final String NONCE = Integer.toString(Math.abs((new java.util.Random()).nextInt()));
|
||||
private static final String NONCE = "0";
|
||||
private static final String SCHEME = "content";
|
||||
public static final String AUTHORITY = "net.i2p.android.router";
|
||||
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
|
||||
/** includes the nonce */
|
||||
public static final Uri CONTENT_URI = Uri.parse(SCHEME + "://" + AUTHORITY + '/' + NONCE);
|
||||
|
||||
@ -83,7 +84,7 @@ public class CacheProvider extends ContentProvider {
|
||||
if (q == null || !a.equals(AUTHORITY))
|
||||
return key;
|
||||
if (p.contains(QUERY_MARKER)) {
|
||||
Util.e("Key contains both queries ?!? " + key);
|
||||
Util.d("Key contains both queries ?!? " + key);
|
||||
return null;
|
||||
}
|
||||
// twizzle query
|
||||
@ -112,7 +113,7 @@ public class CacheProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
Util.e("CacheProvider open " + uri);
|
||||
Util.d("CacheProvider open " + uri);
|
||||
|
||||
// if uri is malformed and we have a current base, rectify it
|
||||
uri = rectifyContentUri(getCurrentBase(), uri);
|
||||
@ -123,17 +124,17 @@ public class CacheProvider extends ContentProvider {
|
||||
try {
|
||||
File file = new File(filePath);
|
||||
if (file.exists())
|
||||
Util.e("CacheProvider returning " + file);
|
||||
Util.d("CacheProvider returning " + file);
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
Util.e("CacheProvider not found", fnfe);
|
||||
Util.d("CacheProvider not found", fnfe);
|
||||
remove(uri);
|
||||
}
|
||||
}
|
||||
Util.e("CacheProvider not in cache " + uri);
|
||||
Util.d("CacheProvider not in cache " + uri);
|
||||
|
||||
Uri newUri = getI2PUri(uri);
|
||||
Util.e("CacheProvider fetching: " + newUri);
|
||||
Util.d("CacheProvider fetching: " + newUri);
|
||||
return eepFetch(newUri);
|
||||
}
|
||||
|
||||
@ -142,7 +143,7 @@ public class CacheProvider extends ContentProvider {
|
||||
*
|
||||
* @param uri must contain a scheme, authority and path with nonce etc. as defined above
|
||||
* @return non-null
|
||||
* @throws FNFE on error
|
||||
* @throws java.io.FileNotFoundException on error
|
||||
*/
|
||||
public static Uri getI2PUri(Uri uri) throws FileNotFoundException {
|
||||
String resPath = uri.getEncodedPath();
|
||||
@ -163,7 +164,7 @@ public class CacheProvider extends ContentProvider {
|
||||
}
|
||||
}
|
||||
String debug = "CacheProvider nonce: " + nonce + " scheme: " + scheme + " host: " + host + " realPath: " + realPath + " query: " + query;
|
||||
Util.e(debug);
|
||||
Util.d(debug);
|
||||
if ((!NONCE.equals(nonce)) ||
|
||||
(!"http".equals(scheme)) ||
|
||||
(host == null) ||
|
||||
@ -186,7 +187,7 @@ public class CacheProvider extends ContentProvider {
|
||||
* or the original uri on error, or if no rectification needed
|
||||
*/
|
||||
public static Uri rectifyContentUri(Uri base, Uri uri) {
|
||||
Util.e("rectifyContentUri base: " + base + " and uri: " + uri);
|
||||
Util.d("rectifyContentUri base: " + base + " and uri: " + uri);
|
||||
if (base == null)
|
||||
return uri;
|
||||
if (!SCHEME.equals(base.getScheme()))
|
||||
@ -224,7 +225,7 @@ public class CacheProvider extends ContentProvider {
|
||||
buf.append(path);
|
||||
if (query != null)
|
||||
buf.append(QUERY_MARKER).append(query);
|
||||
Util.e("rectified from base: " + base + " and uri: " + uri + " to: " + buf);
|
||||
Util.d("rectified from base: " + base + " and uri: " + uri + " to: " + buf);
|
||||
return Uri.parse(buf.toString());
|
||||
}
|
||||
|
||||
@ -244,26 +245,25 @@ public class CacheProvider extends ContentProvider {
|
||||
if (file.length() > 0) {
|
||||
// this call will insert it back to us (don't set as current base)
|
||||
Uri content = cache.addCacheFile(uri, false);
|
||||
ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return parcel;
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
} else {
|
||||
Util.e("CacheProvider Sucess but no data " + uri);
|
||||
Util.d("CacheProvider Sucess but no data " + uri);
|
||||
}
|
||||
} else {
|
||||
Util.e("CacheProvider Eepget fail " + uri);
|
||||
Util.d("CacheProvider Eepget fail " + uri);
|
||||
}
|
||||
AppCache.getInstance().removeCacheFile(uri);
|
||||
throw new FileNotFoundException("eepget fail");
|
||||
}
|
||||
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
Util.e("CacheProvider delete " + uri);
|
||||
Util.d("CacheProvider delete " + uri);
|
||||
boolean deleted = remove(uri);
|
||||
return deleted ? 1 : 0;
|
||||
}
|
||||
|
||||
public String getType(Uri uri) {
|
||||
Util.e("CacheProvider getType " + uri);
|
||||
Util.d("CacheProvider getType " + uri);
|
||||
return "text/html";
|
||||
}
|
||||
|
||||
@ -273,12 +273,12 @@ public class CacheProvider extends ContentProvider {
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
String fileURI = values.getAsString(DATA);
|
||||
if (fileURI != null) {
|
||||
Util.e("CacheProvider insert " + uri);
|
||||
Util.d("CacheProvider insert " + uri);
|
||||
put(uri, fileURI);
|
||||
}
|
||||
Boolean setAsCurrentBase = values.getAsBoolean(CURRENT_BASE);
|
||||
if (setAsCurrentBase != null && setAsCurrentBase.booleanValue()) {
|
||||
Util.e("CacheProvider set current base " + uri);
|
||||
if (setAsCurrentBase != null && setAsCurrentBase) {
|
||||
Util.d("CacheProvider set current base " + uri);
|
||||
setCurrentBase(uri);
|
||||
}
|
||||
return uri;
|
||||
@ -291,7 +291,7 @@ public class CacheProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selecctionArgs, String sortOrder) {
|
||||
Util.e("CacheProvider query " + uri);
|
||||
Util.d("CacheProvider query " + uri);
|
||||
// TODO return a MatrixCursor with a _data entry
|
||||
return null;
|
||||
}
|
||||
@ -304,7 +304,7 @@ public class CacheProvider extends ContentProvider {
|
||||
|
||||
private void cleanup() {
|
||||
String pfx = CONTENT_URI.toString();
|
||||
List<String> toDelete = new ArrayList();
|
||||
List<String> toDelete = new ArrayList<>();
|
||||
Map<String, ?> map = _sharedPrefs.getAll();
|
||||
for (Map.Entry<String, ?> e : map.entrySet()) {
|
||||
String path = (String) e.getValue();
|
@ -9,9 +9,9 @@ import android.content.ServiceConnection;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.IBinder;
|
||||
|
||||
import net.i2p.android.router.binder.RouterBinder;
|
||||
import net.i2p.android.router.service.RouterBinder;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
public class I2PReceiver extends BroadcastReceiver {
|
||||
@ -32,13 +32,14 @@ public class I2PReceiver extends BroadcastReceiver {
|
||||
intents.addAction(Intent.ACTION_TIME_CHANGED);
|
||||
intents.addAction(Intent.ACTION_TIME_TICK); // once per minute, for testing
|
||||
intents.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
context.registerReceiver(this, intents);
|
||||
_wasConnected = Util.isConnected(context);
|
||||
@SuppressWarnings("LeakingThisInConstructor")
|
||||
Intent registerReceiver = context.registerReceiver(this, intents);
|
||||
_wasConnected = Connectivity.isConnected(context);
|
||||
}
|
||||
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
System.err.println("Got broadcast: " + action);
|
||||
//Util.w("Got broadcast: " + action);
|
||||
|
||||
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
|
||||
boolean failover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
|
||||
@ -47,7 +48,7 @@ public class I2PReceiver extends BroadcastReceiver {
|
||||
NetworkInfo other = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
|
||||
|
||||
/*****
|
||||
System.err.println("No conn? " + noConn + " failover? " + failover +
|
||||
Util.w("No conn? " + noConn + " failover? " + failover +
|
||||
" info: " + info + " other: " + other);
|
||||
printInfo(info);
|
||||
printInfo(other);
|
||||
@ -57,16 +58,16 @@ public class I2PReceiver extends BroadcastReceiver {
|
||||
|
||||
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
|
||||
action.equals(Intent.ACTION_TIME_TICK)) {
|
||||
boolean connected = Util.isConnected(context);
|
||||
boolean connected = Connectivity.isConnected(context);
|
||||
if (_wasConnected && !connected) {
|
||||
// notify + 2 timer ticks
|
||||
if (++_unconnectedCount >= 3) {
|
||||
RouterService svc = _routerService;
|
||||
if (_isBound && svc != null) {
|
||||
System.err.println("********* Network down, already bound");
|
||||
Util.w("********* Network down, already bound");
|
||||
svc.networkStop();
|
||||
} else {
|
||||
System.err.println("********* Network down, binding to router");
|
||||
Util.w("********* Network down, binding to router");
|
||||
// connection will call networkStop()
|
||||
bindRouter();
|
||||
}
|
||||
@ -81,10 +82,10 @@ public class I2PReceiver extends BroadcastReceiver {
|
||||
/****
|
||||
private static void printInfo(NetworkInfo ni) {
|
||||
if (ni == null) {
|
||||
System.err.println("Network info is null");
|
||||
Util.w("Network info is null");
|
||||
return;
|
||||
}
|
||||
System.err.println(
|
||||
Util.w(
|
||||
"state: " + ni.getState() +
|
||||
" detail: " + ni.getDetailedState() +
|
||||
" extrainfo: " + ni.getExtraInfo() +
|
||||
@ -101,10 +102,10 @@ public class I2PReceiver extends BroadcastReceiver {
|
||||
private boolean bindRouter() {
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName(_context, "net.i2p.android.router.service.RouterService");
|
||||
System.err.println(this + " calling bindService");
|
||||
Util.w(this + " calling bindService");
|
||||
_connection = new RouterConnection();
|
||||
boolean success = _context.bindService(intent, _connection, 0);
|
||||
System.err.println(this + " got from bindService: " + success);
|
||||
Util.w(this + " got from bindService: " + success);
|
||||
return success;
|
||||
}
|
||||
|
||||
@ -123,7 +124,7 @@ public class I2PReceiver extends BroadcastReceiver {
|
||||
_isBound = true;
|
||||
_unconnectedCount = 0;
|
||||
_wasConnected = false;
|
||||
System.err.println("********* Network down, stopping router");
|
||||
Util.w("********* Network down, stopping router");
|
||||
_routerService.networkStop();
|
||||
// this doesn't work here... TODO where to unbind
|
||||
//_context.unbindService(this);
|
||||
@ -132,7 +133,7 @@ public class I2PReceiver extends BroadcastReceiver {
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
_isBound = false;
|
||||
_routerService = null;
|
||||
System.err.println("********* Receiver unbinding from router");
|
||||
Util.w("********* Receiver unbinding from router");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.i2p.android.router.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import net.i2p.android.router.I2PConstants;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
|
||||
public class OnBootReceiver extends BroadcastReceiver implements I2PConstants {
|
||||
public static final String PREF_START_ON_BOOT = ANDROID_PREF_PREFIX + "startOnBoot";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean startOnBoot = prefs.getBoolean(PREF_START_ON_BOOT, false);
|
||||
|
||||
if (startOnBoot) {
|
||||
Intent routerService = new Intent(context, RouterService.class);
|
||||
context.startService(routerService);
|
||||
}
|
||||
}
|
||||
}
|
43
app/src/main/java/net/i2p/android/router/service/Init.java
Normal file
43
app/src/main/java/net/i2p/android/router/service/Init.java
Normal file
@ -0,0 +1,43 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
class Init {
|
||||
|
||||
private final Context ctx;
|
||||
private final String myDir;
|
||||
private final String _ourVersion;
|
||||
|
||||
public Init(Context c) {
|
||||
ctx = c;
|
||||
myDir = c.getFilesDir().getAbsolutePath();
|
||||
_ourVersion = Util.getOurVersion(c);
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
|
||||
deleteOldFiles();
|
||||
|
||||
// Set up the locations so Router and WorkingDir can find them
|
||||
// We do this again here, in the event settings were changed.
|
||||
System.setProperty("i2p.dir.base", myDir);
|
||||
System.setProperty("i2p.dir.config", myDir);
|
||||
System.setProperty("wrapper.logfile", myDir + "/wrapper.log");
|
||||
}
|
||||
|
||||
private void deleteOldFiles() {
|
||||
File tmp = new File(myDir, "tmp");
|
||||
File[] files = tmp.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
Util.d("Deleting old file/dir " + f);
|
||||
FileUtil.rmdir(f, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.BOB.BOB;
|
||||
import net.i2p.addressbook.DaemonThread;
|
||||
import net.i2p.android.apps.NewsFetcher;
|
||||
import net.i2p.android.router.util.Notifications;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
@ -28,28 +34,34 @@ import net.i2p.util.I2PAppThread;
|
||||
*
|
||||
*/
|
||||
class LoadClientsJob extends JobImpl {
|
||||
|
||||
private Thread _fetcherThread;
|
||||
|
||||
private Notifications _notif;
|
||||
private DaemonThread _addressbook;
|
||||
private BOB _bob;
|
||||
|
||||
/** this is the delay to load (and start) the clients. */
|
||||
private static final long LOAD_DELAY = 90*1000;
|
||||
|
||||
|
||||
public LoadClientsJob(RouterContext ctx) {
|
||||
public LoadClientsJob(RouterContext ctx, Notifications notif) {
|
||||
super(ctx);
|
||||
_notif = notif;
|
||||
getTiming().setStartAfter(getContext().clock().now() + LOAD_DELAY);
|
||||
}
|
||||
|
||||
public String getName() { return "Start Clients"; };
|
||||
public String getName() { return "Start Clients"; }
|
||||
|
||||
public void runJob() {
|
||||
Job j = new RunI2PTunnel(getContext());
|
||||
getContext().jobQueue().addJob(j);
|
||||
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(getContext());
|
||||
_fetcherThread = new I2PAppThread(fetcher, "NewsFetcher", true);
|
||||
_fetcherThread.start();
|
||||
Thread t = new I2PAppThread(new StatSummarizer(), "StatSummarizer", true);
|
||||
t.setPriority(Thread.NORM_PRIORITY - 1);
|
||||
t.start();
|
||||
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(getContext(), _notif);
|
||||
t = new I2PAppThread(fetcher, "NewsFetcher", true);
|
||||
t.start();
|
||||
|
||||
_addressbook = new DaemonThread(new String[] {"addressbook"});
|
||||
_addressbook.setName("Addressbook");
|
||||
@ -57,7 +69,10 @@ class LoadClientsJob extends JobImpl {
|
||||
_addressbook.start();
|
||||
|
||||
// add other clients here
|
||||
|
||||
_bob = new BOB(I2PAppContext.getGlobalContext(), null, new String[0]);
|
||||
try {
|
||||
_bob.startup();
|
||||
} catch (IOException ioe) {}
|
||||
|
||||
getContext().addShutdownTask(new ClientShutdownHook());
|
||||
}
|
||||
@ -68,23 +83,25 @@ class LoadClientsJob extends JobImpl {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
public String getName() { return "Start I2P Tunnel"; };
|
||||
public String getName() { return "Start I2P Tunnel"; }
|
||||
|
||||
public void runJob() {
|
||||
System.err.println("Starting i2ptunnel");
|
||||
Util.d("Starting i2ptunnel");
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
int sz = tcg.getControllers().size();
|
||||
System.err.println("i2ptunnel started " + sz + " clients");
|
||||
Util.d("i2ptunnel started " + sz + " clients");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class ClientShutdownHook implements Runnable {
|
||||
public void run() {
|
||||
System.err.println("client shutdown hook");
|
||||
Util.d("client shutdown hook");
|
||||
// i2ptunnel registers its own hook
|
||||
if (_fetcherThread != null)
|
||||
_fetcherThread.interrupt();
|
||||
// StatSummarizer registers its own hook
|
||||
// NewsFetcher registers its own hook
|
||||
if (_bob != null)
|
||||
_bob.shutdown(null);
|
||||
if (_addressbook != null)
|
||||
_addressbook.halt();
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package net.i2p.android.router.binder;
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import android.os.Binder;
|
||||
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
|
||||
public class RouterBinder extends Binder {
|
||||
|
||||
private final RouterService _routerService;
|
@ -0,0 +1,795 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.receiver.I2PReceiver;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.Notifications;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterLaunch;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Runs the router
|
||||
*/
|
||||
public class RouterService extends Service {
|
||||
|
||||
/**
|
||||
* A request to this service for the current router state. Broadcasting
|
||||
* this will trigger a state notification.
|
||||
*/
|
||||
public static final String LOCAL_BROADCAST_REQUEST_STATE = "net.i2p.android.LOCAL_BROADCAST_REQUEST_STATE";
|
||||
/**
|
||||
* A notification of the current state. This is informational; the state
|
||||
* has not changed.
|
||||
*/
|
||||
public static final String LOCAL_BROADCAST_STATE_NOTIFICATION = "net.i2p.android.LOCAL_BROADCAST_STATE_NOTIFICATION";
|
||||
/**
|
||||
* The state has just changed.
|
||||
*/
|
||||
public static final String LOCAL_BROADCAST_STATE_CHANGED = "net.i2p.android.LOCAL_BROADCAST_STATE_CHANGED";
|
||||
public static final String LOCAL_BROADCAST_EXTRA_STATE = "net.i2p.android.STATE";
|
||||
|
||||
private RouterContext _context;
|
||||
private String _myDir;
|
||||
//private String _apkPath;
|
||||
private State _state = State.INIT;
|
||||
private Thread _starterThread;
|
||||
private StatusBar _statusBar;
|
||||
private Notifications _notif;
|
||||
private I2PReceiver _receiver;
|
||||
private IBinder _binder;
|
||||
private final Object _stateLock = new Object();
|
||||
private Handler _handler;
|
||||
private Runnable _updater;
|
||||
private boolean mStartCalled;
|
||||
private static final String SHARED_PREFS = "net.i2p.android.router";
|
||||
private static final String LAST_STATE = "service.lastState";
|
||||
private static final String EXTRA_RESTART = "restart";
|
||||
private static final String MARKER = "************************************** ";
|
||||
|
||||
/**
|
||||
* This is a list of callbacks that have been registered with the
|
||||
* service. Note that this is package scoped (instead of private) so
|
||||
* that it can be accessed more efficiently from inner classes.
|
||||
*/
|
||||
final RemoteCallbackList<IRouterStateCallback> mStateCallbacks
|
||||
= new RemoteCallbackList<>();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
mStartCalled = false;
|
||||
State lastState = getSavedState();
|
||||
setState(State.INIT);
|
||||
Util.d(this + " onCreate called"
|
||||
+ " Saved state is: " + lastState
|
||||
+ " Current state is: " + _state);
|
||||
|
||||
//(new File(getFilesDir(), "wrapper.log")).delete();
|
||||
_myDir = getFilesDir().getAbsolutePath();
|
||||
// init other stuff here, delete log, etc.
|
||||
Init init = new Init(this);
|
||||
init.initialize();
|
||||
//_apkPath = init.getAPKPath();
|
||||
_statusBar = new StatusBar(this);
|
||||
// Remove stale notification icon.
|
||||
_statusBar.remove();
|
||||
_notif = new Notifications(this);
|
||||
_binder = new RouterBinder(this);
|
||||
_handler = new Handler();
|
||||
_updater = new Updater();
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(onStateRequested,
|
||||
new IntentFilter(LOCAL_BROADCAST_REQUEST_STATE));
|
||||
if(lastState == State.RUNNING || lastState == State.ACTIVE) {
|
||||
Intent intent = new Intent(this, RouterService.class);
|
||||
intent.putExtra(EXTRA_RESTART, true);
|
||||
onStartCommand(intent, 12345, 67890);
|
||||
} else if(lastState == State.MANUAL_QUITTING || lastState == State.GRACEFUL_SHUTDOWN) {
|
||||
synchronized(_stateLock) {
|
||||
setState(State.MANUAL_QUITTED);
|
||||
stopSelf(); // Die.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BroadcastReceiver onStateRequested = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// Broadcast the current state within this app.
|
||||
Intent ni = new Intent(LOCAL_BROADCAST_STATE_NOTIFICATION);
|
||||
ni.putExtra(LOCAL_BROADCAST_EXTRA_STATE, (android.os.Parcelable) _state);
|
||||
LocalBroadcastManager.getInstance(RouterService.this).sendBroadcast(ni);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* NOT called by system if it restarts us after a crash
|
||||
*/
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Util.d(this + " onStart called"
|
||||
+ " Intent is: " + intent
|
||||
+ " Flags is: " + flags
|
||||
+ " ID is: " + startId
|
||||
+ " Current state is: " + _state);
|
||||
mStartCalled = true;
|
||||
boolean restart = intent != null && intent.getBooleanExtra(EXTRA_RESTART, false);
|
||||
if(restart) {
|
||||
Util.d(this + " RESTARTING");
|
||||
}
|
||||
synchronized(_stateLock) {
|
||||
if(_state != State.INIT) //return START_STICKY;
|
||||
{
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
_receiver = new I2PReceiver(this);
|
||||
if(Connectivity.isConnected(this)) {
|
||||
if(restart) {
|
||||
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is restarting");
|
||||
} else {
|
||||
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up");
|
||||
}
|
||||
setState(State.STARTING);
|
||||
_starterThread = new Thread(new Starter());
|
||||
_starterThread.start();
|
||||
} else {
|
||||
_statusBar.replace(StatusBar.ICON_WAITING_NETWORK, "I2P is waiting for a network connection");
|
||||
setState(State.WAITING);
|
||||
_handler.postDelayed(new Waiter(), 10 * 1000);
|
||||
}
|
||||
}
|
||||
_handler.removeCallbacks(_updater);
|
||||
_handler.postDelayed(_updater, 50);
|
||||
if(!restart) {
|
||||
startForeground(1337, _statusBar.getNote());
|
||||
}
|
||||
|
||||
//return START_STICKY;
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
/**
|
||||
* maybe this goes away when the receiver can bind to us
|
||||
*/
|
||||
private class Waiter implements Runnable {
|
||||
|
||||
public void run() {
|
||||
Util.d(MARKER + this + " waiter handler"
|
||||
+ " Current state is: " + _state);
|
||||
if(_state == State.WAITING) {
|
||||
if(Connectivity.isConnected(RouterService.this)) {
|
||||
synchronized(_stateLock) {
|
||||
if(_state != State.WAITING) {
|
||||
return;
|
||||
}
|
||||
_statusBar.replace(StatusBar.ICON_STARTING, "Network connected, I2P is starting up");
|
||||
setState(State.STARTING);
|
||||
_starterThread = new Thread(new Starter());
|
||||
_starterThread.start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_handler.postDelayed(this, 15 * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Starter implements Runnable {
|
||||
|
||||
public void run() {
|
||||
Util.d(MARKER + this + " starter thread"
|
||||
+ " Current state is: " + _state);
|
||||
//Util.d(MARKER + this + " JBigI speed test started");
|
||||
//NativeBigInteger.main(null);
|
||||
//Util.d(MARKER + this + " JBigI speed test finished, launching router");
|
||||
|
||||
// Launch the router!
|
||||
RouterLaunch.main(null);
|
||||
synchronized(_stateLock) {
|
||||
if(_state != State.STARTING) {
|
||||
return;
|
||||
}
|
||||
setState(State.RUNNING);
|
||||
_statusBar.replace(StatusBar.ICON_RUNNING, "I2P is running");
|
||||
_context = Util.getRouterContext();
|
||||
if (_context == null) {
|
||||
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
||||
}
|
||||
_context.router().setKillVMOnEnd(false);
|
||||
Job loadJob = new LoadClientsJob(_context, _notif);
|
||||
_context.jobQueue().addJob(loadJob);
|
||||
_context.addShutdownTask(new ShutdownHook());
|
||||
_context.addFinalShutdownTask(new FinalShutdownHook());
|
||||
_starterThread = null;
|
||||
}
|
||||
Util.d("Router.main finished");
|
||||
}
|
||||
}
|
||||
|
||||
private class Updater implements Runnable {
|
||||
|
||||
public void run() {
|
||||
RouterContext ctx = _context;
|
||||
if(ctx != null && (_state == State.RUNNING || _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN)) {
|
||||
Router router = ctx.router();
|
||||
if(router.isAlive()) {
|
||||
updateStatus(ctx);
|
||||
}
|
||||
}
|
||||
_handler.postDelayed(this, 15 * 1000);
|
||||
}
|
||||
}
|
||||
private String _currTitle;
|
||||
private boolean _hadTunnels;
|
||||
|
||||
private void updateStatus(RouterContext ctx) {
|
||||
int active = ctx.commSystem().countActivePeers();
|
||||
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
|
||||
int inEx = ctx.tunnelManager().getFreeTunnelCount();
|
||||
int outEx = ctx.tunnelManager().getOutboundTunnelCount();
|
||||
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
|
||||
int outCl = ctx.tunnelManager().getOutboundClientTunnelCount();
|
||||
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
|
||||
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
|
||||
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
|
||||
// control total width
|
||||
DecimalFormat fmt;
|
||||
if(inBW >= 1000 || outBW >= 1000) {
|
||||
fmt = new DecimalFormat("#0");
|
||||
} else if(inBW >= 100 || outBW >= 100) {
|
||||
fmt = new DecimalFormat("#0.0");
|
||||
} else {
|
||||
fmt = new DecimalFormat("#0.00");
|
||||
}
|
||||
|
||||
String text =
|
||||
getResources().getString(R.string.notification_status_bw,
|
||||
fmt.format(inBW), fmt.format(outBW));
|
||||
|
||||
String bigText =
|
||||
getResources().getString(R.string.notification_status_bw,
|
||||
fmt.format(inBW), fmt.format(outBW)) + '\n'
|
||||
+ getResources().getString(R.string.notification_status_peers,
|
||||
active, known) + '\n'
|
||||
+ getResources().getString(R.string.notification_status_expl,
|
||||
inEx, outEx) + '\n'
|
||||
+ getResources().getString(R.string.notification_status_client,
|
||||
inCl, outCl);
|
||||
|
||||
boolean haveTunnels = inCl > 0 && outCl > 0;
|
||||
if (isGracefulShutdownInProgress()) {
|
||||
long ms = ctx.router().getShutdownTimeRemaining();
|
||||
if (ms > 1000) {
|
||||
_currTitle = "Stopping I2P in " + DataHelper.formatDuration(ms);
|
||||
} else {
|
||||
_currTitle = "Stopping I2P";
|
||||
}
|
||||
} else if (haveTunnels != _hadTunnels) {
|
||||
if(haveTunnels) {
|
||||
_currTitle = "Client tunnels are ready";
|
||||
setState(State.ACTIVE);
|
||||
_statusBar.replace(StatusBar.ICON_ACTIVE, _currTitle);
|
||||
} else {
|
||||
_currTitle = "Client tunnels are down";
|
||||
setState(State.RUNNING);
|
||||
_statusBar.replace(StatusBar.ICON_RUNNING, _currTitle);
|
||||
}
|
||||
_hadTunnels = haveTunnels;
|
||||
} else if (_currTitle == null || _currTitle.equals(""))
|
||||
_currTitle = "I2P is running";
|
||||
_statusBar.update(_currTitle, text, bigText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
Util.d(this + "onBind called"
|
||||
+ " Current state is: " + _state);
|
||||
Util.d("Intent action: " + intent.getAction());
|
||||
// Select the interface to return.
|
||||
if (RouterBinder.class.getName().equals(intent.getAction())) {
|
||||
// Local Activity wanting access to the RouterContext
|
||||
Util.d("Returning RouterContext binder");
|
||||
return _binder;
|
||||
}
|
||||
if (IRouterState.class.getName().equals(intent.getAction())) {
|
||||
// Someone wants to monitor the router state.
|
||||
Util.d("Returning state binder");
|
||||
return mStatusBinder;
|
||||
}
|
||||
Util.d("Unknown binder request, returning null");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* IRouterState is defined through IDL
|
||||
*/
|
||||
private final IRouterState.Stub mStatusBinder = new IRouterState.Stub() {
|
||||
|
||||
public void registerCallback(IRouterStateCallback cb)
|
||||
throws RemoteException {
|
||||
if (cb != null) mStateCallbacks.register(cb);
|
||||
}
|
||||
|
||||
public void unregisterCallback(IRouterStateCallback cb)
|
||||
throws RemoteException {
|
||||
if (cb != null) mStateCallbacks.unregister(cb);
|
||||
}
|
||||
|
||||
public boolean isStarted() throws RemoteException {
|
||||
return mStartCalled;
|
||||
}
|
||||
|
||||
public State getState() throws RemoteException {
|
||||
return _state;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
|
||||
// ******** following methods may be accessed from Activities and Receivers ************
|
||||
/**
|
||||
* @return null if router is not running
|
||||
*/
|
||||
public RouterContext getRouterContext() {
|
||||
RouterContext rv = _context;
|
||||
if(rv == null) {
|
||||
return null;
|
||||
}
|
||||
if(!rv.router().isAlive()) {
|
||||
return null;
|
||||
}
|
||||
if(_state != State.RUNNING
|
||||
&& _state != State.ACTIVE
|
||||
&& _state != State.STOPPING
|
||||
&& _state != State.MANUAL_STOPPING
|
||||
&& _state != State.MANUAL_QUITTING
|
||||
&& _state != State.NETWORK_STOPPING
|
||||
&& _state != State.GRACEFUL_SHUTDOWN) {
|
||||
return null;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* debug
|
||||
*/
|
||||
public String getState() {
|
||||
return _state.toString();
|
||||
}
|
||||
|
||||
public boolean canManualStop() {
|
||||
return _state == State.WAITING || _state == State.STARTING ||
|
||||
_state == State.RUNNING || _state == State.ACTIVE ||
|
||||
_state == State.GRACEFUL_SHUTDOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop and don't restart the router, but keep the service
|
||||
*
|
||||
* Apparently unused - see manualQuit()
|
||||
*/
|
||||
public void manualStop() {
|
||||
Util.d("manualStop called"
|
||||
+ " Current state is: " + _state);
|
||||
synchronized(_stateLock) {
|
||||
if(!canManualStop()) {
|
||||
return;
|
||||
}
|
||||
if(_state == State.STARTING) {
|
||||
_starterThread.interrupt();
|
||||
}
|
||||
if(_state == State.STARTING || _state == State.RUNNING ||
|
||||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
|
||||
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
|
||||
Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED));
|
||||
stopperThread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the router and kill the service
|
||||
*/
|
||||
public void manualQuit() {
|
||||
Util.d("manualQuit called"
|
||||
+ " Current state is: " + _state);
|
||||
synchronized(_stateLock) {
|
||||
if(!canManualStop()) {
|
||||
return;
|
||||
}
|
||||
if(_state == State.STARTING) {
|
||||
_starterThread.interrupt();
|
||||
}
|
||||
if(_state == State.STARTING || _state == State.RUNNING ||
|
||||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
|
||||
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
|
||||
Thread stopperThread = new Thread(new Stopper(State.MANUAL_QUITTING, State.MANUAL_QUITTED));
|
||||
stopperThread.start();
|
||||
} else if(_state == State.WAITING) {
|
||||
setState(State.MANUAL_QUITTING);
|
||||
(new FinalShutdownHook()).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop and then spin waiting for a network connection, then restart
|
||||
*/
|
||||
public void networkStop() {
|
||||
Util.d("networkStop called"
|
||||
+ " Current state is: " + _state);
|
||||
synchronized(_stateLock) {
|
||||
if(_state == State.STARTING) {
|
||||
_starterThread.interrupt();
|
||||
}
|
||||
if(_state == State.STARTING || _state == State.RUNNING ||
|
||||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
|
||||
_statusBar.replace(StatusBar.ICON_STOPPING, "Network disconnected, stopping I2P");
|
||||
// don't change state, let the shutdown hook do it
|
||||
Thread stopperThread = new Thread(new Stopper(State.NETWORK_STOPPING, State.NETWORK_STOPPING));
|
||||
stopperThread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canManualStart() {
|
||||
// We can be in INIT if we restarted after crash but previous state was not RUNNING.
|
||||
return _state == State.INIT || _state == State.MANUAL_STOPPED || _state == State.STOPPED;
|
||||
}
|
||||
|
||||
public void manualStart() {
|
||||
Util.d("restart called"
|
||||
+ " Current state is: " + _state);
|
||||
synchronized(_stateLock) {
|
||||
if(!canManualStart()) {
|
||||
return;
|
||||
}
|
||||
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up");
|
||||
setState(State.STARTING);
|
||||
_starterThread = new Thread(new Starter());
|
||||
_starterThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Graceful Shutdown
|
||||
*
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public boolean isGracefulShutdownInProgress() {
|
||||
if (_state == State.GRACEFUL_SHUTDOWN) {
|
||||
RouterContext ctx = _context;
|
||||
return ctx != null && ctx.router().gracefulShutdownInProgress();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String _oldTitle;
|
||||
/**
|
||||
* Graceful Shutdown
|
||||
*
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public void gracefulShutdown() {
|
||||
Util.d("gracefulShutdown called"
|
||||
+ " Current state is: " + _state);
|
||||
synchronized(_stateLock) {
|
||||
if(!canManualStop()) {
|
||||
return;
|
||||
}
|
||||
if(_state == State.STARTING || _state == State.WAITING) {
|
||||
manualQuit();
|
||||
return;
|
||||
}
|
||||
if(_state == State.RUNNING || _state == State.ACTIVE) {
|
||||
RouterContext ctx = _context;
|
||||
if(ctx != null && ctx.router().isAlive()) {
|
||||
int part = ctx.tunnelManager().getParticipatingCount();
|
||||
if(part <= 0) {
|
||||
manualQuit();
|
||||
} else {
|
||||
ctx.router().shutdownGracefully();
|
||||
_oldTitle = _currTitle;
|
||||
long ms = ctx.router().getShutdownTimeRemaining();
|
||||
if (ms > 1000) {
|
||||
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P in " + DataHelper.formatDuration(ms));
|
||||
} else {
|
||||
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
|
||||
}
|
||||
setState(State.GRACEFUL_SHUTDOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel Graceful Shutdown
|
||||
*
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public void cancelGracefulShutdown() {
|
||||
Util.d("cancelGracefulShutdown called"
|
||||
+ " Current state is: " + _state);
|
||||
synchronized(_stateLock) {
|
||||
if(_state != State.GRACEFUL_SHUTDOWN) {
|
||||
return;
|
||||
}
|
||||
RouterContext ctx = _context;
|
||||
if(ctx != null && ctx.router().isAlive()) {
|
||||
ctx.router().cancelGracefulShutdown();
|
||||
_currTitle = _oldTitle;
|
||||
if (_hadTunnels) {
|
||||
setState(State.ACTIVE);
|
||||
_statusBar.replace(StatusBar.ICON_ACTIVE, "Shutdown cancelled");
|
||||
} else {
|
||||
setState(State.RUNNING);
|
||||
_statusBar.replace(StatusBar.ICON_RUNNING, "Shutdown cancelled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ******** end methods accessed from Activities and Receivers ************
|
||||
|
||||
private static final int STATE_MSG = 1;
|
||||
|
||||
/**
|
||||
* Our Handler used to execute operations on the main thread.
|
||||
*/
|
||||
private final Handler mHandler = new StateHandler(new WeakReference<>(this));
|
||||
private static class StateHandler extends Handler {
|
||||
WeakReference<RouterService> mReference;
|
||||
|
||||
public StateHandler(WeakReference<RouterService> reference) {
|
||||
mReference = reference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
RouterService parent = mReference.get();
|
||||
if (parent == null)
|
||||
return;
|
||||
|
||||
switch (msg.what) {
|
||||
case STATE_MSG:
|
||||
final State state = parent._state;
|
||||
// Broadcast to all clients the new state.
|
||||
final int N = parent.mStateCallbacks.beginBroadcast();
|
||||
for (int i = 0; i < N; i++) {
|
||||
try {
|
||||
parent.mStateCallbacks.getBroadcastItem(i).stateChanged(state);
|
||||
} catch (RemoteException e) {
|
||||
// The RemoteCallbackList will take care of removing
|
||||
// the dead object for us.
|
||||
}
|
||||
}
|
||||
parent.mStateCallbacks.finishBroadcast();
|
||||
break;
|
||||
default:
|
||||
super.handleMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn off the status bar. Unregister the receiver. If we were running,
|
||||
* fire up the Stopper thread.
|
||||
*/
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Util.d("onDestroy called"
|
||||
+ " Current state is: " + _state);
|
||||
|
||||
_handler.removeCallbacks(_updater);
|
||||
_statusBar.remove();
|
||||
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(onStateRequested);
|
||||
|
||||
I2PReceiver rcvr = _receiver;
|
||||
if(rcvr != null) {
|
||||
synchronized(rcvr) {
|
||||
try {
|
||||
// throws if not registered
|
||||
unregisterReceiver(rcvr);
|
||||
} catch(IllegalArgumentException iae) {
|
||||
}
|
||||
//rcvr.unbindRouter();
|
||||
//_receiver = null;
|
||||
}
|
||||
}
|
||||
synchronized(_stateLock) {
|
||||
if(_state == State.STARTING) {
|
||||
_starterThread.interrupt();
|
||||
}
|
||||
if(_state == State.STARTING || _state == State.RUNNING ||
|
||||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
|
||||
// should this be in a thread?
|
||||
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, "I2P is shutting down");
|
||||
Thread stopperThread = new Thread(new Stopper(State.STOPPING, State.STOPPED));
|
||||
stopperThread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to the next state. If we still have a context, shut down the
|
||||
* router. Turn off the status bar. Then transition to the stop state.
|
||||
*/
|
||||
private class Stopper implements Runnable {
|
||||
|
||||
private final State nextState;
|
||||
private final State stopState;
|
||||
|
||||
/**
|
||||
* call holding statelock
|
||||
*/
|
||||
public Stopper(State next, State stop) {
|
||||
nextState = next;
|
||||
stopState = stop;
|
||||
setState(next);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Util.d(MARKER + this + " stopper thread"
|
||||
+ " Current state is: " + _state);
|
||||
RouterContext ctx = _context;
|
||||
if(ctx != null) {
|
||||
ctx.router().shutdown(Router.EXIT_HARD);
|
||||
}
|
||||
_statusBar.remove();
|
||||
Util.d("********** Router shutdown complete");
|
||||
synchronized(_stateLock) {
|
||||
if(_state == nextState) {
|
||||
setState(stopState);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
stopForeground(true);
|
||||
_statusBar.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* First (early) hook. Update the status bar. Unregister the receiver.
|
||||
*/
|
||||
private class ShutdownHook implements Runnable {
|
||||
|
||||
public void run() {
|
||||
Util.d(this + " shutdown hook"
|
||||
+ " Current state is: " + _state);
|
||||
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, "I2P is shutting down");
|
||||
I2PReceiver rcvr = _receiver;
|
||||
if(rcvr != null) {
|
||||
synchronized(rcvr) {
|
||||
try {
|
||||
// throws if not registered
|
||||
unregisterReceiver(rcvr);
|
||||
} catch(IllegalArgumentException iae) {
|
||||
}
|
||||
//rcvr.unbindRouter();
|
||||
//_receiver = null;
|
||||
}
|
||||
}
|
||||
synchronized(_stateLock) {
|
||||
// null out to release the memory
|
||||
_context = null;
|
||||
if(_state == State.STARTING) {
|
||||
_starterThread.interrupt();
|
||||
}
|
||||
if(_state == State.WAITING || _state == State.STARTING
|
||||
|| _state == State.RUNNING || _state == State.ACTIVE
|
||||
|| _state == State.GRACEFUL_SHUTDOWN) {
|
||||
setState(State.STOPPING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Second (late) hook. Turn off the status bar. Null out the context. If we
|
||||
* were stopped manually, do nothing. If we were stopped because of no
|
||||
* network, start the waiter thread. If it stopped of unknown causes or from
|
||||
* manualQuit(), kill the Service.
|
||||
*/
|
||||
private class FinalShutdownHook implements Runnable {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Util.d(this + " final shutdown hook"
|
||||
+ " Current state is: " + _state);
|
||||
//I2PReceiver rcvr = _receiver;
|
||||
|
||||
synchronized(_stateLock) {
|
||||
// null out to release the memory
|
||||
_context = null;
|
||||
Runtime.getRuntime().gc();
|
||||
if(_state == State.STARTING) {
|
||||
_starterThread.interrupt();
|
||||
}
|
||||
if(_state == State.MANUAL_STOPPING) {
|
||||
setState(State.MANUAL_STOPPED);
|
||||
} else if(_state == State.NETWORK_STOPPING) {
|
||||
// start waiter handler
|
||||
setState(State.WAITING);
|
||||
_handler.postDelayed(new Waiter(), 10 * 1000);
|
||||
} else if(_state == State.STARTING || _state == State.RUNNING
|
||||
|| _state == State.ACTIVE || _state == State.STOPPING) {
|
||||
Util.w(this + " died of unknown causes");
|
||||
setState(State.STOPPED);
|
||||
// Unregister all callbacks.
|
||||
mStateCallbacks.kill();
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
} else if(_state == State.MANUAL_QUITTING || _state == State.GRACEFUL_SHUTDOWN) {
|
||||
setState(State.MANUAL_QUITTED);
|
||||
// Unregister all callbacks.
|
||||
mStateCallbacks.kill();
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
_statusBar.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private State getSavedState() {
|
||||
SharedPreferences prefs = getSharedPreferences(SHARED_PREFS, 0);
|
||||
String stateString = prefs.getString(LAST_STATE, State.INIT.toString());
|
||||
try {
|
||||
return State.valueOf(stateString);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return State.INIT;
|
||||
}
|
||||
}
|
||||
|
||||
private void setState(State s) {
|
||||
_state = s;
|
||||
saveState();
|
||||
|
||||
// Broadcast the new state within this app.
|
||||
Intent intent = new Intent(LOCAL_BROADCAST_STATE_CHANGED);
|
||||
intent.putExtra(LOCAL_BROADCAST_EXTRA_STATE, (android.os.Parcelable) _state);
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||
|
||||
// Notify other apps that the state has changed
|
||||
mHandler.sendEmptyMessage(STATE_MSG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success
|
||||
*/
|
||||
private boolean saveState() {
|
||||
SharedPreferences prefs = getSharedPreferences(SHARED_PREFS, 0);
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putString(LAST_STATE, _state.toString());
|
||||
return edit.commit();
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class StatSummarizer implements Runnable {
|
||||
private final RouterContext _context;
|
||||
private final List<SummaryListener> _listeners;
|
||||
// TODO remove static instance
|
||||
private static StatSummarizer _instance;
|
||||
private volatile boolean _isRunning = true;
|
||||
private Thread _thread;
|
||||
|
||||
public StatSummarizer() {
|
||||
_context = Util.getRouterContext();
|
||||
_listeners = new CopyOnWriteArrayList<>();
|
||||
_instance = this;
|
||||
if (_context != null)
|
||||
_context.addShutdownTask(new Shutdown());
|
||||
}
|
||||
|
||||
public static StatSummarizer instance() { return _instance; }
|
||||
|
||||
public void run() {
|
||||
// We can't do anything without a RouterContext
|
||||
if (_context == null)
|
||||
return;
|
||||
|
||||
_thread = Thread.currentThread();
|
||||
String specs = "";
|
||||
while (_isRunning && _context.router().isAlive()) {
|
||||
specs = adjustDatabases(specs);
|
||||
try { Thread.sleep(60 * 1000);} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
/** list of SummaryListener instances */
|
||||
public List<SummaryListener> getListeners() { return _listeners; }
|
||||
|
||||
public SummaryListener getListener(String rateName, long period) {
|
||||
for (SummaryListener lsnr : _listeners) {
|
||||
if (lsnr.getName().equals(rateName + "." + period))
|
||||
return lsnr;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final String DEFAULT_DATABASES =
|
||||
"bw.sendRate.60000"
|
||||
+ ",bw.recvRate.60000"
|
||||
+ ",router.memoryUsed.60000"
|
||||
+ ",router.activePeers.60000";
|
||||
|
||||
private String adjustDatabases(String oldSpecs) {
|
||||
String spec = _context.getProperty("stat.summaries", DEFAULT_DATABASES);
|
||||
if ( ( (spec == null) && (oldSpecs == null) ) ||
|
||||
( (spec != null) && (oldSpecs != null) && (oldSpecs.equals(spec))) )
|
||||
return oldSpecs;
|
||||
|
||||
List<Rate> old = parseSpecs(oldSpecs);
|
||||
List<Rate> newSpecs = parseSpecs(spec);
|
||||
|
||||
// remove old ones
|
||||
for (Rate r : old) {
|
||||
if (!newSpecs.contains(r))
|
||||
removeDb(r);
|
||||
}
|
||||
// add new ones
|
||||
StringBuilder buf = new StringBuilder();
|
||||
boolean comma = false;
|
||||
for (Rate r : newSpecs) {
|
||||
if (!old.contains(r))
|
||||
addDb(r);
|
||||
if (comma)
|
||||
buf.append(',');
|
||||
else
|
||||
comma = true;
|
||||
buf.append(r.getRateStat().getName()).append(".").append(r.getPeriod());
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private void removeDb(Rate r) {
|
||||
for (SummaryListener lsnr : _listeners) {
|
||||
if (lsnr.getRate().equals(r)) {
|
||||
// no iter.remove() in COWAL
|
||||
_listeners.remove(lsnr);
|
||||
lsnr.stopListening();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void addDb(Rate r) {
|
||||
SummaryListener lsnr = new SummaryListener(r);
|
||||
lsnr.startListening();
|
||||
_listeners.add(lsnr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param specs statName.period,statName.period,statName.period
|
||||
* @return list of Rate objects
|
||||
*/
|
||||
List<Rate> parseSpecs(String specs) {
|
||||
StringTokenizer tok = new StringTokenizer(specs, ",");
|
||||
List<Rate> rv = new ArrayList<>();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String spec = tok.nextToken();
|
||||
int split = spec.lastIndexOf('.');
|
||||
if ( (split <= 0) || (split + 1 >= spec.length()) )
|
||||
continue;
|
||||
String name = spec.substring(0, split);
|
||||
String per = spec.substring(split+1);
|
||||
long period;
|
||||
try {
|
||||
period = Long.parseLong(per);
|
||||
RateStat rs = _context.statManager().getRate(name);
|
||||
if (rs != null) {
|
||||
Rate r = rs.getRate(period);
|
||||
if (r != null)
|
||||
rv.add(r);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private class Shutdown implements Runnable {
|
||||
public void run() {
|
||||
_isRunning = false;
|
||||
if (_thread != null)
|
||||
_thread.interrupt();
|
||||
for (SummaryListener lsnr : _listeners) {
|
||||
lsnr.stopListening();
|
||||
}
|
||||
_listeners.clear();
|
||||
}
|
||||
}
|
||||
}
|
108
app/src/main/java/net/i2p/android/router/service/StatusBar.java
Normal file
108
app/src/main/java/net/i2p/android/router/service/StatusBar.java
Normal file
@ -0,0 +1,108 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import net.i2p.android.router.MainActivity;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
class StatusBar {
|
||||
|
||||
private final Context ctx;
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final NotificationCompat.Builder mNotifyBuilder;
|
||||
private Notification mNotif;
|
||||
|
||||
private static final int ID = 1337;
|
||||
|
||||
public static final int ICON_STARTING = R.drawable.ic_stat_router_starting;
|
||||
public static final int ICON_RUNNING = R.drawable.ic_stat_router_running;
|
||||
public static final int ICON_ACTIVE = R.drawable.ic_stat_router_active;
|
||||
public static final int ICON_STOPPING = R.drawable.ic_stat_router_stopping;
|
||||
public static final int ICON_SHUTTING_DOWN = R.drawable.ic_stat_router_shutting_down;
|
||||
public static final int ICON_WAITING_NETWORK = R.drawable.ic_stat_router_waiting_network;
|
||||
|
||||
StatusBar(Context cx) {
|
||||
ctx = cx;
|
||||
mNotificationManager = (NotificationManager) ctx.getSystemService(
|
||||
Context.NOTIFICATION_SERVICE);
|
||||
Thread.currentThread().setUncaughtExceptionHandler(
|
||||
new CrashHandler(mNotificationManager));
|
||||
|
||||
int icon = ICON_STARTING;
|
||||
// won't be shown if replace() is called
|
||||
String text = "Starting I2P";
|
||||
|
||||
mNotifyBuilder = new NotificationCompat.Builder(ctx)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(icon)
|
||||
.setOngoing(true)
|
||||
.setOnlyAlertOnce(true);
|
||||
|
||||
Intent intent = new Intent(ctx, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
mNotifyBuilder.setContentIntent(pi);
|
||||
}
|
||||
|
||||
public void replace(int icon, String text) {
|
||||
mNotifyBuilder.setSmallIcon(icon)
|
||||
.setStyle(null)
|
||||
.setTicker(text);
|
||||
update(text);
|
||||
}
|
||||
|
||||
public void update(String text) {
|
||||
String title = "I2P Status";
|
||||
update(title, text);
|
||||
}
|
||||
|
||||
public void update(String title, String text, String bigText) {
|
||||
mNotifyBuilder.setStyle(new NotificationCompat.BigTextStyle()
|
||||
.bigText(bigText));
|
||||
update(title, text);
|
||||
}
|
||||
|
||||
public void update(String title, String text) {
|
||||
mNotifyBuilder.setContentTitle(title)
|
||||
.setContentText(text);
|
||||
mNotif = mNotifyBuilder.build();
|
||||
mNotificationManager.notify(ID, mNotif);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
mNotificationManager.cancel(ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/questions/4028742/how-to-clear-a-notification-if-activity-crashes
|
||||
*/
|
||||
private static class CrashHandler implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private final Thread.UncaughtExceptionHandler defaultUEH;
|
||||
private final NotificationManager mgr;
|
||||
|
||||
public CrashHandler(NotificationManager nMgr) {
|
||||
defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
|
||||
mgr = nMgr;
|
||||
}
|
||||
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
if (mgr != null) {
|
||||
try {
|
||||
mgr.cancel(ID);
|
||||
} catch (Throwable ex) {}
|
||||
}
|
||||
System.err.println("In CrashHandler " + e);
|
||||
e.printStackTrace(System.err);
|
||||
defaultUEH.uncaughtException(t, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Notification getNote() {
|
||||
return mNotif;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
|
||||
import com.androidplot.xy.SimpleXYSeries;
|
||||
import com.androidplot.xy.XYSeries;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.stat.RateSummaryListener;
|
||||
|
||||
public class SummaryListener implements RateSummaryListener {
|
||||
public static final int HISTORY_SIZE = 30;
|
||||
|
||||
private final I2PAppContext _context;
|
||||
private final Rate _rate;
|
||||
private String _name;
|
||||
private SimpleXYSeries _series;
|
||||
private MyObservable _notifier;
|
||||
|
||||
public SummaryListener(Rate r) {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_rate = r;
|
||||
_notifier = new MyObservable();
|
||||
}
|
||||
|
||||
// encapsulates management of the observers watching this rate for update events:
|
||||
class MyObservable extends Observable {
|
||||
@Override
|
||||
public void notifyObservers() {
|
||||
setChanged();
|
||||
super.notifyObservers();
|
||||
}
|
||||
}
|
||||
|
||||
public void addObserver(Observer observer) {
|
||||
_notifier.addObserver(observer);
|
||||
}
|
||||
|
||||
public void removeObserver(Observer observer) {
|
||||
_notifier.deleteObserver(observer);
|
||||
}
|
||||
|
||||
public void add(double totalValue, long eventCount, double totalEventTime,
|
||||
long period) {
|
||||
long now = now();
|
||||
long when = now / 1000;
|
||||
double val = eventCount > 0 ? (totalValue / eventCount) : 0d;
|
||||
|
||||
if (_series.size() > HISTORY_SIZE)
|
||||
_series.removeFirst();
|
||||
|
||||
_series.addLast(when, val);
|
||||
|
||||
_notifier.notifyObservers();
|
||||
}
|
||||
|
||||
public Rate getRate() { return _rate; }
|
||||
|
||||
public String getName() { return _name; }
|
||||
|
||||
public XYSeries getSeries() { return _series; }
|
||||
|
||||
long now() { return _context.clock().now(); }
|
||||
|
||||
public void startListening() {
|
||||
RateStat rs = _rate.getRateStat();
|
||||
long period = _rate.getPeriod();
|
||||
_name = rs.getName() + "." + period;
|
||||
_series = new SimpleXYSeries(_name);
|
||||
_series.useImplicitXVals();
|
||||
_rate.setSummaryListener(this);
|
||||
}
|
||||
|
||||
public void stopListening() {
|
||||
_rate.setSummaryListener(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package net.i2p.android.router.stats;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class PeersActivity extends I2PActivityBase {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
// Start with the base view
|
||||
if (savedInstanceState == null) {
|
||||
PeersFragment f = new PeersFragment();
|
||||
f.setArguments(getIntent().getExtras());
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, f).commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not bound by the time onResume() is called, so we have to do it here.
|
||||
* If it is bound we update twice.
|
||||
*/
|
||||
@Override
|
||||
protected void onRouterBind(RouterService svc) {
|
||||
PeersFragment f = (PeersFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||
f.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
PeersFragment f = (PeersFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||
if (!f.onBackPressed())
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package net.i2p.android.router.stats;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebView;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.web.I2PWebViewClient;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
|
||||
public class PeersFragment extends I2PFragmentBase {
|
||||
|
||||
private I2PWebViewClient _wvClient;
|
||||
|
||||
// TODO add some inline style
|
||||
private static final String HEADER = "<html><head></head><body>";
|
||||
private static final String FOOTER = "</body></html>";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
View v = inflater.inflate(R.layout.peers, container, false);
|
||||
WebView wv = (WebView) v.findViewById(R.id.peers_webview);
|
||||
wv.getSettings().setLoadsImagesAutomatically(true); // was false
|
||||
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
||||
wv.getSettings().setUseWideViewPort(true);
|
||||
_wvClient = new I2PWebViewClient();
|
||||
wv.setWebViewClient(_wvClient);
|
||||
wv.getSettings().setBuiltInZoomControls(true);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
update();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
WebView wv = (WebView) getActivity().findViewById(R.id.peers_webview);
|
||||
wv.clearHistory(); // fixes having to hit back.
|
||||
CommSystemFacade comm = getCommSystem();
|
||||
String data;
|
||||
if (comm != null) {
|
||||
StringWriter out = new StringWriter(32*1024);
|
||||
out.append(HEADER);
|
||||
try {
|
||||
comm.renderStatusHTML(out, "http://thiswontwork.i2p/peers", 0);
|
||||
out.append(FOOTER);
|
||||
data = out.toString().replaceAll("/themes", "themes");
|
||||
} catch (IOException ioe) {
|
||||
data = HEADER + "Error: " + ioe + FOOTER;
|
||||
}
|
||||
} else {
|
||||
data = HEADER + "No peer data available. The router is not running." + FOOTER;
|
||||
}
|
||||
try {
|
||||
// wv.loadData(data, "text/html", "UTF-8");
|
||||
// figure out a way to get /themes/console/images/outbound.png to load
|
||||
// String url = "file://" + _myDir + "/docs/";
|
||||
String url = "file:///android_asset/";
|
||||
wv.loadDataWithBaseURL(url, data, "text/html", "UTF-8", url);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
WebView wv = (WebView) getActivity().findViewById(R.id.peers_webview);
|
||||
_wvClient.cancelAll();
|
||||
wv.stopLoading();
|
||||
|
||||
// We do not want to go back, or keep history... There is no need to.
|
||||
// What we DO want to do is exit!
|
||||
//if (wv.canGoBack()) {
|
||||
// wv.goBack();
|
||||
// return true;
|
||||
//}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_web_actions, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_reload:
|
||||
update();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
package net.i2p.android.router.stats;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.SettingsActivity;
|
||||
import net.i2p.android.router.service.StatSummarizer;
|
||||
import net.i2p.android.router.service.SummaryListener;
|
||||
import net.i2p.stat.Rate;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class RateGraphActivity extends I2PActivityBase {
|
||||
private static final String SELECTED_RATE = "selected_rate";
|
||||
|
||||
private String[] mRates;
|
||||
private long[] mPeriods;
|
||||
private Spinner mSpinner;
|
||||
private boolean mFinishOnResume;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
|
||||
if (StatSummarizer.instance() != null) {
|
||||
// Get the rates currently being graphed
|
||||
List<SummaryListener> listeners = StatSummarizer.instance().getListeners();
|
||||
TreeSet<SummaryListener> ordered = new TreeSet<>(new AlphaComparator());
|
||||
ordered.addAll(listeners);
|
||||
|
||||
if (ordered.size() > 0) {
|
||||
// Extract the rates and periods
|
||||
mRates = new String[ordered.size()];
|
||||
mPeriods = new long[ordered.size()];
|
||||
int i = 0;
|
||||
for (SummaryListener listener : ordered) {
|
||||
Rate r = listener.getRate();
|
||||
mRates[i] = r.getRateStat().getName();
|
||||
mPeriods[i] = r.getPeriod();
|
||||
i++;
|
||||
}
|
||||
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
mSpinner.setAdapter(new ArrayAdapter<>(this,
|
||||
android.R.layout.simple_spinner_dropdown_item, mRates));
|
||||
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectRate(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_RATE);
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectRate(0);
|
||||
} else {
|
||||
DialogFragment df = new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setMessage(R.string.no_graphs_configured)
|
||||
.setPositiveButton(R.string.configure_graphs, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
mFinishOnResume = true;
|
||||
Intent i = new Intent(RateGraphActivity.this, SettingsActivity.class);
|
||||
// Navigation to a sub-category doesn't seem to work yet
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
i.setAction(SettingsActivity.ACTION_PREFS_GRAPHS);
|
||||
} else {
|
||||
i.putExtra("settings", "graphs");
|
||||
}
|
||||
startActivity(i);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i) {
|
||||
dialog.cancel();
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setCancelable(false);
|
||||
return builder.create();
|
||||
}
|
||||
};
|
||||
df.show(getSupportFragmentManager(), "nographs");
|
||||
}
|
||||
} else {
|
||||
DialogFragment df = new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setMessage(R.string.graphs_not_ready)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setCancelable(false);
|
||||
return builder.create();
|
||||
}
|
||||
};
|
||||
df.show(getSupportFragmentManager(), "graphsnotready");
|
||||
}
|
||||
}
|
||||
|
||||
private void selectRate(int position) {
|
||||
String rateName = mRates[position];
|
||||
long period = mPeriods[position];
|
||||
RateGraphFragment f = RateGraphFragment.newInstance(rateName, period);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f, rateName).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mFinishOnResume) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mSpinner != null)
|
||||
outState.putInt(SELECTED_RATE, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
private static class AlphaComparator implements Comparator<SummaryListener> {
|
||||
public int compare(SummaryListener l, SummaryListener r) {
|
||||
String lName = l.getRate().getRateStat().getName();
|
||||
String rName = r.getRate().getRateStat().getName();
|
||||
int rv = lName.compareTo(rName);
|
||||
if (rv != 0)
|
||||
return rv;
|
||||
return (int) (l.getRate().getPeriod() - r.getRate().getPeriod());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
package net.i2p.android.router.stats;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.androidplot.Plot;
|
||||
import com.androidplot.xy.BoundaryMode;
|
||||
import com.androidplot.xy.LineAndPointFormatter;
|
||||
import com.androidplot.xy.XYPlot;
|
||||
import com.androidplot.xy.XYSeries;
|
||||
import com.androidplot.xy.XYStepMode;
|
||||
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.service.StatSummarizer;
|
||||
import net.i2p.android.router.service.SummaryListener;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.Format;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
|
||||
public class RateGraphFragment extends I2PFragmentBase {
|
||||
// redraws a plot whenever an update is received:
|
||||
private class MyPlotUpdater implements Observer {
|
||||
Plot plot;
|
||||
|
||||
public MyPlotUpdater(Plot plot) {
|
||||
this.plot = plot;
|
||||
}
|
||||
|
||||
public void update(Observable o, Object arg) {
|
||||
Util.d("Redrawing plot");
|
||||
plot.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
public static final String RATE_NAME = "rate_name";
|
||||
public static final String RATE_PERIOD = "rate_period";
|
||||
|
||||
private Handler _handler;
|
||||
private SetupTask _setupTask;
|
||||
private SummaryListener _listener;
|
||||
private XYPlot _ratePlot;
|
||||
private MyPlotUpdater _plotUpdater;
|
||||
|
||||
public static RateGraphFragment newInstance(String name, long period) {
|
||||
RateGraphFragment f = new RateGraphFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(RATE_NAME, name);
|
||||
args.putLong(RATE_PERIOD, period);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
_handler = new Handler();
|
||||
_setupTask = new SetupTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_graph, container, false);
|
||||
|
||||
_ratePlot = (XYPlot) v.findViewById(R.id.rate_stat_plot);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
_handler.removeCallbacks(_setupTask);
|
||||
_handler.postDelayed(_setupTask, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (_listener != null && _plotUpdater != null) {
|
||||
Util.d("Removing plot updater from listener");
|
||||
_listener.removeObserver(_plotUpdater);
|
||||
}
|
||||
_handler.removeCallbacks(_setupTask);
|
||||
}
|
||||
|
||||
private class SetupTask implements Runnable {
|
||||
public void run() {
|
||||
String rateName = getArguments().getString(RATE_NAME);
|
||||
long period = getArguments().getLong(RATE_PERIOD);
|
||||
|
||||
Util.d("Setting up " + rateName + "." + period);
|
||||
if (StatSummarizer.instance() == null) {
|
||||
Util.d("StatSummarizer is null, delaying setup");
|
||||
_handler.postDelayed(this, 1000);
|
||||
return;
|
||||
}
|
||||
_listener = StatSummarizer.instance().getListener(rateName, period);
|
||||
if (_listener == null) {
|
||||
Util.d("Listener is null, delaying setup");
|
||||
_handler.postDelayed(this, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
XYSeries rateSeries = _listener.getSeries();
|
||||
|
||||
_plotUpdater = new MyPlotUpdater(_ratePlot);
|
||||
|
||||
_ratePlot.addSeries(rateSeries, new LineAndPointFormatter(Color.rgb(0, 0, 0), null, Color.rgb(0, 80, 0), null));
|
||||
|
||||
Util.d("Adding plot updater to listener");
|
||||
_listener.addObserver(_plotUpdater);
|
||||
|
||||
_ratePlot.setDomainStepMode(XYStepMode.SUBDIVIDE);
|
||||
_ratePlot.setDomainStepValue(SummaryListener.HISTORY_SIZE);
|
||||
|
||||
// thin out domain/range tick labels so they dont overlap each other:
|
||||
_ratePlot.setTicksPerDomainLabel(5);
|
||||
_ratePlot.setTicksPerRangeLabel(3);
|
||||
|
||||
_ratePlot.setRangeLowerBoundary(0, BoundaryMode.FIXED);
|
||||
|
||||
_ratePlot.setRangeValueFormat(new Format() {
|
||||
|
||||
@Override
|
||||
public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo,
|
||||
@NonNull FieldPosition pos) {
|
||||
double val = ((Number) obj).doubleValue();
|
||||
if (val >= 10 * 1000 * 1000)
|
||||
return new DecimalFormat("0 M").format(val / (1000 * 1000), toAppendTo, pos);
|
||||
else if (val >= 8 * 100 * 1000)
|
||||
return new DecimalFormat("0.0 M").format(val / (1000 * 1000), toAppendTo, pos);
|
||||
else if (val >= 10 * 1000)
|
||||
return new DecimalFormat("0 k").format(val / (1000), toAppendTo, pos);
|
||||
else if (val >= 8 * 100)
|
||||
return new DecimalFormat("0.0 k").format(val / (1000), toAppendTo, pos);
|
||||
else
|
||||
return new DecimalFormat("0").format(val, toAppendTo, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseObject(String source, @NonNull ParsePosition pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Util.d("Redrawing plot");
|
||||
_ratePlot.redraw();
|
||||
}
|
||||
}
|
||||
}
|
@ -5,9 +5,11 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
import net.i2p.android.router.provider.CacheProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -16,9 +18,6 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.android.router.provider.CacheProvider;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
/**
|
||||
* A least recently used cache with a max number of entries
|
||||
* and a max total disk space.
|
||||
@ -35,7 +34,7 @@ public class AppCache {
|
||||
/** the LRU cache */
|
||||
private final Map<Integer, Object> _cache;
|
||||
|
||||
private static final Integer DUMMY = Integer.valueOf(0);
|
||||
private static final Integer DUMMY = 0;
|
||||
private static final String DIR_NAME = "appCache";
|
||||
/** fragment into this many subdirectories */
|
||||
private static final int NUM_DIRS = 32;
|
||||
@ -62,7 +61,7 @@ public class AppCache {
|
||||
private AppCache(Context ctx) {
|
||||
_cacheDir = new File(ctx.getCacheDir(), DIR_NAME);
|
||||
_cacheDir.mkdir();
|
||||
Util.e("AppCache cache dir " + _cacheDir);
|
||||
Util.d("AppCache cache dir " + _cacheDir);
|
||||
_resolver = ctx.getContentResolver();
|
||||
_cache = new LHM(MAX_FILES);
|
||||
initialize();
|
||||
@ -92,7 +91,7 @@ public class AppCache {
|
||||
public Uri addCacheFile(Uri key, boolean setAsCurrentBase) {
|
||||
int hash = toHash(key);
|
||||
synchronized(_cache) {
|
||||
_cache.put(Integer.valueOf(hash), DUMMY);
|
||||
_cache.put(hash, DUMMY);
|
||||
}
|
||||
// file:/// uri
|
||||
//return Uri.fromFile(toFile(hash)).toString();
|
||||
@ -107,7 +106,7 @@ public class AppCache {
|
||||
public void removeCacheFile(Uri key) {
|
||||
int hash = toHash(key);
|
||||
synchronized(_cache) {
|
||||
_cache.remove(Integer.valueOf(hash));
|
||||
_cache.remove(hash);
|
||||
}
|
||||
deleteContent(key);
|
||||
}
|
||||
@ -122,9 +121,9 @@ public class AppCache {
|
||||
public Uri getCacheUri(Uri key) {
|
||||
int hash = toHash(key);
|
||||
// poke the LRU
|
||||
Object present = null;
|
||||
Object present;
|
||||
synchronized(_cache) {
|
||||
present = _cache.get(Integer.valueOf(hash));
|
||||
present = _cache.get(hash);
|
||||
}
|
||||
if (present != null)
|
||||
setAsCurrentBase(key);
|
||||
@ -145,9 +144,9 @@ public class AppCache {
|
||||
|
||||
private void initialize() {
|
||||
_totalSize = 0;
|
||||
List<File> fileList = new ArrayList(MAX_FILES);
|
||||
List<File> fileList = new ArrayList<File>(MAX_FILES);
|
||||
long total = enumerate(_cacheDir, fileList);
|
||||
Util.e("AppCache found " + fileList.size() + " files totalling " + total + " bytes");
|
||||
Util.d("AppCache found " + fileList.size() + " files totalling " + total + " bytes");
|
||||
Collections.sort(fileList, new FileComparator());
|
||||
// oldest first, delete if too big or too old, else add to LHM
|
||||
long now = System.currentTimeMillis();
|
||||
@ -160,7 +159,7 @@ public class AppCache {
|
||||
// TODO insertContent
|
||||
}
|
||||
}
|
||||
Util.e("after init " + _cache.size() + " files totalling " + total + " bytes");
|
||||
Util.d("after init " + _cache.size() + " files totalling " + total + " bytes");
|
||||
}
|
||||
|
||||
/** oldest first */
|
||||
@ -176,17 +175,16 @@ public class AppCache {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null)
|
||||
return 0;
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
File f = files[i];
|
||||
if (f.isDirectory()) {
|
||||
rv += enumerate(f, fileList);
|
||||
} else {
|
||||
long len = f.length();
|
||||
if (len > 0) {
|
||||
fileList.add(f);
|
||||
rv += len;
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
rv += enumerate(f, fileList);
|
||||
} else {
|
||||
long len = f.length();
|
||||
if (len > 0) {
|
||||
fileList.add(f);
|
||||
rv += len;
|
||||
} else {
|
||||
f.delete();
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -198,10 +196,10 @@ public class AppCache {
|
||||
try {
|
||||
int hash = toHash(f);
|
||||
synchronized(_cache) {
|
||||
_cache.put(Integer.valueOf(hash), DUMMY);
|
||||
_cache.put(hash, DUMMY);
|
||||
}
|
||||
} catch (IllegalArgumentException iae) {
|
||||
Util.e("Huh bad file?" + iae);
|
||||
Util.d("Huh bad file?" + iae);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
@ -285,6 +283,7 @@ public class AppCache {
|
||||
* on remove.
|
||||
*/
|
||||
private static class LHM extends LinkedHashMap<Integer, Object> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final int _max;
|
||||
|
||||
public LHM(int max) {
|
||||
@ -296,7 +295,7 @@ public class AppCache {
|
||||
@Override
|
||||
public Object put(Integer key, Object value) {
|
||||
Object rv = super.put(key, value);
|
||||
File f = toFile(key.intValue());
|
||||
File f = toFile(key);
|
||||
if (f.exists()) {
|
||||
_totalSize += f.length();
|
||||
}
|
||||
@ -308,11 +307,11 @@ public class AppCache {
|
||||
public Object remove(Object key) {
|
||||
Object rv = super.remove(key);
|
||||
if ( /* rv != null && */ key instanceof Integer) {
|
||||
File f = toFile(((Integer)key).intValue());
|
||||
File f = toFile((Integer) key);
|
||||
if (f.exists()) {
|
||||
_totalSize -= f.length();
|
||||
f.delete();
|
||||
Util.e("AppCache deleted file " + f);
|
||||
Util.d("AppCache deleted file " + f);
|
||||
}
|
||||
}
|
||||
return rv;
|
@ -0,0 +1,125 @@
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
public abstract class BetterAsyncTaskLoader<T> extends AsyncTaskLoader<T> {
|
||||
protected T mData;
|
||||
|
||||
public BetterAsyncTaskLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is new data to deliver to the client. The
|
||||
* super class will take care of delivering it; the implementation
|
||||
* here just adds a little more logic.
|
||||
*/
|
||||
@Override
|
||||
public void deliverResult(T data) {
|
||||
if (isReset()) {
|
||||
// An async query came in while the loader is stopped. We
|
||||
// don't need the result.
|
||||
if (data != null) {
|
||||
releaseResources(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||
// We must protect it until the new data has been delivered.
|
||||
T oldData = mData;
|
||||
mData = data;
|
||||
|
||||
if (isStarted()) {
|
||||
// If the Loader is currently started, we can immediately
|
||||
// deliver its results.
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
// Invalidate the old data as we don't need it any more.
|
||||
if (oldData != null && oldData != data) {
|
||||
releaseResources(oldData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to start the Loader.
|
||||
*/
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mData != null) {
|
||||
// Deliver any previously loaded data immediately.
|
||||
deliverResult(mData);
|
||||
}
|
||||
|
||||
// Start watching for changes
|
||||
onStartMonitoring();
|
||||
|
||||
if (takeContentChanged() || mData == null) {
|
||||
// When the observer detects a change, it should call onContentChanged()
|
||||
// on the Loader, which will cause the next call to takeContentChanged()
|
||||
// to return true. If this is ever the case (or if the current data is
|
||||
// null), we force a new load.
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to stop the Loader.
|
||||
*/
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||
// current load (if there is one).
|
||||
cancelLoad();
|
||||
|
||||
// Note that we leave the observer as is. Loaders in a stopped state
|
||||
// should still monitor the data source for changes so that the Loader
|
||||
// will know to force a new load if it is ever started again.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to completely reset the Loader.
|
||||
*/
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
// Ensure the loader has been stopped.
|
||||
onStopLoading();
|
||||
|
||||
// At this point we can release the resources associated with 'mData'.
|
||||
if (mData != null) {
|
||||
releaseResources(mData);
|
||||
mData = null;
|
||||
}
|
||||
|
||||
// Stop monitoring for changes.
|
||||
onStopMonitoring();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to cancel a load.
|
||||
*/
|
||||
@Override
|
||||
public void onCanceled(T data) {
|
||||
// Attempt to cancel the current asynchronous load.
|
||||
super.onCanceled(data);
|
||||
|
||||
// The load has been canceled, so we should release the resources
|
||||
// associated with 'data'.
|
||||
releaseResources(data);
|
||||
}
|
||||
|
||||
protected abstract void onStartMonitoring();
|
||||
protected abstract void onStopMonitoring();
|
||||
|
||||
/**
|
||||
* Helper function to take care of releasing resources associated
|
||||
* with an actively loaded data set.
|
||||
* For a simple List, there is nothing to do. For something like a Cursor, we
|
||||
* would close it in this method. All resources associated with the Loader
|
||||
* should be released here.
|
||||
*/
|
||||
protected abstract void releaseResources(T data);
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class ConnectionLimitPreference extends EditTextPreference {
|
||||
private boolean mValueInTitle;
|
||||
|
||||
public ConnectionLimitPreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ConnectionLimitPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public ConnectionLimitPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
void init(Context context, AttributeSet attrs) {
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.ConnectionLimitPreference, 0, 0);
|
||||
mValueInTitle = attr.getBoolean(R.styleable.ConnectionLimitPreference_clp_valueInTitle, false);
|
||||
attr.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
if (mValueInTitle)
|
||||
return formatValue((String) super.getTitle());
|
||||
else
|
||||
return super.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
if (mValueInTitle)
|
||||
return super.getSummary();
|
||||
else
|
||||
return formatValue((String) super.getSummary());
|
||||
}
|
||||
|
||||
private CharSequence formatValue(String format) {
|
||||
String text = getText();
|
||||
if ("0".equals(text))
|
||||
text = getContext().getString(R.string.unlimited);
|
||||
|
||||
if (format == null)
|
||||
format = "%s";
|
||||
return String.format(format, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPersistedString(String defaultReturnValue) {
|
||||
if(getSharedPreferences().contains(getKey())) {
|
||||
int intValue = getPersistedInt(0);
|
||||
return String.valueOf(intValue);
|
||||
} else {
|
||||
return defaultReturnValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean persistString(String value) {
|
||||
return value != null && persistInt(Integer.valueOf(value));
|
||||
}
|
||||
}
|
134
app/src/main/java/net/i2p/android/router/util/Connectivity.java
Normal file
134
app/src/main/java/net/i2p/android/router/util/Connectivity.java
Normal file
@ -0,0 +1,134 @@
|
||||
// License: MIT
|
||||
// http://opensource.org/licenses/MIT
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
/**
|
||||
* Check device's network connectivity and speed.
|
||||
*
|
||||
* @author emil http://stackoverflow.com/users/220710/emil
|
||||
* @author str4d
|
||||
*/
|
||||
public class Connectivity {
|
||||
/**
|
||||
* Get the network info.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return the active NetworkInfo.
|
||||
*/
|
||||
public static NetworkInfo getNetworkInfo(Context context) {
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return cm.getActiveNetworkInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is any connectivity at all.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return true if we are connected to a network, false otherwise.
|
||||
*/
|
||||
public static boolean isConnected(Context context) {
|
||||
NetworkInfo info = Connectivity.getNetworkInfo(context);
|
||||
// Works on emulator and devices.
|
||||
// Note the use of isAvailable() - without this, isConnected() can
|
||||
// return true when Wifi is disabled.
|
||||
// http://stackoverflow.com/a/2937915
|
||||
return (info != null && info.isAvailable() && info.isConnected());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is any connectivity to a Wifi network.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return true if we are connected to a Wifi network, false otherwise.
|
||||
*/
|
||||
public static boolean isConnectedWifi(Context context) {
|
||||
NetworkInfo info = Connectivity.getNetworkInfo(context);
|
||||
return (info != null && info.isAvailable() && info.isConnected() &&
|
||||
info.getType() == ConnectivityManager.TYPE_WIFI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is any connectivity to a mobile network.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return true if we are connected to a mobile network, false otherwise.
|
||||
*/
|
||||
public static boolean isConnectedMobile(Context context) {
|
||||
NetworkInfo info = Connectivity.getNetworkInfo(context);
|
||||
return (info != null && info.isAvailable() && info.isConnected() &&
|
||||
info.getType() == ConnectivityManager.TYPE_MOBILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is fast connectivity.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return true if we have "fast" connectivity, false otherwise.
|
||||
*/
|
||||
public static boolean isConnectedFast(Context context) {
|
||||
NetworkInfo info = Connectivity.getNetworkInfo(context);
|
||||
return (info != null && info.isAvailable() && info.isConnected() &&
|
||||
Connectivity.isConnectionFast(info.getType(), info.getSubtype()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the connection is fast.
|
||||
*
|
||||
* @param type the network type.
|
||||
* @param subType the network subtype.
|
||||
* @return true if the provided type/subtype combination is classified as fast.
|
||||
*/
|
||||
public static boolean isConnectionFast(int type, int subType) {
|
||||
if (type == ConnectivityManager.TYPE_WIFI) {
|
||||
return true;
|
||||
} else if (type == ConnectivityManager.TYPE_MOBILE) {
|
||||
switch (subType) {
|
||||
case TelephonyManager.NETWORK_TYPE_1xRTT:
|
||||
return false; // ~ 50-100 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_CDMA:
|
||||
return false; // ~ 14-64 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_EDGE:
|
||||
return false; // ~ 50-100 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_0:
|
||||
return true; // ~ 400-1000 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_A:
|
||||
return true; // ~ 600-1400 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_GPRS:
|
||||
return false; // ~ 100 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_HSDPA:
|
||||
return true; // ~ 2-14 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_HSPA:
|
||||
return true; // ~ 700-1700 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_HSUPA:
|
||||
return true; // ~ 1-23 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_UMTS:
|
||||
return true; // ~ 400-7000 kbps
|
||||
/*
|
||||
* Above API level 7, make sure to set android:targetSdkVersion
|
||||
* to appropriate level to use these
|
||||
*/
|
||||
case TelephonyManager.NETWORK_TYPE_EHRPD: // API level 11
|
||||
return true; // ~ 1-2 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_B: // API level 9
|
||||
return true; // ~ 5 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_HSPAP: // API level 13
|
||||
return true; // ~ 10-20 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_IDEN: // API level 8
|
||||
return false; // ~25 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_LTE: // API level 11
|
||||
return true; // ~ 10+ Mbps
|
||||
// Unknown
|
||||
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.util.Patterns;
|
||||
|
||||
public class I2Patterns {
|
||||
/**
|
||||
* The double-parentheses are needed because the included
|
||||
* pattern has an additional closing parenthesis.
|
||||
*/
|
||||
public static final String I2P_TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
|
||||
"(?:("
|
||||
+ Patterns.TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
|
||||
+ "|i2p))";
|
||||
|
||||
/**
|
||||
* Regular expression pattern to match most part of RFC 3987
|
||||
* Internationalized URLs, aka IRIs. Commonly used Unicode characters are
|
||||
* added.
|
||||
* Copied from android.util.Patterns
|
||||
*/
|
||||
public static final Pattern I2P_WEB_URL = Pattern.compile(
|
||||
"((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
|
||||
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
|
||||
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
|
||||
+ "((?:(?:[" + Patterns.GOOD_IRI_CHAR + "][" + Patterns.GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
|
||||
+ I2P_TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
|
||||
+ "|(?:(?:25[0-5]|2[0-4]" // or ip address
|
||||
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
|
||||
+ "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
|
||||
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
|
||||
+ "|[1-9][0-9]|[0-9])))"
|
||||
+ "(?:\\:\\d{1,5})?)" // plus option port number
|
||||
+ "(\\/(?:(?:[" + Patterns.GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
|
||||
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
|
||||
+ "(?:\\b|$)"); // and finally, a word boundary or end of
|
||||
// input. This is to stop foo.sure from
|
||||
// matching as foo.su
|
||||
|
||||
public static final Pattern IRC_URL = Pattern.compile(
|
||||
"(irc:\\/\\/127\\.0\\.0\\.1\\:\\d{1,5})"
|
||||
+ "(\\/(?:(?:[" + Patterns.GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
|
||||
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
|
||||
+ "(?:\\b|$)"); // and finally, a word boundary or end of
|
||||
// input. This is to stop foo.sure from
|
||||
// matching as foo.su
|
||||
|
||||
private I2Patterns() {
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class IntEditTextPreference extends EditTextPreference {
|
||||
|
||||
public IntEditTextPreference(Context context) {
|
||||
super(context);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
|
||||
public IntEditTextPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
|
||||
public IntEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
String summary = (String) super.getSummary();
|
||||
if (summary == null)
|
||||
summary = "%s";
|
||||
return String.format(summary, getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPersistedString(String defaultReturnValue) {
|
||||
if(getSharedPreferences().contains(getKey())) {
|
||||
int intValue = getPersistedInt(0);
|
||||
return String.valueOf(intValue);
|
||||
} else {
|
||||
return defaultReturnValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean persistString(String value) {
|
||||
return value != null && persistInt(Integer.valueOf(value));
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user