Compare commits
846 Commits
android-cl
...
i2p-androi
Author | SHA1 | Date | |
---|---|---|---|
fa668711bf | |||
48d9afea41 | |||
da4ed503c8 | |||
78a0a2b5f4 | |||
42202cc4d2 | |||
49f4562bf7 | |||
ca6031ab47 | |||
6c83285e04 | |||
96bb2cf161 | |||
ddb2482623 | |||
bf2dea5e0a | |||
2d784dbef2 | |||
554a65987b | |||
846795dea6 | |||
7a22ce4bee | |||
0e25d89bd8 | |||
61010b2c93 | |||
5fac82a915 | |||
c8c36540eb | |||
59b1da6950 | |||
40894d8d04 | |||
d0f38397cd | |||
17b86fffdd | |||
a06b449bfb | |||
128f1df0ac | |||
ed085194e6 | |||
52a4c2f430 | |||
784918d220 | |||
f129773255 | |||
b304771eca | |||
f87e8d036b | |||
16c1277935 | |||
a6aa76f9dd | |||
a0ced4133d | |||
e024950567 | |||
ea68f116e1 | |||
3cb746594c | |||
27adae63eb | |||
85890d3b68 | |||
96b045eb2a | |||
8d00c5fa31 | |||
fc93a71552 | |||
18e4aef4ca | |||
5315e35adc | |||
9ad9bc07ff | |||
bde92be29d | |||
849150ffa6 | |||
52dad19c7a | |||
e84a51097d | |||
5289f5068a | |||
09c7d9cfd8 | |||
14d293fe6f | |||
7cd08cfd4c | |||
aa22d83a44 | |||
a1fd8d41b2 | |||
9d24e68b57 | |||
d5e2804ea4 | |||
1edabd7252 | |||
52c1901eba | |||
a6f5221975 | |||
caa57bbc3a | |||
b28e5d741e | |||
0f3a61390c | |||
30a1f1d800 | |||
22d5d126f6 | |||
70e4ea810f | |||
b5c6e1489a | |||
333a1a49b1 | |||
2ea93f106e | |||
d99ed0e5da | |||
3881966ddb | |||
9de9f46489 | |||
149881522e | |||
df26d89310 | |||
707dcbbc78 | |||
812aea9873 | |||
aeb7614b62 | |||
9bf9d27c5a | |||
f2cfe4cee5 | |||
45865ff5b9 | |||
96f9905952 | |||
41ca3fe527 | |||
c5468e4829 | |||
174bb7f827 | |||
3d92f32c13 | |||
2fae27a3b3 | |||
71d38c64e0 | |||
ddb651a602 | |||
a8ad1d8d47 | |||
912602bfc0 | |||
24e3358ffa | |||
fcc51c429d | |||
de0ee87db3 | |||
2102d55315 | |||
2f900ebe2d | |||
5c03981a61 | |||
c68f0ff23a | |||
b01f44ae1f | |||
05aeb0cf37 | |||
fd8d596064 | |||
ea3fe136e4 | |||
47f4530f6f | |||
016a7a47fd | |||
f8410deeba | |||
7268132ddd | |||
62545f1247 | |||
99b6992b5c | |||
e8ebb0a569 | |||
3ab2c83b7b | |||
cb46abca82 | |||
63b345e329 | |||
794b8433d8 | |||
a7891a3674 | |||
7076785ec1 | |||
f8eda45409 | |||
8d0f1689c1 | |||
25ef37cddf | |||
c5936858a2 | |||
17d2e2fc93 | |||
a0e41e5171 | |||
2d1244f339 | |||
9eec1d3348 | |||
5824fd4efa | |||
9c87bfcb51 | |||
0c380f2ef5 | |||
dafe3d0002 | |||
255ac60691 | |||
23f681dd7d | |||
d6a55a122a | |||
454937d345 | |||
ec8cf76fc4 | |||
6788f9a663 | |||
dc96c6251f | |||
c2bced68bb | |||
e21b4e5cbd | |||
1f434d8a05 | |||
cd12d84d47 | |||
82b92caa55 | |||
49e4fcc49c | |||
a9abe05325 | |||
ea6705374f | |||
3e28f2bf93 | |||
4c881525f9 | |||
9280c80a53 | |||
e465a6d232 | |||
d7f79e7b0b | |||
68f44b8ec1 | |||
0835a42fbd | |||
78e649dc9b | |||
84e73a693c | |||
c0ad7dfc09 | |||
dec68432bc | |||
f93fc155ef | |||
e348af340a | |||
aa3631ed33 | |||
f2088ad1e5 | |||
008e9b7961 | |||
90a46bcc3f | |||
f69f748064 | |||
640a2e1918 | |||
8aead91700 | |||
b18a2e6241 | |||
09c3e6e12c | |||
74bf0eade2 | |||
2fa6e7f3cb | |||
24b741be1e | |||
ac8ce6f916 | |||
cd7d0ad723 | |||
c5a0c3608d | |||
3863c0e183 | |||
69ea3d8bde | |||
7cafe6da48 | |||
9464b46ad0 | |||
c230a5a101 | |||
3d37f2ae07 | |||
f88aafe292 | |||
aa2dab1d3c | |||
8e85eaa2f0 | |||
6a1848caf6 | |||
aa36b4cb14 | |||
78d4b12142 | |||
c2f3a80dec | |||
a679784aab | |||
835667437b | |||
f016edec7a | |||
1e3b517219 | |||
f4e3b15fcf | |||
fc4154be67 | |||
5bf0b18767 | |||
eb5ef3129b | |||
6e87d248c0 | |||
c9b0aff142 | |||
5acac0dbc4 | |||
0b42a7ee64 | |||
c3a798ee3d | |||
e682369311 | |||
14b953f145 | |||
036c807d6b | |||
a41fca95df | |||
26fdf40f25 | |||
070af6529c | |||
7ba0892351 | |||
5b9cdb9f9f | |||
b79d39a74a | |||
5fc5aed0c9 | |||
aec25ab374 | |||
9d5c495936 | |||
4dc2bb6b01 | |||
373e013911 | |||
83bb7096a7 | |||
cc1c4690a2 | |||
69ad581235 | |||
4be227631d | |||
3f3f1f8e3d | |||
22290da1a4 | |||
7caf21d552 | |||
701860a525 | |||
7615aca89e | |||
cae8ed2ae3 | |||
a523e1cb4a | |||
5e048af9c1 | |||
77b6c4d30f | |||
983a94e1c4 | |||
1f79323d66 | |||
d86d3ad5ae | |||
c41b064045 | |||
4c299ecda3 | |||
761f427366 | |||
f4c4bfe8be | |||
7f15a6f1e1 | |||
5734760d58 | |||
3244adfcd2 | |||
ce62b0fb97 | |||
64673ee185 | |||
a36cabdcc8 | |||
1e8531c731 | |||
0935659d6d | |||
bef5f7e746 | |||
d64e8359c1 | |||
5fd77ea62d | |||
8626ac2913 | |||
1258f18bc3 | |||
8b677abd3c | |||
f3d1e89002 | |||
f39c9a0fc4 | |||
3186caa6bb | |||
fc1259d8a8 | |||
848d07331e | |||
9794d26d0c | |||
76cd9c85ef | |||
d4c7c480fb | |||
00aa80d104 | |||
20086685aa | |||
86a8effd82 | |||
55d6e6d24e | |||
3b05046df3 | |||
b8587cd0ab | |||
61dd550040 | |||
478ff63889 | |||
e747619b85 | |||
f043bb3d72 | |||
98d8106f56 | |||
74eb0ce4f6 | |||
d79813d6d1 | |||
8f60c6ce9e | |||
7e0d017858 | |||
8470435ee2 | |||
5023d69222 | |||
34c7464f5b | |||
2da6fe9c62 | |||
1927c9e5a3 | |||
1ace085d13 | |||
95e6c1f7a6 | |||
03bdd575a7 | |||
98c5313d75 | |||
c047bdf085 | |||
e113ef0002 | |||
3e9b47307d | |||
2d864ad8b1 | |||
5b7f9bd452 | |||
3c89749f94 | |||
edbd5fd7ea | |||
faf8bf74af | |||
851e774e7a | |||
dcea801116 | |||
cb5235e6da | |||
5c7eaf2484 | |||
764cfc91ec | |||
177a2c6dc1 | |||
4dccd1dbae | |||
50141eb24b | |||
4dab632bc8 | |||
350515041a | |||
5ba294c2c2 | |||
a215363206 | |||
4bd647c67c | |||
b951892c05 | |||
16e05e0dd8 | |||
5bdf119b81 | |||
b73b72c9c8 | |||
0bae211da5 | |||
709392e8b6 | |||
80ed1e71da | |||
6c0a60892f | |||
7f13aa26fb | |||
7167a11844 | |||
1f140bf95a | |||
940b2b83a1 | |||
bdbebe11c4 | |||
51ca137102 | |||
f0ff4eeab7 | |||
93d103e5ad | |||
b87d77d5e3 | |||
ffbd8cfb76 | |||
2d1664574d | |||
5d3aa1f625 | |||
3fa53c7654 | |||
84ecf55ff8 | |||
72ad40ecfc | |||
2f48898235 | |||
39758c8cf4 | |||
ecc5509007 | |||
0e75b3e957 | |||
7b4c80216d | |||
70bbc18054 | |||
09fcef23a4 | |||
333f09073a | |||
58cb33aa77 | |||
9654fa24cc | |||
7843b37a7e | |||
1db9128afc | |||
c03d3a8b92 | |||
80b7455602 | |||
1fcf5aa49b | |||
640803418d | |||
56fa0b0302 | |||
9c10eef0e3 | |||
9fd5e43115 | |||
d5bd9b8eaa | |||
5b1203a1c6 | |||
fbe79eee2e | |||
ffa21fc1e0 | |||
5faf1f5bb0 | |||
e15efb6537 | |||
cd1702d53c | |||
5a6ca8a0a4 | |||
82d184cf90 | |||
5162bb604b | |||
3aff2a7a9d | |||
cae565761d | |||
c2b6cee9a2 | |||
84e7b1f41c | |||
7ebed1e6d2 | |||
2e68122b8d | |||
899a3f4cfc | |||
52d49a4ab6 | |||
75f705125f | |||
722ddf8a47 | |||
524d21631e | |||
86e6060217 | |||
b5c7fad876 | |||
5f3ca0fe69 | |||
ddd9bea786 | |||
6aac99e7ea | |||
da763a7c81 | |||
6cb46c3168 | |||
610e963d22 | |||
96b8ed43e0 | |||
812c28cd33 | |||
3f7312653a | |||
c89d3992c7 | |||
d9394685c9 | |||
b140158b24 | |||
4f5b0bd21a | |||
1d17d89ccb | |||
b6074da7c4 | |||
c0fdb4aff7 | |||
e00c9cc449 | |||
423ca46672 | |||
66ed9d94a7 | |||
a68ef9d372 | |||
cb389123c5 | |||
62cca0ed50 | |||
c99e3c0b41 | |||
302c51ccfa | |||
1e34bc2159 | |||
24f6f4789d | |||
2de11a4067 | |||
d83a2f9919 | |||
bf36b4c2e6 | |||
daa0b739a3 | |||
5e1b0d9b50 | |||
917742847a | |||
9460e3202f | |||
5f388a7c6b | |||
39d5de7eb4 | |||
0fb1ef881c | |||
8230769191 | |||
19036a71cb | |||
40f3fbf9c5 | |||
1127fb0195 | |||
df81efe6bc | |||
784ca3691b | |||
0fa4241ce6 | |||
5063d276de | |||
81d0e43f0f | |||
1637a9007d | |||
ce0f01cf46 | |||
dd579d4f5b | |||
5703d8cc6d | |||
b8768ae9fe | |||
54dc2c88bf | |||
dba01b8c18 | |||
b7b3eb7019 | |||
430e2ab826 | |||
87383a2ec8 | |||
f63bfe1dea | |||
ff2021c0aa | |||
51f7e07080 | |||
7797e067a5 | |||
cf09a21f1e | |||
914294927d | |||
bd0455c413 | |||
97f3d937ee | |||
ff102bfe73 | |||
e31a350398 | |||
43a8f29794 | |||
bbca783b20 | |||
6d4fe52f8e | |||
ecb08a54fb | |||
7bd4524fd8 | |||
40f08d56f6 | |||
fe61e35146 | |||
be3f74d71f | |||
8dcfa816e3 | |||
ae05e22670 | |||
79a4fa0407 | |||
bb958b969a | |||
02030454d1 | |||
e396b0b614 | |||
91cac6b743 | |||
2a6015d890 | |||
049b094627 | |||
077d062e19 | |||
4ad483db71 | |||
3edb8ad0c2 | |||
769f41afe6 | |||
5a9d943a6c | |||
44fb246288 | |||
3cc7498e66 | |||
84ce883285 | |||
f8dd9df285 | |||
860cf6a658 | |||
a24a50ce44 | |||
fc9187297b | |||
eb26df874d | |||
27cbb1e57b | |||
66aa79f90f | |||
8b9a70b386 | |||
872a2d15e2 | |||
7085567a08 | |||
19b07a8a8c | |||
0ed78a4806 | |||
ca8fb4663f | |||
ec34ce481e | |||
b0c4089e26 | |||
b23148c71d | |||
c9a336a0a5 | |||
0744426c15 | |||
e6f4bd5531 | |||
ab6f4799c9 | |||
f4beecead3 | |||
17e6d56bb7 | |||
29881b73f9 | |||
8c30582b9a | |||
06de8abb44 | |||
a65ee65606 | |||
a6f49168b7 | |||
1d1e6121fa | |||
30b86499cd | |||
bb8daa81d2 | |||
763ce08902 | |||
a4e1055d86 | |||
ed89afd1bd | |||
13e26b4a1c | |||
3be56767a1 | |||
d184019a8e | |||
6df542a162 | |||
96ca1d1a37 | |||
6b585822f1 | |||
c81c57daa0 | |||
f28be9cb02 | |||
6582f67ed5 | |||
d138c482d9 | |||
fc2d962cad | |||
c541ae0347 | |||
d6e79ed0a7 | |||
7ec20fe60c | |||
f3464c5095 | |||
32512fecbc | |||
1d7fcd47ef | |||
f156c591ad | |||
fb6ca0d61e | |||
a4662984a7 | |||
6cccf1fb23 | |||
39f32acd5b | |||
3d60d10f8e | |||
8cbc11dff0 | |||
858f007e21 | |||
02f830e472 | |||
d32ef0bb9b | |||
6ae103373e | |||
b2a82b9809 | |||
73b6898d76 | |||
be9336228c | |||
62546a779a | |||
b4afc3b4b2 | |||
4415a5fc25 | |||
bddb02f6ba | |||
f2c3f30224 | |||
90ff16009c | |||
4cd97536c5 | |||
4f2a5fdc1f | |||
c35d13270a | |||
6c9bb31da7 | |||
d5f6be4428 | |||
77c6e8702d | |||
c4fc10f552 | |||
4b0f8eb571 | |||
f94d2c57db | |||
fc2d6f5f09 | |||
c6ab6f64e9 | |||
5315771118 | |||
39b8bff6a2 | |||
c2a7c8ebde | |||
32a8c71bf1 | |||
f35257fc65 | |||
3ab1fa7c97 | |||
551910abce | |||
170f8afafd | |||
20c08b1929 | |||
e943c31ef9 | |||
601b979d4e | |||
973808b392 | |||
25811b742f | |||
2d57d7cad7 | |||
687a62656b | |||
5a7c33d9b9 | |||
e66c98fbfe | |||
2897bfcb77 | |||
b8aca7badc | |||
edb2506083 | |||
eceb7f14a4 | |||
b165f41266 | |||
9dd15e550d | |||
e94984901d | |||
55d682fdfc | |||
c231a9f851 | |||
bbde471e99 | |||
51c5409f12 | |||
1ff96d5530 | |||
5e099d0e3b | |||
c27e8ff513 | |||
632ed5b4b3 | |||
407b5c1441 | |||
fd2050bc1f | |||
c615497a9e | |||
e48b2f1dff | |||
44cd0a6d55 | |||
196b6ffbbc | |||
6cf01e3db0 | |||
204f2d8adc | |||
93853bd6b6 | |||
4095b48a82 | |||
3ab1a68b94 | |||
7675d78d0d | |||
1b2fa9bda6 | |||
8819bbfa44 | |||
2e6ff0ac07 | |||
8d9a532424 | |||
6f5e3e2386 | |||
fb63f1eee7 | |||
8c65812fa0 | |||
adfc6415b3 | |||
bb2a1c7c62 | |||
3d63269286 | |||
6dde8d2a88 | |||
d0c6bcff7d | |||
b938bc9698 | |||
2cb877d61b | |||
77368e370b | |||
2e6589ea74 | |||
90367b0f9c | |||
97a350c482 | |||
57d7708ae6 | |||
913d39a9a2 | |||
fa83742386 | |||
94539b8ebb | |||
bccbe30074 | |||
4e954eef50 | |||
6eeafb64f5 | |||
d7184e9c99 | |||
be8016c02c | |||
11d4e05518 | |||
fdccadda04 | |||
ccb1c73cf3 | |||
f3168f0dc2 | |||
b4210cfb33 | |||
6e0a66292d | |||
71856397de | |||
568cdeca36 | |||
4e64fb3095 | |||
f33f26987b | |||
99fa7fbec6 | |||
315931a032 | |||
8c63fb0ce2 | |||
31b7e18aad | |||
b51ca8b27c | |||
13af3d7869 | |||
c91de6f7ab | |||
d961b5f8b1 | |||
7d8141f62b | |||
6ef5d793f4 | |||
c5c97366b8 | |||
124692db8e | |||
72461cb678 | |||
6f84f3ce06 | |||
cbe21d94c9 | |||
9c8c462089 | |||
cc7c67c494 | |||
0e14ed20e6 | |||
d620535246 | |||
350d8a1363 | |||
23e80ec72b | |||
fd052f1b38 | |||
865f5d271a | |||
d1293f5949 | |||
2a354288bf | |||
f20e82a25e | |||
4a7899ce59 | |||
2adc368307 | |||
2bb57d2c4d | |||
e649ac448b | |||
337c93bb6c | |||
f5624cf259 | |||
6afd9b950d | |||
94c46d4a43 | |||
dcf072905c | |||
c67aeddb3b | |||
f125079aa5 | |||
0ddd9e5f4c | |||
0074cf5a19 | |||
19082db9a6 | |||
109aeb7796 | |||
3e06a11017 | |||
dafd438982 | |||
063fd63fe2 | |||
5e27a93df9 | |||
c428195127 | |||
1fba23a144 | |||
3cc1e2b4bb | |||
77b05fa6e0 | |||
3553065ce2 | |||
167c06225f | |||
3420c04735 | |||
973282ab59 | |||
7c04a96639 | |||
b2db1f4a6c | |||
e3e0960ed8 | |||
78ad153c47 | |||
fa6ba6bdce | |||
8f302e6eeb | |||
93d2677b34 | |||
1d02535158 | |||
955e40b0fc | |||
b4367843cd | |||
76fd69544d | |||
99b582a005 | |||
8e0e5ed5c4 | |||
234bc6e5a0 | |||
b0131843ae | |||
c32bda66b5 | |||
6d726df1dc | |||
5b5a99f512 | |||
a11dd1e4e6 | |||
2190c59d73 | |||
c6e06e25a8 | |||
a559eb4fab | |||
d1110aefc4 | |||
9a5c63f620 | |||
d4e9195a6c | |||
e9d2b9f53c | |||
c124cafe3c | |||
24f0d72aae | |||
8780d69be5 | |||
1853f5bcfd | |||
3f6caf18b4 | |||
97aa6e64bc | |||
f1bfd7d4aa | |||
70ede5a370 | |||
d1d06840fd | |||
5f62418513 | |||
5e6dab9bf1 | |||
ef65a94e4c | |||
c2e00ecf26 | |||
40f3d91ebb | |||
fae4b7e42d | |||
eb700e34ba | |||
6828d985d2 | |||
05a2132295 | |||
dbee390bba | |||
0b5ae4edf8 | |||
bd741cd500 | |||
4d7bc0f92b | |||
dac63d4401 | |||
8ed2ce3e3d | |||
c95f140fb4 | |||
4e21cc890f | |||
cf3594962d | |||
9fa38c2840 | |||
2da8bbf214 | |||
c984206f8e | |||
4a8b87d50c | |||
a5a08a7282 | |||
80f8469154 | |||
490137adc3 | |||
d2959ddc3f | |||
6556156486 | |||
0095777a63 | |||
d8fc177b2d | |||
fcd5b2a503 | |||
0a5312e81b | |||
7b67cf1581 | |||
2975b811d0 | |||
ecb071ee88 | |||
624aa27e31 | |||
a213ac51cd | |||
db4a817ded | |||
2407e9be46 | |||
50973b5c06 | |||
8165e49300 | |||
77749dd7f9 | |||
ef7e4cf610 | |||
a7b2bf148b | |||
3de78063f2 | |||
601d3c0ee3 | |||
3bcbd8c876 | |||
e33be52b97 | |||
f513580525 | |||
ca2fde0b57 | |||
b442552146 | |||
9aee319607 | |||
d112e1a415 | |||
9cd6ab51ac | |||
6477f7d43f | |||
65d1e6e089 | |||
6aff527456 | |||
f32b896bb1 | |||
58624ebf9d | |||
db8355c477 | |||
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 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
app/pkg-temp
|
||||||
|
app/build
|
||||||
|
app/pkg-mavencentral
|
73
.github/workflows/ant.yml
vendored
Normal file
73
.github/workflows/ant.yml
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Mostly copied from https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-ant
|
||||||
|
# zlatinb
|
||||||
|
|
||||||
|
name: Java CI
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: GetText
|
||||||
|
run: sudo apt install gettext git
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '11'
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name : Generate override.properties
|
||||||
|
run: |
|
||||||
|
rm -f override.properties
|
||||||
|
echo "build.built-by=GitHub Actions" >> override.properties
|
||||||
|
echo "noExe=true" >> override.properties
|
||||||
|
- name: build with Gradle
|
||||||
|
run: |
|
||||||
|
echo "i2psrc=$HOME/i2p.i2p" > routerjars/local.properties
|
||||||
|
git clone -b i2p-2.4.0 https://github.com/i2p/i2p.i2p "$HOME/i2p.i2p"
|
||||||
|
./gradlew assembleDebug
|
||||||
|
find . -name '*.apk'
|
||||||
|
- name: Upload i2p-debug-${{ github.sha }}.apk
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: i2p-debug.apk
|
||||||
|
path: ./app/build/outputs/apk/free/debug/app-free-debug.apk
|
||||||
|
|
||||||
|
trunk:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: GetText
|
||||||
|
run: sudo apt install gettext git
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '11'
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name : Generate override.properties
|
||||||
|
run: |
|
||||||
|
rm -f override.properties
|
||||||
|
echo "build.built-by=GitHub Actions" >> override.properties
|
||||||
|
echo "noExe=true" >> override.properties
|
||||||
|
grep -v I2P_ gradle.properties > gradle.properties.update
|
||||||
|
echo "I2P_VERSION=2.5.0-1" >> gradle.properties.update
|
||||||
|
echo "I2P_ANDROID_VERSION=2.5.0-1" >> gradle.properties.update
|
||||||
|
cp -v gradle.properties.update gradle.properties
|
||||||
|
- name: build with Gradle
|
||||||
|
run: |
|
||||||
|
echo "i2psrc=$HOME/i2p.i2p" > routerjars/local.properties
|
||||||
|
git clone -b master https://github.com/i2p/i2p.i2p "$HOME/i2p.i2p"
|
||||||
|
bash -c "cd $HOME/i2p.i2p && ./installer/resources/maven-dev-release.sh 1"
|
||||||
|
./gradlew assembleDebug
|
||||||
|
find . -name '*.apk'
|
||||||
|
- name: Upload i2p-debug-${{ github.sha }}.apk
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: i2p-debug-trunk-${{ github.sha }}.apk
|
||||||
|
path: ./app/build/outputs/apk/free/debug/app-free-debug.apk
|
22
.github/workflows/github-sync.yml
vendored
Normal file
22
.github/workflows/github-sync.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# File: .github/workflows/github-sync.yml for i2p-hackers/i2p.android.base
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "*/5 * * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
repo-sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: repo-sync
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
uses: repo-sync/github-sync@v2
|
||||||
|
with:
|
||||||
|
source_repo: "https://i2pgit.org/i2p-hackers/i2p.android.base"
|
||||||
|
source_branch: "master"
|
||||||
|
destination_branch: "master"
|
||||||
|
github_token: ${{ secrets.PAT }}
|
62
.github/workflows/release.yml
vendored
Normal file
62
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
#on: [push]
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Sequence of patterns matched against refs/tags
|
||||||
|
tags:
|
||||||
|
- 'i2p-android-*.*.*' # Release i2p-firefox-1.2.3
|
||||||
|
- 'i2p-android-*.*.*-*' # Release i2p-firefox-1.2.3
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
sparse-checkout: |
|
||||||
|
CHANGELOG.md
|
||||||
|
sparse-checkout-cone-mode: false
|
||||||
|
- name: sleep 6 minutes
|
||||||
|
run: |
|
||||||
|
echo "sleeping 6 minutes to wait for artifacts"
|
||||||
|
sleep 1m
|
||||||
|
echo "sleeping 5 minutes to wait for artifacts"
|
||||||
|
sleep 1m
|
||||||
|
echo "sleeping 4 minutes to wait for artifacts"
|
||||||
|
sleep 1m
|
||||||
|
echo "sleeping 3 minutes to wait for artifacts"
|
||||||
|
sleep 1m
|
||||||
|
echo "sleeping 2 minutes to wait for artifacts"
|
||||||
|
sleep 1m
|
||||||
|
echo "sleeping 1 minutes to wait for artifacts"
|
||||||
|
sleep 1m
|
||||||
|
- name: Download artifacts
|
||||||
|
id: download-artifact
|
||||||
|
uses: dawidd6/action-download-artifact@v3
|
||||||
|
with:
|
||||||
|
skip_unpack: true
|
||||||
|
workflow: ant.yml
|
||||||
|
if_no_artifact_found: fail
|
||||||
|
# remove .zip file extension
|
||||||
|
#- run: for f in *.zip; do unzip "$f"; rm "$f"; done
|
||||||
|
- run: unzip i2p-debug.apk.zip
|
||||||
|
- run: echo "" | tee -a RELEASE.md
|
||||||
|
- run: echo "## Checksums" | tee -a RELEASE.md
|
||||||
|
- run: echo "" | tee -a RELEASE.md
|
||||||
|
- run: echo '```' | tee -a RELEASE.md
|
||||||
|
- run: sha256sum * | tee -a RELEASE.md
|
||||||
|
- run: echo '```' | tee -a RELEASE.md
|
||||||
|
- run: echo "" | tee -a RELEASE.md
|
||||||
|
- run: echo '```' | tee -a RELEASE.md
|
||||||
|
- run: file * | tee -a RELEASE.md
|
||||||
|
- run: echo '```' | tee -a RELEASE.md
|
||||||
|
- run: echo "" | tee -a RELEASE.md
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
artifacts: "*"
|
||||||
|
bodyFile: "RELEASE.md"
|
95
.github/workflows/sign.yml
vendored
Normal file
95
.github/workflows/sign.yml
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Mostly copied from https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-ant
|
||||||
|
# zlatinb
|
||||||
|
|
||||||
|
name: Java Signed CI
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-signed:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: GetText
|
||||||
|
run: sudo apt install gettext git
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '11'
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name: Generate override.properties
|
||||||
|
env:
|
||||||
|
DEV_SIGNING_KEY: ${{ secrets.DEV_SIGNING_KEY }}
|
||||||
|
DEV_PASSWORD: ${{ secrets.DEV_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
rm -f override.properties
|
||||||
|
mv etc/github.gradle.properties gradle.properties
|
||||||
|
mv etc/github.signing.properties signing.properties
|
||||||
|
mkdir -p $HOME/keystores/
|
||||||
|
echo $DEV_SIGNING_KEY | base64 --decode > $HOME/keystores/android-release.keystore
|
||||||
|
echo "KEY_ALIAS=mykey" >> signing.properties
|
||||||
|
echo "KEY_PASSWORD=$DEV_PASSWORD" >> signing.properties
|
||||||
|
echo "build.built-by=GitHub Actions" >> override.properties
|
||||||
|
echo "noExe=true" >> override.properties
|
||||||
|
- name: build with Gradle
|
||||||
|
run: |
|
||||||
|
echo "i2psrc=$HOME/i2p.i2p" > routerjars/local.properties
|
||||||
|
git clone -b i2p-2.4.0 https://github.com/i2p/i2p.i2p "$HOME/i2p.i2p"
|
||||||
|
./gradlew assembleRelease
|
||||||
|
find . -name '*.apk'
|
||||||
|
ls -lah $HOME/keystores/android-release.keystore
|
||||||
|
ls -d $HOME
|
||||||
|
- name: Upload i2p-debug-${{ github.sha }}.apk
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: i2p-debug.apk
|
||||||
|
path: ./app/build/outputs/apk/free/debug/app-free-debug.apk
|
||||||
|
|
||||||
|
trunk-signed:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: GetText
|
||||||
|
run: sudo apt install gettext git
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '11'
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name: Generate override.properties
|
||||||
|
env:
|
||||||
|
DEV_SIGNING_KEY: ${{ secrets.DEV_SIGNING_KEY }}
|
||||||
|
DEV_PASSWORD: ${{ secrets.DEV_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
rm -f override.properties
|
||||||
|
echo "build.built-by=GitHub Actions" >> override.properties
|
||||||
|
echo "noExe=true" >> override.properties
|
||||||
|
mv etc/github.gradle.properties gradle.properties
|
||||||
|
mv etc/github.signing.properties signing.properties
|
||||||
|
mkdir -p $HOME/keystores/
|
||||||
|
echo $DEV_SIGNING_KEY | base64 --decode > $HOME/keystores/android-release.keystore
|
||||||
|
echo "KEY_ALIAS=mykey" >> signing.properties
|
||||||
|
echo "KEY_PASSWORD=$DEV_PASSWORD" >> signing.properties
|
||||||
|
grep -v I2P_ gradle.properties > gradle.properties.update
|
||||||
|
echo "I2P_VERSION=2.5.0-1" >> gradle.properties.update
|
||||||
|
echo "I2P_ANDROID_VERSION=2.5.0-1" >> gradle.properties.update
|
||||||
|
cp -v gradle.properties.update gradle.properties
|
||||||
|
- name: build with Gradle
|
||||||
|
run: |
|
||||||
|
echo "i2psrc=$HOME/i2p.i2p" > routerjars/local.properties
|
||||||
|
git clone -b master https://github.com/i2p/i2p.i2p "$HOME/i2p.i2p"
|
||||||
|
bash -c "cd $HOME/i2p.i2p && ./installer/resources/maven-dev-release.sh 1"
|
||||||
|
./gradlew assembleRelease
|
||||||
|
find . -name '*.apk'
|
||||||
|
ls -lah $HOME/keystores/android-release.keystore
|
||||||
|
ls -d $HOME
|
||||||
|
- name: Upload i2p-debug-${{ github.sha }}.apk
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: i2p-debug-trunk-${{ github.sha }}.apk
|
||||||
|
path: ./app/build/outputs/apk/free/debug/app-free-debug.apk
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
etc/docker.signing.properties
|
||||||
|
signing.properties
|
||||||
|
.idea
|
||||||
|
.idea/
|
||||||
|
.gradle/
|
||||||
|
local.properties
|
||||||
|
override.properties
|
||||||
|
build
|
51
.mtn-ignore
51
.mtn-ignore
@ -1,51 +0,0 @@
|
|||||||
# Just to try and prevent some noob disasters.
|
|
||||||
# Use mtn add --no-respect-ignore foo.jar to ignore this ignore list
|
|
||||||
_jsp\.java$
|
|
||||||
\.bz2$
|
|
||||||
\.class$
|
|
||||||
\.diff$
|
|
||||||
\.exe$
|
|
||||||
\.fba$
|
|
||||||
\.gz$
|
|
||||||
\.jar$
|
|
||||||
\.out$
|
|
||||||
\.patch$
|
|
||||||
\.sig$
|
|
||||||
\.sud$
|
|
||||||
\.su2$
|
|
||||||
\.tar$
|
|
||||||
\.war$
|
|
||||||
\.zip$
|
|
||||||
^\.
|
|
||||||
^build/
|
|
||||||
^pkg-temp/
|
|
||||||
~$
|
|
||||||
/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
|
|
28
.tx/config
28
.tx/config
@ -1,18 +1,18 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
lang_map = pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, zh_CN: zh
|
lang_map = pt_BR: pt-rBR, yi: ji, zh_CN: zh, zh_TW: zh-rTW, id: in, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA: uk, he: iw, es_AR: es-rAR
|
||||||
|
|
||||||
[I2P.android]
|
[o:otf:p:I2P:r:android]
|
||||||
file_filter = app/src/main/res/values-<lang>/strings.xml
|
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||||
source_file = app/src/main/res/values/strings.xml
|
source_file = app/src/main/res/values/strings.xml
|
||||||
source_lang = en
|
source_lang = en
|
||||||
type = ANDROID
|
type = ANDROID
|
||||||
minimum_perc = 50
|
minimum_perc = 22
|
||||||
|
|
||||||
[I2P.android_lib_client]
|
[o:otf:p:I2P:r:android_lib_helper]
|
||||||
file_filter = client/src/main/res/values-<lang>/strings.xml
|
file_filter = lib/helper/src/main/res/values-<lang>/strings.xml
|
||||||
source_file = client/src/main/res/values/strings.xml
|
source_file = lib/helper/src/main/res/values/strings.xml
|
||||||
source_lang = en
|
source_lang = en
|
||||||
type = ANDROID
|
type = ANDROID
|
||||||
minimum_perc = 50
|
minimum_perc = 50
|
||||||
|
|
||||||
|
277
CHANGELOG
Normal file
277
CHANGELOG
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
2.9.0
|
||||||
|
* Update I2P Library
|
||||||
|
|
||||||
|
2.8.0
|
||||||
|
* Update I2P Library
|
||||||
|
* Migrate to AndroidX
|
||||||
|
* Migrate to MagicIndicator
|
||||||
|
|
||||||
|
2.7.1
|
||||||
|
* Update I2P Library
|
||||||
|
* Roll back ViewPageIndicator version, fixes crash, causes other issues
|
||||||
|
|
||||||
|
2.7.0
|
||||||
|
* Update I2P Library
|
||||||
|
|
||||||
|
2.6.0
|
||||||
|
* Update I2P Library
|
||||||
|
|
||||||
|
2.5.0
|
||||||
|
* Update I2P Library
|
||||||
|
|
||||||
|
2.4.0
|
||||||
|
* Update I2P Library
|
||||||
|
|
||||||
|
2.3.0
|
||||||
|
* Update I2P Library
|
||||||
|
|
||||||
|
2.2.0
|
||||||
|
* Add blocklist feed support
|
||||||
|
* Fix translations on stats page
|
||||||
|
* Various bugfixes
|
||||||
|
|
||||||
|
2.1.0-1
|
||||||
|
* adds support for adding base32 addresses to the local addressbook
|
||||||
|
* adds support for destinations with ports in the string to i2ptunnel
|
||||||
|
* adds support for remote applications launching I2P, via MR#3 from @RyeMantis
|
||||||
|
|
||||||
|
2.1.0
|
||||||
|
* Upgrades router to version 2.1.0
|
||||||
|
* Adds jcenter repository back to app/ target, removes jcenter from other targets
|
||||||
|
* explicitly set android.useAndroidX
|
||||||
|
|
||||||
|
2.0.1
|
||||||
|
* Fixes gitlab#49 by applying MUTABLE or IMMUTABLE flags across all PendingIntents
|
||||||
|
|
||||||
|
2.0.0
|
||||||
|
* Updates router to version 2.0.0
|
||||||
|
* Updates gradle plugin
|
||||||
|
* Remove Firefox from supported browsers list due to lack of about:config support
|
||||||
|
* Add Privacy Browser to supported browsers list and write guide for it
|
||||||
|
|
||||||
|
1.9.1
|
||||||
|
* Switches back to mavenCentral builds
|
||||||
|
* Fixes build process for F-Droid main
|
||||||
|
* Using tag i2p.i2p:i2p-android-1.9.0
|
||||||
|
* Allow b32 and blinded b32 to validate in I2PTunnel UI
|
||||||
|
|
||||||
|
1.9.0
|
||||||
|
* Updates router to version 1.9.0
|
||||||
|
* Using tag i2p.i2p:i2p-android-1.9.0
|
||||||
|
|
||||||
|
1.8.2
|
||||||
|
* This update fixes a bug where Family Keys are incorrectly handled by the Android router, leading
|
||||||
|
to a crash.
|
||||||
|
* This update fixes a bug wherein incorrect type-checking caused a crash when using the Network
|
||||||
|
Config page for some users.
|
||||||
|
|
||||||
|
1.8.1
|
||||||
|
* This update fixes a bug where certificates were unpacked to the wrong location on new installs,
|
||||||
|
resulting in the router failing to start.
|
||||||
|
* Decouples the Android version from the I2P library version to allow for android point
|
||||||
|
releases with less pain.
|
||||||
|
|
||||||
|
1.8.0
|
||||||
|
* This release updates the underlying I2P libraries to I2P version 1.8.0
|
||||||
|
* The SAM API now operates in "Interactive Authentication" mode. Users will be prompted via
|
||||||
|
the notificationArea to approve or ignore new SAM connections initiated by external apps.
|
||||||
|
* The network config menu now features a drop-down configuration instead of a slider configuration
|
||||||
|
(since reversed)
|
||||||
|
|
||||||
|
1.7.1
|
||||||
|
* This release updates the underlying I2P libraries to I2P version 1.7.1
|
||||||
|
|
||||||
|
1.7.0
|
||||||
|
* This release updates the underlying I2P libraries to I2P version 1.7.0
|
||||||
|
* Refactor of the unzipResourceToDir function
|
||||||
|
|
||||||
|
1.6.0
|
||||||
|
* This release updates the underlying I2P libraries to I2P version 1.6.0
|
||||||
|
|
||||||
|
1.5.0
|
||||||
|
* This release updates the underlying I2P libraries to I2P version 1.5.0
|
||||||
|
|
||||||
|
0.9.50 2021-05-18
|
||||||
|
* This release updates the underlying I2P libraries to I2P version 0.9.50
|
||||||
|
|
||||||
|
0.9.48 2020-12-02
|
||||||
|
* This release updates the underlying I2P libraries to I2P version 0.9.48
|
||||||
|
* Updates to browser configuration documentation
|
||||||
|
|
||||||
|
0.9.47-1 2020-10-29
|
||||||
|
* This release fixes a number of bugs arising from a misconfigured bootclasspath
|
||||||
|
* Fix a battery-management issue arising on some phones
|
||||||
|
|
||||||
|
0.9.47 / 2020-08-26
|
||||||
|
* Notification bug-fixes on platforms >8.0
|
||||||
|
|
||||||
|
0.9.46 / 2020-06-03
|
||||||
|
* catch ActivityNotFound exception in MainActivity
|
||||||
|
|
||||||
|
0.9.45 / 2020-03-07
|
||||||
|
* No significant changes
|
||||||
|
|
||||||
|
0.9.44 / 2019-12-03
|
||||||
|
* Updated translations
|
||||||
|
* Bumped target sdk version to 28, enforced by google
|
||||||
|
|
||||||
|
0.9.43-1 / 2019-10-28
|
||||||
|
* Fix crash at startup in TCG
|
||||||
|
|
||||||
|
0.9.43 / 2019-10-27
|
||||||
|
* Save state in background thread (tickets #2595, #2632)
|
||||||
|
* Fix ISE in language dialog (ticket #2631)
|
||||||
|
* Fix NPE in create tunnel (ticket #2629)
|
||||||
|
* Update logo after router killed in background
|
||||||
|
* Fix message in tunnels tabs when tunnels not up yet
|
||||||
|
* Hide tunnel actions while TCG is starting
|
||||||
|
* Add battery permissions dialog (ticket #2607)
|
||||||
|
|
||||||
|
0.9.42 / 2019-08-28
|
||||||
|
* Possible fix for tunnel edit dialog crash (ticket #2598)
|
||||||
|
|
||||||
|
0.9.41 / 2019-07-03
|
||||||
|
* New 64 bit libjbigi (ticket #2503)
|
||||||
|
* Update 32 bit jbigi to GMP 6.1.2
|
||||||
|
* Fix for client tunnels not starting after reseed
|
||||||
|
* UPnP Fixes (ticket #2499)
|
||||||
|
* WebView crash fixes (ticket #2390)
|
||||||
|
* i2ptunnel crash fix (ticket #2552)
|
||||||
|
* i2ptunnel ANR fix (ticket #2491)
|
||||||
|
* Browser help pages improvements (tickets #2521, 2523)
|
||||||
|
* Code cleanups and fixes
|
||||||
|
* Update visibility of floating menu items
|
||||||
|
* Put our router info at top of list
|
||||||
|
* Table layout cleanups
|
||||||
|
* Fixes for threads remaining after router stop
|
||||||
|
* Remove BOB
|
||||||
|
* Remove welterde IRC tunnel
|
||||||
|
* Various lint fixes
|
||||||
|
* New translations: Add cs, da, el
|
||||||
|
* Add missing translations to menu: ar, fi, gl, hu, zh_TW
|
||||||
|
|
||||||
|
0.9.40 / 2019-05-10
|
||||||
|
* Open local I2CP socket for 3rd party apps
|
||||||
|
* Fix News URLs
|
||||||
|
* Numerous bug fixes (see trac)
|
||||||
|
|
||||||
|
0.9.39 / 2019-03-23
|
||||||
|
* Set App ID
|
||||||
|
|
||||||
|
0.9.38 / 2019-01-27
|
||||||
|
|
||||||
|
0.9.37 / 2018-10-10
|
||||||
|
|
||||||
|
0.9.36 / 2018-08-27
|
||||||
|
|
||||||
|
0.9.35 / 2018-07-03
|
||||||
|
|
||||||
|
0.9.34 / 2018-04-25
|
||||||
|
|
||||||
|
0.9.33 / 2018-02-18
|
||||||
|
* Translation updates
|
||||||
|
|
||||||
|
0.9.32 / 2017-11-28
|
||||||
|
* Fixed "Application Not Responding" error when restarting all tunnels
|
||||||
|
* Fixed crashes when:
|
||||||
|
* opening the console menu
|
||||||
|
* starting the router
|
||||||
|
* starting the router for the first time
|
||||||
|
* viewing tunnels
|
||||||
|
* viewing the addressbook
|
||||||
|
* opening the addressbook menu
|
||||||
|
* configuring addressbook subscriptions
|
||||||
|
* using a configuration wizard
|
||||||
|
* loading a B64 Destination from file
|
||||||
|
* viewing tunnel settings
|
||||||
|
* saving tunnel settings
|
||||||
|
* installing a tunnel's recommended app without a market app
|
||||||
|
* installing a browser without a market app
|
||||||
|
* rotating the screen while using the built-in browser
|
||||||
|
* Added a "sync" icon to more clearly indicate tunnel "starting" status
|
||||||
|
* Updated Firefox browser config instructions for Firefox Quantum
|
||||||
|
* Translation updates
|
||||||
|
|
||||||
|
0.9.31 / 2017-08-19
|
||||||
|
* Fixed various crashes in the Tunnels UI
|
||||||
|
* Updated Firefox browser config instructions
|
||||||
|
* Minor bug fixes
|
||||||
|
* Dependency and translation updates
|
||||||
|
|
||||||
|
0.9.30 / 2017-05-20
|
||||||
|
* Fixed crashes when creating or deleting tunnels, or adding names to the
|
||||||
|
private addressbook
|
||||||
|
* Minor bug fixes
|
||||||
|
* Dependency and translation updates
|
||||||
|
|
||||||
|
0.9.29 / 2017-03-27
|
||||||
|
* Dependency and translation updates
|
||||||
|
|
||||||
|
0.9.28 / 2017-01-02
|
||||||
|
* Bug fixes and translation updates
|
||||||
|
|
||||||
|
0.9.27 / 2016-11-20 / 64ff68efe98c345acb6ba1d0432fa49d1d650358
|
||||||
|
* Removed kytv's IRC server from default tunnel list
|
||||||
|
* Translation updates
|
||||||
|
|
||||||
|
0.9.26 / 2016-06-13 / b003272c8b504bb0d904edca2e95359a57c9a52c
|
||||||
|
* Fixed "I2CP already listening" bug
|
||||||
|
* Fixed crash when adding tunnel to empty list
|
||||||
|
* Translation updates
|
||||||
|
|
||||||
|
0.9.25 / 2016-04-17 / 46d45a878a2b73394b26ca27dbe6c696dedcf1c3
|
||||||
|
* Fixed a bug on Samsung Android 4.2 devices
|
||||||
|
* Dependency improvements
|
||||||
|
* Translation updates
|
||||||
|
|
||||||
|
0.9.22 / 2015-10-10 / 0f73ef90b81e2cf3d55f0ea2b0a16e1f10da40ad
|
||||||
|
* Updated browser config guide
|
||||||
|
* Bug fixes and translation updates
|
||||||
|
|
||||||
|
0.9.20 / 2015-06-18 / 5fdaabeb5fa955caac90f1390adbdeaeae42fdf1
|
||||||
|
* Simplified the main interface
|
||||||
|
* Language can be configured
|
||||||
|
* Tunnels can now be edited
|
||||||
|
* Material design improvements
|
||||||
|
* Better support for tablets
|
||||||
|
* Improved graph rendering
|
||||||
|
* Bug fixes and translation updates
|
||||||
|
|
||||||
|
0.9.19.1 / 2015-04-15 / ed86e7e85161dbe3f15932fd4d195c551f8e2c71
|
||||||
|
* Fixed crash when opening advanced settings
|
||||||
|
|
||||||
|
0.9.19 / 2015-04-13 / 3cfb748946a5876dc06d5f81d811b142a88846f7
|
||||||
|
* Made internal state handling more stable
|
||||||
|
* Added graceful shutdown support
|
||||||
|
* Updated libjbigi to use GMP 6.0.0
|
||||||
|
* New libjbigi binary for armeabi-v7a to speed up newer devices
|
||||||
|
* 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
|
11
DEVELOPMENT-README.md
Normal file
11
DEVELOPMENT-README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Development Readme
|
||||||
|
|
||||||
|
## How to build development builds of the router core for android?
|
||||||
|
|
||||||
|
Check the RELEASE-PROCESS.md file for general information about how to build and to bump the version.
|
||||||
|
|
||||||
|
In your i2p.i2p codebase checkout, execute `./installer/resources/maven-dev-release.sh` with your build number as first argument.
|
||||||
|
The script locates itself and uses the same codebase as it's in, to produce the maven builds which will be locally installed.
|
||||||
|
|
||||||
|
Next, add the build number to the gradle.properties and build the android build as usual.
|
||||||
|
|
91
DOCKER.md
Normal file
91
DOCKER.md
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
Docker Build Instructions
|
||||||
|
=========================
|
||||||
|
|
||||||
|
It is possible to build a container with a pre-installed environment for
|
||||||
|
correctly compiling an I2P for Android development build. Unlike the i2p.i2p
|
||||||
|
container, zero attempt has been made to optimize the size of the container,
|
||||||
|
as it contains a copy of the latest Android SDK, toolchains, and Android NDK,
|
||||||
|
which it must download. To save time, this is cached locally. It is likely to
|
||||||
|
take up to 30 GB of disk space to compile in this way, however, it is very easy
|
||||||
|
and convenient compared to the steps in RELEASE-PROCESS.md and may make
|
||||||
|
building Android reproducibly easier in the future.
|
||||||
|
|
||||||
|
Container dependencies
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- `menny/android_ndk` (third-party image) (reviewed by idk) (depends on menny/android_sdk
|
||||||
|
- `menny/android_sdk` (third-party image) (reviewed by idk) (depends on ubuntu/18.04)
|
||||||
|
- `ubuntu/18.04` (official docker container) (base container)
|
||||||
|
|
||||||
|
Build the container locally
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
docker build -t i2p.android.base .
|
||||||
|
|
||||||
|
To build the container. It will have a lot to download the first time, so it may take
|
||||||
|
a while to complete.
|
||||||
|
|
||||||
|
Run an Android build in the container
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Copy the `etc/docker.signing.example.proprties` file to `etc/docker.signing.proprties`,
|
||||||
|
edit it to match your key information and rebuild the container.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
docker run -it \
|
||||||
|
-u $(id -u):$(id -g) \
|
||||||
|
--name i2p.android.base \
|
||||||
|
-v $HOME/.gnupg/:/.gnupg/:ro \
|
||||||
|
-v $HOME/.i2p-plugin-keys/:/.i2p-plugin-keys/:ro \
|
||||||
|
-v /run/user/$(id -u)/:/run/user/$(id -u)/:ro \
|
||||||
|
i2p.android.base
|
||||||
|
|
||||||
|
To get the build artifacts for uploading to Maven out of the container, use:
|
||||||
|
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/pkg-mavencentral app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-i2p.jar app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-mstreaming.jar app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-router.jar app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-servlet-i2p.jar app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-streaming.jar app/pkg-mavencentral
|
||||||
|
|
||||||
|
To get the Android build artifacts out of the container, use:
|
||||||
|
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.android.base/app/build/ app/build
|
||||||
|
|
||||||
|
And your android applications will appear in the `app/build` directory, in the same
|
||||||
|
place where non-container builds would go.
|
||||||
|
|
||||||
|
If you encounter a permissions error when rebuilding, delete the `app/build`,
|
||||||
|
`app/pkg-mavencentral` and `app/pkg-temp` path.
|
||||||
|
|
||||||
|
rm -rf app/pkg-temp app/build app/pkg-mavencentral
|
||||||
|
|
||||||
|
Copypasta
|
||||||
|
---------
|
||||||
|
|
||||||
|
Once you have set up builds for the first time, from then on you can update the container and
|
||||||
|
build a fresh set of Maven jars and a new I2P for Android app by copy-pasting the following
|
||||||
|
commands:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
rm -rf app/pkg-temp app/build app/pkg-mavencentral
|
||||||
|
docker build -t i2p.android.base .
|
||||||
|
docker run -it \
|
||||||
|
-u $(id -u):$(id -g) \
|
||||||
|
--name i2p.android.base \
|
||||||
|
-v $HOME/.gnupg/:/.gnupg/:ro \
|
||||||
|
-v $HOME/.i2p-plugin-keys/:/.i2p-plugin-keys/:ro \
|
||||||
|
-v /run/user/$(id -u)/:/run/user/$(id -u)/:ro \
|
||||||
|
i2p.android.base
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/pkg-mavencentral app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-i2p.jar app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-mstreaming.jar app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-router.jar app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-servlet-i2p.jar app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-streaming.jar app/pkg-mavencentral
|
||||||
|
docker cp i2p.android.base:/opt/workspace/i2p.android.base/app/build/ app/build
|
||||||
|
```
|
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
FROM menny/android_ndk
|
||||||
|
ENV VERSION=0.9.50
|
||||||
|
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/
|
||||||
|
RUN echo 'deb http://deb.i2p2.de/ sid main' >> /etc/apt/sources.list
|
||||||
|
RUN echo 'deb-src http://deb.i2p2.de/ sid main' >> /etc/apt/sources.list
|
||||||
|
RUN echo 'deb http://archive.ubuntu.com/ubuntu trusty universe' >> /etc/apt/sources.list
|
||||||
|
RUN wget -O /etc/apt/trusted.gpg.d/i2p-debian-repo.key.asc https://geti2p.net/_static/i2p-debian-repo.key.asc
|
||||||
|
COPY etc/debian-jessie-repo.key.asc /etc/apt/trusted.gpg.d
|
||||||
|
RUN mkdir -p /opt/packages && wget -O /opt/packages/openjdk-7-jre-headless.deb http://security.debian.org/debian-security/pool/updates/main/o/openjdk-7/openjdk-7-jre-headless_7u261-2.6.22-1~deb8u1_amd64.deb
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get build-dep -y i2p i2p-router
|
||||||
|
RUN apt-get install -y ant openjdk-8* libxml2-utils junit4 libhamcrest-java libmockito-java libmaven-ant-tasks-java dpkg-sig maven
|
||||||
|
RUN cd /opt/packages && dpkg-sig -l openjdk-7-jre-headless.deb && dpkg -x openjdk-7-jre-headless.deb /opt/packages/openjdk-7-jre
|
||||||
|
RUN git clone https://github.com/i2p/i2p.i2p --depth=1 -b i2p-$VERSION /opt/workspace/i2p.i2p
|
||||||
|
RUN update-java-alternatives --jre-headless --set java-1.8.0-openjdk-amd64
|
||||||
|
RUN update-java-alternatives --set java-1.8.0-openjdk-amd64
|
||||||
|
RUN update-alternatives --set javac /usr/lib/jvm/java-8-openjdk-amd64/bin/javac
|
||||||
|
RUN update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
|
||||||
|
RUN rm /opt/java/openjdk/ -rfv
|
||||||
|
COPY . /opt/workspace/i2p.android.base
|
||||||
|
COPY etc/docker.local.ndk.properties /opt/workspace/i2p.android.base/client/local.properties
|
||||||
|
COPY etc/docker.local.router.properties /opt/workspace/i2p.android.base/routerjars/local.properties
|
||||||
|
COPY etc/docker.local.sdk.properties /opt/workspace/i2p.android.base/local.properties
|
||||||
|
COPY etc/docker.override.properties /opt/workspace/i2p.android.base/override.properties
|
||||||
|
COPY etc/docker.override.properties /opt/workspace/i2p.i2p/override.properties
|
||||||
|
COPY etc/docker.signing.properties /opt/workspace/i2p.android.base/signing.properties
|
||||||
|
WORKDIR /opt/workspace/i2p.android.base
|
||||||
|
RUN find /opt/android-sdk-linux -type d -print0 | xargs -0 chown -R 1000:1000
|
||||||
|
RUN find /opt/android-sdk-linux -type d -print0 | xargs -0 chmod -Rc o+rw
|
||||||
|
RUN find /opt/android-sdk-linux -type d -print0 | xargs -0 chmod -c 0755
|
||||||
|
RUN find /opt/workspace -type d -print0 | xargs -0 chown -R 1000:1000
|
||||||
|
RUN find /opt/workspace -type d -print0 | xargs -0 chmod -Rc o+rw
|
||||||
|
RUN find /opt/workspace -type d -print0 | xargs -0 chmod -c 0755
|
||||||
|
CMD cd /opt/workspace/i2p.i2p && \
|
||||||
|
ant -k mavenCentral; \
|
||||||
|
cp -v *.jar pkg-mavencentral/; \
|
||||||
|
cd /opt/workspace/i2p.android.base && \
|
||||||
|
./gradlew --continue dependencies || true ; \
|
||||||
|
./gradlew --continue assembleRelease; tail -f README.md
|
28
LICENSE.txt
28
LICENSE.txt
@ -43,8 +43,34 @@ License for the Android App:
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
|
See the file licenses/LICENSE-Apache2.0.txt
|
||||||
|
|
||||||
|
===================================
|
||||||
|
|
||||||
|
License for the MagicIndicator library:
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 hackware1993
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
===================================
|
===================================
|
||||||
|
|
||||||
|
|
||||||
See the file licenses/LICENSE-Apache2.0.txt
|
|
||||||
|
26
README.md
26
README.md
@ -7,10 +7,10 @@
|
|||||||
- Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
- Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
||||||
- Apache Ant 1.8.0 or higher
|
- Apache Ant 1.8.0 or higher
|
||||||
- I2P source
|
- I2P source
|
||||||
- Android SDK for API 21
|
- Android SDK for API 28 or higher
|
||||||
- Android Build Tools 21.0.2
|
- Android Build Tools 28.0.0 or higher
|
||||||
- Android Support Repository
|
- Android Support Repository
|
||||||
- Gradle 2.1
|
- Gradle 2.2.1
|
||||||
|
|
||||||
### Gradle
|
### Gradle
|
||||||
|
|
||||||
@ -48,6 +48,12 @@ systemProp.socksProxyPort=9150
|
|||||||
|
|
||||||
2. Check out the [`i2p.i2p`](https://github.com/i2p/i2p.i2p) repository.
|
2. Check out the [`i2p.i2p`](https://github.com/i2p/i2p.i2p) repository.
|
||||||
|
|
||||||
|
3. Create a `local.properties` file in `i2p.android.base/lib/client` containing:
|
||||||
|
|
||||||
|
```
|
||||||
|
ndk.dir=/path/to/ndk
|
||||||
|
```
|
||||||
|
|
||||||
3. Create a `local.properties` file in `i2p.android.base/routerjars` containing:
|
3. Create a `local.properties` file in `i2p.android.base/routerjars` containing:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -61,10 +67,18 @@ systemProp.socksProxyPort=9150
|
|||||||
```
|
```
|
||||||
sdk.dir=/path/to/android-studio/sdk
|
sdk.dir=/path/to/android-studio/sdk
|
||||||
```
|
```
|
||||||
|
1a. For building with a local router development build:
|
||||||
|
cd ../i2p.i2p
|
||||||
|
installer/resources/maven-dev-release.sh x // x is the build number, e.g. 6
|
||||||
|
cd back here
|
||||||
|
edit gradle.properties, add the build number x to I2P_PROPERTIES=0.9.xx-x
|
||||||
|
|
||||||
2. `gradle assembleDebug`
|
2. `gradle assembleDebug`
|
||||||
|
|
||||||
3. The APK will be placed in `i2p.android.base/app/build/outputs/apk`.
|
3. The APK files will be placed in `i2p.android.base/app/build/outputs/apk` subdirectories.
|
||||||
|
|
||||||
|
4. Install debug build on phone in USB debugging mode
|
||||||
|
adb install app/build/outputs/apk/free/debug/app-free-debug.apk
|
||||||
|
|
||||||
### Building with Android Studio
|
### Building with Android Studio
|
||||||
|
|
||||||
@ -107,8 +121,8 @@ systemProp.socksProxyPort=9150
|
|||||||
signing.keyId=
|
signing.keyId=
|
||||||
signing.password=
|
signing.password=
|
||||||
signing.secretKeyRingFile=/path/to/secring.gpg
|
signing.secretKeyRingFile=/path/to/secring.gpg
|
||||||
ossrhUsername=
|
NEXUS_USERNAME=
|
||||||
ossrhPassword=
|
NEXUS_PASSWORD=
|
||||||
```
|
```
|
||||||
|
|
||||||
2. `gradle :client:uploadArchives`
|
2. `gradle :client:uploadArchives`
|
||||||
|
128
RELEASE-PROCESS.md
Normal file
128
RELEASE-PROCESS.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Release Process
|
||||||
|
|
||||||
|
Note to all future maintainers: We have 4 channels that need to be updated in order to have a successful
|
||||||
|
Android release. Many of these channels are updated at different rates, and at times you must wait on a
|
||||||
|
third-party service to complete it's tasks before you may continue. When completing an Android release,
|
||||||
|
keep in mind that you must update 1) Maven 2) Google Play 3) f-droid.i2p.io and 4) F-Droid main
|
||||||
|
repository.
|
||||||
|
|
||||||
|
At the time of this revision, 2020/09/13, the main Android maintainer is idk. idk updates Maven, Google
|
||||||
|
Play, and f-droid.i2p.io, and nextl00p handles working with the F-Droid project to provide an I2P release
|
||||||
|
in their main repository.
|
||||||
|
|
||||||
|
NOTE: The docker container built by the Dockerfile in this repostory ensures that the Pre-requisites and
|
||||||
|
Dependencies are properly met by obtaining them from the Debian package in `oldoldstable` and pre-configuring
|
||||||
|
the override.properties that is used in the Docker container.
|
||||||
|
|
||||||
|
## Tag Freezes and Translations
|
||||||
|
|
||||||
|
1-2 weeks before the software is released, I2P Desktop will have a "Tag Freeze" which is the deadline for
|
||||||
|
translations to be checked in. **When** that time comes, someone who has Transifex privileges should use
|
||||||
|
the command: `tx push -s -R I2P.android` to push any and all changed string resources to transifex for
|
||||||
|
translation.
|
||||||
|
|
||||||
|
**>> Beginning of Docker-enabled Steps <<**
|
||||||
|
|
||||||
|
## Prerequirements
|
||||||
|
|
||||||
|
0. Update the changelog!
|
||||||
|
1. Ensure you have the deprecated maven ant tasks. ( https://maven.apache.org/ant-tasks/download.cgi )
|
||||||
|
2. It should exist at `~/.ant/lib/maven-ant-tasks-2.1.3.jar`
|
||||||
|
3. Ensure you have hamcrest-integration, hamcrest-library, hamcrest-core in the hamcrest.home directory.
|
||||||
|
4. Ensure junit 4.12 at least in junit.home, ensure the jar file is named `junit4.jar`.
|
||||||
|
5. Ensure you have the Mockito framework and accompanying documentation in your $JAVA_HOME
|
||||||
|
6. Ensure to have updated the changelog with the changes done.
|
||||||
|
7. Ensure that you are configured to build i2p.i2p with Java 8. On Debian it is easiest to set with
|
||||||
|
`update-java-alternatives --set java-1.8.0-openjdk-amd64` and picking Java 8. **TODO:** add instructions for non-Debian-based
|
||||||
|
systems.
|
||||||
|
8. Ensure that you have a Java 1.7 bootclasspath available. (See **Maven Central** step 2A.)
|
||||||
|
|
||||||
|
## Get all the dependencies ready
|
||||||
|
|
||||||
|
### Maven Central
|
||||||
|
|
||||||
|
1. Check out a clean copy of i2p.i2p at the correct release version. (Make a clean checkout)
|
||||||
|
2. Build the maven packages via `ant mavenCentral` where you end up with mavencentral-*.jar files in the
|
||||||
|
current directory.
|
||||||
|
2. **A)** I2P for Android requires a Java 1.7 bootclasspath, but the servlet jar requires Java 8. So, to do the builds:
|
||||||
|
- First set `javac.compilerargs=-bootclasspath /path/to/java/7/rt.jar:/path/to/java/7/jce.jar` in override.properties
|
||||||
|
- Build with `ant mavenCentral`
|
||||||
|
|
||||||
|
**>> End of Docker-enabled Steps for Maven <<**
|
||||||
|
|
||||||
|
3. Login to http://oss.sonatype.org for uploading the mavencentral-*.jar bundles.
|
||||||
|
4. In nexus, choose "Staging Upload" and upload all of the files with upload mode set to "Artifacts with POM".
|
||||||
|
When uploading the files to nexus, you *must* upload the pom.xml files, and all of their artifacts. For each
|
||||||
|
component, you will need to upload a *.jar, a *.jar.asc, a *sources.jar, a *sources.jar.asc, a javadoc.jar,
|
||||||
|
and a javadoc.jar.asc, and a pom.xml and a pom.xml.asc from the pkg-mavencentral directory during the "Upload
|
||||||
|
Artifacts with POM" operation. You will need to do this once for each component you upload to Nexus.
|
||||||
|
5. Under "Staging Repositories" ensure all where uploaded correctly, select them all and press "Release"
|
||||||
|
in the toolbar.
|
||||||
|
|
||||||
|
#### Example override.properties:
|
||||||
|
|
||||||
|
javac.version=1.7
|
||||||
|
javac.target=1.7
|
||||||
|
javac.source=1.8
|
||||||
|
javac.compilerargs=-bootclasspath /home/user/StudioProjects/java7bootclasspath/rt.jar:/home/user/StudioProjects/java7bootclasspath/jce.jar
|
||||||
|
javac.compilerargs7=-bootclasspath /home/user/StudioProjects/java7bootclasspath/rt.jar:/home/user/StudioProjects/java7bootclasspath/jce.jar
|
||||||
|
build.built-by=name
|
||||||
|
|
||||||
|
### Android Common Build
|
||||||
|
|
||||||
|
Using Docker: in order to use Docker to generate a new Android apk for release, you will
|
||||||
|
need to run the build twice, once for the mavenCentral jars, and once for the actual Android
|
||||||
|
app. After doing the Maven release, follow these steps in the i2p.android.base repository, and re-run
|
||||||
|
the `docker run` step described in `DOCKER.md`
|
||||||
|
|
||||||
|
1. Edit `routerjars/local.properties` to use the clean i2p.i2p copy.
|
||||||
|
2. Pull the latest translations with `tx pull --use-git-timestamps` and commit them. (If you don't have the `tx` command,
|
||||||
|
do `pip install transifex-client` ). If there are broken translations, exclude them and only them.
|
||||||
|
- If there are any new translations, `mtn add` them, and add them to `app/src/main/res/values/arrays.xml`
|
||||||
|
(two places, alphabetical order please)
|
||||||
|
3. Ensure that `signing.properties` contains the details of the release key. If you are using Docker, see
|
||||||
|
`DOCKER.md` to perform this step for Docker builds by editing `etc/docker.signing.properties` instead.
|
||||||
|
4. Edit `gradle.properties` to bump the I2P version.
|
||||||
|
5. Edit `app/build.gradle` to bump the Android version number.
|
||||||
|
6. Edit `CHANGELOG` to add the release and date.
|
||||||
|
7. If the helper has changed since the last release, edit
|
||||||
|
`lib/helper/gradle.properties` to bump the version.
|
||||||
|
8. `./gradlew clean assembleRelease`
|
||||||
|
|
||||||
|
### Libraries
|
||||||
|
|
||||||
|
1. `./gradlew :lib:client:uploadArchives`
|
||||||
|
2. If the helper version was changed and should be released: `./gradlew :lib:helper:uploadArchives`
|
||||||
|
3. Check on Sonatype that everything worked, and close/release.
|
||||||
|
|
||||||
|
## Release Packages
|
||||||
|
|
||||||
|
### F-Droid Guide
|
||||||
|
|
||||||
|
This guide is for f-droid.i2p.io, not for F-Droid's main repository. The repository keystore **and** the
|
||||||
|
config.py used to generate the repository are required to complete this process successfully.
|
||||||
|
|
||||||
|
1. Ensure you have the release keys, the keyfile must be placed at `~/.local/share/fdroidserver/keystore.jks`
|
||||||
|
2. If it's the first time, or you have reinstalled anything, ensure `path/to/fdroid/config.py` has correct
|
||||||
|
information.
|
||||||
|
3. Assuming you already have ran `./gradlew clean assembleRelease` from a earlier step, continue.
|
||||||
|
4. `cp app/build/outputs/apk/free/release/app-free-release.apk path/to/fdroid/repo/I2P-VERSION.apk`
|
||||||
|
5. Update `path/to/fdroid/metadata/net.i2p.android.txt` (The versions at the bottom of the file)
|
||||||
|
6. Run `fdroid update` from inside the fdroid path (install fdroid command via `pip install fdroidserver`)
|
||||||
|
7. Zip/tar the local fdroid repo and archive. `rm fdroid.tgz && tar czf fdroid.tgz archive/ repo/` from the
|
||||||
|
fdroid directory.
|
||||||
|
8. Push to download server and put in place. (via SSH for example, `scp fdroid.tgz download.i2p2.de:~/`)
|
||||||
|
9. On the server run `bin-fd/update-fdroid` and `sudo bin-fd/update-app i2p 0.9.40` (This ensures we use the
|
||||||
|
exact same apk file for the download page as in fdroid and gplay)
|
||||||
|
10. Check F-Droid repo works, and app works.
|
||||||
|
|
||||||
|
### Google Play and finishing up
|
||||||
|
|
||||||
|
1. Verify which files that are changed via `mtn ls cha`. It shouldn't be much more than those bellow this
|
||||||
|
line and possible translations (`mtn ls unk`).
|
||||||
|
2. Commit your release changes, `mtn ci gradle.properties lib/helper/gradle.properties app/build.gradle`
|
||||||
|
3. Push free and donate builds to Google Play via https://play.google.com/apps/publish/
|
||||||
|
4. Tag the new release. Example `mtn tag h: android-0.9.36`
|
||||||
|
5. Push the monotone changes. Make sure that they are there at the next git sync.
|
||||||
|
6. Update download page (version and hash, including F-Droid)
|
||||||
|
|
101
TODO
101
TODO
@ -1,43 +1,128 @@
|
|||||||
# Fixes
|
# Fixes
|
||||||
|
|
||||||
- Better twopane column widths
|
- Create tunnel wizard
|
||||||
<zzz> on the i2ptunnel and addressbook pages on the tablet, the columns are too skinny, they aren't as wide as the tab
|
<zzz> hmm would be nice if they could be shared-client or have an option
|
||||||
<zzz> only a few addressbook entries wrap but on i2ptunnel everything is wrapped and most of the screen is empty
|
<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
|
||||||
|
- Console text change
|
||||||
|
<zzz> "download" and "upload" at the bottom of the status is a little misleading..
|
||||||
|
<zzz> maybe 'downstream bandwidth' or 'inbound usage' ?
|
||||||
|
- Fix visibility of advanced tunnel parameter changes
|
||||||
|
<zzz> when I change an advanced tunnel param e.g. length or variance, the change isn't displayed, I have to go back and forward again to see the change
|
||||||
|
|
||||||
|
# New UI fixes
|
||||||
|
|
||||||
|
- Addressbook action items are in tunnel overflow menu after moving from console to tunnels
|
||||||
|
- Material design:
|
||||||
|
- Style for addressbook headers
|
||||||
|
- Change console FAM icon when possible
|
||||||
|
<zzz> on the bottom right, the + and x icons might be better as a double-up arrow and double-down arrow?
|
||||||
|
- Use Material design for LongPressButton
|
||||||
|
- Highlight selected tunnel in two-pane mode
|
||||||
|
|
||||||
# Short-term
|
# Short-term
|
||||||
|
|
||||||
|
- Remove peers page (HTML version)
|
||||||
|
- Add firewall help page showing current port settings
|
||||||
|
- GMP 6
|
||||||
|
- Fetch all JARs from Maven Central (ie. upload everything that I2P Android uses)
|
||||||
- Disable uPnP when on cell networks
|
- 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
|
<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
|
||||||
|
- Rewrite settings config handling
|
||||||
|
- Rewrite InitActivities
|
||||||
|
- I2PTunnel
|
||||||
|
- Improve tunnel list status indicators
|
||||||
|
- Icon overlay to indicate which tunnels are shared
|
||||||
|
- Or reorder / group tunnels?
|
||||||
|
- Show all messages somewhere
|
||||||
|
- Bottom toolbar?
|
||||||
|
- Icons/header images for tunnel types on details page
|
||||||
|
- Setting to close when not on WiFi
|
||||||
|
- Progress feedback for addressbook subscriptions reload
|
||||||
- Display release notes directly on new router version
|
- Display release notes directly on new router version
|
||||||
- Fill out help pages
|
- Fill out help pages
|
||||||
|
- Fix navigation to specific settings pages
|
||||||
- Rewrite release notes to be release-specific
|
- Rewrite release notes to be release-specific
|
||||||
- Fix release notes UI, either make back button use clear or add buttons
|
- Fix release notes UI, either make back button use clear or add buttons
|
||||||
|
- Notify user when autostart fails?
|
||||||
- NetDB tablet view fixes
|
- NetDB tablet view fixes
|
||||||
- Refresh detail fragment when changing tab
|
- Refresh detail fragment when changing tab
|
||||||
- Move list to correct item when changing tab
|
- Move list to correct item when changing tab
|
||||||
- Create nav history when viewing RI from LS
|
- Create nav history when viewing RI from LS
|
||||||
|
- Handle NetDB null cases (failed lookup of requested hash in detail page)
|
||||||
- Include GeoIP db for country info
|
- Include GeoIP db for country info
|
||||||
- Maybe change router-off mechanic for various pages? Enable as they become available?
|
- Maybe change router-off mechanic for various pages? Enable as they become available?
|
||||||
|
|
||||||
# Medium-term
|
# Medium-term
|
||||||
|
|
||||||
|
- SQLite naming service backend to store addresses more effectively
|
||||||
|
- Leverage for name completion in e.g. browsers
|
||||||
|
- Create/edit tunnels while router is not running
|
||||||
|
- Separate out shared tunnel config
|
||||||
|
- Convey to users that one config controls all shared tunnels
|
||||||
- Network profiles
|
- Network profiles
|
||||||
- User selects profile in settings
|
- User selects profile in settings
|
||||||
- Change network participation etc. based on profile
|
- Change network participation etc. based on profile
|
||||||
- Also look at connection type: Connectivity.isConnectionFast()
|
- Also look at connection type: Connectivity.isConnectionFast()
|
||||||
- Expose log level overrides
|
- Expose log level overrides
|
||||||
|
- Bug report feature
|
||||||
|
- Replace peers page (native version)
|
||||||
- Improve graphs
|
- Improve graphs
|
||||||
- Show time on bottom axis
|
|
||||||
- Show fixed x range, not only available data
|
- Show fixed x range, not only available data
|
||||||
- Think about pan/zoom
|
- Think about pan/zoom
|
||||||
- How to persist data across restarts?
|
- How to persist data across restarts?
|
||||||
- I2PTunnel
|
- Enable apps to specify when they don't need the router anymore
|
||||||
- Show all messages somewhere
|
|
||||||
- Improve detail page, expose advanced settings
|
# Silent Store approval checks to confirm/implement
|
||||||
- Add edit page
|
|
||||||
|
- 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
|
# Long-term
|
||||||
|
|
||||||
|
- Reproducible builds
|
||||||
|
- Extract RouterService into a library
|
||||||
- Remote router support
|
- Remote router support
|
||||||
- Implement a "router wrapper" that can represent a local or remote router
|
- Implement a "router wrapper" that can represent a local or remote router
|
||||||
- Implement/use client APIs to talk to remote router
|
- Implement/use client APIs to talk to remote router
|
||||||
|
250
app/build.gradle
250
app/build.gradle
@ -1,14 +1,22 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'witness'
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
|
namespace 'net.i2p.android.router'
|
||||||
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
|
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode 4745223
|
versionCode Integer.parseInt(project.I2P_ANDROID_VERSION_CODE as String)
|
||||||
versionName '0.9.16-rc1'
|
versionName "$I2P_ANDROID_VERSION"
|
||||||
minSdkVersion 9
|
minSdkVersion 21
|
||||||
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
|
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
|
||||||
|
|
||||||
|
// For Espresso
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release
|
release
|
||||||
@ -16,48 +24,108 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
runProguard false
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
debug {
|
||||||
|
debuggable true
|
||||||
|
applicationIdSuffix '.debug'
|
||||||
|
versionNameSuffix '-DEBUG'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
lintOptions {
|
lintOptions {
|
||||||
abortOnError false
|
abortOnError false
|
||||||
|
checkReleaseBuilds false
|
||||||
|
disable 'MissingDefaultResource'
|
||||||
}
|
}
|
||||||
|
packagingOptions {
|
||||||
|
resources {
|
||||||
|
excludes += ['LICENSE.txt']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flavorDimensions 'tier'
|
||||||
productFlavors {
|
productFlavors {
|
||||||
free {
|
free {
|
||||||
|
dimension 'tier'
|
||||||
applicationId 'net.i2p.android'
|
applicationId 'net.i2p.android'
|
||||||
}
|
}
|
||||||
donate {
|
donate {
|
||||||
|
dimension 'tier'
|
||||||
applicationId 'net.i2p.android.donate'
|
applicationId 'net.i2p.android.donate'
|
||||||
}
|
}
|
||||||
legacy {
|
legacy {
|
||||||
|
dimension 'tier'
|
||||||
applicationId 'net.i2p.android.router'
|
applicationId 'net.i2p.android.router'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':routerjars')
|
implementation "androidx.appcompat:appcompat:1.5.1"
|
||||||
compile project(':client')
|
implementation "androidx.preference:preference:1.2.0"
|
||||||
compile 'com.android.support:support-v4:21.0.2'
|
implementation "androidx.annotation:annotation:1.5.0"
|
||||||
compile 'com.android.support:appcompat-v7:21.0.2'
|
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||||
compile 'com.android.support:recyclerview-v7:21.0.2'
|
// Local dependencies
|
||||||
compile 'com.android.support:cardview-v7:21.0.2'
|
implementation project(':lib:client')
|
||||||
compile 'net.i2p.android.ext:floatingactionbutton:1.1.0'
|
implementation project(':lib:helper')
|
||||||
compile files('libs/androidplot-core-0.6.1.jar')
|
implementation project(path: ':routerjars', configuration: 'routerjars')
|
||||||
|
// Android Support Repository dependencies
|
||||||
|
/*def supportVersion = '28.0.0'
|
||||||
|
implementation "com.android.support:support-v4:$supportVersion"
|
||||||
|
implementation "com.android.support:appcompat-v7:$supportVersion"
|
||||||
|
implementation "com.android.support:preference-v7:$supportVersion"
|
||||||
|
implementation "com.android.support:preference-v14:$supportVersion"
|
||||||
|
implementation "com.android.support:recyclerview-v7:$supportVersion"*/
|
||||||
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
|
// Remote dependencies
|
||||||
|
implementation 'com.androidplot:androidplot-core:1.5.11'
|
||||||
|
implementation 'com.eowise:recyclerview-stickyheaders:0.5.2@aar'
|
||||||
|
//implementation 'com.inkapplications.viewpageindicator:library:2.4.4'
|
||||||
|
implementation 'com.github.hackware1993:MagicIndicator:1.7.0' // for androidx
|
||||||
|
implementation 'com.pnikosis:materialish-progress:1.7'
|
||||||
|
implementation "net.i2p:router:$I2P_VERSION"
|
||||||
|
implementation "net.i2p:i2p:$I2P_VERSION"
|
||||||
|
implementation "net.i2p.client:mstreaming:$I2P_VERSION"
|
||||||
|
implementation "net.i2p.client:streaming:$I2P_VERSION"
|
||||||
|
implementation 'net.i2p.android.ext:floatingactionbutton:1.10.1'
|
||||||
|
implementation 'com.github.SufficientlySecure:html-textview:v3.6'
|
||||||
|
// Testing-only dependencies
|
||||||
|
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.2') {
|
||||||
|
exclude group: 'com.android.support', module: 'support-annotations'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyVerification {
|
dependencies {
|
||||||
verify = [
|
implementation "androidx.appcompat:appcompat:1.5.1"
|
||||||
'com.android.support:support-v4:126a4c291f41f75f3fff4968e9d397bc8454cdff4d8f994cbe0524e3bad76e72',
|
implementation "androidx.preference:preference:1.2.0"
|
||||||
'com.android.support:appcompat-v7:b760fd3d0b0b0547a1bcef9031b40939f31049ba955f04c8fdc5aa09a25d19e9',
|
implementation "androidx.annotation:annotation:1.5.0"
|
||||||
'com.android.support:recyclerview-v7:71ef0f5659b3019dc33c5ffb346ea01df1f66735506f38d43fd783fbcb0370ce',
|
implementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||||
'com.android.support:cardview-v7:cb4d7ee9ebb6edffa7203eff0d207b4e88425599a8ed37d94b650bc84390c4eb',
|
implementation 'androidx.test.ext:junit:1.2.1'
|
||||||
'net.i2p.android.ext:floatingactionbutton:84cf5b67a66337bef59b46f57468c730387ca766b5a5f06ca852ba46cabbc0fb',
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.i2pbase = '../i2p.i2p'
|
dependencies {
|
||||||
|
// ...existing code...
|
||||||
|
|
||||||
|
// Force consistent lifecycle versions
|
||||||
|
implementation('androidx.lifecycle:lifecycle-viewmodel:2.5.1')
|
||||||
|
implementation('androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1')
|
||||||
|
|
||||||
|
// Exclude older versions
|
||||||
|
configurations.all {
|
||||||
|
resolutionStrategy {
|
||||||
|
force 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
|
||||||
|
force 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.ext.i2pbase = "../i2p.i2p"
|
||||||
def Properties props = new Properties()
|
def Properties props = new Properties()
|
||||||
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
|
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
|
||||||
|
|
||||||
@ -74,68 +142,89 @@ if (propFile.canRead()) {
|
|||||||
println 'local.properties not found'
|
println 'local.properties not found'
|
||||||
}
|
}
|
||||||
|
|
||||||
task copyDrawableResources(type: Copy) {
|
|
||||||
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
|
||||||
into 'src/main/res/drawable'
|
|
||||||
}
|
|
||||||
task certificatesZip(type: Zip) {
|
task certificatesZip(type: Zip) {
|
||||||
archiveName = 'certificates_zip'
|
archiveName = 'certificates_zip'
|
||||||
from files('' + i2pbase + '/installer/resources/certificates')
|
from files('' + i2pbase + '/installer/resources/certificates')
|
||||||
}
|
}
|
||||||
task copyRawResources(type:Copy) {
|
task copyI2PResources(type: Copy) {
|
||||||
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
|
// Force this to always run: Copy only detects source changes, not if missing in destination
|
||||||
from(i2pbase + '/installer/resources/hosts.txt') { rename { 'hosts_txt' } }
|
outputs.upToDateWhen { false }
|
||||||
from('../LICENSE.txt') { rename { 'license_app_txt' } }
|
into 'src/main/res'
|
||||||
from('../licenses/LICENSE-Apache2.0.txt') { rename { 'license_apache20_txt' } }
|
into('drawable') {
|
||||||
from(i2pbase + '/licenses') {
|
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/i2plogo.png')
|
||||||
include { elem ->
|
}
|
||||||
elem.name in [
|
into('raw') {
|
||||||
'LICENSE-ElGamalDSA.txt',
|
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
|
||||||
'LICENSE-SHA256.txt',
|
from(i2pbase + '/installer/resources/hosts.txt') { rename { 'hosts_txt' } }
|
||||||
'LICENSE-BSD.txt',
|
from(i2pbase + '/installer/resources/proxy') {
|
||||||
'LICENSE-SNTP.txt',
|
include { elem ->
|
||||||
'LICENSE-LGPLv2.1.txt',
|
elem.name.endsWith('.ht')
|
||||||
'LICENSE-InstallCert.txt',
|
}
|
||||||
'LICENSE-BlockFile.txt',
|
rename { String name ->
|
||||||
'LICENSE-GPLv2.txt',
|
name.toLowerCase(Locale.US).replace('-', '_').replace('.', '_')
|
||||||
'LICENSE-GPLv3.txt',
|
}
|
||||||
'LICENSE-LGPLv3.txt',
|
filter { String line ->
|
||||||
'LICENSE-FatCowIcons.txt',
|
// Remove links to routerconsole
|
||||||
'LICENSE-Addressbook.txt',
|
def m = line =~ /127.0.0.1:7657/
|
||||||
]
|
if (m.getCount()) {
|
||||||
}
|
// Links around content
|
||||||
rename { String name ->
|
line = line.replaceAll(/<a href="http:\/\/127.0.0.1:7657[^>]*>(.+?)<\/a>/) { fullmatch, content ->
|
||||||
String part = name.substring(8, name.lastIndexOf('.txt'))
|
content
|
||||||
String.format('license_%s_txt',
|
}
|
||||||
part.toLowerCase(Locale.US).replace('.', '_'))
|
// Links in translation substitutions
|
||||||
}
|
line = line.replaceAll(/"<a href=\\"http:\/\/127.0.0.1:7657[^>]*>", "<\/a>"/, '"", ""')
|
||||||
|
}
|
||||||
|
// Remove "Configuration - Help - Addressbook" heading
|
||||||
|
def n = line =~ /Configuration.+Help.+Addressbook/
|
||||||
|
if (n.getCount())
|
||||||
|
""
|
||||||
|
else
|
||||||
|
line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 ->
|
||||||
|
name.toLowerCase(Locale.US).replace('-', '_').replace('.', '_')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
from certificatesZip
|
||||||
}
|
}
|
||||||
from certificatesZip
|
|
||||||
into 'src/main/res/raw'
|
|
||||||
}
|
}
|
||||||
task copyI2PResources
|
|
||||||
copyI2PResources.dependsOn copyDrawableResources
|
|
||||||
copyI2PResources.dependsOn copyRawResources
|
|
||||||
|
|
||||||
// For peers WebView
|
// For peers WebView
|
||||||
task copyConsoleImagesAssets(type: Copy) {
|
task copyI2PAssets(type: Copy) {
|
||||||
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
// Force this to always run: Copy only detects source changes, not if missing in destination
|
||||||
from file(i2pbase + '/installer/resources/themes/console/images/inbound.png')
|
outputs.upToDateWhen { false }
|
||||||
from file(i2pbase + '/installer/resources/themes/console/images/outbound.png')
|
into 'src/main/assets/themes/console'
|
||||||
into 'src/main/assets/themes/console/images'
|
into('images') {
|
||||||
|
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/i2plogo.png')
|
||||||
|
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/inbound.png')
|
||||||
|
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/outbound.png')
|
||||||
|
}
|
||||||
|
into('light') {
|
||||||
|
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/light/console.css')
|
||||||
|
}
|
||||||
|
into('light/images') {
|
||||||
|
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/light/images/header.png')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
task copyConsoleLightAssets(type: Copy) {
|
|
||||||
from file(i2pbase + '/installer/resources/themes/console/light/console.css')
|
|
||||||
into 'src/main/assets/themes/console/light'
|
|
||||||
}
|
|
||||||
task copyConsoleLightImagesAssets(type: Copy) {
|
|
||||||
from file(i2pbase + '/installer/resources/themes/console/light/images/header.png')
|
|
||||||
into 'src/main/assets/themes/console/light/images'
|
|
||||||
}
|
|
||||||
task copyI2PAssets
|
|
||||||
copyI2PAssets.dependsOn copyConsoleImagesAssets
|
|
||||||
copyI2PAssets.dependsOn copyConsoleLightAssets
|
|
||||||
copyI2PAssets.dependsOn copyConsoleLightImagesAssets
|
|
||||||
|
|
||||||
preBuild.dependsOn copyI2PResources
|
preBuild.dependsOn copyI2PResources
|
||||||
preBuild.dependsOn copyI2PAssets
|
preBuild.dependsOn copyI2PAssets
|
||||||
@ -145,6 +234,7 @@ task cleanI2PResources(type: Delete) {
|
|||||||
delete fileTree('src/main/res/raw') {
|
delete fileTree('src/main/res/raw') {
|
||||||
include 'blocklist_txt'
|
include 'blocklist_txt'
|
||||||
include 'hosts_txt'
|
include 'hosts_txt'
|
||||||
|
include '*_ht'
|
||||||
include 'license_*'
|
include 'license_*'
|
||||||
include 'certificates_zip'
|
include 'certificates_zip'
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
29
app/proguard-rules.pro
vendored
Normal file
29
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
-dontobfuscate
|
||||||
|
-dontoptimize
|
||||||
|
-dontpreverify
|
||||||
|
-dontshrink
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
|
||||||
|
# Workaround for Samsung Android 4.2 bug
|
||||||
|
# https://code.google.com/p/android/issues/detail?id=78377
|
||||||
|
# https://code.google.com/p/android/issues/detail?id=78377#c188
|
||||||
|
# https://code.google.com/p/android/issues/detail?id=78377#c302
|
||||||
|
-keepattributes **
|
||||||
|
-keep class !android.support.v7.view.menu.**,** {*;}
|
||||||
|
-dontwarn **
|
||||||
|
-dontnote **
|
115
app/src/androidTest/java/net/i2p/android/I2PActivityTest.java
Normal file
115
app/src/androidTest/java/net/i2p/android/I2PActivityTest.java
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package net.i2p.android;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ActivityScenario;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static androidx.test.espresso.Espresso.closeSoftKeyboard;
|
||||||
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
|
import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
|
||||||
|
import static androidx.test.espresso.Espresso.pressBack;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.swipeLeft;
|
||||||
|
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
|
||||||
|
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.hasSibling;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class I2PActivityTest {
|
||||||
|
|
||||||
|
private ActivityScenario<I2PActivity> scenario;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
scenario = ActivityScenario.launch(I2PActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainTabs() {
|
||||||
|
onView(withId(R.id.router_onoff_button)).check(matches(isDisplayed()));
|
||||||
|
|
||||||
|
// Press "Tunnels" tab
|
||||||
|
onView(allOf(withText(R.string.label_tunnels),
|
||||||
|
not(isDescendantOfA(withId(R.id.main_scrollview))))).perform(click());
|
||||||
|
onView(withId(R.id.router_onoff_button)).check(matches(not(isDisplayed())));
|
||||||
|
onView(withText(R.string.label_i2ptunnel_client)).check(matches(isDisplayed()));
|
||||||
|
|
||||||
|
// Press "Addresses" tab
|
||||||
|
onView(withText(R.string.label_addresses)).perform(click());
|
||||||
|
onView(withText(R.string.label_i2ptunnel_client)).check(matches(not(isDisplayed())));
|
||||||
|
onView(withText(R.string.label_router)).check(matches(isDisplayed()));
|
||||||
|
|
||||||
|
// Press "Console" tab
|
||||||
|
onView(withText(R.string.label_console)).perform(click());
|
||||||
|
// Addressbook fragment should have been destroyed
|
||||||
|
onView(withText(R.string.label_router)).check(doesNotExist());
|
||||||
|
onView(withId(R.id.router_onoff_button)).check(matches(isDisplayed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainSwipe() {
|
||||||
|
onView(withId(R.id.router_onoff_button)).check(matches(isDisplayed()));
|
||||||
|
|
||||||
|
onView(allOf(withId(R.id.pager), withParent(hasSibling(withId(R.id.main_toolbar))))).perform(swipeLeft());
|
||||||
|
onView(withId(R.id.router_onoff_button)).check(matches(not(isDisplayed())));
|
||||||
|
onView(withText(R.string.label_i2ptunnel_client)).check(matches(isDisplayed()));
|
||||||
|
|
||||||
|
onView(allOf(withId(R.id.pager), withParent(hasSibling(withId(R.id.main_toolbar))))).perform(swipeLeft());
|
||||||
|
// TODO: test tunnels ViewPager
|
||||||
|
onView(allOf(withId(R.id.pager), withParent(hasSibling(withId(R.id.main_toolbar))))).perform(swipeLeft());
|
||||||
|
onView(withText(R.string.label_i2ptunnel_client)).check(matches(not(isDisplayed())));
|
||||||
|
onView(withText(R.string.label_router)).check(matches(isDisplayed()));
|
||||||
|
// TODO: test addressbook ViewPager
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSettingsNavigation() {
|
||||||
|
// Open settings menu
|
||||||
|
openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
|
||||||
|
onView(withText(R.string.menu_settings)).perform(click());
|
||||||
|
|
||||||
|
// Open bandwidth page
|
||||||
|
onView(withText(R.string.settings_label_bandwidth_net)).perform(click());
|
||||||
|
onView(withText(R.string.settings_label_startOnBoot)).check(matches(isDisplayed()));
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
// Open graphs page
|
||||||
|
onView(withText(R.string.label_graphs)).perform(click());
|
||||||
|
onView(withText(R.string.router_not_running)).check(matches(isDisplayed()));
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
// Open logging page
|
||||||
|
onView(withText(R.string.settings_label_logging)).perform(click());
|
||||||
|
onView(withText(R.string.settings_label_default_log_level)).check(matches(isDisplayed()));
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
// Open addressbook page
|
||||||
|
onView(withText(R.string.label_addressbook)).perform(click());
|
||||||
|
onView(withText("Subscriptions")).check(matches(isDisplayed()));
|
||||||
|
closeSoftKeyboard();
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
// Open graphs page
|
||||||
|
onView(withText(R.string.settings_label_advanced)).perform(click());
|
||||||
|
onView(withText(R.string.settings_label_transports)).check(matches(isDisplayed()));
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
// Check back exits settings
|
||||||
|
onView(withText(R.string.settings_label_advanced)).check(matches(isDisplayed()));
|
||||||
|
pressBack();
|
||||||
|
onView(withText(R.string.settings_label_advanced)).check(doesNotExist());
|
||||||
|
}
|
||||||
|
}
|
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>
|
@ -1,10 +1,25 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="net.i2p.android.router"
|
package="net.i2p.android.router"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto"
|
||||||
|
android:sharedUserId="net.i2p">
|
||||||
|
|
||||||
|
<uses-sdk xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:overrideLibrary="android.support.v14.preference" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<!-- following two are for UPnP -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||||
|
<!-- required for reliable core functionality on Android, see:
|
||||||
|
https://geti2p.net/en/docs/applications/embedding
|
||||||
|
heading: "Design for and Encourage Long Uptimes"
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:icon="@drawable/ic_launcher_itoopie"
|
android:icon="@drawable/ic_launcher_itoopie"
|
||||||
@ -13,20 +28,42 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".service.RouterService"
|
android:name=".service.RouterService"
|
||||||
android:icon="@drawable/ic_launcher_itoopie"
|
android:icon="@drawable/ic_launcher_itoopie"
|
||||||
android:label="@string/app_name">
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:foregroundServiceType="specialUse">
|
||||||
|
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||||
|
android:value="i2p_router_background_process_required_network_operation"/>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="net.i2p.android.router.service.IRouterState" />
|
<action android:name="net.i2p.android.router.service.IRouterState" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<provider
|
<provider
|
||||||
android:name=".provider.CacheProvider"
|
android:name=".provider.CacheProvider"
|
||||||
android:authorities="net.i2p.android" />
|
android:authorities="${applicationId}.provider" />
|
||||||
|
<receiver
|
||||||
|
android:name=".receiver.OnBootReceiver"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<receiver
|
||||||
|
android:name=".receiver.RemoteStartReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="net.i2p.android.router.receiver.START_I2P" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name="net.i2p.android.I2PActivity"
|
||||||
android:icon="@drawable/ic_launcher_itoopie"
|
android:icon="@drawable/ic_launcher_itoopie"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop"
|
||||||
|
android:exported="true">
|
||||||
|
<!-- Console filters -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
@ -35,35 +72,52 @@
|
|||||||
<action android:name="net.i2p.android.router.START_I2P" />
|
<action android:name="net.i2p.android.router.START_I2P" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="net.i2p.android.router.service.APPROVE_SAM" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Addressbook filters -->
|
||||||
|
<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>
|
||||||
<activity
|
<activity
|
||||||
android:name=".NewsActivity"
|
android:name=".NewsActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:label="I2P News"
|
android:label="@string/label_news"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.MainActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="net.i2p.android.help.HelpActivity"
|
android:name="net.i2p.android.help.HelpActivity"
|
||||||
android:label="Help"
|
android:label="@string/menu_help"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.MainActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="net.i2p.android.help.BrowserConfigActivity"
|
android:name="net.i2p.android.help.BrowserConfigActivity"
|
||||||
android:label="Browser Configuration"
|
android:label="@string/label_browser_configuration"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.MainActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".LicenseActivity"
|
android:name=".LicenseActivity"
|
||||||
android:label="I2P License Information"
|
android:label="@string/label_licenses"
|
||||||
android:parentActivityName="net.i2p.android.help.HelpActivity">
|
android:parentActivityName="net.i2p.android.help.HelpActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
@ -72,6 +126,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".web.WebActivity"
|
android:name=".web.WebActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
|
android:exported="true"
|
||||||
android:label="I2P Web Browser">
|
android:label="I2P Web Browser">
|
||||||
<!-- Disabled, this browser is not very secure
|
<!-- Disabled, this browser is not very secure
|
||||||
Temporarily enabled until an alternative browser is ready -->
|
Temporarily enabled until an alternative browser is ready -->
|
||||||
@ -88,81 +143,61 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:label="I2P Settings"
|
android:label="@string/menu_settings"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.MainActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".addressbook.AddressbookSettingsActivity"
|
android:name=".addressbook.AddressbookSettingsActivity"
|
||||||
android:label="Addressbook Settings"
|
android:label="@string/label_addressbook"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:parentActivityName=".addressbook.AddressbookActivity">
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name=".addressbook.AddressbookActivity"
|
|
||||||
android: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>
|
||||||
<activity
|
<activity
|
||||||
android:name=".addressbook.AddressbookAddWizardActivity"
|
android:name=".addressbook.AddressbookAddWizardActivity"
|
||||||
android:label="Add new Destination"
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
android:parentActivityName=".addressbook.AddressbookActivity">
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name="net.i2p.android.i2ptunnel.TunnelListActivity"
|
|
||||||
android: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>
|
||||||
<activity
|
<activity
|
||||||
android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity"
|
android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity"
|
||||||
android:label="I2PTunnel"
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="net.i2p.android.i2ptunnel.preferences.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>
|
||||||
<activity
|
<activity
|
||||||
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
|
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
|
||||||
android:label="Tunnel Creation Wizard"
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".log.LogActivity"
|
android:name=".log.LogActivity"
|
||||||
android:label="I2P Logs"
|
android:label="@string/label_logs"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.MainActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".log.LogDetailActivity"
|
android:name=".log.LogDetailActivity"
|
||||||
android:label="Log Entry"
|
android:label="@string/log_entry"
|
||||||
android:parentActivityName=".log.LogActivity">
|
android:parentActivityName=".log.LogActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
@ -170,29 +205,29 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".stats.RateGraphActivity"
|
android:name=".stats.RateGraphActivity"
|
||||||
android:label="Rate Graph"
|
android:label="@string/label_graphs"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.MainActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".stats.PeersActivity"
|
android:name=".stats.PeersActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:label="I2P Peers and Transport Status"
|
android:label="@string/label_peers_status"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.MainActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".netdb.NetDbActivity"
|
android:name=".netdb.NetDbActivity"
|
||||||
android:label="NetDB"
|
android:label="NetDB"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName="net.i2p.android.I2PActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="net.i2p.android.router.MainActivity" />
|
android:value="net.i2p.android.I2PActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".netdb.NetDbDetailActivity"
|
android:name=".netdb.NetDbDetailActivity"
|
||||||
|
152
app/src/main/java/androidx/viewpager/widget/CustomViewPager.java
Normal file
152
app/src/main/java/androidx/viewpager/widget/CustomViewPager.java
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
//package android.support.v4.view;
|
||||||
|
package androidx.viewpager.widget;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
//import android.support.v4.os.ParcelableCompat;
|
||||||
|
import androidx.core.os.ParcelableCompat;
|
||||||
|
//import android.support.v4.os.ParcelableCompatCreatorCallbacks;
|
||||||
|
import androidx.core.os.ParcelableCompatCreatorCallbacks;
|
||||||
|
|
||||||
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
|
||||||
|
public class CustomViewPager extends ViewPager {
|
||||||
|
private boolean mEnabled;
|
||||||
|
private int mFixedPage;
|
||||||
|
private int mFixedPageString;
|
||||||
|
|
||||||
|
public CustomViewPager(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
mEnabled = false;
|
||||||
|
mFixedPage = -1;
|
||||||
|
mFixedPageString = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
return mEnabled && mFixedPage < 0 && super.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||||
|
// See Nov. 20, 2013 comment at:
|
||||||
|
// https://github.com/JakeWharton/ViewPagerIndicator/pull/257
|
||||||
|
// Our ticket #2488
|
||||||
|
|
||||||
|
// prevent NPE if fake dragging and touching ViewPager
|
||||||
|
if(isFakeDragging()) return false;
|
||||||
|
|
||||||
|
return mEnabled && mFixedPage < 0 && super.onInterceptTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentItem(int item) {
|
||||||
|
if ((mEnabled && (mFixedPage < 0 || item == mFixedPage))
|
||||||
|
|| (!mEnabled && item == 0))
|
||||||
|
super.setCurrentItem(item);
|
||||||
|
else if (!mEnabled)
|
||||||
|
Toast.makeText(getContext(), Util.getRouterContext() == null ?
|
||||||
|
R.string.router_not_running : R.string.router_shutting_down,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
else if (mFixedPageString > 0)
|
||||||
|
Toast.makeText(getContext(), getContext().getString(mFixedPageString),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPagingEnabled(boolean enabled) {
|
||||||
|
mEnabled = enabled;
|
||||||
|
updatePagingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFixedPage(int page, int res) {
|
||||||
|
mFixedPage = page;
|
||||||
|
mFixedPageString = res;
|
||||||
|
updatePagingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePagingState() {
|
||||||
|
if (mEnabled) {
|
||||||
|
if (mFixedPage >= 0 && getCurrentItem() != mFixedPage)
|
||||||
|
setCurrentItem(mFixedPage);
|
||||||
|
|
||||||
|
} else if (getCurrentItem() != 0)
|
||||||
|
setCurrentItem(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SavedState extends ViewPager.SavedState {
|
||||||
|
boolean enabled;
|
||||||
|
int fixedPage;
|
||||||
|
int fixedPageString;
|
||||||
|
|
||||||
|
public SavedState(Parcelable superState) {
|
||||||
|
super(superState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
|
super.writeToParcel(out, flags);
|
||||||
|
out.writeInt(enabled ? 1 : 0);
|
||||||
|
out.writeInt(fixedPage);
|
||||||
|
out.writeInt(fixedPageString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CustomViewPager.SavedState{"
|
||||||
|
+ Integer.toHexString(System.identityHashCode(this))
|
||||||
|
+ " enabled=" + enabled + " fixedPage=" + fixedPage + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<SavedState> CREATOR
|
||||||
|
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
|
||||||
|
@Override
|
||||||
|
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
|
||||||
|
return new SavedState(in, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SavedState[] newArray(int size) {
|
||||||
|
return new SavedState[size];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
SavedState(Parcel in, ClassLoader loader) {
|
||||||
|
super(in, loader);
|
||||||
|
enabled = in.readInt() != 0;
|
||||||
|
fixedPage = in.readInt();
|
||||||
|
fixedPageString = in.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Parcelable onSaveInstanceState() {
|
||||||
|
Parcelable superState = super.onSaveInstanceState();
|
||||||
|
SavedState ss = new SavedState(superState);
|
||||||
|
ss.enabled = mEnabled;
|
||||||
|
ss.fixedPage = mFixedPage;
|
||||||
|
ss.fixedPageString = mFixedPageString;
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRestoreInstanceState(Parcelable state) {
|
||||||
|
if (!(state instanceof SavedState)) {
|
||||||
|
super.onRestoreInstanceState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SavedState ss = (SavedState)state;
|
||||||
|
super.onRestoreInstanceState(ss.getSuperState());
|
||||||
|
|
||||||
|
mEnabled = ss.enabled;
|
||||||
|
mFixedPage = ss.fixedPage;
|
||||||
|
mFixedPageString = ss.fixedPageString;
|
||||||
|
}
|
||||||
|
}
|
@ -1,162 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,163 @@
|
|||||||
|
package com.pavelsikun.seekbarpreference;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
//import android.support.v7.preference.Preference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on MaterialSeekBarController created by mrbimc on 30.09.15.
|
||||||
|
*/
|
||||||
|
public class MaterialSeekBarController implements SeekBar.OnSeekBarChangeListener {
|
||||||
|
|
||||||
|
private final String TAG = getClass().getName();
|
||||||
|
|
||||||
|
public static final int DEFAULT_CURRENT_VALUE = 50;
|
||||||
|
private static final int DEFAULT_MAX_VALUE = 100;
|
||||||
|
private static final String DEFAULT_MEASUREMENT_UNIT = "";
|
||||||
|
|
||||||
|
private int mMaxValue;
|
||||||
|
private int mMaxDigits;
|
||||||
|
private int mCurrentValue;
|
||||||
|
private String mMeasurementUnit;
|
||||||
|
|
||||||
|
private SeekBar mSeekBar;
|
||||||
|
private TextView mSeekBarValue;
|
||||||
|
private TextView mMeasurementUnitView;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
private Persistable mPersistable;
|
||||||
|
|
||||||
|
public MaterialSeekBarController(Context context, AttributeSet attrs, Persistable persistable) {
|
||||||
|
mContext = context;
|
||||||
|
mPersistable = persistable;
|
||||||
|
init(attrs, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(AttributeSet attrs, View view) {
|
||||||
|
setValuesFromXml(attrs);
|
||||||
|
if(view != null) onBindView(view);
|
||||||
|
}
|
||||||
|
private void setValuesFromXml(@Nullable AttributeSet attrs) {
|
||||||
|
if (attrs == null) {
|
||||||
|
mCurrentValue = DEFAULT_CURRENT_VALUE;
|
||||||
|
mMaxValue = DEFAULT_MAX_VALUE;
|
||||||
|
mMeasurementUnit = DEFAULT_MEASUREMENT_UNIT;
|
||||||
|
} else {
|
||||||
|
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.SeekBarPreference);
|
||||||
|
try {
|
||||||
|
mMaxValue = a.getInt(R.styleable.SeekBarPreference_msbp_maxValue, DEFAULT_MAX_VALUE);
|
||||||
|
mCurrentValue = a.getInt(R.styleable.SeekBarPreference_msbp_defaultValue, DEFAULT_CURRENT_VALUE);
|
||||||
|
|
||||||
|
if(mCurrentValue > mMaxValue) mCurrentValue = mMaxValue / 2;
|
||||||
|
mMeasurementUnit = a.getString(R.styleable.SeekBarPreference_msbp_measurementUnit);
|
||||||
|
if (mMeasurementUnit == null)
|
||||||
|
mMeasurementUnit = DEFAULT_MEASUREMENT_UNIT;
|
||||||
|
} finally {
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mMaxDigits = (int) Math.log10(mMaxValue) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBindView(@NonNull View view) {
|
||||||
|
|
||||||
|
mSeekBar = (SeekBar) view.findViewById(R.id.seekbar);
|
||||||
|
mSeekBar.setMax(mMaxValue);
|
||||||
|
mSeekBar.setOnSeekBarChangeListener(this);
|
||||||
|
|
||||||
|
mSeekBarValue = (TextView) view.findViewById(R.id.seekbar_value);
|
||||||
|
setPaddedValue(mCurrentValue);
|
||||||
|
|
||||||
|
mMeasurementUnitView = (TextView) view.findViewById(R.id.measurement_unit);
|
||||||
|
mMeasurementUnitView.setText(mMeasurementUnit);
|
||||||
|
|
||||||
|
mSeekBar.setProgress(mCurrentValue);
|
||||||
|
|
||||||
|
if (!view.isEnabled()) {
|
||||||
|
mSeekBar.setEnabled(false);
|
||||||
|
mSeekBarValue.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onSetInitialValue(boolean restoreValue, @NonNull Object defaultValue) {
|
||||||
|
mCurrentValue = mMaxValue / 2;
|
||||||
|
try {
|
||||||
|
mCurrentValue = (Integer) defaultValue;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "Invalid default value: " + defaultValue.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
if (mSeekBar != null) mSeekBar.setEnabled(enabled);
|
||||||
|
if (mSeekBarValue != null) mSeekBarValue.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDependencyChanged(Preference dependency, boolean disableDependent) {
|
||||||
|
if (mSeekBar != null) mSeekBar.setEnabled(!disableDependent);
|
||||||
|
if (mSeekBarValue != null) mSeekBarValue.setEnabled(!disableDependent);
|
||||||
|
}
|
||||||
|
|
||||||
|
//SeekBarListener:
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(@NonNull SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
|
mCurrentValue = progress;
|
||||||
|
setPaddedValue(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(@NonNull SeekBar seekBar) {
|
||||||
|
setCurrentValue(mCurrentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPaddedValue(int value) {
|
||||||
|
//mSeekBarValue.setText(String.format("%0" + mMaxDigits +"d", value));
|
||||||
|
mSeekBarValue.setText(String.format("%" + mMaxDigits +"d", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//public methods for manipulating this widget from java:
|
||||||
|
public void setCurrentValue(int value) {
|
||||||
|
mCurrentValue = value;
|
||||||
|
if (mPersistable != null) mPersistable.onPersist(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentValue() {
|
||||||
|
return mCurrentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setMaxValue(int maxValue) {
|
||||||
|
mMaxValue = maxValue;
|
||||||
|
if (mSeekBar != null) mSeekBar.setMax(mMaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxValue() {
|
||||||
|
return mMaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setMeasurementUnit(String measurementUnit) {
|
||||||
|
mMeasurementUnit = measurementUnit;
|
||||||
|
if (mMeasurementUnitView != null) mMeasurementUnitView.setText(mMeasurementUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMeasurementUnit() {
|
||||||
|
return mMeasurementUnit;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.pavelsikun.seekbarpreference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by mrbimc on 04.10.15.
|
||||||
|
*/
|
||||||
|
public interface Persistable {
|
||||||
|
void onPersist(int value);
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package com.pavelsikun.seekbarpreference;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.os.Build;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
//import android.support.v7.preference.Preference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
//import android.support.v7.preference.PreferenceViewHolder;
|
||||||
|
import androidx.preference.PreferenceViewHolder;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
public class SeekBarPreference extends Preference implements Persistable {
|
||||||
|
|
||||||
|
private MaterialSeekBarController mController;
|
||||||
|
|
||||||
|
public SeekBarPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeekBarPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
init(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
init(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(AttributeSet attrs) {
|
||||||
|
setLayoutResource(R.layout.seekbar_preference);
|
||||||
|
mController = new MaterialSeekBarController(getContext(), attrs, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull PreferenceViewHolder viewHolder) {
|
||||||
|
super.onBindViewHolder(viewHolder);
|
||||||
|
mController.onBindView(viewHolder.itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object onGetDefaultValue(@NonNull TypedArray ta, int index) {
|
||||||
|
if(mController != null) return ta.getInt(index, mController.getCurrentValue());
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSetInitialValue(boolean restoreValue, @NonNull Object defaultValue) {
|
||||||
|
int average = mController.getMaxValue() / 2;
|
||||||
|
if(restoreValue) mController.setCurrentValue(getPersistedInt(average));
|
||||||
|
else mController.onSetInitialValue(restoreValue, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
super.setEnabled(enabled);
|
||||||
|
mController.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDependencyChanged(Preference dependency, boolean disableDependent) {
|
||||||
|
super.onDependencyChanged(dependency, disableDependent);
|
||||||
|
mController.onDependencyChanged(dependency, disableDependent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPersist(int value) {
|
||||||
|
persistInt(value);
|
||||||
|
notifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMeasurementUnit() {
|
||||||
|
return mController.getMeasurementUnit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMeasurementUnit(String measurementUnit) {
|
||||||
|
mController.setMeasurementUnit(measurementUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxValue() {
|
||||||
|
return mController.getMaxValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxValue(int maxValue) {
|
||||||
|
mController.setMaxValue(maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentValue() {
|
||||||
|
return mController.getCurrentValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentValue(int value) {
|
||||||
|
mController.setCurrentValue(value);
|
||||||
|
}
|
||||||
|
}
|
382
app/src/main/java/net/i2p/android/I2PActivity.java
Normal file
382
app/src/main/java/net/i2p/android/I2PActivity.java
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
package net.i2p.android;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
//import android.support.v4.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
//import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import net.i2p.android.help.HelpActivity;
|
||||||
|
import net.i2p.android.i2ptunnel.TunnelsContainer;
|
||||||
|
import net.i2p.android.router.ConsoleContainer;
|
||||||
|
import net.i2p.android.router.MainFragment;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
import net.i2p.android.router.addressbook.AddressbookContainer;
|
||||||
|
import net.i2p.android.router.service.AndroidSAMSecureSession;
|
||||||
|
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 net.i2p.android.util.MemoryFragmentPagerAdapter;
|
||||||
|
import androidx.viewpager.widget.CustomViewPager;
|
||||||
|
import net.i2p.android.widget.SlidingTabLayout;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main activity of the app. Contains a ViewPager that holds the three main
|
||||||
|
* views:
|
||||||
|
* <ul>
|
||||||
|
* <li>The console</li>
|
||||||
|
* <li>The addressbook</li>
|
||||||
|
* <li>The tunnel manager</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class I2PActivity extends I2PActivityBase implements
|
||||||
|
MainFragment.RouterControlListener {
|
||||||
|
CustomViewPager mViewPager;
|
||||||
|
ViewPagerAdapter mViewPagerAdapter;
|
||||||
|
SlidingTabLayout mSlidingTabLayout;
|
||||||
|
|
||||||
|
private boolean mAutoStartFromIntent = false;
|
||||||
|
private boolean _keep = true;
|
||||||
|
private boolean _startPressed = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_viewpager);
|
||||||
|
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
mViewPager = (CustomViewPager) findViewById(R.id.pager);
|
||||||
|
mViewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager());
|
||||||
|
mViewPager.setAdapter(mViewPagerAdapter);
|
||||||
|
|
||||||
|
mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs);
|
||||||
|
// Center the tabs in the layout
|
||||||
|
mSlidingTabLayout.setDistributeEvenly(true);
|
||||||
|
// Customize tab color
|
||||||
|
mSlidingTabLayout.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
|
||||||
|
@Override
|
||||||
|
public int getIndicatorColor(int position) {
|
||||||
|
return getResources().getColor(R.color.accent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Give the SlidingTabLayout the ViewPager
|
||||||
|
mSlidingTabLayout.setViewPager(mViewPager);
|
||||||
|
|
||||||
|
_keep = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewPagerAdapter extends MemoryFragmentPagerAdapter {
|
||||||
|
private static final int NUM_ITEMS = 3;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
public ViewPagerAdapter(Context context, FragmentManager fm) {
|
||||||
|
super(fm);
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return NUM_ITEMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
return new ConsoleContainer();
|
||||||
|
case 1:
|
||||||
|
return new TunnelsContainer();
|
||||||
|
case 2:
|
||||||
|
return new AddressbookContainer();
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
return mContext.getString(R.string.label_console);
|
||||||
|
case 1:
|
||||||
|
return mContext.getString(R.string.label_tunnels);
|
||||||
|
case 2:
|
||||||
|
return mContext.getString(R.string.label_addresses);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
setIntent(intent);
|
||||||
|
handleIntents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleIntents() {
|
||||||
|
if (getIntent() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String action = intent.getAction();
|
||||||
|
Util.d("handleIntent: intent=" + intent.toString());
|
||||||
|
|
||||||
|
if (action == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Util.d("handleIntent: action=" + action);
|
||||||
|
|
||||||
|
Bundle extra = intent.getExtras();
|
||||||
|
if (extra != null)
|
||||||
|
Util.d("handleIntent extra=" + extra.toString());
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case "net.i2p.android.router.START_I2P":
|
||||||
|
if (mViewPager.getCurrentItem() != 0)
|
||||||
|
mViewPager.setCurrentItem(0, false);
|
||||||
|
autoStart();
|
||||||
|
break;
|
||||||
|
case "net.i2p.android.router.service.APPROVE_SAM":
|
||||||
|
Util.w("Affirmed SAM Connection");
|
||||||
|
String ID = extra.getString("ID");
|
||||||
|
Util.d("SAM ID was: " + ID);
|
||||||
|
AndroidSAMSecureSession.affirmResult(ID);
|
||||||
|
break;
|
||||||
|
case Intent.ACTION_PICK:
|
||||||
|
mViewPager.setFixedPage(2, R.string.select_an_address);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BroadcastReceiver onStateChange = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
State state = intent.getParcelableExtra(RouterService.LOCAL_BROADCAST_EXTRA_STATE);
|
||||||
|
|
||||||
|
if (_startPressed && Util.getRouterContext() != null)
|
||||||
|
_startPressed = false;
|
||||||
|
|
||||||
|
// Update menus, FAMs etc.
|
||||||
|
supportInvalidateOptionsMenu();
|
||||||
|
|
||||||
|
// Update main paging state
|
||||||
|
mViewPager.setPagingEnabled(!(Util.isStopping(state) || Util.isStopped(state)));
|
||||||
|
|
||||||
|
// If I2P was started by another app and is running, return to that app
|
||||||
|
if (state == State.RUNNING && mAutoStartFromIntent) {
|
||||||
|
I2PActivity.this.setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
// Handle edge cases after shutting down router
|
||||||
|
mViewPager.updatePagingState();
|
||||||
|
|
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().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(this, SettingsActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.menu_help:
|
||||||
|
Intent hi = new Intent(this, HelpActivity.class);
|
||||||
|
switch (mViewPager.getCurrentItem()) {
|
||||||
|
case 1:
|
||||||
|
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_I2PTUNNEL);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_ADDRESSBOOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
startActivity(hi);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBackPressed() {
|
||||||
|
super.onBackPressed();
|
||||||
|
|
||||||
|
RouterContext ctx = Util.getRouterContext();
|
||||||
|
// RouterService svc = _routerService; Which is better to use?!
|
||||||
|
_keep = Connectivity.isConnected(this) && (ctx != null || _startPressed);
|
||||||
|
Util.d("*********************************************************");
|
||||||
|
Util.d("Back pressed, Keep? " + _keep);
|
||||||
|
Util.d("*********************************************************");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(onStateChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
_startPressed = true;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
208
app/src/main/java/net/i2p/android/I2PActivityBase.java
Normal file
208
app/src/main/java/net/i2p/android/I2PActivityBase.java
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
package net.i2p.android;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
//import android.support.v7.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import net.i2p.android.router.service.RouterBinder;
|
||||||
|
import net.i2p.android.router.service.RouterService;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.android.util.LocaleManager;
|
||||||
|
|
||||||
|
public abstract class I2PActivityBase extends AppCompatActivity {
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
private final LocaleManager localeManager = new LocaleManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is first created.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
Util.d(this + " onCreate called");
|
||||||
|
localeManager.onCreate(this);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
_sharedPrefs = getSharedPreferences(SHARED_PREFS, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
localeManager.onResume(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyLocaleChanged() {
|
||||||
|
localeManager.onResume(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
|
||||||
|
////// 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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback from ServiceConnection, override as necessary
|
||||||
|
*/
|
||||||
|
protected void onRouterUnbind() {
|
||||||
|
}
|
||||||
|
}
|
325
app/src/main/java/net/i2p/android/InitActivities.java
Normal file
325
app/src/main/java/net/i2p/android/InitActivities.java
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
package net.i2p.android;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
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);
|
||||||
|
Util.i("My app directory is "+myDir);
|
||||||
|
_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() {
|
||||||
|
Util.i("Initializing the I2P resources");
|
||||||
|
|
||||||
|
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();
|
||||||
|
copyResourceToFileIfAbsent(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.ahelper_notfound_header_ht, "docs/ahelper-notfound-header.ht");
|
||||||
|
copyResourceToFile(R.raw.auth_header_ht, "docs/auth-header.ht");
|
||||||
|
copyResourceToFile(R.raw.baduri_header_ht, "docs/baduri-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.enc_header_ht, "docs/enc-header.ht");
|
||||||
|
copyResourceToFile(R.raw.encp_header_ht, "docs/encp-header.ht");
|
||||||
|
copyResourceToFile(R.raw.localhost_header_ht, "docs/localhost-header.ht");
|
||||||
|
copyResourceToFile(R.raw.nols_header_ht, "docs/nols-header.ht");
|
||||||
|
copyResourceToFile(R.raw.nolsp_header_ht, "docs/nolsp-header.ht");
|
||||||
|
copyResourceToFile(R.raw.noproxy_header_ht, "docs/noproxy-header.ht");
|
||||||
|
copyResourceToFile(R.raw.protocol_header_ht, "docs/protocol-header.ht");
|
||||||
|
copyResourceToFile(R.raw.reset_header_ht, "docs/reset-header.ht");
|
||||||
|
copyResourceToFile(R.raw.resetp_header_ht, "docs/resetp-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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File netDBDir = new File(myDir, "netDB");
|
||||||
|
netDBDir.mkdir();
|
||||||
|
//unzipResourceToDir(R.raw.netdb_zip, "netDB");
|
||||||
|
unzipResourceToDir(R.raw.certificates_zip, "certificates");
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
Util.e("copyResourceToFile" + "IOE: ", ioe);
|
||||||
|
} catch (Resources.NotFoundException nfe) {
|
||||||
|
Util.e("copyResourceToFile" + "NFE: ", nfe);
|
||||||
|
} finally {
|
||||||
|
if (in != null) try { in.close(); } catch (IOException ioe) {
|
||||||
|
Util.e("copyResourceToFile" + "IOE in.close(): ", ioe);
|
||||||
|
}
|
||||||
|
if (out != null) try { out.close(); } catch (IOException ioe) {
|
||||||
|
Util.e("copyResourceToFile" + "IOE out.close(): ", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param folder relative to base dir
|
||||||
|
*/
|
||||||
|
private void unzipResourceToDir(int resID, String folder) {
|
||||||
|
InputStream in = null;
|
||||||
|
FileOutputStream out = null;
|
||||||
|
ZipInputStream zis = null;
|
||||||
|
|
||||||
|
Util.i("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;
|
||||||
|
Util.i("unzipping "+ze);
|
||||||
|
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);
|
||||||
|
String canonicalPath = f.getCanonicalPath().replace("/user/0/", "/data/");
|
||||||
|
// account for canonical path differences when using .aab bundles
|
||||||
|
if (!canonicalPath.startsWith(myDir.replace("/user/0/", "/data/"))) {
|
||||||
|
// If these don't match, there's a path-traversal possibility.
|
||||||
|
// So ignore it.
|
||||||
|
Util.e("Path mismatch bug " + canonicalPath.toString() + " " + myDir.toString());
|
||||||
|
} else if (ze.isDirectory()) {
|
||||||
|
Util.i("Creating directory " + myDir + "/" + folder +"/" + name + " from resource");
|
||||||
|
f.mkdir();
|
||||||
|
} else {
|
||||||
|
Util.i("Creating file " + myDir + "/" + folder +"/" + name + " from resource");
|
||||||
|
//create all the leading directories
|
||||||
|
File newFile = new File(myDir+"/"+folder+"/"+name);
|
||||||
|
newFile.getParentFile().mkdirs();
|
||||||
|
byte[] bytes = baos.toByteArray();
|
||||||
|
out = new FileOutputStream(f);
|
||||||
|
out.write(bytes);
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Util.e("unzipResourceToDir" + "IOE: ", ioe);
|
||||||
|
} finally {
|
||||||
|
if (out != null) {
|
||||||
|
try {
|
||||||
|
out.close();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Util.e("unzipResourceToDir" + "IOE: interior out.close ", ioe);
|
||||||
|
}
|
||||||
|
out = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Util.e("unzipResourceToDir" + "IOE: ", ioe);
|
||||||
|
} catch (Resources.NotFoundException nfe) {
|
||||||
|
Util.e("unzipResourceToDir" + "NFE: ", nfe);
|
||||||
|
} finally {
|
||||||
|
if (in != null) try { in.close(); } catch (IOException ioe) {
|
||||||
|
Util.e("unzipResourceToDir" + "IOE: in.close() ", ioe);
|
||||||
|
}
|
||||||
|
if (out != null) try { out.close(); } catch (IOException ioe) {
|
||||||
|
Util.e("unzipResourceToDir" + "IOE: out.close() ", ioe);
|
||||||
|
}
|
||||||
|
if (zis != null) try { zis.close(); } catch (IOException ioe) {
|
||||||
|
Util.e("unzipResourceToDir" + "IOE: zis.close() ", 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();
|
||||||
|
|
||||||
|
Util.i("Checking for a new install/version");
|
||||||
|
InputStream fin = null;
|
||||||
|
try {
|
||||||
|
fin = ctx.openFileInput(CONFIG_FILE);
|
||||||
|
DataHelper.loadProps(props, fin);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Util.i("Looks like a new install");
|
||||||
|
} finally {
|
||||||
|
if (fin != null) {
|
||||||
|
try {
|
||||||
|
Util.i("fin was not null "+CONFIG_FILE);
|
||||||
|
fin.close();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Util.i("Error loading config:", ioe);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
Util.i("fin was null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String oldVersion = props.getProperty(PROP_INSTALLED_VERSION);
|
||||||
|
Util.i("Old version is:"+oldVersion);
|
||||||
|
boolean newInstall = oldVersion == null;
|
||||||
|
if (newInstall)
|
||||||
|
return true;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,8 @@ import java.io.FileInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.util.EepGet;
|
import net.i2p.util.EepGet;
|
||||||
@ -83,7 +85,7 @@ public class EepGetFetcher implements EepGet.StatusListener {
|
|||||||
int semi = rv.indexOf(";");
|
int semi = rv.indexOf(";");
|
||||||
if (semi > 0)
|
if (semi > 0)
|
||||||
rv = rv.substring(0, semi);
|
rv = rv.substring(0, semi);
|
||||||
return rv.toLowerCase();
|
return rv.toLowerCase(Locale.US);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,22 +1,63 @@
|
|||||||
package net.i2p.android.apps;
|
package net.i2p.android.apps;
|
||||||
|
|
||||||
import java.io.File;
|
import static net.i2p.app.ClientAppState.INITIALIZED;
|
||||||
|
import static net.i2p.app.ClientAppState.RUNNING;
|
||||||
|
import static net.i2p.app.ClientAppState.STARTING;
|
||||||
|
import static net.i2p.app.ClientAppState.STOPPED;
|
||||||
|
import static net.i2p.app.ClientAppState.STOPPING;
|
||||||
|
import static net.i2p.app.ClientAppState.UNINITIALIZED;
|
||||||
|
import static net.i2p.update.UpdateType.BLOCKLIST;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
import net.i2p.android.router.NewsActivity;
|
import net.i2p.android.router.NewsActivity;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
import net.i2p.android.router.util.Notifications;
|
import net.i2p.android.router.util.Notifications;
|
||||||
|
import net.i2p.app.ClientApp;
|
||||||
|
import net.i2p.app.ClientAppManager;
|
||||||
|
import net.i2p.app.ClientAppState;
|
||||||
|
import net.i2p.crypto.SU3File;
|
||||||
|
import net.i2p.data.Base64;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.data.Hash;
|
||||||
|
import net.i2p.router.Banlist;
|
||||||
|
import net.i2p.router.Blocklist;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.router.util.RFC822Date;
|
import net.i2p.router.news.BlocklistEntries;
|
||||||
|
import net.i2p.router.news.NewsEntry;
|
||||||
|
import net.i2p.router.news.NewsMetadata;
|
||||||
|
import net.i2p.router.news.NewsXMLParser;
|
||||||
|
import net.i2p.util.Addresses;
|
||||||
import net.i2p.util.EepGet;
|
import net.i2p.util.EepGet;
|
||||||
import net.i2p.util.FileUtil;
|
import net.i2p.util.FileUtil;
|
||||||
|
import net.i2p.util.I2PAppThread;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.Translate;
|
import net.i2p.util.RFC822Date;
|
||||||
|
import net.i2p.util.ReusableGZIPInputStream;
|
||||||
|
import net.i2p.util.SecureFile;
|
||||||
|
import net.i2p.util.SecureFileOutputStream;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* From router console, simplified since we don't deal with router versions
|
* From router console, simplified since we don't deal with router versions
|
||||||
* or updates.
|
* or updates.
|
||||||
|
*
|
||||||
|
* As of 0.9.41, implements ClientApp to hang us off the ClientAppManager,
|
||||||
|
* so we can remove the static reference.
|
||||||
*/
|
*/
|
||||||
public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
public class NewsFetcher implements Runnable, EepGet.StatusListener, ClientApp {
|
||||||
|
private final Context mCtx;
|
||||||
private final RouterContext _context;
|
private final RouterContext _context;
|
||||||
private final Notifications _notif;
|
private final Notifications _notif;
|
||||||
private final Log _log;
|
private final Log _log;
|
||||||
@ -26,20 +67,22 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
private boolean _invalidated;
|
private boolean _invalidated;
|
||||||
private File _newsFile;
|
private File _newsFile;
|
||||||
private File _tempFile;
|
private File _tempFile;
|
||||||
private static NewsFetcher _instance;
|
|
||||||
private volatile boolean _isRunning = true;
|
private volatile boolean _isRunning = true;
|
||||||
private Thread _thread;
|
private Thread _thread;
|
||||||
|
private final ClientAppManager _mgr;
|
||||||
|
private volatile ClientAppState _state = UNINITIALIZED;
|
||||||
|
public static final String APP_NAME = "NewsFetcher";
|
||||||
|
|
||||||
public static /*final */ NewsFetcher getInstance() {
|
static final String PROP_BLOCKLIST_TIME = "router.blocklistVersion";
|
||||||
return _instance;
|
private static final String BLOCKLIST_DIR = "docs/feed/blocklist";
|
||||||
}
|
private static final String BLOCKLIST_FILE = "blocklist.txt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As of 0.9.41, returns a new one every time. Only call once.
|
||||||
|
*/
|
||||||
public static /* final */ synchronized NewsFetcher getInstance(
|
public static /* final */ synchronized NewsFetcher getInstance(
|
||||||
RouterContext ctx, Notifications notif) {
|
Context context, RouterContext ctx, Notifications notif) {
|
||||||
if (_instance != null)
|
return new NewsFetcher(context, ctx, notif);
|
||||||
return _instance;
|
|
||||||
_instance = new NewsFetcher(ctx, notif);
|
|
||||||
return _instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String NEWS_DIR = "docs";
|
private static final String NEWS_DIR = "docs";
|
||||||
@ -47,35 +90,39 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
private static final String TEMP_NEWS_FILE = "news.xml.temp";
|
private static final String TEMP_NEWS_FILE = "news.xml.temp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changed in 0.9.11 to the b32 for psi.i2p, run by psi.
|
* 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
|
* We may be able to change it to psi.i2p in a future release after
|
||||||
* the hostname propagates.
|
* the hostname propagates.
|
||||||
*
|
*
|
||||||
* @since 0.7.14 not configurable
|
* @since 0.7.14 not configurable
|
||||||
*/
|
*/
|
||||||
private static final String BACKUP_NEWS_URL = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.xml";
|
private static final String BACKUP_NEWS_URL_SU3 = "http://dn3tvalnjz432qkqsvpfdqrwpqkw3ye4n4i2uyfr4jexvo3sp5ka.b32.i2p/news/news.su3";
|
||||||
private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
|
private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
|
||||||
private static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
|
private static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
|
||||||
private static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + "";
|
private static final String DEFAULT_REFRESH_FREQUENCY = 24 * 60 * 60 * 1000 + "";
|
||||||
private static final String PROP_NEWS_URL = "router.newsURL";
|
private static final String PROP_NEWS_URL = "router.newsURL";
|
||||||
private static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml";
|
public static final String DEFAULT_NEWS_URL_SU3 = "http://tc73n4kivdroccekirco7rhgxdg5f3cjvbaapabupeyzrqwv5guq.b32.i2p/news.su3";
|
||||||
|
|
||||||
private NewsFetcher(RouterContext ctx, Notifications notif) {
|
private NewsFetcher(Context context, RouterContext ctx, Notifications notif) {
|
||||||
|
mCtx = context;
|
||||||
_context = ctx;
|
_context = ctx;
|
||||||
_notif = notif;
|
_notif = notif;
|
||||||
_context.addShutdownTask(new Shutdown());
|
|
||||||
_log = ctx.logManager().getLog(NewsFetcher.class);
|
_log = ctx.logManager().getLog(NewsFetcher.class);
|
||||||
try {
|
try {
|
||||||
String last = ctx.getProperty(PROP_LAST_CHECKED);
|
String last = ctx.getProperty(PROP_LAST_CHECKED);
|
||||||
if (last != null)
|
if (last != null)
|
||||||
_lastFetch = Long.parseLong(last);
|
_lastFetch = Long.parseLong(last);
|
||||||
} catch (NumberFormatException nfe) {}
|
} catch (NumberFormatException nfe) {
|
||||||
|
}
|
||||||
File newsDir = new File(_context.getRouterDir(), NEWS_DIR);
|
File newsDir = new File(_context.getRouterDir(), NEWS_DIR);
|
||||||
// isn't already there on android
|
// isn't already there on android
|
||||||
newsDir.mkdir();
|
newsDir.mkdir();
|
||||||
_newsFile = new File(newsDir, NEWS_FILE);
|
_newsFile = new File(newsDir, NEWS_FILE);
|
||||||
_tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
|
_tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
|
||||||
updateLastFetched();
|
updateLastFetched();
|
||||||
|
_mgr = ctx.clientAppManager();
|
||||||
|
changeState(INITIALIZED);
|
||||||
|
_mgr.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLastFetched() {
|
private void updateLastFetched() {
|
||||||
@ -94,27 +141,37 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String status() {
|
public String status() {
|
||||||
StringBuilder buf = new StringBuilder(128);
|
StringBuilder buf = new StringBuilder(128);
|
||||||
long now = _context.clock().now();
|
long now = _context.clock().now();
|
||||||
if (_lastUpdated > 0) {
|
if (_lastUpdated > 0) {
|
||||||
buf.append(Translate.getString("News last updated {0} ago.",
|
buf.append(mCtx.getString(R.string.news_last_updated,
|
||||||
DataHelper.formatDuration2(now - _lastUpdated),
|
DataHelper.formatDuration2(now - _lastUpdated)))
|
||||||
_context, "foo"))
|
.append('\n');
|
||||||
.append('\n');
|
}
|
||||||
}
|
if (_lastFetch > _lastUpdated) {
|
||||||
if (_lastFetch > _lastUpdated) {
|
buf.append(mCtx.getString(R.string.news_last_checked,
|
||||||
buf.append(Translate.getString("News last checked {0} ago.",
|
DataHelper.formatDuration2(now - _lastFetch)));
|
||||||
DataHelper.formatDuration2(now - _lastFetch),
|
}
|
||||||
_context, "foo"));
|
return buf.toString();
|
||||||
}
|
|
||||||
return buf.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final long INITIAL_DELAY = 5*60*1000;
|
// Runnable
|
||||||
private static final long RUN_DELAY = 30*60*1000;
|
|
||||||
|
private static final long INITIAL_DELAY = 5 * 60 * 1000;
|
||||||
|
private static final long RUN_DELAY = 30 * 60 * 1000;
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
_thread = Thread.currentThread();
|
changeState(RUNNING);
|
||||||
|
try {
|
||||||
|
run2();
|
||||||
|
} finally {
|
||||||
|
_mgr.unregister(this);
|
||||||
|
changeState(STOPPED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void run2() {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(INITIAL_DELAY);
|
Thread.sleep(INITIAL_DELAY);
|
||||||
} catch (InterruptedException ie) {
|
} catch (InterruptedException ie) {
|
||||||
@ -137,7 +194,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
return true;
|
return true;
|
||||||
updateLastFetched();
|
updateLastFetched();
|
||||||
String freq = _context.getProperty(PROP_REFRESH_FREQUENCY,
|
String freq = _context.getProperty(PROP_REFRESH_FREQUENCY,
|
||||||
DEFAULT_REFRESH_FREQUENCY);
|
DEFAULT_REFRESH_FREQUENCY);
|
||||||
try {
|
try {
|
||||||
long ms = Long.parseLong(freq);
|
long ms = Long.parseLong(freq);
|
||||||
if (ms <= 0)
|
if (ms <= 0)
|
||||||
@ -158,8 +215,9 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this when changing news URLs to force an update next time the timer fires.
|
* Call this when changing news URLs to force an update next time the timer fires.
|
||||||
* @since 0.8.7
|
*
|
||||||
|
* @since 0.8.7
|
||||||
*/
|
*/
|
||||||
void invalidateNews() {
|
void invalidateNews() {
|
||||||
_lastModified = null;
|
_lastModified = null;
|
||||||
@ -167,7 +225,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void fetchNews() {
|
public void fetchNews() {
|
||||||
String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL);
|
String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL_SU3);
|
||||||
String proxyHost = "127.0.0.1";
|
String proxyHost = "127.0.0.1";
|
||||||
int proxyPort = 4444;
|
int proxyPort = 4444;
|
||||||
if (_tempFile.exists())
|
if (_tempFile.exists())
|
||||||
@ -183,7 +241,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
} else {
|
} else {
|
||||||
// backup news location - always proxied
|
// backup news location - always proxied
|
||||||
_tempFile.delete();
|
_tempFile.delete();
|
||||||
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL, true, null, _lastModified);
|
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL_SU3, true, null, _lastModified);
|
||||||
get.addStatusListener(this);
|
get.addStatusListener(this);
|
||||||
if (get.fetch())
|
if (get.fetch())
|
||||||
_lastModified = get.getLastModified();
|
_lastModified = get.getLastModified();
|
||||||
@ -193,25 +251,38 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
// EepGet.StatusListener
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||||
if (_log.shouldLog(Log.INFO))
|
if (_log.shouldLog(Log.INFO))
|
||||||
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
|
_log.info("News fetched from " + url + " with " + (alreadyTransferred + bytesTransferred));
|
||||||
|
|
||||||
long now = _context.clock().now();
|
long now = _context.clock().now();
|
||||||
if (_tempFile.exists()) {
|
if (_tempFile.exists() && _tempFile.length() > 0) {
|
||||||
boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true);
|
File from;
|
||||||
|
if (url.endsWith(".su3")) {
|
||||||
|
try {
|
||||||
|
from = processSU3();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("Failed to extract the news file", ioe);
|
||||||
|
_tempFile.delete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
from = _tempFile;
|
||||||
|
}
|
||||||
|
boolean copied = FileUtil.rename(from, _newsFile);
|
||||||
|
_tempFile.delete();
|
||||||
if (copied) {
|
if (copied) {
|
||||||
_lastUpdated = now;
|
_lastUpdated = now;
|
||||||
_tempFile.delete();
|
|
||||||
|
|
||||||
// Notify user
|
// Notify user
|
||||||
_notif.notify("News Updated", "Touch to view latest I2P news",
|
_notif.notify(mCtx.getString(R.string.news_updated),
|
||||||
|
mCtx.getString(R.string.view_news),
|
||||||
NewsActivity.class);
|
NewsActivity.class);
|
||||||
} else {
|
} else {
|
||||||
if (_log.shouldLog(Log.ERROR))
|
if (_log.shouldLog(Log.ERROR))
|
||||||
@ -226,19 +297,321 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
_context.router().saveConfig();
|
_context.router().saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||||
if (_log.shouldLog(Log.WARN))
|
if (_log.shouldLog(Log.WARN))
|
||||||
_log.warn("Failed to fetch the news from " + url);
|
_log.warn("Failed to fetch the news from " + url);
|
||||||
_tempFile.delete();
|
_tempFile.delete();
|
||||||
}
|
}
|
||||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
|
||||||
public void attempting(String url) {}
|
|
||||||
|
|
||||||
private class Shutdown implements Runnable {
|
public void headerReceived(String url, int attemptNum, String key, String val) {
|
||||||
public void run() {
|
}
|
||||||
_isRunning = false;
|
|
||||||
if (_thread != null)
|
public void attempting(String url) {
|
||||||
_thread.interrupt();
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// SU3 handlers
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the fetched su3 news file _tempFile.
|
||||||
|
* Handles 3 types of contained files: xml.gz (preferred), xml, and html (old format fake xml)
|
||||||
|
*
|
||||||
|
* @return the temp file contining the HTML-format news.xml
|
||||||
|
* @since 0.9.20
|
||||||
|
*/
|
||||||
|
private File processSU3() throws IOException {
|
||||||
|
SU3File su3 = new SU3File(_context, _tempFile);
|
||||||
|
// real xml, maybe gz, maybe not
|
||||||
|
File to1 = new File(_context.getTempDir(), "tmp-" + _context.random().nextInt() + ".xml");
|
||||||
|
// real xml
|
||||||
|
File to2 = new File(_context.getTempDir(), "tmp2-" + _context.random().nextInt() + ".xml");
|
||||||
|
try {
|
||||||
|
su3.verifyAndMigrate(to1);
|
||||||
|
int type = su3.getFileType();
|
||||||
|
if (su3.getContentType() != SU3File.CONTENT_NEWS)
|
||||||
|
throw new IOException("bad content type: " + su3.getContentType());
|
||||||
|
if (type == SU3File.TYPE_HTML)
|
||||||
|
return to1;
|
||||||
|
if (type != SU3File.TYPE_XML && type != SU3File.TYPE_XML_GZ)
|
||||||
|
throw new IOException("bad file type: " + type);
|
||||||
|
File xml;
|
||||||
|
if (type == SU3File.TYPE_XML_GZ) {
|
||||||
|
gunzip(to1, to2);
|
||||||
|
xml = to2;
|
||||||
|
to1.delete();
|
||||||
|
} else {
|
||||||
|
xml = to1;
|
||||||
|
}
|
||||||
|
NewsXMLParser parser = new NewsXMLParser(_context);
|
||||||
|
parser.parse(xml);
|
||||||
|
xml.delete();
|
||||||
|
NewsMetadata data = parser.getMetadata();
|
||||||
|
List<NewsEntry> entries = parser.getEntries();
|
||||||
|
BlocklistEntries ble = parser.getBlocklistEntries();
|
||||||
|
if (ble != null && ble.isVerified())
|
||||||
|
processBlocklistEntries(ble);
|
||||||
|
else
|
||||||
|
_log.info("No blocklist entries found in news feed");
|
||||||
|
String sudVersion = su3.getVersionString();
|
||||||
|
String signingKeyName = su3.getSignerString();
|
||||||
|
File to3 = new File(_context.getTempDir(), "tmp3-" + _context.random().nextInt() + ".xml");
|
||||||
|
outputOldNewsXML(data, entries, sudVersion, signingKeyName, to3);
|
||||||
|
return to3;
|
||||||
|
} finally {
|
||||||
|
to2.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process blocklist entries
|
||||||
|
*
|
||||||
|
* @since 0.9.57
|
||||||
|
*/
|
||||||
|
private void processBlocklistEntries(BlocklistEntries ble) {
|
||||||
|
long oldTime = _context.getProperty(PROP_BLOCKLIST_TIME, 0L);
|
||||||
|
if (ble.updated <= oldTime) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Not processing blocklist " + DataHelper.formatDate(ble.updated) +
|
||||||
|
", already have " + DataHelper.formatDate(oldTime));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Blocklist bl = _context.blocklist();
|
||||||
|
Banlist ban = _context.banlist();
|
||||||
|
String reason = "Blocklist feed " + DataHelper.formatDate(ble.updated);
|
||||||
|
int banned = 0;
|
||||||
|
for (Iterator<String> iter = ble.entries.iterator(); iter.hasNext(); ) {
|
||||||
|
String s = iter.next();
|
||||||
|
if (s.length() == 44) {
|
||||||
|
byte[] b = Base64.decode(s);
|
||||||
|
if (b == null || b.length != Hash.HASH_LENGTH) {
|
||||||
|
iter.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Hash h = Hash.create(b);
|
||||||
|
if (!ban.isBanlistedForever(h)) {
|
||||||
|
ban.banlistRouterForever(h, reason);
|
||||||
|
_context.commSystem().forceDisconnect(h);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byte[] ip = Addresses.getIP(s);
|
||||||
|
if (ip == null) {
|
||||||
|
iter.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!bl.isBlocklisted(ip))
|
||||||
|
bl.add(ip);
|
||||||
|
}
|
||||||
|
if (++banned >= BlocklistEntries.MAX_ENTRIES) {
|
||||||
|
// prevent somebody from destroying the whole network
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String s : ble.removes) {
|
||||||
|
if (s.length() == 44) {
|
||||||
|
byte[] b = Base64.decode(s);
|
||||||
|
if (b == null || b.length != Hash.HASH_LENGTH)
|
||||||
|
continue;
|
||||||
|
Hash h = Hash.create(b);
|
||||||
|
if (ban.isBanlistedForever(h))
|
||||||
|
ban.unbanlistRouter(h);
|
||||||
|
} else {
|
||||||
|
byte[] ip = Addresses.getIP(s);
|
||||||
|
if (ip == null)
|
||||||
|
continue;
|
||||||
|
if (bl.isBlocklisted(ip))
|
||||||
|
bl.remove(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save the blocks. We do not save the unblocks.
|
||||||
|
File f = new SecureFile(_context.getConfigDir(), BLOCKLIST_DIR);
|
||||||
|
f.mkdirs();
|
||||||
|
f = new File(f, BLOCKLIST_FILE);
|
||||||
|
boolean fail = false;
|
||||||
|
BufferedWriter out = null;
|
||||||
|
try {
|
||||||
|
out = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(f), "UTF-8"));
|
||||||
|
out.write("# ");
|
||||||
|
out.write(ble.supdated);
|
||||||
|
out.newLine();
|
||||||
|
banned = 0;
|
||||||
|
for (String s : ble.entries) {
|
||||||
|
s = s.replace(':', ';'); // IPv6
|
||||||
|
out.write(reason);
|
||||||
|
out.write(':');
|
||||||
|
out.write(s);
|
||||||
|
out.newLine();
|
||||||
|
if (++banned >= BlocklistEntries.MAX_ENTRIES)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("Error writing blocklist", ioe);
|
||||||
|
fail = true;
|
||||||
|
} finally {
|
||||||
|
if (out != null) try {
|
||||||
|
out.close();
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
if (!fail) {
|
||||||
|
f.setLastModified(ble.updated);
|
||||||
|
String upd = Long.toString(ble.updated);
|
||||||
|
_context.router().saveConfig(PROP_BLOCKLIST_TIME, upd);
|
||||||
|
}
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Processed " + ble.entries.size() + " blocks and " + ble.removes.size() + " unblocks from news feed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gunzip the file
|
||||||
|
*
|
||||||
|
* @since 0.9.20
|
||||||
|
*/
|
||||||
|
private static void gunzip(File from, File to) throws IOException {
|
||||||
|
ReusableGZIPInputStream in = ReusableGZIPInputStream.acquire();
|
||||||
|
OutputStream out = null;
|
||||||
|
try {
|
||||||
|
in.initialize(new FileInputStream(from));
|
||||||
|
out = new SecureFileOutputStream(to);
|
||||||
|
byte buf[] = new byte[4096];
|
||||||
|
int read;
|
||||||
|
while ((read = in.read(buf)) != -1) {
|
||||||
|
out.write(buf, 0, read);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (out != null) try {
|
||||||
|
out.close();
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
ReusableGZIPInputStream.release(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output in the old format.
|
||||||
|
*
|
||||||
|
* @since 0.9.20
|
||||||
|
*/
|
||||||
|
private void outputOldNewsXML(NewsMetadata data, List<NewsEntry> entries,
|
||||||
|
String sudVersion, String signingKeyName, File to) throws IOException {
|
||||||
|
NewsMetadata.Release latestRelease = data.releases.get(0);
|
||||||
|
Writer out = null;
|
||||||
|
try {
|
||||||
|
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(to), "UTF-8"));
|
||||||
|
out.write("<!--\n");
|
||||||
|
// update metadata in old format
|
||||||
|
out.write("<i2p.release ");
|
||||||
|
if (latestRelease.i2pVersion != null)
|
||||||
|
out.write(" version=\"" + latestRelease.i2pVersion + '"');
|
||||||
|
if (latestRelease.minVersion != null)
|
||||||
|
out.write(" minVersion=\"" + latestRelease.minVersion + '"');
|
||||||
|
if (latestRelease.minJavaVersion != null)
|
||||||
|
out.write(" minJavaVersion=\"" + latestRelease.minJavaVersion + '"');
|
||||||
|
String su3Torrent = "";
|
||||||
|
String su2Torrent = "";
|
||||||
|
for (NewsMetadata.Update update : latestRelease.updates) {
|
||||||
|
if (update.torrent != null) {
|
||||||
|
if ("su3".equals(update.type))
|
||||||
|
su3Torrent = update.torrent;
|
||||||
|
else if ("su2".equals(update.type))
|
||||||
|
su2Torrent = update.torrent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!su2Torrent.isEmpty())
|
||||||
|
out.write(" su2Torrent=\"" + su2Torrent + '"');
|
||||||
|
if (!su3Torrent.isEmpty())
|
||||||
|
out.write(" su3Torrent=\"" + su3Torrent + '"');
|
||||||
|
out.write("/>\n");
|
||||||
|
// su3 and feed metadata for debugging
|
||||||
|
out.write("** News version:\t" + DataHelper.stripHTML(sudVersion) + '\n');
|
||||||
|
out.write("** Signed by:\t" + signingKeyName + '\n');
|
||||||
|
out.write("** Feed:\t" + DataHelper.stripHTML(data.feedTitle) + '\n');
|
||||||
|
out.write("** Feed ID:\t" + DataHelper.stripHTML(data.feedID) + '\n');
|
||||||
|
out.write("** Feed Date:\t" + (new Date(data.feedUpdated)) + '\n');
|
||||||
|
out.write("-->\n");
|
||||||
|
if (entries == null)
|
||||||
|
return;
|
||||||
|
for (NewsEntry e : entries) {
|
||||||
|
if (e.title == null || e.content == null)
|
||||||
|
continue;
|
||||||
|
out.write("<!-- Entry Date: " + (new Date(e.updated)) + " -->\n");
|
||||||
|
out.write("<h3>");
|
||||||
|
out.write(e.title);
|
||||||
|
out.write("</h3>\n");
|
||||||
|
out.write(e.content);
|
||||||
|
out.write("\n\n");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (out != null) try {
|
||||||
|
out.close();
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////// begin ClientApp interface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.41
|
||||||
|
*/
|
||||||
|
public synchronized void startup() {
|
||||||
|
changeState(STARTING);
|
||||||
|
_thread = new I2PAppThread(this, "NewsFetcher", true);
|
||||||
|
_thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.41
|
||||||
|
*/
|
||||||
|
public synchronized void shutdown(String[] args) {
|
||||||
|
if (_state != RUNNING)
|
||||||
|
return;
|
||||||
|
changeState(STOPPING);
|
||||||
|
_isRunning = false;
|
||||||
|
if (_thread != null)
|
||||||
|
_thread.interrupt();
|
||||||
|
changeState(STOPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.41
|
||||||
|
*/
|
||||||
|
public ClientAppState getState() {
|
||||||
|
return _state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.41
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return APP_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.41
|
||||||
|
*/
|
||||||
|
public String getDisplayName() {
|
||||||
|
return APP_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
////// end ClientApp interface
|
||||||
|
////// begin ClientApp helpers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.41
|
||||||
|
*/
|
||||||
|
private void changeState(ClientAppState state) {
|
||||||
|
changeState(state, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.41
|
||||||
|
*/
|
||||||
|
private synchronized void changeState(ClientAppState state, Exception e) {
|
||||||
|
_state = state;
|
||||||
|
_mgr.notify(this, state, null, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
////// end ClientApp helpers
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,26 @@
|
|||||||
package net.i2p.android.help;
|
package net.i2p.android.help;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class Browser implements Comparable<Browser> {
|
public class Browser implements Comparable<Browser> {
|
||||||
public final String packageName;
|
public final String packageName;
|
||||||
public final CharSequence label;
|
public final CharSequence label;
|
||||||
public final Drawable icon;
|
public final Drawable icon;
|
||||||
public final boolean isInstalled;
|
|
||||||
public final boolean isKnown;
|
public final boolean isKnown;
|
||||||
public final boolean isSupported;
|
public final boolean isSupported;
|
||||||
public final boolean isRecommended;
|
public final boolean isRecommended;
|
||||||
|
|
||||||
|
private boolean isInstalled;
|
||||||
/**
|
/**
|
||||||
* A browser that we don't know about.
|
* A browser that we don't know about.
|
||||||
*
|
*
|
||||||
@ -55,7 +63,7 @@ public class Browser implements Comparable<Browser> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Browser browser) {
|
public int compareTo(@NonNull Browser browser) {
|
||||||
// Sort order: supported -> unknown -> unsupported
|
// Sort order: supported -> unknown -> unsupported
|
||||||
int a = getOrder(this);
|
int a = getOrder(this);
|
||||||
int b = getOrder(browser);
|
int b = getOrder(browser);
|
||||||
@ -79,4 +87,23 @@ public class Browser implements Comparable<Browser> {
|
|||||||
} else
|
} else
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInstalled(Context context){
|
||||||
|
if (isInstalled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 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 = context.getPackageManager();
|
||||||
|
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
|
||||||
|
for (ResolveInfo browser : installedBrowsers) {
|
||||||
|
if (browser.activityInfo.packageName.equals(packageName)) {
|
||||||
|
isInstalled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isInstalled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
package net.i2p.android.help;
|
package net.i2p.android.help;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.ColorMatrix;
|
import android.graphics.ColorMatrix;
|
||||||
import android.graphics.ColorMatrixColorFilter;
|
import android.graphics.ColorMatrixColorFilter;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.v7.widget.RecyclerView;
|
//import android.support.v7.widget.RecyclerView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
@ -35,8 +38,8 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static interface OnBrowserSelectedListener {
|
public interface OnBrowserSelectedListener {
|
||||||
public void onBrowserSelected(Browser browser);
|
void onBrowserSelected(Browser browser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BrowserAdapter(Context ctx, OnBrowserSelectedListener listener) {
|
public BrowserAdapter(Context ctx, OnBrowserSelectedListener listener) {
|
||||||
@ -71,11 +74,15 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
|
|||||||
holder.mLabel.setText(browser.label);
|
holder.mLabel.setText(browser.label);
|
||||||
|
|
||||||
if (browser.isKnown) {
|
if (browser.isKnown) {
|
||||||
if (browser.isRecommended && browser.isInstalled) {
|
if (browser.isRecommended && browser.isInstalled(mCtx)) {
|
||||||
holder.mStatus.setImageDrawable(
|
holder.mStatus.setImageDrawable(
|
||||||
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
|
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
|
||||||
holder.mStatus.setVisibility(View.VISIBLE);
|
holder.mStatus.setVisibility(View.VISIBLE);
|
||||||
} else if (browser.isSupported && !browser.isInstalled) {
|
} else if (browser.isSupported && browser.isInstalled(mCtx)) {
|
||||||
|
holder.mStatus.setImageDrawable(
|
||||||
|
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
|
||||||
|
holder.mStatus.setVisibility(View.INVISIBLE);
|
||||||
|
} else if (browser.isSupported && !browser.isInstalled(mCtx)) {
|
||||||
holder.mStatus.setImageDrawable(
|
holder.mStatus.setImageDrawable(
|
||||||
mCtx.getResources().getDrawable(R.drawable.ic_shop_white_24dp));
|
mCtx.getResources().getDrawable(R.drawable.ic_shop_white_24dp));
|
||||||
holder.mStatus.setOnClickListener(new View.OnClickListener() {
|
holder.mStatus.setOnClickListener(new View.OnClickListener() {
|
||||||
@ -84,11 +91,15 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
|
|||||||
String uriMarket = "market://search?q=pname:" + browser.packageName;
|
String uriMarket = "market://search?q=pname:" + browser.packageName;
|
||||||
Uri uri = Uri.parse(uriMarket);
|
Uri uri = Uri.parse(uriMarket);
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
mCtx.startActivity(intent);
|
try {
|
||||||
|
mCtx.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(mCtx, R.string.no_market_app, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
holder.mStatus.setVisibility(View.VISIBLE);
|
holder.mStatus.setVisibility(View.VISIBLE);
|
||||||
} else if (!browser.isSupported) {
|
} else if (browser.isInstalled(mCtx) && !browser.isSupported) {
|
||||||
// Make the icon gray-scale to show it is unsupported
|
// Make the icon gray-scale to show it is unsupported
|
||||||
ColorMatrix matrix = new ColorMatrix();
|
ColorMatrix matrix = new ColorMatrix();
|
||||||
matrix.setSaturation(0);
|
matrix.setSaturation(0);
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
package net.i2p.android.help;
|
package net.i2p.android.help;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
//import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.util.LocaleManager;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
public class BrowserConfigActivity extends ActionBarActivity implements
|
public class BrowserConfigActivity extends AppCompatActivity implements
|
||||||
BrowserAdapter.OnBrowserSelectedListener {
|
BrowserAdapter.OnBrowserSelectedListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,8 +21,11 @@ public class BrowserConfigActivity extends ActionBarActivity implements
|
|||||||
*/
|
*/
|
||||||
private boolean mTwoPane;
|
private boolean mTwoPane;
|
||||||
|
|
||||||
|
private final LocaleManager localeManager = new LocaleManager();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
localeManager.onCreate(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_help);
|
setContentView(R.layout.activity_help);
|
||||||
|
|
||||||
@ -43,6 +49,12 @@ public class BrowserConfigActivity extends ActionBarActivity implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
localeManager.onResume(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
|
@ -8,23 +8,31 @@ import android.content.pm.ResolveInfo;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
//import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.LoaderManager;
|
import androidx.fragment.app.Fragment;
|
||||||
import android.support.v4.content.Loader;
|
//import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
//import android.support.v4.content.Loader;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
//import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
//import android.support.v7.widget.RecyclerView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
import net.i2p.android.router.util.BetterAsyncTaskLoader;
|
import net.i2p.android.router.util.BetterAsyncTaskLoader;
|
||||||
|
import net.i2p.android.widget.DividerItemDecoration;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -55,6 +63,9 @@ public class BrowserListFragment extends Fragment implements
|
|||||||
View v = inflater.inflate(R.layout.fragment_help_browsers, container, false);
|
View v = inflater.inflate(R.layout.fragment_help_browsers, container, false);
|
||||||
RecyclerView mRecyclerView = (RecyclerView) v.findViewById(R.id.browser_list);
|
RecyclerView mRecyclerView = (RecyclerView) v.findViewById(R.id.browser_list);
|
||||||
|
|
||||||
|
mRecyclerView.setHasFixedSize(true);
|
||||||
|
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
|
||||||
|
|
||||||
// use a linear layout manager
|
// use a linear layout manager
|
||||||
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
|
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
|
||||||
mRecyclerView.setLayoutManager(mLayoutManager);
|
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||||
@ -96,18 +107,38 @@ public class BrowserListFragment extends Fragment implements
|
|||||||
getContext().getResources().getStringArray(R.array.supported_browsers));
|
getContext().getResources().getStringArray(R.array.supported_browsers));
|
||||||
supportedLabels = Arrays.asList(
|
supportedLabels = Arrays.asList(
|
||||||
getContext().getResources().getStringArray(R.array.supported_browser_labels));
|
getContext().getResources().getStringArray(R.array.supported_browser_labels));
|
||||||
unsupported = Arrays.asList(
|
unsupported = allBrowsers(context);//Arrays.asList(
|
||||||
context.getResources().getStringArray(R.array.unsupported_browsers));
|
//context.getResources().getStringArray(R.array.unsupported_browsers));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> allBrowsers(Context context){
|
||||||
|
//try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse("http://www.google.com"));
|
||||||
|
List<ResolveInfo> browserList;
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||||
|
// gets all
|
||||||
|
browserList = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
|
||||||
|
} else {
|
||||||
|
browserList = pm.queryIntentActivities(intent, 0);
|
||||||
|
}
|
||||||
|
//}catch()
|
||||||
|
List<String> finalResult = new ArrayList<String>();
|
||||||
|
for (ResolveInfo ri : browserList){
|
||||||
|
finalResult.add(ri.resolvePackageName);
|
||||||
|
}
|
||||||
|
return finalResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Browser> loadInBackground() {
|
public List<Browser> loadInBackground() {
|
||||||
List<Browser> browsers = new ArrayList<Browser>();
|
List<Browser> browsers = new ArrayList<>();
|
||||||
Map<String, String> recommendedMap = new HashMap<String, String>();
|
Map<String, String> recommendedMap = new HashMap<>();
|
||||||
for (int i = 0; i < recommended.size(); i++) {
|
for (int i = 0; i < recommended.size(); i++) {
|
||||||
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
|
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
|
||||||
}
|
}
|
||||||
Map<String, String> supportedMap = new HashMap<String, String>();
|
Map<String, String> supportedMap = new HashMap<>();
|
||||||
for (int i = 0; i < supported.size(); i++) {
|
for (int i = 0; i < supported.size(); i++) {
|
||||||
supportedMap.put(supported.get(i), supportedLabels.get(i));
|
supportedMap.put(supported.get(i), supportedLabels.get(i));
|
||||||
}
|
}
|
||||||
@ -117,8 +148,9 @@ public class BrowserListFragment extends Fragment implements
|
|||||||
intent.setData(Uri.parse("http://stats.i2p"));
|
intent.setData(Uri.parse("http://stats.i2p"));
|
||||||
|
|
||||||
final PackageManager pm = getContext().getPackageManager();
|
final PackageManager pm = getContext().getPackageManager();
|
||||||
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
|
Set<ResolveInfo> installedBrowsers = new HashSet<>(pm.queryIntentActivities(intent, 0));
|
||||||
|
|
||||||
|
// Compare installed browsers to supported browsers
|
||||||
for (ResolveInfo browser : installedBrowsers) {
|
for (ResolveInfo browser : installedBrowsers) {
|
||||||
if (recommended.contains(browser.activityInfo.packageName)) {
|
if (recommended.contains(browser.activityInfo.packageName)) {
|
||||||
browsers.add(new Browser(pm, browser, true, true));
|
browsers.add(new Browser(pm, browser, true, true));
|
||||||
|
@ -2,16 +2,23 @@ package net.i2p.android.help;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
//import android.support.v4.app.NavUtils;
|
||||||
import android.support.v7.widget.Toolbar;
|
import androidx.core.app.NavUtils;
|
||||||
|
//import android.support.v4.app.TaskStackBuilder;
|
||||||
|
import androidx.core.app.TaskStackBuilder;
|
||||||
|
//import android.support.v7.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import net.i2p.android.router.LicenseActivity;
|
import net.i2p.android.router.LicenseActivity;
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||||
|
import net.i2p.android.util.LocaleManager;
|
||||||
|
|
||||||
public class HelpActivity extends ActionBarActivity implements
|
public class HelpActivity extends AppCompatActivity implements
|
||||||
HelpListFragment.OnEntrySelectedListener {
|
HelpListFragment.OnEntrySelectedListener {
|
||||||
public static final String CATEGORY = "help_category";
|
public static final String CATEGORY = "help_category";
|
||||||
public static final int CAT_MAIN = 0;
|
public static final int CAT_MAIN = 0;
|
||||||
@ -24,9 +31,13 @@ public class HelpActivity extends ActionBarActivity implements
|
|||||||
* device.
|
* device.
|
||||||
*/
|
*/
|
||||||
private boolean mTwoPane;
|
private boolean mTwoPane;
|
||||||
|
private int mCategory;
|
||||||
|
|
||||||
|
private final LocaleManager localeManager = new LocaleManager();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
localeManager.onCreate(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_help);
|
setContentView(R.layout.activity_help);
|
||||||
|
|
||||||
@ -49,9 +60,16 @@ public class HelpActivity extends ActionBarActivity implements
|
|||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
int category = getIntent().getIntExtra(CATEGORY, -1);
|
mCategory = getIntent().getIntExtra(CATEGORY, -1);
|
||||||
if (category >= 0)
|
if (mCategory >= 0) {
|
||||||
showCategory(category);
|
showCategory(mCategory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
localeManager.onResume(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -64,7 +82,24 @@ public class HelpActivity extends ActionBarActivity implements
|
|||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
onBackPressed();
|
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;
|
return true;
|
||||||
case R.id.menu_help_licenses:
|
case R.id.menu_help_licenses:
|
||||||
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
|
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
|
||||||
@ -84,6 +119,13 @@ public class HelpActivity extends ActionBarActivity implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
super.onBackPressed();
|
||||||
|
if (mCategory >= 0)
|
||||||
|
mCategory = -1;
|
||||||
|
}
|
||||||
|
|
||||||
// HelpListFragment.OnEntrySelectedListener
|
// HelpListFragment.OnEntrySelectedListener
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -91,8 +133,10 @@ public class HelpActivity extends ActionBarActivity implements
|
|||||||
if (entry == CAT_CONFIGURE_BROWSER) {
|
if (entry == CAT_CONFIGURE_BROWSER) {
|
||||||
Intent i = new Intent(this, BrowserConfigActivity.class);
|
Intent i = new Intent(this, BrowserConfigActivity.class);
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
} else
|
} else {
|
||||||
|
mCategory = entry;
|
||||||
showCategory(entry);
|
showCategory(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showCategory(int category) {
|
private void showCategory(int category) {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package net.i2p.android.help;
|
package net.i2p.android.help;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -30,7 +31,7 @@ public class HelpHtmlFragment extends Fragment {
|
|||||||
int padH = getResources().getDimensionPixelOffset(R.dimen.activity_horizontal_margin);
|
int padH = getResources().getDimensionPixelOffset(R.dimen.activity_horizontal_margin);
|
||||||
int padV = getResources().getDimensionPixelOffset(R.dimen.activity_vertical_margin);
|
int padV = getResources().getDimensionPixelOffset(R.dimen.activity_vertical_margin);
|
||||||
text.setPadding(padH, padV, padH, padV);
|
text.setPadding(padH, padV, padH, padV);
|
||||||
text.setHtmlFromRawResource(getActivity(), getArguments().getInt(ARG_HTML_FILE), true);
|
text.setHtml(getArguments().getInt(ARG_HTML_FILE));
|
||||||
return scroller;
|
return scroller;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ package net.i2p.android.help;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.ListFragment;
|
//import android.support.v4.app.ListFragment;
|
||||||
|
import androidx.fragment.app.ListFragment;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
@ -14,7 +15,7 @@ public class HelpListFragment extends ListFragment {
|
|||||||
|
|
||||||
// Container Activity must implement this interface
|
// Container Activity must implement this interface
|
||||||
public interface OnEntrySelectedListener {
|
public interface OnEntrySelectedListener {
|
||||||
public void onEntrySelected(int entry);
|
void onEntrySelected(int entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,25 +1,65 @@
|
|||||||
package net.i2p.android.i2ptunnel;
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
import net.i2p.android.router.I2PActivityBase;
|
import android.content.Intent;
|
||||||
import net.i2p.android.router.R;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
//import android.support.v4.app.ActivityCompat;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import net.i2p.android.I2PActivityBase;
|
||||||
|
import net.i2p.android.i2ptunnel.preferences.EditTunnelActivity;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
public class TunnelDetailActivity extends I2PActivityBase implements
|
public class TunnelDetailActivity extends I2PActivityBase implements
|
||||||
TunnelDetailFragment.OnTunnelDeletedListener {
|
TunnelDetailFragment.TunnelDetailListener {
|
||||||
|
private boolean transitionReversed;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
transitionReversed = false;
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
|
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
|
||||||
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
|
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.add(R.id.main_fragment, detailFrag).commit();
|
.add(android.R.id.content, detailFrag).commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TunnelDetailFragment.OnTunnelDeletedListener
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar);
|
||||||
|
toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
|
||||||
|
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onBackPressed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
if (transitionReversed)
|
||||||
|
super.finish();
|
||||||
|
else {
|
||||||
|
transitionReversed = true;
|
||||||
|
ActivityCompat.finishAfterTransition(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft) {
|
||||||
finish();
|
finish();
|
||||||
|
@ -1,33 +1,55 @@
|
|||||||
package net.i2p.android.i2ptunnel;
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
import java.util.List;
|
import android.annotation.TargetApi;
|
||||||
|
|
||||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
|
||||||
import net.i2p.android.router.R;
|
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.DialogFragment;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.v4.app.Fragment;
|
//import android.support.v4.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
//import android.support.v4.view.ViewCompat;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.android.util.FragmentUtils;
|
||||||
|
import net.i2p.app.ClientAppState;
|
||||||
|
import net.i2p.i2ptunnel.TunnelController;
|
||||||
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class TunnelDetailFragment extends Fragment {
|
public class TunnelDetailFragment extends Fragment {
|
||||||
public static final String TUNNEL_ID = "tunnel_id";
|
public static final String TUNNEL_ID = "tunnel_id";
|
||||||
|
|
||||||
OnTunnelDeletedListener mCallback;
|
TunnelDetailListener mCallback;
|
||||||
private TunnelControllerGroup mGroup;
|
private TunnelControllerGroup mGroup;
|
||||||
private TunnelEntry mTunnel;
|
private TunnelEntry mTunnel;
|
||||||
|
private Toolbar mToolbar;
|
||||||
|
private ImageView mStatus;
|
||||||
|
|
||||||
public static TunnelDetailFragment newInstance(int tunnelId) {
|
public static TunnelDetailFragment newInstance(int tunnelId) {
|
||||||
TunnelDetailFragment f = new TunnelDetailFragment();
|
TunnelDetailFragment f = new TunnelDetailFragment();
|
||||||
@ -38,8 +60,9 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Container Activity must implement this interface
|
// Container Activity must implement this interface
|
||||||
public interface OnTunnelDeletedListener {
|
public interface TunnelDetailListener {
|
||||||
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
|
void onEditTunnel(int tunnelId);
|
||||||
|
void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -48,36 +71,44 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
|
|
||||||
// This makes sure that the container activity has implemented
|
// This makes sure that the container activity has implemented
|
||||||
// the callback interface. If not, it throws an exception
|
// the callback interface. If not, it throws an exception
|
||||||
try {
|
mCallback = FragmentUtils.getParent(this, TunnelDetailListener.class);
|
||||||
mCallback = (OnTunnelDeletedListener) activity;
|
if (mCallback == null)
|
||||||
} catch (ClassCastException e) {
|
throw new ClassCastException("Parent must implement TunnelDetailListener");
|
||||||
throw new ClassCastException(activity.toString()
|
|
||||||
+ " must implement OnTunnelDeletedListener");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
|
||||||
|
|
||||||
String error;
|
String error;
|
||||||
|
List<TunnelController> controllers;
|
||||||
try {
|
try {
|
||||||
mGroup = TunnelControllerGroup.getInstance();
|
mGroup = TunnelControllerGroup.getInstance();
|
||||||
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
||||||
|
controllers = mGroup == null ? null : mGroup.getControllers();
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
mGroup = null;
|
mGroup = null;
|
||||||
|
controllers = null;
|
||||||
error = iae.toString();
|
error = iae.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mGroup == null) {
|
if (mGroup == null) {
|
||||||
// Show error
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
error, Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
} else if (getArguments().containsKey(TUNNEL_ID)) {
|
} else if (getArguments().containsKey(TUNNEL_ID)) {
|
||||||
int tunnelId = getArguments().getInt(TUNNEL_ID);
|
int tunnelId = getArguments().getInt(TUNNEL_ID);
|
||||||
mTunnel = new TunnelEntry(getActivity(),
|
try {
|
||||||
mGroup.getControllers().get(tunnelId),
|
TunnelController controller = controllers.get(tunnelId);
|
||||||
tunnelId);
|
mTunnel = new TunnelEntry(getActivity(), controller, tunnelId);
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
// Tunnel doesn't exist
|
||||||
|
Util.e("Could not load tunnel details", e);
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
R.string.i2ptunnel_no_tunnel_details, Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +117,21 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View v = inflater.inflate(R.layout.fragment_i2ptunnel_detail, container, false);
|
View v = inflater.inflate(R.layout.fragment_i2ptunnel_detail, container, false);
|
||||||
|
|
||||||
|
mToolbar = (Toolbar) v.findViewById(R.id.detail_toolbar);
|
||||||
|
mToolbar.inflateMenu(R.menu.fragment_i2ptunnel_detail_actions);
|
||||||
|
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||||
|
return onToolbarItemSelected(menuItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateToolbar();
|
||||||
|
|
||||||
if (mTunnel != null) {
|
if (mTunnel != null) {
|
||||||
|
mStatus = (ImageView) v.findViewById(R.id.tunnel_status);
|
||||||
|
updateStatus();
|
||||||
|
ViewCompat.setTransitionName(mStatus, "status" + mTunnel.getId());
|
||||||
|
|
||||||
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
||||||
name.setText(mTunnel.getName());
|
name.setText(mTunnel.getName());
|
||||||
|
|
||||||
@ -96,14 +141,62 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
|
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
|
||||||
description.setText(mTunnel.getDescription());
|
description.setText(mTunnel.getDescription());
|
||||||
|
|
||||||
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
|
if (!mTunnel.getDetails().isEmpty()) {
|
||||||
details.setText(mTunnel.getDetails());
|
v.findViewById(R.id.tunnel_details_container).setVisibility(View.VISIBLE);
|
||||||
|
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
|
||||||
|
View copyDetails = v.findViewById(R.id.tunnel_details_copy);
|
||||||
|
details.setText(mTunnel.getDetails());
|
||||||
|
if (!mTunnel.isClient()) {
|
||||||
|
copyDetails.setVisibility(View.VISIBLE);
|
||||||
|
copyDetails.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
copyToClipbardLegacy();
|
||||||
|
else
|
||||||
|
copyToClipboardHoneycomb();
|
||||||
|
|
||||||
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
|
Toast.makeText(getActivity(), R.string.address_copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||||
targetIfacePort.setText(mTunnel.getTunnelLink(false));
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
View accessIfacePortItem = v.findViewById(R.id.tunnel_access_interface_port_item);
|
||||||
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
|
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
|
||||||
accessIfacePort.setText(mTunnel.getTunnelLink(false));
|
View accessIfaceOpen = v.findViewById(R.id.tunnel_access_open);
|
||||||
|
View targetIfacePortItem = v.findViewById(R.id.tunnel_target_interface_port_item);
|
||||||
|
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
|
||||||
|
View targetIfaceOpen = v.findViewById(R.id.tunnel_target_open);
|
||||||
|
switch (mTunnel.getInternalType()) {
|
||||||
|
case "httpbidirserver":
|
||||||
|
accessIfacePort.setText(mTunnel.getClientLink(false));
|
||||||
|
setupOpen(accessIfaceOpen, true);
|
||||||
|
v.findViewById(R.id.icon_link_access).setVisibility(View.GONE);
|
||||||
|
targetIfacePort.setText(mTunnel.getServerLink(false));
|
||||||
|
setupOpen(targetIfaceOpen, false);
|
||||||
|
break;
|
||||||
|
case "streamrserver":
|
||||||
|
accessIfacePort.setText(mTunnel.getServerLink(false));
|
||||||
|
setupOpen(accessIfaceOpen, true);
|
||||||
|
targetIfacePortItem.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
case "streamrclient":
|
||||||
|
accessIfacePortItem.setVisibility(View.GONE);
|
||||||
|
targetIfacePort.setText(mTunnel.getClientLink(false));
|
||||||
|
setupOpen(targetIfaceOpen, false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (mTunnel.isClient()) {
|
||||||
|
accessIfacePort.setText(mTunnel.getClientLink(false));
|
||||||
|
setupOpen(accessIfaceOpen, true);
|
||||||
|
targetIfacePortItem.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
accessIfacePortItem.setVisibility(View.GONE);
|
||||||
|
targetIfacePort.setText(mTunnel.getServerLink(false));
|
||||||
|
setupOpen(targetIfaceOpen, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CheckBox autoStart = (CheckBox) v.findViewById(R.id.tunnel_autostart);
|
CheckBox autoStart = (CheckBox) v.findViewById(R.id.tunnel_autostart);
|
||||||
autoStart.setChecked(mTunnel.startAutomatically());
|
autoStart.setChecked(mTunnel.startAutomatically());
|
||||||
@ -112,19 +205,56 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void setupOpen(View open, final boolean client) {
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
if (mTunnel.isRunning() &&
|
||||||
inflater.inflate(R.menu.fragment_i2ptunnel_detail_actions, menu);
|
(client ? mTunnel.isClientLinkValid() : mTunnel.isServerLinkValid())) {
|
||||||
// Hide the edit action until we have an edit UI
|
open.setVisibility(View.VISIBLE);
|
||||||
menu.findItem(R.id.action_edit_tunnel).setVisible(false);
|
open.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||||
|
i.setData(Uri.parse(client ? mTunnel.getClientLink(true) : mTunnel.getServerLink(true)));
|
||||||
|
try {
|
||||||
|
startActivity(i);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
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 = mTunnel.getRecommendedAppForTunnel();
|
||||||
|
if (uri != null) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
try {
|
||||||
|
startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(getContext(),
|
||||||
|
R.string.no_market_app,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
open.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void updateToolbar() {
|
||||||
public void onPrepareOptionsMenu(Menu menu) {
|
Menu menu = mToolbar.getMenu();
|
||||||
MenuItem start = menu.findItem(R.id.action_start_tunnel);
|
MenuItem start = menu.findItem(R.id.action_start_tunnel);
|
||||||
MenuItem stop = menu.findItem(R.id.action_stop_tunnel);
|
MenuItem stop = menu.findItem(R.id.action_stop_tunnel);
|
||||||
|
|
||||||
if (mTunnel != null) {
|
if (mTunnel != null && mGroup != null &&
|
||||||
|
(mGroup.getState() == ClientAppState.STARTING ||
|
||||||
|
mGroup.getState() == ClientAppState.RUNNING)) {
|
||||||
boolean isStopped = mTunnel.getStatus() == TunnelEntry.NOT_RUNNING;
|
boolean isStopped = mTunnel.getStatus() == TunnelEntry.NOT_RUNNING;
|
||||||
|
|
||||||
start.setVisible(isStopped);
|
start.setVisible(isStopped);
|
||||||
@ -141,8 +271,18 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void updateStatus() {
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
mStatus.setImageDrawable(mTunnel.getStatusIcon());
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
mStatus.setBackgroundDrawable(mTunnel.getStatusBackground());
|
||||||
|
else
|
||||||
|
mStatus.setBackground(mTunnel.getStatusBackground());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onToolbarItemSelected(MenuItem item) {
|
||||||
|
if (mTunnel == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Handle presses on the action bar items
|
// Handle presses on the action bar items
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_start_tunnel:
|
case R.id.action_start_tunnel:
|
||||||
@ -150,46 +290,96 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getActivity().getApplicationContext(),
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
getResources().getString(R.string.i2ptunnel_msg_tunnel_starting)
|
getResources().getString(R.string.i2ptunnel_msg_tunnel_starting)
|
||||||
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||||
// Reload the action bar to change the start/stop action
|
// Reload the toolbar to change the start/stop action
|
||||||
getActivity().supportInvalidateOptionsMenu();
|
updateToolbar();
|
||||||
|
// Update the status icon
|
||||||
|
updateStatus();
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_stop_tunnel:
|
case R.id.action_stop_tunnel:
|
||||||
mTunnel.getController().stopTunnel();
|
mTunnel.getController().stopTunnel();
|
||||||
Toast.makeText(getActivity().getApplicationContext(),
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
getResources().getString(R.string.i2ptunnel_msg_tunnel_stopping)
|
getResources().getString(R.string.i2ptunnel_msg_tunnel_stopping)
|
||||||
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||||
// Reload the action bar to change the start/stop action
|
// Reload the toolbar to change the start/stop action
|
||||||
getActivity().supportInvalidateOptionsMenu();
|
updateToolbar();
|
||||||
|
// Update the status icon
|
||||||
|
updateStatus();
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_edit_tunnel:
|
case R.id.action_edit_tunnel:
|
||||||
|
mCallback.onEditTunnel(mTunnel.getId());
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_delete_tunnel:
|
case R.id.action_delete_tunnel:
|
||||||
DialogFragment dg = new DialogFragment() {
|
DialogFragment dg = DeleteTunnelDialogFragment.newInstance();
|
||||||
@Override
|
dg.show(getChildFragmentManager(), "delete_tunnel_dialog");
|
||||||
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(
|
|
||||||
getActivity(), mGroup, mTunnel.getId());
|
|
||||||
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;
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onDeleteTunnel() {
|
||||||
|
List<String> msgs = TunnelUtil.deleteTunnel(
|
||||||
|
I2PAppContext.getGlobalContext(),
|
||||||
|
mGroup, mTunnel.getId(), null);
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||||
|
mCallback.onTunnelDeleted(mTunnel.getId(),
|
||||||
|
mGroup.getControllers().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyToClipbardLegacy() {
|
||||||
|
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
clipboard.setText(mTunnel.getDetails());
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
private void copyToClipboardHoneycomb() {
|
||||||
|
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
android.content.ClipData clip = android.content.ClipData.newPlainText(
|
||||||
|
mTunnel.getName(), mTunnel.getDetails());
|
||||||
|
clipboard.setPrimaryClip(clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DeleteTunnelDialogFragment extends DialogFragment {
|
||||||
|
TunnelDetailFragment mListener;
|
||||||
|
|
||||||
|
public static DialogFragment newInstance() {
|
||||||
|
return new DeleteTunnelDialogFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAttachToParentFragment(Fragment fragment) {
|
||||||
|
// Verify that the host fragment implements the callback interface
|
||||||
|
try {
|
||||||
|
// Instantiate the TunnelDetailFragment so we can send events to the host
|
||||||
|
mListener = (TunnelDetailFragment) fragment;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// The fragment doesn't implement the interface, throw exception
|
||||||
|
throw new ClassCastException(fragment.toString()
|
||||||
|
+ " must be TunnelDetailFragment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
onAttachToParentFragment(getParentFragment());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
dialog.dismiss();
|
||||||
|
mListener.onDeleteTunnel();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,29 @@
|
|||||||
package net.i2p.android.i2ptunnel;
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import net.i2p.android.i2ptunnel.util.TunnelConfig;
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.android.i2ptunnel.util.SaveTunnelTask;
|
||||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.data.PrivateKeyFile;
|
import net.i2p.data.PrivateKeyFile;
|
||||||
import net.i2p.i2ptunnel.TunnelController;
|
import net.i2p.i2ptunnel.TunnelController;
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single tunnel.
|
||||||
|
* Stored by the TunnelEntryAdapter.
|
||||||
|
*/
|
||||||
public class TunnelEntry {
|
public class TunnelEntry {
|
||||||
public static final int RUNNING = 1;
|
public static final int RUNNING = 1;
|
||||||
public static final int STARTING = 2;
|
public static final int STARTING = 2;
|
||||||
@ -23,18 +34,33 @@ public class TunnelEntry {
|
|||||||
private final TunnelController mController;
|
private final TunnelController mController;
|
||||||
private final int mId;
|
private final int mId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param tcg non-null
|
||||||
|
* @return the new TunnelEntry, or null if there was an error.
|
||||||
|
*/
|
||||||
public static TunnelEntry createNewTunnel(
|
public static TunnelEntry createNewTunnel(
|
||||||
Context ctx,
|
Context ctx,
|
||||||
TunnelControllerGroup tcg,
|
TunnelControllerGroup tcg,
|
||||||
TunnelConfig cfg) {
|
TunnelConfig cfg) {
|
||||||
int tunnelId = tcg.getControllers().size();
|
int tunnelId = tcg.getControllers().size();
|
||||||
List<String> msgs = TunnelUtil.saveTunnel(
|
TunnelEntry ret = null;
|
||||||
ctx, tcg, -1, cfg.getConfig());
|
List<String> msgs = new ArrayList<>();
|
||||||
|
SaveTunnelTask task = new SaveTunnelTask(tcg, -1, cfg);
|
||||||
|
try {
|
||||||
|
msgs.addAll(task.execute().get());
|
||||||
|
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
|
||||||
|
ret = new TunnelEntry(ctx, cur, tunnelId);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Util.e("Interrupted while saving tunnel config", e);
|
||||||
|
msgs.add(ctx.getString(R.string.i2ptunnel_msg_config_save_failed));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Util.e("Error while saving tunnel config", e);
|
||||||
|
msgs.add(ctx.getString(R.string.i2ptunnel_msg_config_save_failed));
|
||||||
|
}
|
||||||
// TODO: Do something else with the other messages.
|
// TODO: Do something else with the other messages.
|
||||||
Toast.makeText(ctx.getApplicationContext(),
|
Toast.makeText(ctx.getApplicationContext(),
|
||||||
msgs.get(0), Toast.LENGTH_LONG).show();
|
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||||
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
|
return ret;
|
||||||
return new TunnelEntry(ctx, cur, tunnelId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TunnelEntry(Context context, TunnelController controller, int id) {
|
public TunnelEntry(Context context, TunnelController controller, int id) {
|
||||||
@ -233,12 +259,23 @@ public class TunnelEntry {
|
|||||||
else return getServerLink(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() {
|
public String getDetails() {
|
||||||
String details;
|
String details;
|
||||||
if (isClient())
|
if (isClient())
|
||||||
details = getClientDestination();
|
details = getClientDestination();
|
||||||
else
|
else
|
||||||
details = "";
|
details = getDestHashBase32();
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +285,8 @@ public class TunnelEntry {
|
|||||||
return mContext.getResources()
|
return mContext.getResources()
|
||||||
.getDrawable(R.drawable.ic_schedule_black_24dp);
|
.getDrawable(R.drawable.ic_schedule_black_24dp);
|
||||||
case STARTING:
|
case STARTING:
|
||||||
|
return mContext.getResources()
|
||||||
|
.getDrawable(R.drawable.ic_sync_black_24dp);
|
||||||
case RUNNING:
|
case RUNNING:
|
||||||
case NOT_RUNNING:
|
case NOT_RUNNING:
|
||||||
default:
|
default:
|
||||||
|
@ -1,71 +1,233 @@
|
|||||||
package net.i2p.android.i2ptunnel;
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
//import android.support.v4.util.Pair;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
//import android.support.v4.view.ViewCompat;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
//import android.support.v7.widget.RecyclerView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.util.FragmentUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TunnelEntryAdapter extends ArrayAdapter<TunnelEntry> {
|
/**
|
||||||
private final LayoutInflater mInflater;
|
* Contains the List of TunnelEntries.
|
||||||
|
* There's two of these, one for client tunnels and
|
||||||
|
* one for server tunnels.
|
||||||
|
* Created by the TunnelListFragment.
|
||||||
|
*/
|
||||||
|
public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
private final Context mCtx;
|
||||||
|
private final boolean mClientTunnels;
|
||||||
|
private final TunnelListFragment.OnTunnelSelectedListener mListener;
|
||||||
|
private final FragmentUtils.TwoPaneProvider mTwoPane;
|
||||||
|
private List<TunnelEntry> mTunnels;
|
||||||
|
/**
|
||||||
|
* The current activated item position. Only used on tablets.
|
||||||
|
*/
|
||||||
|
private int mActivatedPosition = -1;
|
||||||
|
|
||||||
public TunnelEntryAdapter(Context context) {
|
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
|
||||||
super(context, android.R.layout.simple_list_item_2);
|
public SimpleViewHolder(View itemView) {
|
||||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
super(itemView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setData(List<TunnelEntry> tunnels) {
|
public static class TunnelViewHolder extends RecyclerView.ViewHolder {
|
||||||
clear();
|
public final ImageView status;
|
||||||
if (tunnels != null) {
|
public final TextView name;
|
||||||
for (TunnelEntry tunnel : tunnels) {
|
public final TextView description;
|
||||||
add(tunnel);
|
public final TextView interfacePort;
|
||||||
}
|
|
||||||
|
public TunnelViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
|
||||||
|
status = (ImageView) itemView.findViewById(R.id.tunnel_status);
|
||||||
|
name = (TextView) itemView.findViewById(R.id.tunnel_name);
|
||||||
|
description = (TextView) itemView.findViewById(R.id.tunnel_description);
|
||||||
|
interfacePort = (TextView) itemView.findViewById(R.id.tunnel_interface_port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TunnelEntryAdapter(Context context, boolean clientTunnels,
|
||||||
|
TunnelListFragment.OnTunnelSelectedListener listener,
|
||||||
|
FragmentUtils.TwoPaneProvider twoPane) {
|
||||||
|
super();
|
||||||
|
mCtx = context;
|
||||||
|
mClientTunnels = clientTunnels;
|
||||||
|
mListener = listener;
|
||||||
|
mTwoPane = twoPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTunnels(List<TunnelEntry> tunnels) {
|
||||||
|
mTunnels = tunnels;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTunnel(TunnelEntry tunnel) {
|
||||||
|
if (mTunnels == null)
|
||||||
|
mTunnels = new ArrayList<TunnelEntry>();
|
||||||
|
boolean wasEmpty = mTunnels.isEmpty();
|
||||||
|
mTunnels.add(tunnel);
|
||||||
|
if (wasEmpty) {
|
||||||
|
notifyDataSetChanged();
|
||||||
|
} else {
|
||||||
|
notifyItemInserted(mTunnels.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TunnelEntry getTunnel(int position) {
|
||||||
|
if (position < 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return mTunnels.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivatedPosition(int position) {
|
||||||
|
mActivatedPosition = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getActivatedPosition() {
|
||||||
|
return mActivatedPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearActivatedPosition() {
|
||||||
|
mActivatedPosition = -1;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public int getItemViewType(int position) {
|
||||||
View v = mInflater.inflate(R.layout.listitem_i2ptunnel, parent, false);
|
if (mTunnels == null)
|
||||||
final TunnelEntry tunnel = getItem(position);
|
return R.string.router_not_running;
|
||||||
|
else if (mTunnels.isEmpty())
|
||||||
ImageView status = (ImageView) v.findViewById(R.id.tunnel_status);
|
return R.layout.listitem_empty;
|
||||||
status.setImageDrawable(tunnel.getStatusIcon());
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
|
||||||
status.setBackgroundDrawable(tunnel.getStatusBackground());
|
|
||||||
else
|
else
|
||||||
status.setBackground(tunnel.getStatusBackground());
|
return R.layout.listitem_i2ptunnel;
|
||||||
|
}
|
||||||
|
|
||||||
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
// Create new views (invoked by the layout manager)
|
||||||
name.setText(tunnel.getName());
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
int vt = viewType;
|
||||||
|
if (viewType == R.string.router_not_running)
|
||||||
|
vt = R.layout.listitem_empty;
|
||||||
|
|
||||||
TextView type = (TextView) v.findViewById(R.id.tunnel_description);
|
View v = LayoutInflater.from(parent.getContext())
|
||||||
type.setText(tunnel.getDescription());
|
.inflate(vt, parent, false);
|
||||||
|
switch (viewType) {
|
||||||
|
case R.layout.listitem_i2ptunnel:
|
||||||
|
return new TunnelViewHolder(v);
|
||||||
|
default:
|
||||||
|
return new SimpleViewHolder(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TextView ifacePort = (TextView) v.findViewById(R.id.tunnel_interface_port);
|
private void setClipboard(Context context, String text) {
|
||||||
ifacePort.setText(tunnel.getTunnelLink(false));
|
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
clipboard.setText(text);
|
||||||
|
} else {
|
||||||
|
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
android.content.ClipData clip = android.content.ClipData.newPlainText("Copied Text", text);
|
||||||
|
clipboard.setPrimaryClip(clip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tunnel.isRunning() && tunnel.isTunnelLinkValid()) {
|
// Replace the contents of a view (invoked by the layout manager)
|
||||||
View open = v.findViewById(R.id.tunnel_open);
|
@Override
|
||||||
open.setVisibility(View.VISIBLE);
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
|
||||||
open.setOnClickListener(new View.OnClickListener() {
|
switch (holder.getItemViewType()) {
|
||||||
@Override
|
case R.string.router_not_running:
|
||||||
public void onClick(View view) {
|
((TextView) holder.itemView).setText(
|
||||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
mCtx.getString(R.string.i2ptunnel_not_initialized));
|
||||||
i.setData(Uri.parse(tunnel.getTunnelLink(true)));
|
break;
|
||||||
getContext().startActivity(i);
|
|
||||||
}
|
case R.layout.listitem_empty:
|
||||||
});
|
((TextView) holder.itemView).setText(mClientTunnels ?
|
||||||
|
R.string.no_configured_client_tunnels :
|
||||||
|
R.string.no_configured_server_tunnels);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case R.layout.listitem_i2ptunnel:
|
||||||
|
final TunnelViewHolder tvh = (TunnelViewHolder) holder;
|
||||||
|
final TunnelEntry tunnel = getTunnel(position);
|
||||||
|
|
||||||
|
tvh.status.setImageDrawable(tunnel.getStatusIcon());
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
tvh.status.setBackgroundDrawable(tunnel.getStatusBackground());
|
||||||
|
else
|
||||||
|
tvh.status.setBackground(tunnel.getStatusBackground());
|
||||||
|
ViewCompat.setTransitionName(tvh.status,
|
||||||
|
"status" + tunnel.getId());
|
||||||
|
|
||||||
|
tvh.name.setText(tunnel.getName());
|
||||||
|
tvh.description.setText(tunnel.getDescription());
|
||||||
|
tvh.interfacePort.setText(tunnel.getTunnelLink(false));
|
||||||
|
|
||||||
|
tvh.itemView.setSelected(mTwoPane.isTwoPane() && position == mActivatedPosition);
|
||||||
|
tvh.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
// TODO
|
||||||
|
// lint priority 8/10
|
||||||
|
// lint: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later
|
||||||
|
// javadocs: Note that unlike ListView, RecyclerView will not call this method again
|
||||||
|
// if the position of the item changes in the data set unless the item itself is invalidated
|
||||||
|
// or the new position cannot be determined.
|
||||||
|
// For this reason, you should only use the position parameter while acquiring
|
||||||
|
// the related data item inside this method and should not keep a copy of it.
|
||||||
|
// If you need the position of an item later on (e.g. in a click listener),
|
||||||
|
// use RecyclerView.ViewHolder.getAdapterPosition() which will have the updated adapter position.
|
||||||
|
int oldPosition = mActivatedPosition;
|
||||||
|
mActivatedPosition = position;
|
||||||
|
notifyItemChanged(oldPosition);
|
||||||
|
notifyItemChanged(position);
|
||||||
|
Pair<View, String> statusPair = Pair.create(
|
||||||
|
(View)tvh.status,
|
||||||
|
ViewCompat.getTransitionName(tvh.status));
|
||||||
|
Pair<View, String>[] pairs = new Pair[]{ statusPair};
|
||||||
|
mListener.onTunnelSelected(tunnel.getId(), pairs);
|
||||||
|
view.invalidate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tvh.itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
//@Override
|
||||||
|
public boolean onLongClick(View view) {
|
||||||
|
setClipboard(mCtx, tunnel.getDestHashBase32());
|
||||||
|
Toast clipboardMessage = Toast.makeText(mCtx, R.string.copied_base32_system_notification_title, Toast. LENGTH_LONG);
|
||||||
|
clipboardMessage.setGravity(Gravity.TOP, 0, 0); //optional
|
||||||
|
clipboardMessage.show();
|
||||||
|
view.invalidate();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return v;
|
}
|
||||||
|
|
||||||
|
// Return the size of the dataset (invoked by the layout manager)
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
if (mTunnels == null || mTunnels.isEmpty())
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return mTunnels.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
package net.i2p.android.i2ptunnel;
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
//import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
import androidx.loader.content.AsyncTaskLoader;
|
||||||
|
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.i2ptunnel.TunnelController;
|
||||||
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import net.i2p.i2ptunnel.TunnelController;
|
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
|
|
||||||
public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
|
public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
|
||||||
private TunnelControllerGroup mGroup;
|
private final TunnelControllerGroup mGroup;
|
||||||
private boolean mClientTunnels;
|
private final boolean mClientTunnels;
|
||||||
private List<TunnelEntry> mData;
|
private List<TunnelEntry> mData;
|
||||||
private Handler mHandler;
|
private final Handler mHandler;
|
||||||
private TunnelControllerMonitor mMonitor;
|
private TunnelControllerMonitor mMonitor;
|
||||||
|
|
||||||
public TunnelEntryLoader(Context context, TunnelControllerGroup tcg, boolean clientTunnels) {
|
public TunnelEntryLoader(Context context, TunnelControllerGroup tcg, boolean clientTunnels) {
|
||||||
@ -26,7 +29,13 @@ public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TunnelEntry> loadInBackground() {
|
public List<TunnelEntry> loadInBackground() {
|
||||||
List<TunnelEntry> ret = new ArrayList<TunnelEntry>();
|
// Don't load tunnels if the router is not running
|
||||||
|
// TODO: in future we might be able to view and edit tunnels while router is not running
|
||||||
|
RouterContext routerContext = Util.getRouterContext();
|
||||||
|
if (routerContext == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<TunnelEntry> ret = new ArrayList<>();
|
||||||
List<TunnelController> controllers = mGroup.getControllers();
|
List<TunnelController> controllers = mGroup.getControllers();
|
||||||
for (int i = 0; i < controllers.size(); i++) {
|
for (int i = 0; i < controllers.size(); i++) {
|
||||||
TunnelEntry tunnel = new TunnelEntry(getContext(), controllers.get(i), i);
|
TunnelEntry tunnel = new TunnelEntry(getContext(), controllers.get(i), i);
|
||||||
|
@ -1,125 +0,0 @@
|
|||||||
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.OnTunnelDeletedListener {
|
|
||||||
/**
|
|
||||||
* 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.OnTunnelDeletedListener
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +1,52 @@
|
|||||||
package net.i2p.android.i2ptunnel;
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.ListFragment;
|
//import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.LoaderManager;
|
import androidx.fragment.app.Fragment;
|
||||||
import android.support.v4.content.Loader;
|
//import android.support.v4.app.LoaderManager;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
//import android.support.v4.content.Loader;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
//import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
//import android.support.v4.util.Pair;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
//import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
//import android.support.v7.widget.RecyclerView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
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 com.pnikosis.materialishprogress.ProgressWheel;
|
||||||
import net.i2p.android.i2ptunnel.util.TunnelConfig;
|
|
||||||
import net.i2p.android.router.I2PFragmentBase;
|
|
||||||
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.service.RouterService;
|
||||||
|
import net.i2p.android.router.service.State;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.android.util.FragmentUtils;
|
||||||
|
import net.i2p.android.widget.DividerItemDecoration;
|
||||||
|
import net.i2p.android.widget.LoadingRecyclerView;
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
import net.i2p.router.RouterContext;
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TunnelListFragment extends ListFragment implements
|
/**
|
||||||
I2PFragmentBase.RouterContextUser,
|
* The list of tunnels.
|
||||||
|
* There's two of these, one for client tunnels and
|
||||||
|
* one for server tunnels.
|
||||||
|
* Creates the TunnelEntryAdapter.
|
||||||
|
*/
|
||||||
|
public class TunnelListFragment extends Fragment implements
|
||||||
LoaderManager.LoaderCallbacks<List<TunnelEntry>> {
|
LoaderManager.LoaderCallbacks<List<TunnelEntry>> {
|
||||||
public static final String SHOW_CLIENT_TUNNELS = "show_client_tunnels";
|
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 CLIENT_LOADER_ID = 1;
|
||||||
private static final int SERVER_LOADER_ID = 2;
|
private static final int SERVER_LOADER_ID = 2;
|
||||||
@ -43,23 +56,27 @@ public class TunnelListFragment extends ListFragment implements
|
|||||||
*/
|
*/
|
||||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||||
|
|
||||||
private boolean mOnActivityCreated;
|
|
||||||
RouterContextProvider mRouterContextProvider;
|
|
||||||
OnTunnelSelectedListener mCallback;
|
OnTunnelSelectedListener mCallback;
|
||||||
|
FragmentUtils.TwoPaneProvider mTwoPane;
|
||||||
private TunnelControllerGroup mGroup;
|
private TunnelControllerGroup mGroup;
|
||||||
|
|
||||||
|
private LoadingRecyclerView mRecyclerView;
|
||||||
private TunnelEntryAdapter mAdapter;
|
private TunnelEntryAdapter mAdapter;
|
||||||
private boolean mClientTunnels;
|
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;
|
private static final String KEY_SELECTED_TUNNEL = "selected_tunnel";
|
||||||
|
|
||||||
// Container Activity must implement this interface
|
// Container Activity must implement this interface
|
||||||
public interface OnTunnelSelectedListener {
|
public interface OnTunnelSelectedListener {
|
||||||
public void onTunnelSelected(int tunnelId);
|
void onTunnelSelected(int tunnelId, Pair<View, String>[] pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TunnelListFragment newInstance(boolean showClientTunnels) {
|
||||||
|
TunnelListFragment f = new TunnelListFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putBoolean(TunnelListFragment.SHOW_CLIENT_TUNNELS, showClientTunnels);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -68,47 +85,23 @@ public class TunnelListFragment extends ListFragment implements
|
|||||||
|
|
||||||
// This makes sure that the container activity has implemented
|
// This makes sure that the container activity has implemented
|
||||||
// the callback interface. If not, it throws an exception
|
// the callback interface. If not, it throws an exception
|
||||||
try {
|
mCallback = FragmentUtils.getParent(this, OnTunnelSelectedListener.class);
|
||||||
mRouterContextProvider = (RouterContextProvider) activity;
|
if (mCallback == null)
|
||||||
} catch (ClassCastException e) {
|
throw new ClassCastException("Parent must implement OnTunnelSelectedListener");
|
||||||
throw new ClassCastException(activity.toString()
|
mTwoPane = FragmentUtils.getParent(this, FragmentUtils.TwoPaneProvider.class);
|
||||||
+ " must implement RouterContextProvider");
|
if (mTwoPane == null)
|
||||||
}
|
throw new ClassCastException("Parent must implement TwoPaneProvider");
|
||||||
|
|
||||||
// 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
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
// Create the list fragment's content view by calling the super method
|
View v = inflater.inflate(R.layout.fragment_list, container, false);
|
||||||
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
|
mRecyclerView = (LoadingRecyclerView) v.findViewById(R.id.list);
|
||||||
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
|
View empty = v.findViewById(R.id.empty);
|
||||||
listContainer.addView(listFragmentView);
|
ProgressWheel loading = (ProgressWheel) v.findViewById(R.id.loading);
|
||||||
|
mRecyclerView.setLoadingView(empty, loading);
|
||||||
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;
|
return v;
|
||||||
}
|
}
|
||||||
@ -116,156 +109,129 @@ public class TunnelListFragment extends ListFragment implements
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
if (savedInstanceState != null) {
|
||||||
// Restore the previously serialized activated item position.
|
int tunnelId = savedInstanceState.getInt(KEY_SELECTED_TUNNEL, -1);
|
||||||
if (savedInstanceState != null
|
if (tunnelId != -1) {
|
||||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
mCallback.onTunnelSelected(tunnelId, null);
|
||||||
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
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
mAdapter = new TunnelEntryAdapter(getActivity());
|
|
||||||
mClientTunnels = getArguments().getBoolean(SHOW_CLIENT_TUNNELS);
|
mClientTunnels = getArguments().getBoolean(SHOW_CLIENT_TUNNELS);
|
||||||
|
|
||||||
setListAdapter(mAdapter);
|
mRecyclerView.setHasFixedSize(true);
|
||||||
|
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
|
||||||
|
|
||||||
mOnActivityCreated = true;
|
// use a linear layout manager
|
||||||
if (getRouterContext() != null)
|
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
|
||||||
onRouterConnectionReady();
|
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||||
|
|
||||||
|
// Set the adapter for the list view
|
||||||
|
mAdapter = new TunnelEntryAdapter(getActivity(), mClientTunnels, mCallback, mTwoPane);
|
||||||
|
mRecyclerView.setAdapter(mAdapter);
|
||||||
|
|
||||||
|
// Restore the previously serialized activated item position.
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION))
|
||||||
|
mAdapter.setActivatedPosition(savedInstanceState
|
||||||
|
.getInt(STATE_ACTIVATED_POSITION));
|
||||||
else
|
else
|
||||||
setEmptyText(getResources().getString(
|
mAdapter.clearActivatedPosition();
|
||||||
R.string.router_not_running));
|
|
||||||
|
// Initialize the adapter in case the RouterService has not been created
|
||||||
|
if (Util.getRouterContext() == null)
|
||||||
|
mAdapter.setTunnels(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRouterConnectionReady() {
|
@Override
|
||||||
String error;
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getActivity());
|
||||||
|
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
|
||||||
|
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
|
||||||
|
lbm.registerReceiver(onStateChange, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
updateState(state);
|
||||||
|
lastRouterState = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public void updateState(State state) {
|
||||||
try {
|
try {
|
||||||
mGroup = TunnelControllerGroup.getInstance();
|
if (state == State.INIT ||
|
||||||
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
state == State.STARTING || // Wait until RouterContext is initialised
|
||||||
} catch (IllegalArgumentException iae) {
|
state == State.STOPPING ||
|
||||||
mGroup = null;
|
state == State.STOPPED ||
|
||||||
error = iae.toString();
|
state == State.MANUAL_STOPPING ||
|
||||||
|
state == State.MANUAL_STOPPED ||
|
||||||
|
state == State.MANUAL_QUITTING ||
|
||||||
|
state == State.MANUAL_QUITTED)
|
||||||
|
getLoaderManager().destroyLoader(mClientTunnels ? CLIENT_LOADER_ID : SERVER_LOADER_ID);
|
||||||
|
else
|
||||||
|
initTunnels();
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
// Fragment isn't attached to any activity, so ignore state change
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTunnels() {
|
||||||
|
if (mGroup == null) {
|
||||||
|
try {
|
||||||
|
mGroup = TunnelControllerGroup.getInstance();
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
Util.e("Could not load tunnels", iae);
|
||||||
|
mGroup = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mGroup == null) {
|
if (mGroup != null && isAdded()) {
|
||||||
setEmptyText(error);
|
mRecyclerView.setLoading(true);
|
||||||
} else {
|
|
||||||
if (mClientTunnels)
|
|
||||||
setEmptyText("No configured client tunnels.");
|
|
||||||
else
|
|
||||||
setEmptyText("No configured server tunnels.");
|
|
||||||
|
|
||||||
setListShown(false);
|
|
||||||
getLoaderManager().initLoader(mClientTunnels ? CLIENT_LOADER_ID
|
getLoaderManager().initLoader(mClientTunnels ? CLIENT_LOADER_ID
|
||||||
: SERVER_LOADER_ID, null, this);
|
: SERVER_LOADER_ID, null, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
public void onResume() {
|
||||||
super.onListItemClick(parent, view, pos, id);
|
super.onResume();
|
||||||
mCallback.onTunnelSelected(mAdapter.getItem(pos).getId());
|
|
||||||
|
// Triggers loader init via updateState() if the router is running
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
int activatedPosition = mAdapter.getActivatedPosition();
|
||||||
|
if (activatedPosition >= 0) {
|
||||||
// Serialize and persist the activated item position.
|
// Serialize and persist the activated item position.
|
||||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
outState.putInt(STATE_ACTIVATED_POSITION, activatedPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onStop() {
|
||||||
inflater.inflate(R.menu.fragment_i2ptunnel_list_actions, menu);
|
super.onStop();
|
||||||
if (getRouterContext() == null) {
|
|
||||||
mNewTunnel.setVisibility(View.GONE);
|
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onStateChange);
|
||||||
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 void addTunnel(TunnelEntry tunnelEntry) {
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
mAdapter.addTunnel(tunnelEntry);
|
||||||
// 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 = TunnelConfig.createFromWizard(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>>
|
// LoaderManager.LoaderCallbacks<List<TunnelEntry>>
|
||||||
@ -278,20 +244,17 @@ public class TunnelListFragment extends ListFragment implements
|
|||||||
List<TunnelEntry> data) {
|
List<TunnelEntry> data) {
|
||||||
if (loader.getId() == (mClientTunnels ?
|
if (loader.getId() == (mClientTunnels ?
|
||||||
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
|
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
|
||||||
mAdapter.setData(data);
|
mAdapter.setTunnels(data);
|
||||||
|
|
||||||
if (isResumed()) {
|
|
||||||
setListShown(true);
|
|
||||||
} else {
|
|
||||||
setListShownNoAnimation(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onLoaderReset(Loader<List<TunnelEntry>> loader) {
|
public void onLoaderReset(Loader<List<TunnelEntry>> loader) {
|
||||||
if (loader.getId() == (mClientTunnels ?
|
if (loader.getId() == (mClientTunnels ?
|
||||||
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
|
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
|
||||||
mAdapter.setData(null);
|
if (Util.getRouterContext() == null)
|
||||||
|
mAdapter.setTunnels(null);
|
||||||
|
else
|
||||||
|
mAdapter.setTunnels(new ArrayList<TunnelEntry>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
package net.i2p.android.i2ptunnel;
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
//import android.support.v4.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||||
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
||||||
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.v4.app.DialogFragment;
|
|
||||||
|
|
||||||
public class TunnelWizardActivity extends AbstractWizardActivity {
|
public class TunnelWizardActivity extends AbstractWizardActivity {
|
||||||
@Override
|
@Override
|
||||||
@ -19,25 +24,51 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DialogFragment onGetFinishWizardDialog() {
|
protected DialogFragment onGetFinishWizardDialog() {
|
||||||
return new DialogFragment() {
|
return FinishWizardDialogFragment.newInstance();
|
||||||
@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) {
|
public void onFinishWizard() {
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
result.putExtra(TunnelListFragment.TUNNEL_WIZARD_DATA, mWizardModel.save());
|
result.putExtra(TunnelsContainer.TUNNEL_WIZARD_DATA, mWizardModel.save());
|
||||||
setResult(Activity.RESULT_OK, result);
|
setResult(Activity.RESULT_OK, result);
|
||||||
dialog.dismiss();
|
finish();
|
||||||
finish();
|
}
|
||||||
}
|
|
||||||
})
|
public static class FinishWizardDialogFragment extends DialogFragment {
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
TunnelWizardActivity mListener;
|
||||||
.create();
|
|
||||||
|
public static DialogFragment newInstance() {
|
||||||
|
return new FinishWizardDialogFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
// Verify that the host fragment implements the callback interface
|
||||||
|
try {
|
||||||
|
// Instantiate the TunnelWizardActivity so we can send events to the host
|
||||||
|
mListener = (TunnelWizardActivity) context;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// The fragment doesn't implement the interface, throw exception
|
||||||
|
throw new ClassCastException(context.toString()
|
||||||
|
+ " must be TunnelWizardActivity");
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
dialog.dismiss();
|
||||||
|
mListener.onFinishWizard();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,394 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v4.app.ActivityCompat;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
//import android.support.v4.app.ActivityOptionsCompat;
|
||||||
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
//import android.support.v4.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
//import android.support.v4.app.FragmentPagerAdapter;
|
||||||
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
|
//import android.support.v4.util.Pair;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
//import android.support.v4.view.ViewPager;
|
||||||
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
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.ImageButton;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
import net.lucode.hackware.magicindicator.MagicIndicator;
|
||||||
|
import net.lucode.hackware.magicindicator.ViewPagerHelper;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.CommonNavigator;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerIndicator;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerTitleView;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.ColorTransitionPagerTitleView;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.SimplePagerTitleView;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import net.i2p.android.i2ptunnel.preferences.EditTunnelContainerFragment;
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.android.util.FragmentUtils;
|
||||||
|
import net.i2p.app.ClientAppState;
|
||||||
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The top level Fragment of the tunnels tabs.
|
||||||
|
* Creates client and server TunnelListFragments,
|
||||||
|
* the options menu, and the new tunnel wizard button.
|
||||||
|
*/
|
||||||
|
public class TunnelsContainer extends Fragment implements
|
||||||
|
FragmentUtils.TwoPaneProvider,
|
||||||
|
TunnelListFragment.OnTunnelSelectedListener,
|
||||||
|
TunnelDetailFragment.TunnelDetailListener {
|
||||||
|
static final int TUNNEL_WIZARD_REQUEST = 1;
|
||||||
|
public static final String TUNNEL_WIZARD_DATA = "tunnel_wizard_data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||||
|
* device.
|
||||||
|
*/
|
||||||
|
private boolean mTwoPane;
|
||||||
|
|
||||||
|
ViewPager mViewPager;
|
||||||
|
MagicIndicator mPageIndicator;
|
||||||
|
FragmentPagerAdapter mFragPagerAdapter;
|
||||||
|
|
||||||
|
private static final String FRAGMENT_CLIENT = "client_fragment";
|
||||||
|
private static final String FRAGMENT_SERVER = "server_fragment";
|
||||||
|
private static final int FRAGMENT_ID_CLIENT = 0;
|
||||||
|
private static final int FRAGMENT_ID_SERVER = 1;
|
||||||
|
TunnelListFragment mClientFrag;
|
||||||
|
TunnelListFragment mServerFrag;
|
||||||
|
|
||||||
|
private ImageButton mNewTunnel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean showActions() {
|
||||||
|
RouterContext rCtx = Util.getRouterContext();
|
||||||
|
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||||
|
return rCtx != null && tcg != null &&
|
||||||
|
tcg.getState() == ClientAppState.RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.container_tunnels, container, false);
|
||||||
|
|
||||||
|
mViewPager = v.findViewById(R.id.pager);
|
||||||
|
mPageIndicator = v.findViewById(R.id.magic_indicator);
|
||||||
|
|
||||||
|
mNewTunnel = v.findViewById(R.id.promoted_action);
|
||||||
|
mNewTunnel.setVisibility(showActions() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
// Initialize ViewPager adapter
|
||||||
|
mFragPagerAdapter = new TunnelsPagerAdapter(getChildFragmentManager());
|
||||||
|
mViewPager.setAdapter(mFragPagerAdapter);
|
||||||
|
|
||||||
|
if (v.findViewById(R.id.detail_fragment) != null) {
|
||||||
|
mTwoPane = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
mClientFrag = (TunnelListFragment) getChildFragmentManager().getFragment(
|
||||||
|
savedInstanceState, FRAGMENT_CLIENT);
|
||||||
|
mServerFrag = (TunnelListFragment) getChildFragmentManager().getFragment(
|
||||||
|
savedInstanceState, FRAGMENT_SERVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupMagicIndicator();
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
// Initialize ViewPager and adapter
|
||||||
|
mFragPagerAdapter = new TunnelsPagerAdapter(getChildFragmentManager());
|
||||||
|
mViewPager.setAdapter(mFragPagerAdapter);
|
||||||
|
|
||||||
|
setupMagicIndicator();
|
||||||
|
|
||||||
|
// Setup New Tunnel button
|
||||||
|
mNewTunnel.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
|
||||||
|
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TunnelsPagerAdapter extends FragmentPagerAdapter {
|
||||||
|
private static final int NUM_ITEMS = 2;
|
||||||
|
|
||||||
|
public TunnelsPagerAdapter(FragmentManager fm) {
|
||||||
|
super(fm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return NUM_ITEMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case FRAGMENT_ID_CLIENT:
|
||||||
|
return (mClientFrag = TunnelListFragment.newInstance(true));
|
||||||
|
case FRAGMENT_ID_SERVER:
|
||||||
|
return (mServerFrag = TunnelListFragment.newInstance(false));
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case FRAGMENT_ID_CLIENT:
|
||||||
|
return getActivity().getString(R.string.label_i2ptunnel_client);
|
||||||
|
case FRAGMENT_ID_SERVER:
|
||||||
|
return getActivity().getString(R.string.label_i2ptunnel_server);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
inflater.inflate(R.menu.fragment_i2ptunnel_list_actions, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareOptionsMenu(Menu menu) {
|
||||||
|
boolean showActions = showActions();
|
||||||
|
|
||||||
|
menu.findItem(R.id.action_start_all_tunnels).setVisible(showActions);
|
||||||
|
menu.findItem(R.id.action_stop_all_tunnels).setVisible(showActions);
|
||||||
|
menu.findItem(R.id.action_restart_all_tunnels).setVisible(showActions);
|
||||||
|
|
||||||
|
// Was causing a NPE in version 4745238 (0.9.31)
|
||||||
|
if (mNewTunnel != null) {
|
||||||
|
mNewTunnel.setVisibility(showActions ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||||
|
if (tcg == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Handle presses on the action bar items
|
||||||
|
List<String> msgs;
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_start_all_tunnels:
|
||||||
|
msgs = tcg.startAllControllers();
|
||||||
|
break;
|
||||||
|
case R.id.action_stop_all_tunnels:
|
||||||
|
msgs = tcg.stopAllControllers();
|
||||||
|
break;
|
||||||
|
case R.id.action_restart_all_tunnels:
|
||||||
|
// Do a manual stop-start cycle, because tcg.restartAllControllers() happens in the
|
||||||
|
// foreground, whereas tcg.startAllControllers() fires off threads for starting.
|
||||||
|
msgs = tcg.stopAllControllers();
|
||||||
|
msgs.addAll(tcg.startAllControllers());
|
||||||
|
break;
|
||||||
|
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);
|
||||||
|
// ticket #2483
|
||||||
|
if (tunnelData == null)
|
||||||
|
return;
|
||||||
|
// TODO fetch earlier
|
||||||
|
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||||
|
if (tcg == null) {
|
||||||
|
// router went away
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
R.string.router_not_running, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TunnelConfig cfg = TunnelUtil.createConfigFromWizard(getActivity(), tcg, tunnelData);
|
||||||
|
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), tcg, cfg);
|
||||||
|
|
||||||
|
if (tunnel != null) {
|
||||||
|
if (tunnel.isClient() && mClientFrag != null)
|
||||||
|
mClientFrag.addTunnel(tunnel);
|
||||||
|
else if (mServerFrag != null)
|
||||||
|
mServerFrag.addTunnel(tunnel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
// Since the pager fragments don't have known tags or IDs, the only way to persist the
|
||||||
|
// reference is to use putFragment/getFragment. Remember, we're not persisting the exact
|
||||||
|
// Fragment instance. This mechanism simply gives us a way to persist access to the
|
||||||
|
// 'current' fragment instance for the given fragment (which changes across orientation
|
||||||
|
// changes).
|
||||||
|
//
|
||||||
|
// The outcome of all this is that the "Refresh" menu button refreshes the stream across
|
||||||
|
// orientation changes.
|
||||||
|
if (mClientFrag != null)
|
||||||
|
getChildFragmentManager().putFragment(outState, FRAGMENT_CLIENT, mClientFrag);
|
||||||
|
if (mServerFrag != null)
|
||||||
|
getChildFragmentManager().putFragment(outState, FRAGMENT_SERVER, mServerFrag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FragmentUtils.TwoPaneProvider
|
||||||
|
|
||||||
|
public boolean isTwoPane() {
|
||||||
|
return mTwoPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TunnelListFragment.OnTunnelSelectedListener
|
||||||
|
|
||||||
|
public final void onTunnelSelected(int tunnelId, Pair<View, String>[] pairs) {
|
||||||
|
if (mTwoPane) {
|
||||||
|
// In two-pane mode, show the detail view in this activity by
|
||||||
|
// adding or replacing the detail fragment using a
|
||||||
|
// fragment transaction.
|
||||||
|
try {
|
||||||
|
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
|
||||||
|
getChildFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.detail_fragment, detailFrag)
|
||||||
|
.commitNow(); // Use commitNow() to execute synchronously
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("TunnelsContainer", "Failed to update detail fragment", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In single-pane mode, simply start the detail activity
|
||||||
|
// for the selected item ID.
|
||||||
|
Intent detailIntent = new Intent(getActivity(), TunnelDetailActivity.class);
|
||||||
|
detailIntent.putExtra(TunnelDetailFragment.TUNNEL_ID, tunnelId);
|
||||||
|
|
||||||
|
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||||
|
getActivity(), pairs);
|
||||||
|
ActivityCompat.startActivity(getActivity(), detailIntent, options.toBundle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TunnelDetailFragment.TunnelDetailListener
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEditTunnel(int tunnelId) {
|
||||||
|
Fragment editFrag = EditTunnelContainerFragment.newInstance(tunnelId);
|
||||||
|
getChildFragmentManager().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));
|
||||||
|
getChildFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||||
|
} else {
|
||||||
|
TunnelDetailFragment detailFrag = (TunnelDetailFragment) getChildFragmentManager().findFragmentById(R.id.detail_fragment);
|
||||||
|
getChildFragmentManager().beginTransaction()
|
||||||
|
.remove(detailFrag).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMagicIndicator() {
|
||||||
|
if (mPageIndicator == null || getContext() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonNavigator commonNavigator = new CommonNavigator(getContext());
|
||||||
|
commonNavigator.setAdjustMode(true); // Add this line for better spacing
|
||||||
|
commonNavigator.setAdapter(new CommonNavigatorAdapter() {
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mFragPagerAdapter.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPagerTitleView getTitleView(Context context, final int index) {
|
||||||
|
SimplePagerTitleView simplePagerTitleView = new ColorTransitionPagerTitleView(context);
|
||||||
|
simplePagerTitleView.setText(mFragPagerAdapter.getPageTitle(index));
|
||||||
|
simplePagerTitleView.setTextSize(16); // Add this line to increase text size
|
||||||
|
simplePagerTitleView.setNormalColor(ContextCompat.getColor(context,
|
||||||
|
R.color.primary_text_disabled_material_dark));
|
||||||
|
simplePagerTitleView.setSelectedColor(ContextCompat.getColor(context,
|
||||||
|
R.color.primary_text_default_material_dark));
|
||||||
|
simplePagerTitleView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
mViewPager.setCurrentItem(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return simplePagerTitleView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPagerIndicator getIndicator(Context context) {
|
||||||
|
LinePagerIndicator indicator = new LinePagerIndicator(context);
|
||||||
|
indicator.setMode(LinePagerIndicator.MODE_WRAP_CONTENT);
|
||||||
|
indicator.setColors(ContextCompat.getColor(context, R.color.primary));
|
||||||
|
indicator.setLineHeight(dpToPx(context, 3));
|
||||||
|
return indicator;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mPageIndicator.setNavigator(commonNavigator);
|
||||||
|
ViewPagerHelper.bind(mPageIndicator, mViewPager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int dpToPx(Context context, int dp) {
|
||||||
|
float density = context.getResources().getDisplayMetrics().density;
|
||||||
|
return Math.round(dp * density);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
package net.i2p.android.i2ptunnel.preferences;
|
||||||
|
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
//import android.support.v7.preference.CheckBoxPreference;
|
||||||
|
import androidx.preference.CheckBoxPreference;
|
||||||
|
//import android.support.v7.preference.Preference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
//import android.support.v7.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
//import android.support.v7.preference.PreferenceScreen;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelLogic;
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
public class AdvancedTunnelPreferenceFragment extends BaseTunnelPreferenceFragment {
|
||||||
|
public static AdvancedTunnelPreferenceFragment newInstance(int tunnelId) {
|
||||||
|
AdvancedTunnelPreferenceFragment f = new AdvancedTunnelPreferenceFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(ARG_TUNNEL_ID, tunnelId);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void loadPreferences() {
|
||||||
|
String type = TunnelUtil.getController(mGroup, mTunnelId).getType();
|
||||||
|
new TunnelPreferences(type).runLogic();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TunnelPreferences extends TunnelLogic {
|
||||||
|
PreferenceScreen ps;
|
||||||
|
PreferenceCategory tunParamCategory;
|
||||||
|
|
||||||
|
public TunnelPreferences(String type) {
|
||||||
|
super(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void general() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalClient() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalClientStreamr(boolean isStreamr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalClientPort() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalClientPortStreamr(boolean isStreamr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalClientProxy(boolean isProxy) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalClientProxyHttp(boolean isHttp) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalClientIrc() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalServerHttp() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalServerPort() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generalServerPortStreamr(boolean isStreamr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advanced() {
|
||||||
|
addPreferencesFromResource(R.xml.tunnel_adv);
|
||||||
|
ps = getPreferenceScreen();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedServerHttp(boolean isHttp) {
|
||||||
|
if (isHttp)
|
||||||
|
addPreferencesFromResource(R.xml.tunnel_adv_server_http);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||||
|
if (isServerOrStreamrClient)
|
||||||
|
ps.removePreference(ps.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);
|
||||||
|
|
||||||
|
// PERSISTENT_KEY and NEW_KEYS can't be set simultaneously
|
||||||
|
final CheckBoxPreference nk = (CheckBoxPreference) findPreference(getString(R.string.TUNNEL_OTP_NEW_KEYS));
|
||||||
|
nk.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||||
|
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
|
||||||
|
if ((Boolean) o && prefs.getBoolean(getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
|
||||||
|
getResources().getBoolean(R.bool.DEFAULT_PERSISTENT_KEY))) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
builder.setTitle(R.string.new_keys_on_reopen_conflict_title)
|
||||||
|
.setMessage(R.string.new_keys_on_reopen_conflict_msg)
|
||||||
|
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(getString(R.string.TUNNEL_OPT_PERSISTENT_KEY), false);
|
||||||
|
editor.apply();
|
||||||
|
nk.setChecked(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedClientHttp() {
|
||||||
|
addPreferencesFromResource(R.xml.tunnel_adv_client_http);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedClientProxy() {
|
||||||
|
addPreferencesFromResource(R.xml.tunnel_adv_client_proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedOther() {
|
||||||
|
addPreferencesFromResource(R.xml.tunnel_adv_other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
package net.i2p.android.i2ptunnel.preferences;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.preference.Preference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
//import android.support.v7.preference.PreferenceGroup;
|
||||||
|
import androidx.preference.PreferenceGroup;
|
||||||
|
//import android.support.v7.preference.PreferenceScreen;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import net.i2p.android.i2ptunnel.util.SaveTunnelTask;
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
|
import net.i2p.android.preferences.util.CustomPreferenceFragment;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragment {
|
||||||
|
protected static final String ARG_TUNNEL_ID = "tunnelId";
|
||||||
|
|
||||||
|
protected TunnelControllerGroup mGroup;
|
||||||
|
protected int mTunnelId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||||
|
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) {
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
error, Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
|
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
|
||||||
|
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
|
||||||
|
try {
|
||||||
|
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Tunnel doesn't exist, or the tunnel config file could not be read
|
||||||
|
Util.e("Could not load tunnel details", e);
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
R.string.i2ptunnel_no_tunnel_details, Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
|
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
|
||||||
|
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
|
||||||
|
try {
|
||||||
|
loadPreferences();
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
// mGroup couldn't load its config file
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
iae.toString(), Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
SaveTunnelTask task = new SaveTunnelTask(mGroup, mTunnelId, cfg);
|
||||||
|
try {
|
||||||
|
// TODO: There used to be a possible ANR here, because the underlying I2P code
|
||||||
|
// checks if the session is open as part of updating its config. We may need to save
|
||||||
|
// completely asynchronously (and ensure we do actually save before the app closes).
|
||||||
|
task.execute().get(2, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Util.e("Interrupted while saving tunnel config", e);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Util.e("Error while saving tunnel config", e);
|
||||||
|
} catch (CancellationException e) {
|
||||||
|
Util.e("Cancelled while saving tunnel config", e);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
Util.e("Timed out while savomg tunnel config", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void loadPreferences();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http://stackoverflow.com/a/20806812
|
||||||
|
*
|
||||||
|
* @param id the Preferences XML to load
|
||||||
|
* @param newParent the parent PreferenceGroup to add the new Preferences to.
|
||||||
|
*/
|
||||||
|
protected 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,63 @@
|
|||||||
|
package net.i2p.android.i2ptunnel.preferences;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
//import android.support.v4.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
//import android.support.v7.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
import net.i2p.android.i2ptunnel.TunnelDetailActivity;
|
||||||
|
import net.i2p.android.i2ptunnel.TunnelDetailFragment;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.util.LocaleManager;
|
||||||
|
|
||||||
|
public class EditTunnelActivity extends AppCompatActivity {
|
||||||
|
private int mTunnelId;
|
||||||
|
|
||||||
|
private final LocaleManager localeManager = new LocaleManager();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
localeManager.onCreate(this);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_single_fragment);
|
||||||
|
|
||||||
|
// Set the action bar
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
mTunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
|
||||||
|
Fragment editFrag = GeneralTunnelPreferenceFragment.newInstance(mTunnelId);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.fragment, editFrag).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
localeManager.onResume(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSupportNavigateUp() {
|
||||||
|
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
|
if (fragmentManager.getBackStackEntryCount() > 0) {
|
||||||
|
fragmentManager.popBackStack();
|
||||||
|
} else {
|
||||||
|
Intent intent = new Intent(this, TunnelDetailActivity.class);
|
||||||
|
intent.putExtra(TunnelDetailFragment.TUNNEL_ID, mTunnelId);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package net.i2p.android.i2ptunnel.preferences;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
//import android.support.v4.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shim that emulates EditTunnelActivity to provide a Toolbar with navigation
|
||||||
|
* in two-pane mode.
|
||||||
|
*/
|
||||||
|
public class EditTunnelContainerFragment extends Fragment {
|
||||||
|
private static final String ARG_TUNNEL_ID = "tunnelId";
|
||||||
|
|
||||||
|
public static EditTunnelContainerFragment newInstance(int tunnelId) {
|
||||||
|
EditTunnelContainerFragment f = new EditTunnelContainerFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(ARG_TUNNEL_ID, tunnelId);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.activity_single_fragment, container, false);
|
||||||
|
|
||||||
|
// Set the action bar
|
||||||
|
Toolbar toolbar = (Toolbar) v.findViewById(R.id.main_toolbar);
|
||||||
|
toolbar.setTitle(R.string.edit_tunnel);
|
||||||
|
toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
|
||||||
|
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
// Try and navigate back through the edit tunnel fragments.
|
||||||
|
// Otherwise, pop us back off.
|
||||||
|
FragmentManager fragmentManager = getChildFragmentManager();
|
||||||
|
if (fragmentManager.getBackStackEntryCount() > 0)
|
||||||
|
fragmentManager.popBackStack();
|
||||||
|
else
|
||||||
|
getFragmentManager().popBackStack();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
int tunnelId = getArguments().getInt(ARG_TUNNEL_ID);
|
||||||
|
BaseTunnelPreferenceFragment editFrag = GeneralTunnelPreferenceFragment.newInstance(tunnelId);
|
||||||
|
getChildFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.fragment, editFrag).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,280 @@
|
|||||||
|
package net.i2p.android.i2ptunnel.preferences;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
//import android.support.v7.preference.CheckBoxPreference;
|
||||||
|
import androidx.preference.CheckBoxPreference;
|
||||||
|
//import android.support.v7.preference.ListPreference;
|
||||||
|
import androidx.preference.ListPreference;
|
||||||
|
//import android.support.v7.preference.Preference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
//import android.support.v7.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
//import android.support.v7.preference.PreferenceScreen;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelLogic;
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.util.Addresses;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragment {
|
||||||
|
private CheckBoxPreference persistentKeys;
|
||||||
|
|
||||||
|
public static GeneralTunnelPreferenceFragment newInstance(int tunnelId) {
|
||||||
|
GeneralTunnelPreferenceFragment f = new GeneralTunnelPreferenceFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(ARG_TUNNEL_ID, tunnelId);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void loadPreferences() {
|
||||||
|
String type = TunnelUtil.getController(mGroup, mTunnelId).getType();
|
||||||
|
new TunnelPreferences(type).runLogic();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
// In case this was changed when toggling NEW_KEYS and then we navigated back
|
||||||
|
if (persistentKeys != null)
|
||||||
|
persistentKeys.setChecked(getPreferenceManager().getSharedPreferences().getBoolean(
|
||||||
|
getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
|
||||||
|
getResources().getBoolean(R.bool.DEFAULT_PERSISTENT_KEY)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
class TunnelPreferences extends TunnelLogic {
|
||||||
|
PreferenceScreen ps;
|
||||||
|
PreferenceCategory generalCategory;
|
||||||
|
PreferenceCategory portCategory;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// PERSISTENT_KEY and NEW_KEYS can't be set simultaneously
|
||||||
|
persistentKeys = (CheckBoxPreference) findPreference(getString(R.string.TUNNEL_OPT_PERSISTENT_KEY));
|
||||||
|
persistentKeys.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||||
|
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
|
||||||
|
if ((Boolean) o && prefs.getBoolean(getString(R.string.TUNNEL_OTP_NEW_KEYS),
|
||||||
|
getResources().getBoolean(R.bool.DEFAULT_NEW_KEYS))) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
builder.setTitle(R.string.persistent_key_conflict_title)
|
||||||
|
.setMessage(R.string.persistent_key_conflict_msg)
|
||||||
|
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(getString(R.string.TUNNEL_OTP_NEW_KEYS), false);
|
||||||
|
editor.apply();
|
||||||
|
persistentKeys.setChecked(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)));
|
||||||
|
// # TODO: See trac issue #2296
|
||||||
|
//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) {
|
||||||
|
ListPreference reachableBy = (ListPreference) portCategory.findPreference(getString(R.string.TUNNEL_INTERFACE));
|
||||||
|
if (isStreamr)
|
||||||
|
portCategory.removePreference(reachableBy);
|
||||||
|
else
|
||||||
|
setupReachableBy(reachableBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupReachableBy(final ListPreference reachableBy) {
|
||||||
|
reachableBy.setEnabled(false);
|
||||||
|
new AsyncTask<Void, Void, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
Set<String> interfaceSet = Addresses.getAllAddresses();
|
||||||
|
final String[] interfaces = interfaceSet.toArray(new String[interfaceSet.size()]);
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
reachableBy.setEntries(interfaces);
|
||||||
|
reachableBy.setEntryValues(interfaces);
|
||||||
|
reachableBy.setEnabled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
/*
|
||||||
|
# TODO: See trac issue #2296
|
||||||
|
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);
|
||||||
|
// # TODO: See trac issue #2296
|
||||||
|
//portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||||
|
if (isStreamr)
|
||||||
|
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_LISTEN_PORT)));
|
||||||
|
|
||||||
|
setupReachableBy((ListPreference) portCategory.findPreference(getString(R.string.TUNNEL_INTERFACE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)));
|
||||||
|
// # TODO: See trac issue #2296
|
||||||
|
//portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advanced() {
|
||||||
|
Preference advanced = new Preference(getActivity());
|
||||||
|
advanced.setKey(getString(R.string.TUNNEL_CAT_ADVANCED));
|
||||||
|
advanced.setTitle(R.string.settings_label_advanced);
|
||||||
|
advanced.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
Fragment fragment = AdvancedTunnelPreferenceFragment.newInstance(mTunnelId);
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.fragment, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ps.addPreference(advanced);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedStreamr(boolean isStreamr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedServer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedServerHttp(boolean isHttp) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedIdle() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedClient() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedClientHttp() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedClientProxy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void advancedOther() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package net.i2p.android.i2ptunnel.util;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a TunnelConfig.
|
||||||
|
*
|
||||||
|
* This must be performed in a background thread, because the underlying I2P code calls
|
||||||
|
* InetAddress.getByName(), which will trigger a NetworkOnMainThreadException otherwise.
|
||||||
|
*/
|
||||||
|
public class SaveTunnelTask extends AsyncTask<Void, Void, List<String>> {
|
||||||
|
final TunnelControllerGroup mGroup;
|
||||||
|
final int mTunnelId;
|
||||||
|
final TunnelConfig mCfg;
|
||||||
|
|
||||||
|
public SaveTunnelTask(TunnelControllerGroup group, int tunnelId, TunnelConfig cfg) {
|
||||||
|
mGroup = group;
|
||||||
|
mTunnelId = tunnelId;
|
||||||
|
mCfg = cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> doInBackground(Void... voids) {
|
||||||
|
return TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, mCfg);
|
||||||
|
}
|
||||||
|
}
|
@ -1,668 +0,0 @@
|
|||||||
package net.i2p.android.i2ptunnel.util;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
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.I2PTunnelConnectClient;
|
|
||||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
|
|
||||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
|
|
||||||
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
|
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
|
||||||
import net.i2p.util.ConcurrentHashSet;
|
|
||||||
import net.i2p.util.PasswordManager;
|
|
||||||
|
|
||||||
public class TunnelConfig {
|
|
||||||
protected final I2PAppContext _context;
|
|
||||||
|
|
||||||
private String _type;
|
|
||||||
private String _name;
|
|
||||||
private String _description;
|
|
||||||
private String _i2cpHost;
|
|
||||||
private String _i2cpPort;
|
|
||||||
private String _tunnelDepth;
|
|
||||||
private String _tunnelQuantity;
|
|
||||||
private String _tunnelVariance;
|
|
||||||
private String _tunnelBackupQuantity;
|
|
||||||
private boolean _connectDelay;
|
|
||||||
private String _customOptions;
|
|
||||||
private String _proxyList;
|
|
||||||
private String _port;
|
|
||||||
private String _reachableBy;
|
|
||||||
private String _targetDestination;
|
|
||||||
private String _targetHost;
|
|
||||||
private String _targetPort;
|
|
||||||
private String _spoofedHost;
|
|
||||||
private String _privKeyFile;
|
|
||||||
private String _profile;
|
|
||||||
private boolean _startOnLoad;
|
|
||||||
private boolean _sharedClient;
|
|
||||||
private final Set<String> _booleanOptions;
|
|
||||||
private final Map<String, String> _otherOptions;
|
|
||||||
private String _newProxyUser;
|
|
||||||
private String _newProxyPW;
|
|
||||||
|
|
||||||
static final String CLIENT_NICKNAME = "shared clients";
|
|
||||||
|
|
||||||
public static TunnelConfig createFromWizard(
|
|
||||||
Context ctx, TunnelControllerGroup tcg, Bundle data) {
|
|
||||||
// Get the Bundle keys
|
|
||||||
Resources res = ctx.getResources();
|
|
||||||
|
|
||||||
String kClientServer = res.getString(R.string.i2ptunnel_wizard_k_client_server);
|
|
||||||
String kType = res.getString(R.string.i2ptunnel_wizard_k_type);
|
|
||||||
|
|
||||||
String kName = res.getString(R.string.i2ptunnel_wizard_k_name);
|
|
||||||
String kDesc = res.getString(R.string.i2ptunnel_wizard_k_desc);
|
|
||||||
String kDest = res.getString(R.string.i2ptunnel_wizard_k_dest);
|
|
||||||
String kOutproxies = res.getString(R.string.i2ptunnel_wizard_k_outproxies);
|
|
||||||
String kTargetHost = res.getString(R.string.i2ptunnel_wizard_k_target_host);
|
|
||||||
String kTargetPort = res.getString(R.string.i2ptunnel_wizard_k_target_port);
|
|
||||||
String kReachableOn = res.getString(R.string.i2ptunnel_wizard_k_reachable_on);
|
|
||||||
String kBindingPort = res.getString(R.string.i2ptunnel_wizard_k_binding_port);
|
|
||||||
String kAutoStart = res.getString(R.string.i2ptunnel_wizard_k_auto_start);
|
|
||||||
|
|
||||||
// Create the TunnelConfig
|
|
||||||
TunnelConfig cfg = new TunnelConfig();
|
|
||||||
|
|
||||||
// Get/set the tunnel wizard settings
|
|
||||||
String clientServer = data.getBundle(kClientServer).getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
String typeName = data.getBundle(clientServer + ":" + kType).getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
String type = TunnelUtil.getTypeFromName(typeName, ctx);
|
|
||||||
cfg.setType(type);
|
|
||||||
|
|
||||||
String name = data.getBundle(kName).getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
cfg.setName(name);
|
|
||||||
|
|
||||||
String desc = data.getBundle(kDesc).getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
cfg.setDescription(desc);
|
|
||||||
|
|
||||||
String dest = null;
|
|
||||||
Bundle pageData = data.getBundle(kDest);
|
|
||||||
if (pageData != null) dest = pageData.getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
cfg.setTargetDestination(dest);
|
|
||||||
|
|
||||||
String outproxies = null;
|
|
||||||
pageData = data.getBundle(kOutproxies);
|
|
||||||
if (pageData != null) outproxies = pageData.getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
cfg.setProxyList(outproxies);
|
|
||||||
|
|
||||||
String targetHost = null;
|
|
||||||
pageData = data.getBundle(kTargetHost);
|
|
||||||
if (pageData != null) targetHost = pageData.getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
cfg.setTargetHost(targetHost);
|
|
||||||
|
|
||||||
String targetPort = null;
|
|
||||||
pageData = data.getBundle(kTargetPort);
|
|
||||||
if (pageData != null) targetPort = pageData.getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
cfg.setTargetPort(targetPort);
|
|
||||||
|
|
||||||
String reachableOn = null;
|
|
||||||
pageData = data.getBundle(kReachableOn);
|
|
||||||
if (pageData != null) reachableOn = pageData.getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
cfg.setReachableBy(reachableOn);
|
|
||||||
|
|
||||||
String bindingPort = null;
|
|
||||||
pageData = data.getBundle(kBindingPort);
|
|
||||||
if (pageData != null) bindingPort = pageData.getString(Page.SIMPLE_DATA_KEY);
|
|
||||||
cfg.setPort(bindingPort);
|
|
||||||
|
|
||||||
boolean autoStart = data.getBundle(kAutoStart).getBoolean(Page.SIMPLE_DATA_KEY);
|
|
||||||
cfg.setStartOnLoad(autoStart);
|
|
||||||
|
|
||||||
// Set sensible defaults for a new tunnel
|
|
||||||
cfg.setTunnelDepth("3");
|
|
||||||
cfg.setTunnelVariance("0");
|
|
||||||
cfg.setTunnelQuantity("2");
|
|
||||||
cfg.setTunnelBackupQuantity("0");
|
|
||||||
cfg.setClientHost("internal");
|
|
||||||
cfg.setClientport("internal");
|
|
||||||
cfg.setCustomOptions("");
|
|
||||||
if (!"streamrclient".equals(type)) {
|
|
||||||
cfg.setProfile("bulk");
|
|
||||||
cfg.setReduceCount("1");
|
|
||||||
cfg.setReduceTime("20");
|
|
||||||
}
|
|
||||||
if (TunnelUtil.isClient(type)) { /* Client-only defaults */
|
|
||||||
if (!"streamrclient".equals(type)) {
|
|
||||||
cfg.setNewDest("0");
|
|
||||||
cfg.setCloseTime("30");
|
|
||||||
}
|
|
||||||
if ("httpclient".equals(type) ||
|
|
||||||
"connectclient".equals(type) ||
|
|
||||||
"sockstunnel".equals(type) |
|
|
||||||
"socksirctunnel".equals(type)) {
|
|
||||||
cfg.setProxyUsername("");
|
|
||||||
cfg.setProxyPassword("");
|
|
||||||
cfg.setOutproxyUsername("");
|
|
||||||
cfg.setOutproxyPassword("");
|
|
||||||
}
|
|
||||||
if ("httpclient".equals(type))
|
|
||||||
cfg.setJumpList("http://i2host.i2p/cgi-bin/i2hostjump?\nhttp://stats.i2p/cgi-bin/jump.cgi?a=");
|
|
||||||
} else { /* Server-only defaults */
|
|
||||||
cfg.setPrivKeyFile(TunnelUtil.getPrivateKeyFile(tcg, -1));
|
|
||||||
cfg.setEncrypt("");
|
|
||||||
cfg.setEncryptKey("");
|
|
||||||
cfg.setAccessMode("0");
|
|
||||||
cfg.setAccessList("");
|
|
||||||
cfg.setLimitMinute("0");
|
|
||||||
cfg.setLimitHour("0");
|
|
||||||
cfg.setLimitDay("0");
|
|
||||||
cfg.setTotalMinute("0");
|
|
||||||
cfg.setTotalHour("0");
|
|
||||||
cfg.setTotalDay("0");
|
|
||||||
cfg.setMaxStreams("0");
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TunnelConfig() {
|
|
||||||
_context = I2PAppContext.getGlobalContext();
|
|
||||||
_booleanOptions = new ConcurrentHashSet<String>(4);
|
|
||||||
_otherOptions = new ConcurrentHashMap<String,String>(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* What type of tunnel (httpclient, ircclient, client, or server). This is
|
|
||||||
* required when adding a new tunnel.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public void setType(String type) {
|
|
||||||
_type = (type != null ? type.trim() : null);
|
|
||||||
}
|
|
||||||
String getType() { return _type; }
|
|
||||||
|
|
||||||
/** Short name of the tunnel */
|
|
||||||
public void setName(String name) {
|
|
||||||
_name = (name != null ? name.trim() : null);
|
|
||||||
}
|
|
||||||
/** one line description */
|
|
||||||
public void setDescription(String description) {
|
|
||||||
_description = (description != null ? description.trim() : null);
|
|
||||||
}
|
|
||||||
/** I2CP host the router is on, ignored when in router context */
|
|
||||||
public void setClientHost(String host) {
|
|
||||||
_i2cpHost = (host != null ? host.trim() : null);
|
|
||||||
}
|
|
||||||
/** I2CP port the router is on, ignored when in router context */
|
|
||||||
public void setClientport(String port) {
|
|
||||||
_i2cpPort = (port != null ? port.trim() : null);
|
|
||||||
}
|
|
||||||
/** how many hops to use for inbound tunnels */
|
|
||||||
public void setTunnelDepth(String tunnelDepth) {
|
|
||||||
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
|
|
||||||
}
|
|
||||||
/** how many parallel inbound tunnels to use */
|
|
||||||
public void setTunnelQuantity(String tunnelQuantity) {
|
|
||||||
_tunnelQuantity = (tunnelQuantity != null ? tunnelQuantity.trim() : null);
|
|
||||||
}
|
|
||||||
/** how much randomisation to apply to the depth of tunnels */
|
|
||||||
public void setTunnelVariance(String tunnelVariance) {
|
|
||||||
_tunnelVariance = (tunnelVariance != null ? tunnelVariance.trim() : null);
|
|
||||||
}
|
|
||||||
/** how many tunnels to hold in reserve to guard against failures */
|
|
||||||
public void setTunnelBackupQuantity(String tunnelBackupQuantity) {
|
|
||||||
_tunnelBackupQuantity = (tunnelBackupQuantity != null ? tunnelBackupQuantity.trim() : null);
|
|
||||||
}
|
|
||||||
/** what I2P session overrides should be used */
|
|
||||||
public void setCustomOptions(String customOptions) {
|
|
||||||
_customOptions = (customOptions != null ? customOptions.trim() : null);
|
|
||||||
}
|
|
||||||
/** what HTTP outproxies should be used (httpclient specific) */
|
|
||||||
public void setProxyList(String proxyList) {
|
|
||||||
_proxyList = (proxyList != null ? proxyList.trim() : null);
|
|
||||||
}
|
|
||||||
/** what port should this client/httpclient/ircclient listen on */
|
|
||||||
public void setPort(String port) {
|
|
||||||
_port = (port != null ? port.trim() : null);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* what interface should this client/httpclient/ircclient listen on
|
|
||||||
*/
|
|
||||||
public void setReachableBy(String reachableBy) {
|
|
||||||
_reachableBy = (reachableBy != null ? reachableBy.trim() : null);
|
|
||||||
}
|
|
||||||
/** What peer does this client tunnel point at */
|
|
||||||
public void setTargetDestination(String dest) {
|
|
||||||
_targetDestination = (dest != null ? dest.trim() : null);
|
|
||||||
}
|
|
||||||
/** What host does this server tunnel point at */
|
|
||||||
public void setTargetHost(String host) {
|
|
||||||
_targetHost = (host != null ? host.trim() : null);
|
|
||||||
}
|
|
||||||
/** What port does this server tunnel point at */
|
|
||||||
public void setTargetPort(String port) {
|
|
||||||
_targetPort = (port != null ? port.trim() : null);
|
|
||||||
}
|
|
||||||
/** What host does this http server tunnel spoof */
|
|
||||||
public void setSpoofedHost(String host) {
|
|
||||||
_spoofedHost = (host != null ? host.trim() : null);
|
|
||||||
}
|
|
||||||
/** What filename is this server tunnel's private keys stored in */
|
|
||||||
public void setPrivKeyFile(String file) {
|
|
||||||
_privKeyFile = (file != null ? file.trim() : null);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* If called with true, we want this tunnel to start whenever it is
|
|
||||||
* loaded (aka right now and whenever the router is started up)
|
|
||||||
*/
|
|
||||||
public void setStartOnLoad(boolean val) {
|
|
||||||
_startOnLoad = val;
|
|
||||||
}
|
|
||||||
public void setShared(boolean val) {
|
|
||||||
_sharedClient=val;
|
|
||||||
}
|
|
||||||
public void setConnectDelay(String moo) {
|
|
||||||
_connectDelay = true;
|
|
||||||
}
|
|
||||||
public void setProfile(String profile) {
|
|
||||||
_profile = profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReduce(String moo) {
|
|
||||||
_booleanOptions.add("i2cp.reduceOnIdle");
|
|
||||||
}
|
|
||||||
public void setClose(String moo) {
|
|
||||||
_booleanOptions.add("i2cp.closeOnIdle");
|
|
||||||
}
|
|
||||||
public void setEncrypt(String moo) {
|
|
||||||
_booleanOptions.add("i2cp.encryptLeaseSet");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @since 0.8.9 */
|
|
||||||
public void setDCC(String moo) {
|
|
||||||
_booleanOptions.add(I2PTunnelIRCClient.PROP_DCC);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static final String PROP_ENABLE_ACCESS_LIST = "i2cp.enableAccessList";
|
|
||||||
protected static final String PROP_ENABLE_BLACKLIST = "i2cp.enableBlackList";
|
|
||||||
|
|
||||||
public void setAccessMode(String val) {
|
|
||||||
if ("1".equals(val))
|
|
||||||
_booleanOptions.add(PROP_ENABLE_ACCESS_LIST);
|
|
||||||
else if ("2".equals(val))
|
|
||||||
_booleanOptions.add(PROP_ENABLE_BLACKLIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDelayOpen(String moo) {
|
|
||||||
_booleanOptions.add("i2cp.delayOpen");
|
|
||||||
}
|
|
||||||
public void setNewDest(String val) {
|
|
||||||
if ("1".equals(val))
|
|
||||||
_booleanOptions.add("i2cp.newDestOnResume");
|
|
||||||
else if ("2".equals(val))
|
|
||||||
_booleanOptions.add("persistentClientKey");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReduceTime(String val) {
|
|
||||||
if (val != null) {
|
|
||||||
try {
|
|
||||||
_otherOptions.put("i2cp.reduceIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
|
|
||||||
} catch (NumberFormatException nfe) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void setReduceCount(String val) {
|
|
||||||
if (val != null)
|
|
||||||
_otherOptions.put("i2cp.reduceQuantity", val.trim());
|
|
||||||
}
|
|
||||||
public void setEncryptKey(String val) {
|
|
||||||
if (val != null)
|
|
||||||
_otherOptions.put("i2cp.leaseSetKey", val.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccessList(String val) {
|
|
||||||
if (val != null)
|
|
||||||
_otherOptions.put("i2cp.accessList", val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ","));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJumpList(String val) {
|
|
||||||
if (val != null)
|
|
||||||
_otherOptions.put(I2PTunnelHTTPClient.PROP_JUMP_SERVERS, val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ","));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCloseTime(String val) {
|
|
||||||
if (val != null) {
|
|
||||||
try {
|
|
||||||
_otherOptions.put("i2cp.closeIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
|
|
||||||
} catch (NumberFormatException nfe) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** all proxy auth @since 0.8.2 */
|
|
||||||
public void setProxyAuth(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProxyUsername(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_newProxyUser = s.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProxyPassword(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_newProxyPW = s.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOutproxyAuth(String s) {
|
|
||||||
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOutproxyUsername(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER, s.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOutproxyPassword(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW, s.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** all of these are @since 0.8.3 */
|
|
||||||
protected static final String PROP_MAX_CONNS_MIN = "i2p.streaming.maxConnsPerMinute";
|
|
||||||
protected static final String PROP_MAX_CONNS_HOUR = "i2p.streaming.maxConnsPerHour";
|
|
||||||
protected static final String PROP_MAX_CONNS_DAY = "i2p.streaming.maxConnsPerDay";
|
|
||||||
protected static final String PROP_MAX_TOTAL_CONNS_MIN = "i2p.streaming.maxTotalConnsPerMinute";
|
|
||||||
protected static final String PROP_MAX_TOTAL_CONNS_HOUR = "i2p.streaming.maxTotalConnsPerHour";
|
|
||||||
protected static final String PROP_MAX_TOTAL_CONNS_DAY = "i2p.streaming.maxTotalConnsPerDay";
|
|
||||||
protected static final String PROP_MAX_STREAMS = "i2p.streaming.maxConcurrentStreams";
|
|
||||||
|
|
||||||
public void setLimitMinute(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(PROP_MAX_CONNS_MIN, s.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLimitHour(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(PROP_MAX_CONNS_HOUR, s.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLimitDay(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(PROP_MAX_CONNS_DAY, s.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTotalMinute(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(PROP_MAX_TOTAL_CONNS_MIN, s.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTotalHour(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(PROP_MAX_TOTAL_CONNS_HOUR, s.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTotalDay(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(PROP_MAX_TOTAL_CONNS_DAY, s.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaxStreams(String s) {
|
|
||||||
if (s != null)
|
|
||||||
_otherOptions.put(PROP_MAX_STREAMS, s.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Based on all provided data, create a set of configuration parameters
|
|
||||||
* suitable for use in a TunnelController. This will replace (not add to)
|
|
||||||
* any existing parameters, so this should return a comprehensive mapping.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public Properties getConfig() {
|
|
||||||
Properties config = new Properties();
|
|
||||||
updateConfigGeneric(config);
|
|
||||||
|
|
||||||
if ((TunnelUtil.isClient(_type) && !"streamrclient".equals(_type)) || "streamrserver".equals(_type)) {
|
|
||||||
// streamrserver uses interface
|
|
||||||
if (_reachableBy != null)
|
|
||||||
config.setProperty("interface", _reachableBy);
|
|
||||||
else
|
|
||||||
config.setProperty("interface", "");
|
|
||||||
} else {
|
|
||||||
// streamrclient uses targetHost
|
|
||||||
if (_targetHost != null)
|
|
||||||
config.setProperty("targetHost", _targetHost);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TunnelUtil.isClient(_type)) {
|
|
||||||
// generic client stuff
|
|
||||||
if (_port != null)
|
|
||||||
config.setProperty("listenPort", _port);
|
|
||||||
config.setProperty("sharedClient", _sharedClient + "");
|
|
||||||
for (String p : _booleanClientOpts)
|
|
||||||
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
|
|
||||||
for (String p : _otherClientOpts)
|
|
||||||
if (_otherOptions.containsKey(p))
|
|
||||||
config.setProperty("option." + p, _otherOptions.get(p));
|
|
||||||
} else {
|
|
||||||
// generic server stuff
|
|
||||||
if (_targetPort != null)
|
|
||||||
config.setProperty("targetPort", _targetPort);
|
|
||||||
for (String p : _booleanServerOpts)
|
|
||||||
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
|
|
||||||
for (String p : _otherServerOpts)
|
|
||||||
if (_otherOptions.containsKey(p))
|
|
||||||
config.setProperty("option." + p, _otherOptions.get(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
// generic proxy stuff
|
|
||||||
if ("httpclient".equals(_type) || "connectclient".equals(_type) ||
|
|
||||||
"sockstunnel".equals(_type) ||"socksirctunnel".equals(_type)) {
|
|
||||||
for (String p : _booleanProxyOpts)
|
|
||||||
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
|
|
||||||
if (_proxyList != null)
|
|
||||||
config.setProperty("proxyList", _proxyList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy auth including migration to MD5
|
|
||||||
if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
|
|
||||||
// Migrate even if auth is disabled
|
|
||||||
// go get the old from custom options that updateConfigGeneric() put in there
|
|
||||||
String puser = "option." + I2PTunnelHTTPClientBase.PROP_USER;
|
|
||||||
String user = config.getProperty(puser);
|
|
||||||
String ppw = "option." + I2PTunnelHTTPClientBase.PROP_PW;
|
|
||||||
String pw = config.getProperty(ppw);
|
|
||||||
if (user != null && pw != null && user.length() > 0 && pw.length() > 0) {
|
|
||||||
String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
|
|
||||||
user + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
|
|
||||||
if (config.getProperty(pmd5) == null) {
|
|
||||||
// not in there, migrate
|
|
||||||
String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
|
|
||||||
: I2PTunnelConnectClient.AUTH_REALM;
|
|
||||||
String hex = PasswordManager.md5Hex(realm, user, pw);
|
|
||||||
if (hex != null) {
|
|
||||||
config.setProperty(pmd5, hex);
|
|
||||||
config.remove(puser);
|
|
||||||
config.remove(ppw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// New user/password
|
|
||||||
String auth = _otherOptions.get(I2PTunnelHTTPClientBase.PROP_AUTH);
|
|
||||||
if (auth != null && !auth.equals("false")) {
|
|
||||||
if (_newProxyUser != null && _newProxyPW != null &&
|
|
||||||
_newProxyUser.length() > 0 && _newProxyPW.length() > 0) {
|
|
||||||
String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
|
|
||||||
_newProxyUser + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
|
|
||||||
String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
|
|
||||||
: I2PTunnelConnectClient.AUTH_REALM;
|
|
||||||
String hex = PasswordManager.md5Hex(realm, _newProxyUser, _newProxyPW);
|
|
||||||
if (hex != null)
|
|
||||||
config.setProperty(pmd5, hex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
|
|
||||||
if (_targetDestination != null)
|
|
||||||
config.setProperty("targetDestination", _targetDestination);
|
|
||||||
} else if ("httpserver".equals(_type) || "httpbidirserver".equals(_type)) {
|
|
||||||
if (_spoofedHost != null)
|
|
||||||
config.setProperty("spoofedHost", _spoofedHost);
|
|
||||||
}
|
|
||||||
if ("httpbidirserver".equals(_type)) {
|
|
||||||
if (_port != null)
|
|
||||||
config.setProperty("listenPort", _port);
|
|
||||||
if (_reachableBy != null)
|
|
||||||
config.setProperty("interface", _reachableBy);
|
|
||||||
else if (_targetHost != null)
|
|
||||||
config.setProperty("interface", _targetHost);
|
|
||||||
else
|
|
||||||
config.setProperty("interface", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("ircclient".equals(_type)) {
|
|
||||||
boolean dcc = _booleanOptions.contains(I2PTunnelIRCClient.PROP_DCC);
|
|
||||||
config.setProperty("option." + I2PTunnelIRCClient.PROP_DCC,
|
|
||||||
"" + dcc);
|
|
||||||
// add some sane server options since they aren't in the GUI (yet)
|
|
||||||
if (dcc) {
|
|
||||||
config.setProperty("option." + PROP_MAX_CONNS_MIN, "3");
|
|
||||||
config.setProperty("option." + PROP_MAX_CONNS_HOUR, "10");
|
|
||||||
config.setProperty("option." + PROP_MAX_TOTAL_CONNS_MIN, "5");
|
|
||||||
config.setProperty("option." + PROP_MAX_TOTAL_CONNS_HOUR, "25");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String _noShowOpts[] = {
|
|
||||||
"inbound.length", "outbound.length", "inbound.lengthVariance", "outbound.lengthVariance",
|
|
||||||
"inbound.backupQuantity", "outbound.backupQuantity", "inbound.quantity", "outbound.quantity",
|
|
||||||
"inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize",
|
|
||||||
I2PTunnelIRCClient.PROP_DCC
|
|
||||||
};
|
|
||||||
private static final String _booleanClientOpts[] = {
|
|
||||||
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
|
|
||||||
};
|
|
||||||
private static final String _booleanProxyOpts[] = {
|
|
||||||
I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH
|
|
||||||
};
|
|
||||||
private static final String _booleanServerOpts[] = {
|
|
||||||
"i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST
|
|
||||||
};
|
|
||||||
private static final String _otherClientOpts[] = {
|
|
||||||
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime",
|
|
||||||
"outproxyUsername", "outproxyPassword",
|
|
||||||
I2PTunnelHTTPClient.PROP_JUMP_SERVERS,
|
|
||||||
I2PTunnelHTTPClientBase.PROP_AUTH
|
|
||||||
};
|
|
||||||
private static final String _otherServerOpts[] = {
|
|
||||||
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList",
|
|
||||||
PROP_MAX_CONNS_MIN, PROP_MAX_CONNS_HOUR, PROP_MAX_CONNS_DAY,
|
|
||||||
PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY,
|
|
||||||
PROP_MAX_STREAMS
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* do NOT add these to noShoOpts, we must leave them in for HTTPClient and ConnectCLient
|
|
||||||
* so they will get migrated to MD5
|
|
||||||
* TODO migrate socks to MD5
|
|
||||||
*/
|
|
||||||
private static final String _otherProxyOpts[] = {
|
|
||||||
"proxyUsername", "proxyPassword"
|
|
||||||
};
|
|
||||||
|
|
||||||
protected static final Set<String> _noShowSet = new HashSet<String>(64);
|
|
||||||
protected static final Set<String> _nonProxyNoShowSet = new HashSet<String>(4);
|
|
||||||
static {
|
|
||||||
_noShowSet.addAll(Arrays.asList(_noShowOpts));
|
|
||||||
_noShowSet.addAll(Arrays.asList(_booleanClientOpts));
|
|
||||||
_noShowSet.addAll(Arrays.asList(_booleanProxyOpts));
|
|
||||||
_noShowSet.addAll(Arrays.asList(_booleanServerOpts));
|
|
||||||
_noShowSet.addAll(Arrays.asList(_otherClientOpts));
|
|
||||||
_noShowSet.addAll(Arrays.asList(_otherServerOpts));
|
|
||||||
_nonProxyNoShowSet.addAll(Arrays.asList(_otherProxyOpts));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateConfigGeneric(Properties config) {
|
|
||||||
config.setProperty("type", _type);
|
|
||||||
if (_name != null)
|
|
||||||
config.setProperty("name", _name);
|
|
||||||
if (_description != null)
|
|
||||||
config.setProperty("description", _description);
|
|
||||||
if (!_context.isRouterContext()) {
|
|
||||||
if (_i2cpHost != null)
|
|
||||||
config.setProperty("i2cpHost", _i2cpHost);
|
|
||||||
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) ) {
|
|
||||||
config.setProperty("i2cpPort", _i2cpPort);
|
|
||||||
} else {
|
|
||||||
config.setProperty("i2cpPort", "7654");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_privKeyFile != null)
|
|
||||||
config.setProperty("privKeyFile", _privKeyFile);
|
|
||||||
|
|
||||||
if (_customOptions != null) {
|
|
||||||
StringTokenizer tok = new StringTokenizer(_customOptions);
|
|
||||||
while (tok.hasMoreTokens()) {
|
|
||||||
String pair = tok.nextToken();
|
|
||||||
int eq = pair.indexOf('=');
|
|
||||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
|
||||||
continue;
|
|
||||||
String key = pair.substring(0, eq);
|
|
||||||
if (_noShowSet.contains(key))
|
|
||||||
continue;
|
|
||||||
// leave in for HTTP and Connect so it can get migrated to MD5
|
|
||||||
// hide for SOCKS until migrated to MD5
|
|
||||||
if ((!"httpclient".equals(_type)) &&
|
|
||||||
(! "connectclient".equals(_type)) &&
|
|
||||||
_nonProxyNoShowSet.contains(key))
|
|
||||||
continue;
|
|
||||||
String val = pair.substring(eq+1);
|
|
||||||
config.setProperty("option." + key, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.setProperty("startOnLoad", _startOnLoad + "");
|
|
||||||
|
|
||||||
if (_tunnelQuantity != null) {
|
|
||||||
config.setProperty("option.inbound.quantity", _tunnelQuantity);
|
|
||||||
config.setProperty("option.outbound.quantity", _tunnelQuantity);
|
|
||||||
}
|
|
||||||
if (_tunnelDepth != null) {
|
|
||||||
config.setProperty("option.inbound.length", _tunnelDepth);
|
|
||||||
config.setProperty("option.outbound.length", _tunnelDepth);
|
|
||||||
}
|
|
||||||
if (_tunnelVariance != null) {
|
|
||||||
config.setProperty("option.inbound.lengthVariance", _tunnelVariance);
|
|
||||||
config.setProperty("option.outbound.lengthVariance", _tunnelVariance);
|
|
||||||
}
|
|
||||||
if (_tunnelBackupQuantity != null) {
|
|
||||||
config.setProperty("option.inbound.backupQuantity", _tunnelBackupQuantity);
|
|
||||||
config.setProperty("option.outbound.backupQuantity", _tunnelBackupQuantity);
|
|
||||||
}
|
|
||||||
if (_connectDelay)
|
|
||||||
config.setProperty("option.i2p.streaming.connectDelay", "1000");
|
|
||||||
else
|
|
||||||
config.setProperty("option.i2p.streaming.connectDelay", "0");
|
|
||||||
if (TunnelUtil.isClient(_type) && _sharedClient) {
|
|
||||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
|
||||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
|
||||||
} else if (_name != null) {
|
|
||||||
config.setProperty("option.inbound.nickname", _name);
|
|
||||||
config.setProperty("option.outbound.nickname", _name);
|
|
||||||
}
|
|
||||||
if ("interactive".equals(_profile))
|
|
||||||
// This was 1 which doesn't make much sense
|
|
||||||
// The real way to make it interactive is to make the streaming lib
|
|
||||||
// MessageInputStream flush faster but there's no option for that yet,
|
|
||||||
// Setting it to 16 instead of the default but not sure what good that is either.
|
|
||||||
config.setProperty("option.i2p.streaming.maxWindowSize", "16");
|
|
||||||
else
|
|
||||||
config.remove("option.i2p.streaming.maxWindowSize");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 final 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();
|
||||||
|
}
|
@ -1,161 +1,28 @@
|
|||||||
package net.i2p.android.i2ptunnel.util;
|
package net.i2p.android.i2ptunnel.util;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
import net.i2p.android.router.util.Util;
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.android.wizard.model.Page;
|
||||||
import net.i2p.i2ptunnel.TunnelController;
|
import net.i2p.i2ptunnel.TunnelController;
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
import net.i2p.util.FileUtil;
|
import net.i2p.i2ptunnel.ui.GeneralHelper;
|
||||||
import net.i2p.util.SecureFile;
|
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||||
|
|
||||||
public abstract class TunnelUtil {
|
public class TunnelUtil extends GeneralHelper {
|
||||||
public static TunnelController getController(TunnelControllerGroup tcg, int tunnel) {
|
public static final String PREFERENCES_FILENAME_PREFIX = "tunnel.";
|
||||||
if (tunnel < 0) return null;
|
|
||||||
if (tcg == null) return null;
|
public TunnelUtil(I2PAppContext context, TunnelControllerGroup tcg) {
|
||||||
List<TunnelController> controllers = tcg.getControllers();
|
super(context, tcg);
|
||||||
if (controllers.size() > tunnel)
|
|
||||||
return controllers.get(tunnel);
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> saveTunnel(Context ctx,
|
public TunnelUtil(TunnelControllerGroup tcg) {
|
||||||
TunnelControllerGroup tcg,
|
super(tcg);
|
||||||
int tunnelId,
|
|
||||||
Properties config) {
|
|
||||||
// Get current tunnel controller
|
|
||||||
TunnelController cur = getController(tcg, tunnelId);
|
|
||||||
|
|
||||||
if (config == null) {
|
|
||||||
List<String> ret = new ArrayList<String>();
|
|
||||||
ret.add("Invalid params");
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cur == null) {
|
|
||||||
// creating new
|
|
||||||
cur = new TunnelController(config, "", true);
|
|
||||||
tcg.addController(cur);
|
|
||||||
if (cur.getStartOnLoad())
|
|
||||||
cur.startTunnelBackground();
|
|
||||||
} else {
|
|
||||||
cur.setConfig(config, "");
|
|
||||||
}
|
|
||||||
// Only modify other shared tunnels
|
|
||||||
// if the current tunnel is shared, and of supported type
|
|
||||||
if (Boolean.parseBoolean(cur.getSharedClient()) && isClient(cur.getType())) {
|
|
||||||
// all clients use the same I2CP session, and as such, use the same I2CP options
|
|
||||||
List<TunnelController> controllers = tcg.getControllers();
|
|
||||||
|
|
||||||
for (int i = 0; i < controllers.size(); i++) {
|
|
||||||
TunnelController c = controllers.get(i);
|
|
||||||
|
|
||||||
// Current tunnel modified by user, skip
|
|
||||||
if (c == cur) continue;
|
|
||||||
|
|
||||||
// Only modify this non-current tunnel
|
|
||||||
// if it belongs to a shared destination, and is of supported type
|
|
||||||
if (Boolean.parseBoolean(c.getSharedClient()) && isClient(c.getType())) {
|
|
||||||
Properties cOpt = c.getConfig("");
|
|
||||||
if (config.getProperty("option.inbound.quantity") != null)
|
|
||||||
cOpt.setProperty("option.inbound.quantity", config.getProperty("option.inbound.quantity"));
|
|
||||||
if (config.getProperty("option.outbound.quantity") != null)
|
|
||||||
cOpt.setProperty("option.outbound.quantity", config.getProperty("option.outbound.quantity"));
|
|
||||||
if (config.getProperty("option.inbound.length") != null)
|
|
||||||
cOpt.setProperty("option.inbound.length", config.getProperty("option.inbound.length"));
|
|
||||||
if (config.getProperty("option.outbound.length") != null)
|
|
||||||
cOpt.setProperty("option.outbound.length", config.getProperty("option.outbound.length"));
|
|
||||||
if (config.getProperty("option.inbound.lengthVariance") != null)
|
|
||||||
cOpt.setProperty("option.inbound.lengthVariance", config.getProperty("option.inbound.lengthVariance"));
|
|
||||||
if (config.getProperty("option.outbound.lengthVariance") != null)
|
|
||||||
cOpt.setProperty("option.outbound.lengthVariance", config.getProperty("option.outbound.lengthVariance"));
|
|
||||||
if (config.getProperty("option.inbound.backupQuantity") != null)
|
|
||||||
cOpt.setProperty("option.inbound.backupQuantity", config.getProperty("option.inbound.backupQuantity"));
|
|
||||||
if (config.getProperty("option.outbound.backupQuantity") != null)
|
|
||||||
cOpt.setProperty("option.outbound.backupQuantity", config.getProperty("option.outbound.backupQuantity"));
|
|
||||||
cOpt.setProperty("option.inbound.nickname", TunnelConfig.CLIENT_NICKNAME);
|
|
||||||
cOpt.setProperty("option.outbound.nickname", TunnelConfig.CLIENT_NICKNAME);
|
|
||||||
|
|
||||||
c.setConfig(cOpt, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return doSave(ctx, tcg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the tunnel, delete from config,
|
|
||||||
* rename the private key file if in the default directory
|
|
||||||
*/
|
|
||||||
public static List<String> deleteTunnel(Context ctx, TunnelControllerGroup tcg, int tunnelId) {
|
|
||||||
List<String> msgs;
|
|
||||||
TunnelController cur = getController(tcg, tunnelId);
|
|
||||||
if (cur == null) {
|
|
||||||
msgs = new ArrayList<String>();
|
|
||||||
msgs.add("Invalid tunnel number");
|
|
||||||
return msgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
msgs = tcg.removeController(cur);
|
|
||||||
msgs.addAll(doSave(ctx, tcg));
|
|
||||||
|
|
||||||
// Rename private key file if it was a default name in
|
|
||||||
// the default directory, so it doesn't get reused when a new
|
|
||||||
// tunnel is created.
|
|
||||||
// Use configured file name if available, not the one from the form.
|
|
||||||
String pk = cur.getPrivKeyFile();
|
|
||||||
//if (pk == null)
|
|
||||||
// pk = _privKeyFile;
|
|
||||||
if (pk != null && pk.startsWith("i2ptunnel") && pk.endsWith("-privKeys.dat") &&
|
|
||||||
((!isClient(cur.getType())) || cur.getPersistentClientKey())) {
|
|
||||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
|
||||||
File pkf = new File(context.getConfigDir(), pk);
|
|
||||||
if (pkf.exists()) {
|
|
||||||
String name = cur.getName();
|
|
||||||
if (name == null) {
|
|
||||||
name = cur.getDescription();
|
|
||||||
if (name == null) {
|
|
||||||
name = cur.getType();
|
|
||||||
if (name == null)
|
|
||||||
name = Long.toString(context.clock().now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name = "i2ptunnel-deleted-" + name.replace(' ', '_') + '-' + context.clock().now() + "-privkeys.dat";
|
|
||||||
File backupDir = new SecureFile(context.getConfigDir(), TunnelController.KEY_BACKUP_DIR);
|
|
||||||
File to;
|
|
||||||
if (backupDir.isDirectory() || backupDir.mkdir())
|
|
||||||
to = new File(backupDir, name);
|
|
||||||
else
|
|
||||||
to = new File(context.getConfigDir(), name);
|
|
||||||
boolean success = FileUtil.rename(pkf, to);
|
|
||||||
if (success)
|
|
||||||
msgs.add("Private key file " + pkf.getAbsolutePath() +
|
|
||||||
" renamed to " + to.getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<String> doSave(Context ctx, TunnelControllerGroup tcg) {
|
|
||||||
List<String> rv = tcg.clearAllMessages();
|
|
||||||
try {
|
|
||||||
tcg.saveConfig();
|
|
||||||
rv.add(0, ctx.getResources().getString(R.string.i2ptunnel_msg_config_saved));
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
Util.e("Failed to save config file", ioe);
|
|
||||||
rv.add(0, ctx.getResources().getString(R.string.i2ptunnel_msg_config_save_failed) + ": " + ioe.toString());
|
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* General tunnel data for any type */
|
/* General tunnel data for any type */
|
||||||
@ -192,50 +59,745 @@ public abstract class TunnelUtil {
|
|||||||
|
|
||||||
public static String getTypeName(String type, Context context) {
|
public static String getTypeName(String type, Context context) {
|
||||||
Resources res = context.getResources();
|
Resources res = context.getResources();
|
||||||
if ("client".equals(type))
|
switch (type) {
|
||||||
return res.getString(R.string.i2ptunnel_type_client);
|
case "client":
|
||||||
else if ("httpclient".equals(type))
|
return res.getString(R.string.i2ptunnel_type_client);
|
||||||
return res.getString(R.string.i2ptunnel_type_httpclient);
|
case "httpclient":
|
||||||
else if ("ircclient".equals(type))
|
return res.getString(R.string.i2ptunnel_type_httpclient);
|
||||||
return res.getString(R.string.i2ptunnel_type_ircclient);
|
case "ircclient":
|
||||||
else if ("server".equals(type))
|
return res.getString(R.string.i2ptunnel_type_ircclient);
|
||||||
return res.getString(R.string.i2ptunnel_type_server);
|
case "server":
|
||||||
else if ("httpserver".equals(type))
|
return res.getString(R.string.i2ptunnel_type_server);
|
||||||
return res.getString(R.string.i2ptunnel_type_httpserver);
|
case "httpserver":
|
||||||
else if ("sockstunnel".equals(type))
|
return res.getString(R.string.i2ptunnel_type_httpserver);
|
||||||
return res.getString(R.string.i2ptunnel_type_sockstunnel);
|
case "sockstunnel":
|
||||||
else if ("socksirctunnel".equals(type))
|
return res.getString(R.string.i2ptunnel_type_sockstunnel);
|
||||||
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
|
case "socksirctunnel":
|
||||||
else if ("connectclient".equals(type))
|
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
|
||||||
return res.getString(R.string.i2ptunnel_type_connectclient);
|
case "connectclient":
|
||||||
else if ("ircserver".equals(type))
|
return res.getString(R.string.i2ptunnel_type_connectclient);
|
||||||
return res.getString(R.string.i2ptunnel_type_ircserver);
|
case "ircserver":
|
||||||
else if ("streamrclient".equals(type))
|
return res.getString(R.string.i2ptunnel_type_ircserver);
|
||||||
return res.getString(R.string.i2ptunnel_type_streamrclient);
|
case "streamrclient":
|
||||||
else if ("streamrserver".equals(type))
|
return res.getString(R.string.i2ptunnel_type_streamrclient);
|
||||||
return res.getString(R.string.i2ptunnel_type_streamrserver);
|
case "streamrserver":
|
||||||
else if ("httpbidirserver".equals(type))
|
return res.getString(R.string.i2ptunnel_type_streamrserver);
|
||||||
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
|
case "httpbidirserver":
|
||||||
else
|
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
|
||||||
return type;
|
default:
|
||||||
|
return type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isClient(String type) {
|
public static boolean isClient(String type) {
|
||||||
return ( ("client".equals(type)) ||
|
return TunnelController.isClient(type);
|
||||||
("httpclient".equals(type)) ||
|
|
||||||
("sockstunnel".equals(type)) ||
|
|
||||||
("socksirctunnel".equals(type)) ||
|
|
||||||
("connectclient".equals(type)) ||
|
|
||||||
("streamrclient".equals(type)) ||
|
|
||||||
("ircclient".equals(type)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) {
|
public static String getPreferencesFilename(int tunnel) {
|
||||||
TunnelController tun = getController(tcg, tunnel);
|
return PREFERENCES_FILENAME_PREFIX + tunnel;
|
||||||
if (tun != null && tun.getPrivKeyFile() != null)
|
}
|
||||||
return tun.getPrivKeyFile();
|
|
||||||
if (tunnel < 0)
|
public static void writeTunnelToPreferences(Context ctx, TunnelControllerGroup tcg, int tunnel) {
|
||||||
tunnel = tcg == null ? 999 : tcg.getControllers().size();
|
new TunnelUtil(tcg).writeTunnelToPreferences(ctx, tunnel);
|
||||||
return "i2ptunnel" + tunnel + "-privKeys.dat";
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
final SharedPreferences.Editor ed;
|
||||||
|
final Resources res;
|
||||||
|
final 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) {
|
||||||
|
/* # TODO: See trac issue #2296
|
||||||
|
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));
|
||||||
|
// # TODO: See trac issue #2296
|
||||||
|
//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),
|
||||||
|
getTunnelBackupQuantity(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 {
|
||||||
|
final TunnelConfig cfg;
|
||||||
|
final SharedPreferences prefs;
|
||||||
|
final Resources res;
|
||||||
|
final TunnelControllerGroup tcg;
|
||||||
|
final 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) {
|
||||||
|
/* # TODO: See trac issue #2296
|
||||||
|
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"));
|
||||||
|
// # TODO: See trac issue #2296
|
||||||
|
//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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data non-null
|
||||||
|
*/
|
||||||
|
public static TunnelConfig createConfigFromWizard(
|
||||||
|
Context ctx, TunnelControllerGroup tcg, Bundle data) {
|
||||||
|
return new TunnelUtil(tcg).createConfigFromWizard(ctx, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data non-null
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
try {
|
||||||
|
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();
|
||||||
|
} catch (NullPointerException ex) {
|
||||||
|
Util.e("Exception while trying to create config from wizard: "+ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TunnelConfigFromWizard extends TunnelLogic {
|
||||||
|
final TunnelConfig cfg;
|
||||||
|
final Bundle data;
|
||||||
|
final Resources res;
|
||||||
|
final 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)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package net.i2p.android.preferences;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
//import android.support.v7.preference.Preference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
|
||||||
|
public class AdvancedPreferenceFragment extends I2PreferenceFragment {
|
||||||
|
private static final String PREFERENCE_CATEGORY_TRANSPORTS = "preference_category_transports";
|
||||||
|
private static final String PREFERENCE_CATEGORY_EXPL_TUNNELS = "preference_category_expl_tunnels";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_advanced);
|
||||||
|
|
||||||
|
findPreference(PREFERENCE_CATEGORY_TRANSPORTS)
|
||||||
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_TRANSPORTS));
|
||||||
|
findPreference(PREFERENCE_CATEGORY_EXPL_TUNNELS)
|
||||||
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_EXPL_TUNNELS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_advanced);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
public CategoryClickListener(String category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
Fragment fragment;
|
||||||
|
switch (category) {
|
||||||
|
case PREFERENCE_CATEGORY_TRANSPORTS:
|
||||||
|
fragment = new TransportsPreferenceFragment();
|
||||||
|
break;
|
||||||
|
case PREFERENCE_CATEGORY_EXPL_TUNNELS:
|
||||||
|
fragment = new ExploratoryPoolPreferenceFragment();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
getActivity().getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.fragment, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package net.i2p.android.preferences;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
|
||||||
|
public class AppearancePreferenceFragment extends I2PreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_appearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(
|
||||||
|
(SettingsActivity) getActivity()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_appearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
|
||||||
|
(SettingsActivity) getActivity()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.i2p.android.preferences;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
|
||||||
|
public class ExploratoryPoolPreferenceFragment extends I2PreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_expl_tunnels);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_exploratory_pool);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package net.i2p.android.preferences;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.preference.CheckBoxPreference;
|
||||||
|
import androidx.preference.CheckBoxPreference;
|
||||||
|
//import android.support.v7.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
//import android.support.v7.preference.PreferenceManager;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
//import android.support.v7.preference.PreferenceScreen;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
import net.i2p.android.router.service.StatSummarizer;
|
||||||
|
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 java.util.Map;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
|
public class GraphsPreferenceFragment extends I2PreferenceFragment {
|
||||||
|
public static final String GRAPH_PREFERENCES_SEEN = "graphPreferencesSeen";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_graphs);
|
||||||
|
setupGraphSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.label_graphs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupGraphSettings() {
|
||||||
|
PreferenceScreen ps = getPreferenceScreen();
|
||||||
|
RouterContext ctx = Util.getRouterContext();
|
||||||
|
if (ctx == null) {
|
||||||
|
PreferenceCategory noRouter = new PreferenceCategory(getActivity());
|
||||||
|
noRouter.setTitle(R.string.router_not_running);
|
||||||
|
ps.addPreference(noRouter);
|
||||||
|
} else if (StatSummarizer.instance() == null) {
|
||||||
|
PreferenceCategory noStats = new PreferenceCategory(getActivity());
|
||||||
|
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(getActivity());
|
||||||
|
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(getActivity());
|
||||||
|
statPref.setKey("stat.summaries." + key);
|
||||||
|
statPref.setTitle(stat);
|
||||||
|
statPref.setSummary(description);
|
||||||
|
statPref.setEnabled(canBeGraphed);
|
||||||
|
statPref.setChecked(currentIsGraphed);
|
||||||
|
groupPrefs.addPreference(statPref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user has now seen the current (possibly default) configuration
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
|
if (!prefs.getBoolean(GRAPH_PREFERENCES_SEEN, false))
|
||||||
|
prefs.edit()
|
||||||
|
.putBoolean(GRAPH_PREFERENCES_SEEN, true)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package net.i2p.android.preferences;
|
||||||
|
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.android.preferences.util.CustomPreferenceFragment;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A PreferenceFragment that handles saving router settings.
|
||||||
|
*/
|
||||||
|
public abstract class I2PreferenceFragment extends CustomPreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
List<Properties> lProps = Util.getPropertiesFromPreferences(getActivity());
|
||||||
|
Properties props = lProps.get(0);
|
||||||
|
Properties propsToRemove = lProps.get(1);
|
||||||
|
Properties logSettings = lProps.get(2);
|
||||||
|
|
||||||
|
Set toRemove = propsToRemove.keySet();
|
||||||
|
|
||||||
|
boolean restartRequired = Util.checkAndCorrectRouterConfig(getActivity(), 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(getActivity(),
|
||||||
|
Util.getFileDir(getActivity()),
|
||||||
|
"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(getActivity(), 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package net.i2p.android.preferences;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.preference.PreferenceScreen;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.util.LogManager;
|
||||||
|
|
||||||
|
public class LoggingPreferenceFragment extends I2PreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_logging);
|
||||||
|
setupLoggingSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_logging);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupLoggingSettings() {
|
||||||
|
PreferenceScreen ps = getPreferenceScreen();
|
||||||
|
RouterContext ctx = Util.getRouterContext();
|
||||||
|
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(getActivity());
|
||||||
|
noRouter.setTitle(R.string.router_not_running);
|
||||||
|
ps.addPreference(noRouter);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.i2p.android.preferences;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
|
||||||
|
public class NetworkPreferenceFragment extends I2PreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_net);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_bandwidth_net);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
package net.i2p.android.preferences;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.preference.CheckBoxPreference;
|
||||||
|
//import android.support.v7.preference.Preference;
|
||||||
|
//import android.support.v7.preference.PreferenceManager;
|
||||||
|
//import android.support.v7.preference.PreferenceScreen;
|
||||||
|
import androidx.preference.CheckBoxPreference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
import net.i2p.android.preferences.util.PortPreference;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
|
public class TransportsPreferenceFragment extends I2PreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||||
|
// Load any properties that the router might have changed on us.
|
||||||
|
loadProperties();
|
||||||
|
addPreferencesFromResource(R.xml.settings_transports);
|
||||||
|
setupTransportSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_transports);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ApplySharedPref")
|
||||||
|
private void loadProperties() {
|
||||||
|
Context context= getActivity();
|
||||||
|
RouterContext ctx = Util.getRouterContext();
|
||||||
|
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 AdvancedPreferenceFragment loads its Preferences.
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTransportSettings() {
|
||||||
|
final Context context= getActivity();
|
||||||
|
PreferenceScreen ps = getPreferenceScreen();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package net.i2p.android.preferences.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
//import android.support.v7.preference.EditTextPreference;
|
||||||
|
import androidx.preference.EditTextPreference;
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
try {
|
||||||
|
return value != null && persistInt(Integer.valueOf(value));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package net.i2p.android.preferences.util;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat;
|
||||||
|
import androidx.preference.EditTextPreferenceDialogFragmentCompat;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
public class ConnectionLimitPreferenceDialog extends EditTextPreferenceDialogFragmentCompat {
|
||||||
|
public static ConnectionLimitPreferenceDialog newInstance(String key) {
|
||||||
|
final ConnectionLimitPreferenceDialog fragment = new ConnectionLimitPreferenceDialog();
|
||||||
|
final Bundle b = new Bundle(1);
|
||||||
|
b.putString(ARG_KEY, key);
|
||||||
|
fragment.setArguments(b);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindDialogView(View view) {
|
||||||
|
super.onBindDialogView(view);
|
||||||
|
((EditText)view.findViewById(android.R.id.edit)).setInputType(
|
||||||
|
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package net.i2p.android.preferences.util;
|
||||||
|
|
||||||
|
//import android.support.v4.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles custom Preferences.
|
||||||
|
*/
|
||||||
|
public abstract class CustomPreferenceFragment extends PreferenceFragmentCompat {
|
||||||
|
private static final String DIALOG_FRAGMENT_TAG =
|
||||||
|
"android.support.v7.preference.PreferenceFragment.DIALOG";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisplayPreferenceDialog(Preference preference) {
|
||||||
|
// check if dialog is already showing
|
||||||
|
if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogFragment f = null;
|
||||||
|
if (preference instanceof ConnectionLimitPreference) {
|
||||||
|
f = ConnectionLimitPreferenceDialog.newInstance(preference.getKey());
|
||||||
|
} else if (preference instanceof IntEditTextPreference) {
|
||||||
|
f = IntEditTextPreferenceDialog.newInstance(preference.getKey());
|
||||||
|
} else if (preference instanceof PortPreference) {
|
||||||
|
f = PortPreferenceDialog.newInstance(preference.getKey());
|
||||||
|
} else {
|
||||||
|
super.onDisplayPreferenceDialog(preference);
|
||||||
|
}
|
||||||
|
if (f != null) {
|
||||||
|
f.setTargetFragment(this, 0);
|
||||||
|
f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package net.i2p.android.preferences.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
//import android.support.v7.preference.EditTextPreference;
|
||||||
|
import androidx.preference.EditTextPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
public class IntEditTextPreference extends EditTextPreference {
|
||||||
|
|
||||||
|
public IntEditTextPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntEditTextPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
try {
|
||||||
|
return value != null && persistInt(Integer.valueOf(value));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package net.i2p.android.preferences.util;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat;
|
||||||
|
import androidx.preference.EditTextPreferenceDialogFragmentCompat;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
public class IntEditTextPreferenceDialog extends EditTextPreferenceDialogFragmentCompat {
|
||||||
|
public static IntEditTextPreferenceDialog newInstance(String key) {
|
||||||
|
final IntEditTextPreferenceDialog fragment = new IntEditTextPreferenceDialog();
|
||||||
|
final Bundle b = new Bundle(1);
|
||||||
|
b.putString(ARG_KEY, key);
|
||||||
|
fragment.setArguments(b);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindDialogView(View view) {
|
||||||
|
super.onBindDialogView(view);
|
||||||
|
((EditText)view.findViewById(android.R.id.edit)).setInputType(
|
||||||
|
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package net.i2p.android.preferences.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
//import android.support.v7.preference.ListPreference;
|
||||||
|
import androidx.preference.ListPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
public class IntListPreference extends ListPreference {
|
||||||
|
public IntListPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntListPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean persistString(String value) {
|
||||||
|
if (getSharedPreferences().contains(getKey())) {
|
||||||
|
try {
|
||||||
|
getPersistedInt(0);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// Fix for where this preference was previously stored in a ListPreference
|
||||||
|
getSharedPreferences().edit().remove(getKey()).apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value != null && persistInt(Integer.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getPersistedString(String defaultReturnValue) {
|
||||||
|
if(getSharedPreferences().contains(getKey())) {
|
||||||
|
try {
|
||||||
|
int intValue = getPersistedInt(0);
|
||||||
|
return String.valueOf(intValue);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
return super.getPersistedString("0");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return defaultReturnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package net.i2p.android.preferences.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
//import android.support.v7.preference.EditTextPreference;
|
||||||
|
import androidx.preference.EditTextPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
public class PortPreference extends EditTextPreference {
|
||||||
|
public PortPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PortPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PortPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getSummary() {
|
||||||
|
int port = getPersistedInt(-1);
|
||||||
|
if (port < 0)
|
||||||
|
return getContext().getResources().getString(R.string.unset);
|
||||||
|
else
|
||||||
|
return String.valueOf(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getPersistedString(String defaultReturnValue) {
|
||||||
|
int port = getPersistedInt(-1);
|
||||||
|
if (port < 0)
|
||||||
|
return defaultReturnValue;
|
||||||
|
else
|
||||||
|
return String.valueOf(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean persistString(String value) {
|
||||||
|
if (value == null || value.isEmpty())
|
||||||
|
return persistInt(-1);
|
||||||
|
|
||||||
|
int port;
|
||||||
|
try {
|
||||||
|
port = Integer.valueOf(value);
|
||||||
|
if (port < 0)
|
||||||
|
port = -1;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
port = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return persistInt(port);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package net.i2p.android.preferences.util;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat;
|
||||||
|
import androidx.preference.EditTextPreferenceDialogFragmentCompat;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
public class PortPreferenceDialog extends EditTextPreferenceDialogFragmentCompat {
|
||||||
|
public static PortPreferenceDialog newInstance(String key) {
|
||||||
|
final PortPreferenceDialog fragment = new PortPreferenceDialog();
|
||||||
|
final Bundle b = new Bundle(1);
|
||||||
|
b.putString(ARG_KEY, key);
|
||||||
|
fragment.setArguments(b);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindDialogView(View view) {
|
||||||
|
super.onBindDialogView(view);
|
||||||
|
((EditText)view.findViewById(android.R.id.edit)).setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package net.i2p.android.preferences.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
//import android.support.v7.preference.EditTextPreference;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
public class SummaryEditTextPreference extends EditTextPreference {
|
||||||
|
|
||||||
|
public SummaryEditTextPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SummaryEditTextPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getSummary() {
|
||||||
|
String summary = (String) super.getSummary();
|
||||||
|
if (summary == null)
|
||||||
|
summary = "%s";
|
||||||
|
return String.format(summary, getText());
|
||||||
|
}
|
||||||
|
}
|
140
app/src/main/java/net/i2p/android/router/ConsoleContainer.java
Normal file
140
app/src/main/java/net/i2p/android/router/ConsoleContainer.java
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.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 net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu;
|
||||||
|
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu.OnFloatingActionsMenuUpdateListener;
|
||||||
|
import net.i2p.android.router.dialog.AboutDialog;
|
||||||
|
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||||
|
import net.i2p.android.router.log.LogActivity;
|
||||||
|
import net.i2p.android.router.netdb.NetDbActivity;
|
||||||
|
import net.i2p.android.router.stats.PeersActivity;
|
||||||
|
import net.i2p.android.router.stats.RateGraphActivity;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
|
||||||
|
public class ConsoleContainer extends Fragment {
|
||||||
|
MainFragment mMainFragment = null;
|
||||||
|
FloatingActionsMenu mConsoleMenu;
|
||||||
|
|
||||||
|
@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.container_console, container, false);
|
||||||
|
// Start with the home view
|
||||||
|
if (savedInstanceState == null && getChildFragmentManager().findFragmentById(R.id.main_fragment) == null) {
|
||||||
|
mMainFragment = new MainFragment();
|
||||||
|
mMainFragment.setArguments(getActivity().getIntent().getExtras());
|
||||||
|
getChildFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.main_fragment, mMainFragment).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mConsoleMenu = (FloatingActionsMenu) v.findViewById(R.id.console_action_menu);
|
||||||
|
// update visibility based on router state
|
||||||
|
mConsoleMenu.setOnFloatingActionsMenuUpdateListener(new OnFloatingActionsMenuUpdateListener() {
|
||||||
|
public void onMenuExpanded() {
|
||||||
|
// this is called after the animation starts, sadly
|
||||||
|
setMenuVisibility();
|
||||||
|
}
|
||||||
|
public void onMenuCollapsed() {
|
||||||
|
// call it here too so the expand animation isn't glitchy as often
|
||||||
|
setMenuVisibility();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mConsoleMenu.findViewById(R.id.action_news).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent news = new Intent(getActivity(), NewsActivity.class);
|
||||||
|
startActivity(news);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mConsoleMenu.findViewById(R.id.action_logs).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent log = new Intent(getActivity(), LogActivity.class);
|
||||||
|
startActivity(log);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mConsoleMenu.findViewById(R.id.action_graphs).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent graphs = new Intent(getActivity(), RateGraphActivity.class);
|
||||||
|
startActivity(graphs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// mConsoleMenu.findViewById(R.id.action_peers).setOnClickListener(new View.OnClickListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onClick(View view) {
|
||||||
|
// Intent peers = new Intent(getActivity(), PeersActivity.class);
|
||||||
|
// startActivity(peers);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
mConsoleMenu.findViewById(R.id.action_netdb).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent netdb = new Intent(getActivity(), NetDbActivity.class);
|
||||||
|
startActivity(netdb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setMenuVisibility();
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.activity_main_actions, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMenuVisibility() {
|
||||||
|
boolean routerRunning = Util.getRouterContext() != null;
|
||||||
|
mConsoleMenu.findViewById(R.id.action_logs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
|
||||||
|
mConsoleMenu.findViewById(R.id.action_graphs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
if (getActivity() != null) {
|
||||||
|
boolean advanced = PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||||
|
.getBoolean("i2pandroid.main.showStats", false);
|
||||||
|
// mConsoleMenu.findViewById(R.id.action_peers).setVisibility(
|
||||||
|
// advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||||
|
mConsoleMenu.findViewById(R.id.action_netdb).setVisibility(
|
||||||
|
advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_about:
|
||||||
|
AboutDialog dialog = new AboutDialog();
|
||||||
|
dialog.show(getFragmentManager(), "about");
|
||||||
|
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(getFragmentManager(), "release_notes");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,479 +0,0 @@
|
|||||||
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 String _myDir;
|
|
||||||
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);
|
|
||||||
_myDir = getFilesDir().getAbsolutePath();
|
|
||||||
|
|
||||||
// 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<String>(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 {
|
||||||
|
String ANDROID_PREF_PREFIX = "i2pandroid.";
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
package net.i2p.android.router;
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
//import android.support.v4.app.Fragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
import net.i2p.router.CommSystemFacade;
|
import net.i2p.router.CommSystemFacade;
|
||||||
import net.i2p.router.NetworkDatabaseFacade;
|
import net.i2p.router.NetworkDatabaseFacade;
|
||||||
import net.i2p.router.Router;
|
import net.i2p.router.Router;
|
||||||
@ -8,40 +13,12 @@ import net.i2p.router.TunnelManagerFacade;
|
|||||||
import net.i2p.router.peermanager.ProfileOrganizer;
|
import net.i2p.router.peermanager.ProfileOrganizer;
|
||||||
import net.i2p.router.transport.FIFOBandwidthLimiter;
|
import net.i2p.router.transport.FIFOBandwidthLimiter;
|
||||||
import net.i2p.stat.StatManager;
|
import net.i2p.stat.StatManager;
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
|
|
||||||
public class I2PFragmentBase extends Fragment {
|
public class I2PFragmentBase extends Fragment {
|
||||||
private boolean mOnActivityCreated;
|
private boolean mOnActivityCreated;
|
||||||
RouterContextProvider mCallback;
|
|
||||||
|
|
||||||
public static final String PREF_INSTALLED_VERSION = "app.version";
|
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
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
@ -64,7 +41,7 @@ public class I2PFragmentBase extends Fragment {
|
|||||||
public void onRouterConnectionNotReady() {}
|
public void onRouterConnectionNotReady() {}
|
||||||
|
|
||||||
protected RouterContext getRouterContext() {
|
protected RouterContext getRouterContext() {
|
||||||
return mCallback.getRouterContext();
|
return Util.getRouterContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Router getRouter() {
|
protected Router getRouter() {
|
||||||
|
@ -1,272 +0,0 @@
|
|||||||
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 (int i = 0; i < allcertificates.length; i++) {
|
|
||||||
File f = allcertificates[i];
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,19 @@
|
|||||||
package net.i2p.android.router;
|
package net.i2p.android.router;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
import net.i2p.android.I2PActivityBase;
|
||||||
|
|
||||||
public class LicenseActivity extends I2PActivityBase {
|
public class LicenseActivity extends I2PActivityBase {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
setContentView(R.layout.activity_onepane);
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
// Start with the base view
|
// Start with the base view
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
LicenseFragment f = new LicenseFragment();
|
LicenseFragment f = new LicenseFragment();
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package net.i2p.android.router;
|
package net.i2p.android.router;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.ListFragment;
|
//import android.support.v4.app.ListFragment;
|
||||||
|
import androidx.fragment.app.ListFragment;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import net.i2p.android.router.R;
|
|
||||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||||
|
|
||||||
public class LicenseFragment extends ListFragment {
|
public class LicenseFragment extends ListFragment {
|
||||||
@ -28,7 +29,7 @@ public class LicenseFragment extends ListFragment {
|
|||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, names));
|
setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, names));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,316 +0,0 @@
|
|||||||
package net.i2p.android.router;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
|
||||||
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.IRouterState;
|
|
||||||
import net.i2p.android.router.service.IRouterStateCallback;
|
|
||||||
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 net.i2p.router.RouterContext;
|
|
||||||
import net.i2p.util.OrderedProperties;
|
|
||||||
|
|
||||||
public class MainActivity extends I2PActivityBase implements
|
|
||||||
MainFragment.RouterControlListener {
|
|
||||||
IRouterState mStateService = null;
|
|
||||||
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 onResume() {
|
|
||||||
super.onResume();
|
|
||||||
if (mStateService != null) {
|
|
||||||
try {
|
|
||||||
if (mStateService.isStarted()) {
|
|
||||||
// Update for the current state.
|
|
||||||
Util.d("Fetching state.");
|
|
||||||
State curState = mStateService.getState();
|
|
||||||
Message msg = mHandler.obtainMessage(STATE_MSG);
|
|
||||||
msg.getData().putParcelable(MSG_DATA, curState);
|
|
||||||
mHandler.sendMessage(msg);
|
|
||||||
} else {
|
|
||||||
Util.d("StateService not started yet");
|
|
||||||
}
|
|
||||||
} catch (RemoteException e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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() {
|
|
||||||
if (mStateService != null) {
|
|
||||||
try {
|
|
||||||
mStateService.unregisterCallback(mStateCallback);
|
|
||||||
} catch (RemoteException e) {}
|
|
||||||
}
|
|
||||||
if (mTriedBindState)
|
|
||||||
unbindService(mStateConnection);
|
|
||||||
mTriedBindState = false;
|
|
||||||
super.onStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRouterBind(RouterService svc) {
|
|
||||||
if (mStateService == null) {
|
|
||||||
// Try binding for state updates.
|
|
||||||
// Don't auto-create the RouterService.
|
|
||||||
Intent intent = new Intent(IRouterState.class.getName());
|
|
||||||
intent.setClassName(this, "net.i2p.android.router.service.RouterService");
|
|
||||||
mTriedBindState = bindService(intent,
|
|
||||||
mStateConnection, 0);
|
|
||||||
Util.d("Bind to IRouterState successful: " + mTriedBindState);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onRouterBind(svc);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean mTriedBindState;
|
|
||||||
private ServiceConnection mStateConnection = new ServiceConnection() {
|
|
||||||
public void onServiceConnected(ComponentName className,
|
|
||||||
IBinder service) {
|
|
||||||
mStateService = IRouterState.Stub.asInterface(service);
|
|
||||||
Util.d("StateService bound");
|
|
||||||
try {
|
|
||||||
if (mStateService.isStarted()) {
|
|
||||||
mStateService.registerCallback(mStateCallback);
|
|
||||||
// Update for the current state.
|
|
||||||
Util.d("Fetching state.");
|
|
||||||
State curState = mStateService.getState();
|
|
||||||
Message msg = mHandler.obtainMessage(STATE_MSG);
|
|
||||||
msg.getData().putParcelable(MSG_DATA, curState);
|
|
||||||
mHandler.sendMessage(msg);
|
|
||||||
} else {
|
|
||||||
// Unbind
|
|
||||||
unbindService(mStateConnection);
|
|
||||||
mStateService = null;
|
|
||||||
mTriedBindState = false;
|
|
||||||
}
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
// In this case the service has crashed before we could even
|
|
||||||
// do anything with it; we can count on soon being
|
|
||||||
// disconnected (and then reconnected if it can be restarted)
|
|
||||||
// so there is no need to do anything here.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
|
||||||
// This is called when the connection with the service has been
|
|
||||||
// unexpectedly disconnected -- that is, its process crashed.
|
|
||||||
mStateService = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private IRouterStateCallback mStateCallback = new IRouterStateCallback.Stub() {
|
|
||||||
/**
|
|
||||||
* This is called by the RouterService regularly to tell us about
|
|
||||||
* new states. Note that IPC calls are dispatched through a thread
|
|
||||||
* pool running in each process, so the code executing here will
|
|
||||||
* NOT be running in our main thread like most other things -- so,
|
|
||||||
* to update the UI, we need to use a Handler to hop over there.
|
|
||||||
*/
|
|
||||||
public void stateChanged(State newState) throws RemoteException {
|
|
||||||
Message msg = mHandler.obtainMessage(STATE_MSG);
|
|
||||||
msg.getData().putParcelable(MSG_DATA, newState);
|
|
||||||
mHandler.sendMessage(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final int STATE_MSG = 1;
|
|
||||||
private static final String MSG_DATA = "state";
|
|
||||||
|
|
||||||
private Handler mHandler = new Handler() {
|
|
||||||
private State lastRouterState = null;
|
|
||||||
@Override
|
|
||||||
public void handleMessage(Message msg) {
|
|
||||||
switch (msg.what) {
|
|
||||||
case STATE_MSG:
|
|
||||||
State state = msg.getData().getParcelable(MSG_DATA);
|
|
||||||
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) {
|
|
||||||
setResult(RESULT_OK);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
super.handleMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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(_myDir, "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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +1,42 @@
|
|||||||
package net.i2p.android.router;
|
package net.i2p.android.router;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Color;
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.PowerManager;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
//import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import android.util.AndroidRuntimeException;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TableLayout;
|
import android.widget.TableLayout;
|
||||||
import android.widget.TableRow;
|
import android.widget.TableRow;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
import android.widget.ToggleButton;
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import net.i2p.android.router.dialog.ConfigureBrowserDialog;
|
import net.i2p.android.I2PActivityBase;
|
||||||
|
import net.i2p.android.help.BrowserConfigActivity;
|
||||||
import net.i2p.android.router.dialog.FirstStartDialog;
|
import net.i2p.android.router.dialog.FirstStartDialog;
|
||||||
import net.i2p.android.router.dialog.VersionDialog;
|
import net.i2p.android.router.service.RouterService;
|
||||||
import net.i2p.android.router.service.State;
|
import net.i2p.android.router.service.State;
|
||||||
import net.i2p.android.router.util.Connectivity;
|
import net.i2p.android.router.util.Connectivity;
|
||||||
import net.i2p.android.router.util.LongToggleButton;
|
import net.i2p.android.router.util.LongToggleButton;
|
||||||
@ -31,10 +47,8 @@ import net.i2p.data.Hash;
|
|||||||
import net.i2p.data.LeaseSet;
|
import net.i2p.data.LeaseSet;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.router.TunnelPoolSettings;
|
import net.i2p.router.TunnelPoolSettings;
|
||||||
import net.i2p.util.Translate;
|
|
||||||
|
|
||||||
import java.text.Collator;
|
import java.text.Collator;
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@ -46,9 +60,24 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
private Runnable _updater;
|
private Runnable _updater;
|
||||||
private Runnable _oneShotUpdate;
|
private Runnable _oneShotUpdate;
|
||||||
private String _savedStatus;
|
private String _savedStatus;
|
||||||
private boolean _keep = true;
|
|
||||||
private boolean _startPressed = false;
|
private ImageView mConsoleLights;
|
||||||
|
private LongToggleButton mOnOffButton;
|
||||||
|
private LinearLayout vGracefulButtons;
|
||||||
|
private ScrollView mScrollView;
|
||||||
|
private View vStatusContainer;
|
||||||
|
private ImageView vNetStatusLevel;
|
||||||
|
private TextView vNetStatusText;
|
||||||
|
private View vNonNetStatus;
|
||||||
|
private TextView vUptime;
|
||||||
|
private TextView vActive;
|
||||||
|
private TextView vKnown;
|
||||||
|
private TableLayout vTunnels;
|
||||||
|
private LinearLayout vAdvStatus;
|
||||||
|
private TextView vAdvStatusText;
|
||||||
|
|
||||||
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
|
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
|
||||||
|
private static final String PREF_CONFIGURE_BATTERY = "app.dialog.configureBattery";
|
||||||
private static final String PREF_FIRST_START = "app.router.firstStart";
|
private static final String PREF_FIRST_START = "app.router.firstStart";
|
||||||
private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats";
|
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_INSTALL = "i2p.newInstall";
|
||||||
@ -57,10 +86,28 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
|
|
||||||
// Container Activity must implement this interface
|
// Container Activity must implement this interface
|
||||||
public interface RouterControlListener {
|
public interface RouterControlListener {
|
||||||
public boolean shouldShowOnOff();
|
boolean shouldShowOnOff();
|
||||||
public boolean shouldBeOn();
|
|
||||||
public void onStartRouterClicked();
|
boolean shouldBeOn();
|
||||||
public boolean onStopRouterClicked();
|
|
||||||
|
void onStartRouterClicked();
|
||||||
|
|
||||||
|
boolean onStopRouterClicked();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.19
|
||||||
|
*/
|
||||||
|
boolean isGracefulShutdownInProgress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.19
|
||||||
|
*/
|
||||||
|
boolean onGracefulShutdownClicked();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.19
|
||||||
|
*/
|
||||||
|
boolean onCancelGracefulShutdownClicked();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -85,15 +132,14 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
// Init stuff here so settings work.
|
// Init stuff here so settings work.
|
||||||
if(savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
|
lastRouterState = savedInstanceState.getParcelable("lastState");
|
||||||
String saved = savedInstanceState.getString("status");
|
String saved = savedInstanceState.getString("status");
|
||||||
if(saved != null) {
|
if (saved != null) {
|
||||||
_savedStatus = saved;
|
_savedStatus = saved;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_keep = true;
|
|
||||||
|
|
||||||
_handler = new Handler();
|
_handler = new Handler();
|
||||||
_updater = new Updater();
|
_updater = new Updater();
|
||||||
_oneShotUpdate = new OneShotUpdate();
|
_oneShotUpdate = new OneShotUpdate();
|
||||||
@ -104,24 +150,54 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View v = inflater.inflate(R.layout.fragment_main, container, false);
|
View v = inflater.inflate(R.layout.fragment_main, container, false);
|
||||||
|
|
||||||
final ImageView lightImage = (ImageView) v.findViewById(R.id.main_lights);
|
mConsoleLights = (ImageView) v.findViewById(R.id.console_lights);
|
||||||
lightImage.setImageResource(R.drawable.routerlogo_0);
|
mOnOffButton = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
|
||||||
|
vGracefulButtons = (LinearLayout) v.findViewById(R.id.router_graceful_buttons);
|
||||||
|
mScrollView = (ScrollView) v.findViewById(R.id.main_scrollview);
|
||||||
|
vStatusContainer = v.findViewById(R.id.status_container);
|
||||||
|
vNetStatusLevel = (ImageView) v.findViewById(R.id.console_net_status_level);
|
||||||
|
vNetStatusText = (TextView) v.findViewById(R.id.console_net_status_text);
|
||||||
|
vNonNetStatus = v.findViewById(R.id.console_non_net_status_container);
|
||||||
|
vUptime = (TextView) v.findViewById(R.id.console_uptime);
|
||||||
|
vActive = (TextView) v.findViewById(R.id.console_active);
|
||||||
|
vKnown = (TextView) v.findViewById(R.id.console_known);
|
||||||
|
vTunnels = (TableLayout) v.findViewById(R.id.main_tunnels);
|
||||||
|
vAdvStatus = (LinearLayout) v.findViewById(R.id.console_advanced_status);
|
||||||
|
vAdvStatusText = (TextView) v.findViewById(R.id.console_advanced_status_text);
|
||||||
|
|
||||||
LongToggleButton b = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
|
updateState(lastRouterState);
|
||||||
b.setOnLongClickListener(new View.OnLongClickListener() {
|
|
||||||
|
mOnOffButton.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
|
||||||
public boolean onLongClick(View view) {
|
public boolean onLongClick(View view) {
|
||||||
boolean on = ((ToggleButton) view).isChecked();
|
boolean on = ((ToggleButton) view).isChecked();
|
||||||
if (on) {
|
if (on) {
|
||||||
_startPressed = true;
|
|
||||||
mCallback.onStartRouterClicked();
|
mCallback.onStartRouterClicked();
|
||||||
updateOneShot();
|
updateOneShot();
|
||||||
checkFirstStart();
|
checkFirstStart();
|
||||||
} else {
|
} else if (mCallback.onGracefulShutdownClicked())
|
||||||
if(mCallback.onStopRouterClicked()) {
|
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();
|
updateOneShot();
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -134,19 +210,46 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
super.onStart();
|
super.onStart();
|
||||||
_handler.removeCallbacks(_updater);
|
_handler.removeCallbacks(_updater);
|
||||||
_handler.removeCallbacks(_oneShotUpdate);
|
_handler.removeCallbacks(_oneShotUpdate);
|
||||||
if(_savedStatus != null) {
|
if (_savedStatus != null) {
|
||||||
TextView tv = (TextView) getActivity().findViewById(R.id.main_status_text);
|
TextView tv = (TextView) getActivity().findViewById(R.id.console_advanced_status_text);
|
||||||
tv.setText(_savedStatus);
|
tv.setText(_savedStatus);
|
||||||
}
|
}
|
||||||
checkDialog();
|
checkDialog();
|
||||||
_handler.postDelayed(_updater, 100);
|
_handler.postDelayed(_updater, 100);
|
||||||
|
|
||||||
|
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getActivity());
|
||||||
|
|
||||||
|
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;
|
||||||
|
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) {
|
||||||
|
updateState(state);
|
||||||
|
// If we have stopped, clear the status info immediately
|
||||||
|
if (Util.isStopped(state)) {
|
||||||
|
updateOneShot();
|
||||||
|
}
|
||||||
|
lastRouterState = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
_handler.removeCallbacks(_updater);
|
_handler.removeCallbacks(_updater);
|
||||||
_handler.removeCallbacks(_oneShotUpdate);
|
_handler.removeCallbacks(_oneShotUpdate);
|
||||||
|
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onStateChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -157,9 +260,10 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
if(_savedStatus != null) {
|
if (lastRouterState != null)
|
||||||
|
outState.putParcelable("lastState", lastRouterState);
|
||||||
|
if (_savedStatus != null)
|
||||||
outState.putString("status", _savedStatus);
|
outState.putString("status", _savedStatus);
|
||||||
}
|
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +275,12 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
updateVisibility();
|
updateVisibility();
|
||||||
updateStatus();
|
try {
|
||||||
|
updateStatus();
|
||||||
|
} catch (NullPointerException npe) {
|
||||||
|
// RouterContext wasn't quite ready
|
||||||
|
Util.w("Status was updated before RouterContext was ready", npe);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,10 +289,16 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
private int counter;
|
private int counter;
|
||||||
private final int delay = 1000;
|
private final int delay = 1000;
|
||||||
private final int toloop = delay / 500;
|
private final int toloop = delay / 500;
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
updateVisibility();
|
updateVisibility();
|
||||||
if(counter++ % toloop == 0) {
|
if (counter++ % toloop == 0) {
|
||||||
updateStatus();
|
try {
|
||||||
|
updateStatus();
|
||||||
|
} catch (NullPointerException npe) {
|
||||||
|
// RouterContext wasn't quite ready
|
||||||
|
Util.w("Status was updated before RouterContext was ready", npe);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//_handler.postDelayed(this, 2500);
|
//_handler.postDelayed(this, 2500);
|
||||||
_handler.postDelayed(this, delay);
|
_handler.postDelayed(this, delay);
|
||||||
@ -192,101 +307,94 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
|
|
||||||
private void updateVisibility() {
|
private void updateVisibility() {
|
||||||
boolean showOnOff = mCallback.shouldShowOnOff();
|
boolean showOnOff = mCallback.shouldShowOnOff();
|
||||||
ToggleButton b = (ToggleButton) getActivity().findViewById(R.id.router_onoff_button);
|
mOnOffButton.setVisibility(showOnOff ? View.VISIBLE : View.GONE);
|
||||||
b.setVisibility(showOnOff ? View.VISIBLE : View.INVISIBLE);
|
|
||||||
|
|
||||||
boolean isOn = mCallback.shouldBeOn();
|
boolean isOn = mCallback.shouldBeOn();
|
||||||
b.setChecked(isOn);
|
mOnOffButton.setChecked(isOn);
|
||||||
|
|
||||||
if (showOnOff && !isOn) {
|
boolean isGraceful = mCallback.isGracefulShutdownInProgress();
|
||||||
// Sometimes the final state message from the RouterService
|
vGracefulButtons.setVisibility(isGraceful ? View.VISIBLE : View.GONE);
|
||||||
// is not received. Ensure that the state image is correct.
|
if (isOn && isGraceful) {
|
||||||
// TODO: Fix the race between RouterService shutdown and
|
RouterContext ctx = getRouterContext();
|
||||||
// IRouterState unbinding.
|
if (ctx != null) {
|
||||||
updateState(State.INIT);
|
TextView tv = (TextView) vGracefulButtons.findViewById(R.id.router_graceful_status);
|
||||||
}
|
long ms = ctx.router().getShutdownTimeRemaining();
|
||||||
}
|
if (ms > 1000) {
|
||||||
|
tv.setText(getActivity().getResources().getString(R.string.button_router_graceful,
|
||||||
public boolean onBackPressed() {
|
DataHelper.formatDuration(ms)));
|
||||||
RouterContext ctx = getRouterContext();
|
} else {
|
||||||
// RouterService svc = _routerService; Which is better to use?!
|
tv.setText(getActivity().getString(R.string.notification_status_stopping));
|
||||||
_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);
|
* Changes the logo based on the state.
|
||||||
|
*/
|
||||||
|
private void updateState(State newState) {
|
||||||
if (newState == State.INIT ||
|
if (newState == State.INIT ||
|
||||||
newState == State.STOPPED ||
|
newState == State.STOPPED ||
|
||||||
newState == State.MANUAL_STOPPED ||
|
newState == State.MANUAL_STOPPED ||
|
||||||
newState == State.MANUAL_QUITTED ||
|
newState == State.MANUAL_QUITTED ||
|
||||||
newState == State.NETWORK_STOPPED) {
|
newState == State.NETWORK_STOPPED) {
|
||||||
lightImage.setImageResource(R.drawable.routerlogo_0);
|
mConsoleLights.setImageResource(R.drawable.routerlogo_0);
|
||||||
} else if (newState == State.STARTING ||
|
} else if (newState == State.STARTING ||
|
||||||
newState == State.STOPPING ||
|
newState == State.STOPPING ||
|
||||||
newState == State.MANUAL_STOPPING ||
|
newState == State.MANUAL_STOPPING ||
|
||||||
newState == State.MANUAL_QUITTING ||
|
newState == State.MANUAL_QUITTING ||
|
||||||
newState == State.NETWORK_STOPPING) {
|
newState == State.NETWORK_STOPPING) {
|
||||||
lightImage.setImageResource(R.drawable.routerlogo_1);
|
mConsoleLights.setImageResource(R.drawable.routerlogo_1);
|
||||||
} else if (newState == State.RUNNING) {
|
} else if (newState == State.RUNNING ||
|
||||||
lightImage.setImageResource(R.drawable.routerlogo_2);
|
newState == State.GRACEFUL_SHUTDOWN) {
|
||||||
|
mConsoleLights.setImageResource(R.drawable.routerlogo_2);
|
||||||
} else if (newState == State.ACTIVE) {
|
} else if (newState == State.ACTIVE) {
|
||||||
lightImage.setImageResource(R.drawable.routerlogo_3);
|
mConsoleLights.setImageResource(R.drawable.routerlogo_3);
|
||||||
} else if (newState == State.WAITING) {
|
} else if (newState == State.WAITING) {
|
||||||
lightImage.setImageResource(R.drawable.routerlogo_4);
|
mConsoleLights.setImageResource(R.drawable.routerlogo_4);
|
||||||
} // Ignore unknown states.
|
} // Ignore unknown states.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStatus() {
|
private void updateStatus() {
|
||||||
RouterContext ctx = getRouterContext();
|
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())) {
|
if (!Connectivity.isConnected(getActivity())) {
|
||||||
// Manually set state, RouterService won't be running
|
// Manually set state, RouterService won't be running
|
||||||
updateState(State.WAITING);
|
updateState(State.WAITING);
|
||||||
vStatusText.setText("No Internet connection is available");
|
vNetStatusText.setText(R.string.no_internet);
|
||||||
vStatus.setVisibility(View.VISIBLE);
|
vStatusContainer.setVisibility(View.VISIBLE);
|
||||||
sv.setVisibility(View.VISIBLE);
|
vNonNetStatus.setVisibility(View.GONE);
|
||||||
} else if(ctx != null) {
|
} else if (lastRouterState != null &&
|
||||||
if(_startPressed) {
|
!Util.isStopping(lastRouterState) &&
|
||||||
_startPressed = false;
|
!Util.isStopped(lastRouterState) &&
|
||||||
|
ctx != null) {
|
||||||
|
Util.NetStatus netStatus = Util.getNetStatus(getActivity(), ctx);
|
||||||
|
switch (netStatus.level) {
|
||||||
|
case ERROR:
|
||||||
|
vNetStatusLevel.setImageDrawable(getResources().getDrawable(R.drawable.ic_error_red_24dp));
|
||||||
|
vNetStatusLevel.setVisibility(View.VISIBLE);
|
||||||
|
break;
|
||||||
|
case WARN:
|
||||||
|
vNetStatusLevel.setImageDrawable(getResources().getDrawable(R.drawable.ic_warning_amber_24dp));
|
||||||
|
vNetStatusLevel.setVisibility(View.VISIBLE);
|
||||||
|
break;
|
||||||
|
case INFO:
|
||||||
|
default:
|
||||||
|
vNetStatusLevel.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
vNetStatusText.setText(getString(R.string.settings_label_network) + ": " + netStatus.status);
|
||||||
|
|
||||||
|
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
|
||||||
|
int active = ctx.commSystem().countActivePeers();
|
||||||
|
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
|
||||||
|
vUptime.setText(uptime);
|
||||||
|
vActive.setText(Integer.toString(active));
|
||||||
|
vKnown.setText(Integer.toString(known));
|
||||||
|
|
||||||
// Load running tunnels
|
// Load running tunnels
|
||||||
loadDestinations(ctx);
|
loadDestinations(ctx);
|
||||||
|
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(PREF_SHOW_STATS, false)) {
|
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 inEx = ctx.tunnelManager().getFreeTunnelCount();
|
||||||
int outEx = ctx.tunnelManager().getOutboundTunnelCount();
|
int outEx = ctx.tunnelManager().getOutboundTunnelCount();
|
||||||
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
|
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
|
||||||
@ -295,80 +403,54 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
double dLag = ctx.statManager().getRate("jobQueue.jobLag").getRate(60000).getAverageValue();
|
double dLag = ctx.statManager().getRate("jobQueue.jobLag").getRate(60000).getAverageValue();
|
||||||
String jobLag = DataHelper.formatDuration((long) dLag);
|
String jobLag = DataHelper.formatDuration((long) dLag);
|
||||||
String msgDelay = DataHelper.formatDuration(ctx.throttle().getMessageDelay());
|
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();
|
String tunnelStatus = ctx.throttle().getTunnelStatus();
|
||||||
//ctx.commSystem().getReachabilityStatus();
|
//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 =
|
String status =
|
||||||
"Network: " + netstatus
|
getString(R.string.notification_status_expl, inEx, outEx) + '\n' +
|
||||||
+ "\nPeers active/known: " + active + " / " + known
|
getString(R.string.notification_status_client, inCl, outCl);
|
||||||
+ "\nExploratory Tunnels in/out: " + inEx + " / " + outEx
|
|
||||||
+ "\nClient Tunnels in/out: " + inCl + " / " + outCl;
|
|
||||||
|
|
||||||
|
|
||||||
// Need to see if we have the participation option set to on.
|
// 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?
|
// 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.
|
// It would be easier if we had a number to test status.
|
||||||
String participate = "\nParticipation: " + tunnelStatus +" (" + part + ")";
|
String participate = '\n' + getString(R.string.settings_label_hiddenMode) + ": " + tunnelStatus + " (" + part + ")";
|
||||||
|
|
||||||
String details =
|
String details =
|
||||||
"\nBandwidth in/out: " + fmt.format(inBW) + " / " + fmt.format(outBW) + " KBps"
|
'\n' + getString(R.string.stats_memory) + ": " +
|
||||||
+ "\nData usage in/out: " + kBfmt.format(kBytesIn) + " / " + kBfmt.format(kBytesOut) + " KB"
|
DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) +
|
||||||
+ "\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
|
"B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B' +
|
||||||
+ "B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B'
|
'\n' + getString(R.string.stats_lag) + ": " + jobLag +
|
||||||
+ "\nJob Lag: " + jobLag
|
'\n' + getString(R.string.stats_delay) + ": " + msgDelay;
|
||||||
+ "\nMsg Delay: " + msgDelay
|
|
||||||
+ "\nUptime: " + uptime;
|
|
||||||
|
|
||||||
_savedStatus = status + participate + details;
|
_savedStatus = status + participate + details;
|
||||||
vStatusText.setText(_savedStatus);
|
vAdvStatusText.setText(_savedStatus);
|
||||||
vStatus.setVisibility(View.VISIBLE);
|
vAdvStatus.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
vStatus.setVisibility(View.GONE);
|
vAdvStatus.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
sv.setVisibility(View.VISIBLE);
|
vStatusContainer.setVisibility(View.VISIBLE);
|
||||||
|
vNonNetStatus.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// Usage stats in bottom toolbar
|
||||||
|
|
||||||
|
double inBw = ctx.bandwidthLimiter().getReceiveBps();
|
||||||
|
double outBw = ctx.bandwidthLimiter().getSendBps();
|
||||||
|
double inData = ctx.bandwidthLimiter().getTotalAllocatedInboundBytes();
|
||||||
|
double outData = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes();
|
||||||
|
|
||||||
|
((TextView) getActivity().findViewById(R.id.console_download_stats)).setText(
|
||||||
|
Util.formatSpeed(inBw) + "Bps / " + Util.formatSize(inData) + "B");
|
||||||
|
((TextView) getActivity().findViewById(R.id.console_upload_stats)).setText(
|
||||||
|
Util.formatSpeed(outBw) + "Bps / " + Util.formatSize(outData) + "B");
|
||||||
|
|
||||||
|
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
// network but no router context
|
// network but no router context
|
||||||
vStatusText.setText("Not running");
|
vStatusContainer.setVisibility(View.GONE);
|
||||||
sv.setVisibility(View.INVISIBLE);
|
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.INVISIBLE);
|
||||||
|
updateState(State.STOPPED);
|
||||||
/**
|
/**
|
||||||
* **
|
* **
|
||||||
* RouterService svc = _routerService; String status = "connected? "
|
* RouterService svc = _routerService; String status = "connected? "
|
||||||
@ -382,21 +464,24 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
* "null" : svc.canManualStart()) + "\ncan stop? " + (svc == null ?
|
* "null" : svc.canManualStart()) + "\ncan stop? " + (svc == null ?
|
||||||
* "null" : svc.canManualStop()); tv.setText(status);
|
* "null" : svc.canManualStop()); tv.setText(status);
|
||||||
* tv.setVisibility(View.VISIBLE);
|
* tv.setVisibility(View.VISIBLE);
|
||||||
***
|
***
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on net.i2p.router.web.SummaryHelper.getDestinations()
|
* Based on net.i2p.router.web.SummaryHelper.getDestinations()
|
||||||
|
*
|
||||||
* @param ctx The RouterContext
|
* @param ctx The RouterContext
|
||||||
*/
|
*/
|
||||||
private void loadDestinations(RouterContext ctx) {
|
private void loadDestinations(RouterContext ctx) {
|
||||||
TableLayout dests = (TableLayout) getView().findViewById(R.id.main_tunnels);
|
vTunnels.removeAllViews();
|
||||||
dests.removeAllViews();
|
|
||||||
|
|
||||||
List<Destination> clients = new ArrayList<Destination>(ctx.clientManager().listClients());
|
List<Destination> clients = null;
|
||||||
if (!clients.isEmpty()) {
|
if (ctx.clientManager() != null)
|
||||||
|
clients = new ArrayList<Destination>(ctx.clientManager().listClients());
|
||||||
|
|
||||||
|
if (clients != null && !clients.isEmpty()) {
|
||||||
Collections.sort(clients, new AlphaComparator(ctx));
|
Collections.sort(clients, new AlphaComparator(ctx));
|
||||||
for (Destination client : clients) {
|
for (Destination client : clients) {
|
||||||
String name = getName(ctx, client);
|
String name = getName(ctx, client);
|
||||||
@ -438,25 +523,29 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
type.setBackgroundResource(R.drawable.tunnel_yellow);
|
type.setBackgroundResource(R.drawable.tunnel_yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
dests.addView(dest);
|
vTunnels.addView(dest);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TableRow empty = new TableRow(getActivity());
|
TableRow empty = new TableRow(getActivity());
|
||||||
TextView emptyText = new TextView(getActivity());
|
TextView emptyText = new TextView(getActivity());
|
||||||
emptyText.setText(R.string.no_client_tunnels_running);
|
emptyText.setText(R.string.no_tunnels_running);
|
||||||
empty.addView(emptyText);
|
empty.addView(emptyText);
|
||||||
dests.addView(empty);
|
vTunnels.addView(empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** compare translated nicknames - put "shared clients" first in the sort */
|
private static final String SHARED_CLIENTS = "shared clients";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compare translated nicknames - put "shared clients" first in the sort
|
||||||
|
*/
|
||||||
private class AlphaComparator implements Comparator<Destination> {
|
private class AlphaComparator implements Comparator<Destination> {
|
||||||
private String xsc;
|
private final String xsc;
|
||||||
private RouterContext _ctx;
|
private final RouterContext _ctx;
|
||||||
|
|
||||||
public AlphaComparator(RouterContext ctx) {
|
public AlphaComparator(RouterContext ctx) {
|
||||||
_ctx = ctx;
|
_ctx = ctx;
|
||||||
xsc = _(ctx, "shared clients");
|
xsc = _t(ctx, SHARED_CLIENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int compare(Destination lhs, Destination rhs) {
|
public int compare(Destination lhs, Destination rhs) {
|
||||||
@ -470,34 +559,133 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** translate here so collation works above */
|
/**
|
||||||
|
* translate here so collation works above
|
||||||
|
*/
|
||||||
private String getName(RouterContext ctx, Destination d) {
|
private String getName(RouterContext ctx, Destination d) {
|
||||||
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(d.calculateHash());
|
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(d.calculateHash());
|
||||||
String name = (in != null ? in.getDestinationNickname() : null);
|
String name = (in != null ? in.getDestinationNickname() : null);
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
TunnelPoolSettings out = ctx.tunnelManager().getOutboundSettings(d.calculateHash());
|
TunnelPoolSettings out = ctx.tunnelManager().getOutboundSettings(d.calculateHash());
|
||||||
name = (out != null ? out.getDestinationNickname() : null);
|
name = (out != null ? out.getDestinationNickname() : null);
|
||||||
if (name == null)
|
|
||||||
name = d.calculateHash().toBase64().substring(0,6);
|
|
||||||
else
|
|
||||||
name = _(ctx, name);
|
|
||||||
} else {
|
|
||||||
name = _(ctx, name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name == null)
|
||||||
|
name = d.calculateHash().toBase64().substring(0, 6);
|
||||||
|
else
|
||||||
|
name = _t(ctx, name);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String _(RouterContext ctx, String s) {
|
private String _t(RouterContext ctx, String s) {
|
||||||
return Translate.getString(s, ctx, "net.i2p.router.web.messages");
|
if (SHARED_CLIENTS.equals(s))
|
||||||
|
return getString(R.string.shared_clients);
|
||||||
|
else
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkDialog() {
|
private void checkDialog() {
|
||||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
final I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||||
boolean configureBrowser = ab.getPref(PREF_CONFIGURE_BROWSER, true);
|
String language = PreferenceManager.getDefaultSharedPreferences(ab).getString(
|
||||||
if (configureBrowser) {
|
getString(R.string.PREF_LANGUAGE), null
|
||||||
ConfigureBrowserDialog dialog = new ConfigureBrowserDialog();
|
);
|
||||||
dialog.show(getActivity().getSupportFragmentManager(), "configurebrowser");
|
if (language == null) {
|
||||||
ab.setPref(PREF_CONFIGURE_BROWSER, false);
|
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||||
|
// avoid ISE caused by fragment detachment ticket #2631
|
||||||
|
final String languages[] = getResources().getStringArray(R.array.languages);
|
||||||
|
b.setTitle(R.string.choose_language)
|
||||||
|
.setItems(R.array.language_names, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
// Save the language choice
|
||||||
|
String language = languages[which];
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||||
|
.edit()
|
||||||
|
.putString(getString(R.string.PREF_LANGUAGE), language)
|
||||||
|
.apply();
|
||||||
|
// Close the dialog
|
||||||
|
dialog.dismiss();
|
||||||
|
// Broadcast the change to RouterService just in case the router is running
|
||||||
|
Intent intent = new Intent(RouterService.LOCAL_BROADCAST_LOCALE_CHANGED);
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
|
||||||
|
// Update the parent
|
||||||
|
ab.notifyLocaleChanged();
|
||||||
|
// Run checkDialog() again to show the next dialog
|
||||||
|
// (if the change doesn't restart the Activity)
|
||||||
|
checkDialog();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setCancelable(false)
|
||||||
|
.show();
|
||||||
|
} else if (ab.getPref(PREF_CONFIGURE_BROWSER, true)) {
|
||||||
|
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 dialog, int i) {
|
||||||
|
dialog.dismiss();
|
||||||
|
ab.setPref(PREF_CONFIGURE_BROWSER, false);
|
||||||
|
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
|
||||||
|
startActivity(hi);
|
||||||
|
checkDialog();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int i) {
|
||||||
|
dialog.dismiss();
|
||||||
|
ab.setPref(PREF_CONFIGURE_BROWSER, false);
|
||||||
|
checkDialog();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
} else if (ab.getPref(PREF_CONFIGURE_BATTERY, true)) {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||||
|
// only for Marshmallow and newer versions
|
||||||
|
final Intent intent = new Intent();
|
||||||
|
final Context mContext = ab.getApplicationContext();
|
||||||
|
String packageName = mContext.getPackageName();
|
||||||
|
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
|
||||||
|
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||||
|
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||||
|
b.setTitle(R.string.configure_no_doze_title);
|
||||||
|
b.setMessage(R.string.configure_no_doze);
|
||||||
|
b.setCancelable(false);
|
||||||
|
b.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int i) {
|
||||||
|
String packageName = mContext.getPackageName();
|
||||||
|
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||||
|
dialog.dismiss();
|
||||||
|
// Simply do not re-attempt a battery optimization after the first time,
|
||||||
|
// even if an error occurs. http://trac.i2p2.i2p/ticket/2783
|
||||||
|
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
|
intent.setData(Uri.parse("package:" + packageName));
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
try {
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException activityNotFound) {
|
||||||
|
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||||
|
} catch (AndroidRuntimeException activityNotFound) {
|
||||||
|
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
b.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int i) {
|
||||||
|
dialog.cancel();
|
||||||
|
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
b.show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*VersionDialog dialog = new VersionDialog();
|
/*VersionDialog dialog = new VersionDialog();
|
||||||
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");
|
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");
|
||||||
@ -522,9 +710,11 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
private void checkFirstStart() {
|
private void checkFirstStart() {
|
||||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||||
boolean firstStart = ab.getPref(PREF_FIRST_START, true);
|
boolean firstStart = ab.getPref(PREF_FIRST_START, true);
|
||||||
if (firstStart) {
|
// Check ab.isFinishing() because DialogFragment.show() will throw IllegalStateException if
|
||||||
|
// called after ab.onSaveInstanceState().
|
||||||
|
if (firstStart && !ab.isFinishing()) {
|
||||||
FirstStartDialog dialog = new FirstStartDialog();
|
FirstStartDialog dialog = new FirstStartDialog();
|
||||||
dialog.show(getActivity().getSupportFragmentManager(), "firststart");
|
dialog.show(ab.getSupportFragmentManager(), "firststart");
|
||||||
ab.setPref(PREF_FIRST_START, false);
|
ab.setPref(PREF_FIRST_START, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
package net.i2p.android.router;
|
package net.i2p.android.router;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
import net.i2p.android.I2PActivityBase;
|
||||||
|
|
||||||
public class NewsActivity extends I2PActivityBase {
|
public class NewsActivity extends I2PActivityBase {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
setContentView(R.layout.activity_onepane);
|
||||||
|
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
// Start with the base view
|
// Start with the base view
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
NewsFragment f = new NewsFragment();
|
NewsFragment f = new NewsFragment();
|
||||||
|
@ -13,6 +13,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import net.i2p.android.apps.NewsFetcher;
|
import net.i2p.android.apps.NewsFetcher;
|
||||||
import net.i2p.android.router.util.Util;
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -33,12 +34,16 @@ public class NewsFragment extends I2PFragmentBase {
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
NewsFetcher nf = NewsFetcher.getInstance();
|
RouterContext ctx = getRouterContext();
|
||||||
if (nf != null) {
|
if (ctx != null) {
|
||||||
// Always update the status
|
NewsFetcher nf = (NewsFetcher) ctx.clientAppManager().getRegisteredApp(NewsFetcher.APP_NAME);
|
||||||
TextView tv = (TextView) getActivity().findViewById(R.id.news_status);
|
if (nf != null) {
|
||||||
tv.setText(nf.status().replace(" ", " "));
|
// Always update the status
|
||||||
tv.setVisibility(View.VISIBLE);
|
// This is the news last updated/checked text at the bottom
|
||||||
|
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
|
// Only update the content if we need to
|
||||||
|
@ -1,261 +1,185 @@
|
|||||||
package net.i2p.android.router;
|
package net.i2p.android.router;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.content.Intent;
|
||||||
import android.content.Context;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.CheckBoxPreference;
|
//import android.support.v4.app.Fragment;
|
||||||
import android.preference.PreferenceActivity;
|
import androidx.fragment.app.Fragment;
|
||||||
import android.preference.PreferenceCategory;
|
//import android.support.v4.app.FragmentManager;
|
||||||
import android.preference.PreferenceFragment;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import android.preference.PreferenceScreen;
|
//import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.support.v7.widget.Toolbar;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
import android.view.LayoutInflater;
|
//import android.support.v7.app.AppCompatActivity;
|
||||||
import android.view.View;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import android.view.ViewGroup;
|
//import android.support.v7.preference.Preference;
|
||||||
import android.widget.Toast;
|
import androidx.preference.Preference;
|
||||||
|
//import android.support.v7.preference.PreferenceFragmentCompat;
|
||||||
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.android.I2PActivity;
|
||||||
import net.i2p.android.router.service.StatSummarizer;
|
import net.i2p.android.preferences.AdvancedPreferenceFragment;
|
||||||
import net.i2p.android.router.util.Util;
|
import net.i2p.android.preferences.AppearancePreferenceFragment;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.android.preferences.GraphsPreferenceFragment;
|
||||||
import net.i2p.stat.FrequencyStat;
|
import net.i2p.android.preferences.LoggingPreferenceFragment;
|
||||||
import net.i2p.stat.Rate;
|
import net.i2p.android.preferences.NetworkPreferenceFragment;
|
||||||
import net.i2p.stat.RateStat;
|
import net.i2p.android.router.addressbook.AddressbookSettingsActivity;
|
||||||
import net.i2p.stat.StatManager;
|
import net.i2p.android.router.service.RouterService;
|
||||||
import net.i2p.util.LogManager;
|
import net.i2p.android.util.LocaleManager;
|
||||||
|
|
||||||
import java.util.List;
|
public class SettingsActivity extends AppCompatActivity implements
|
||||||
import java.util.Map;
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
import java.util.Properties;
|
public static final String PREFERENCE_CATEGORY = "preference_category";
|
||||||
import java.util.SortedSet;
|
public static final String PREFERENCE_CATEGORY_NETWORK = "preference_category_network";
|
||||||
|
public static final String PREFERENCE_CATEGORY_GRAPHS = "preference_category_graphs";
|
||||||
|
public static final String PREFERENCE_CATEGORY_LOGGING = "preference_category_logging";
|
||||||
|
public static final String PREFERENCE_CATEGORY_ADDRESSBOOK = "preference_category_addressbook";
|
||||||
|
public static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
|
||||||
|
public static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
|
||||||
|
|
||||||
public class SettingsActivity extends PreferenceActivity {
|
private final LocaleManager localeManager = new LocaleManager();
|
||||||
// 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
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
localeManager.onCreate(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_single_fragment);
|
||||||
|
|
||||||
String action = getIntent().getAction();
|
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||||
if (action != null) {
|
setSupportActionBar(toolbar);
|
||||||
if (ACTION_PREFS_NET.equals(action)) {
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
addPreferencesFromResource(R.xml.settings_net);
|
|
||||||
} else if (ACTION_PREFS_GRAPHS.equals(action)){
|
|
||||||
addPreferencesFromResource(R.xml.settings_graphs);
|
|
||||||
setupGraphSettings(this, getPreferenceScreen(), Util.getRouterContext());
|
|
||||||
} else if (ACTION_PREFS_LOGGING.equals(action)) {
|
|
||||||
addPreferencesFromResource(R.xml.settings_logging);
|
|
||||||
setupLoggingSettings(this, getPreferenceScreen(), Util.getRouterContext());
|
|
||||||
} else if (ACTION_PREFS_ADVANCED.equals(action)) {
|
|
||||||
addPreferencesFromResource(R.xml.settings_advanced);
|
|
||||||
}
|
|
||||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
|
||||||
// Load the legacy preferences headers
|
|
||||||
addPreferencesFromResource(R.xml.settings_headers_legacy);
|
|
||||||
}
|
|
||||||
|
|
||||||
mToolbar.setTitle(getTitle());
|
Fragment fragment;
|
||||||
}
|
String category = getIntent().getStringExtra(PREFERENCE_CATEGORY);
|
||||||
|
if (category != null)
|
||||||
|
fragment = getFragmentForCategory(category);
|
||||||
|
else
|
||||||
|
fragment = new SettingsFragment();
|
||||||
|
|
||||||
protected static void setupGraphSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
|
getSupportFragmentManager().beginTransaction()
|
||||||
if (ctx == null) {
|
.replace(R.id.fragment, fragment)
|
||||||
PreferenceCategory noRouter = new PreferenceCategory(context);
|
.commit();
|
||||||
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);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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
|
@Override
|
||||||
public void setContentView(int layoutResID) {
|
public void onResume() {
|
||||||
ViewGroup contentView = (ViewGroup) LayoutInflater.from(this).inflate(
|
super.onResume();
|
||||||
R.layout.activity_settings,
|
localeManager.onResume(this);
|
||||||
(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
|
@Override
|
||||||
protected void onPause() {
|
public boolean onSupportNavigateUp() {
|
||||||
List<Properties> lProps = Util.getPropertiesFromPreferences(this);
|
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
Properties props = lProps.get(0);
|
if (fragmentManager.getBackStackEntryCount() > 0) {
|
||||||
Properties logSettings = lProps.get(1);
|
fragmentManager.popBackStack();
|
||||||
|
|
||||||
boolean restartRequired = Util.checkAndCorrectRouterConfig(this, props);
|
|
||||||
|
|
||||||
// Apply new config if we are running.
|
|
||||||
RouterContext rCtx = Util.getRouterContext();
|
|
||||||
if (rCtx != null) {
|
|
||||||
rCtx.router().saveConfig(props, null);
|
|
||||||
|
|
||||||
// Merge in new log settings
|
|
||||||
saveLoggingChanges(rCtx, logSettings);
|
|
||||||
} else {
|
} else {
|
||||||
// Merge in new config settings, write the file.
|
Intent intent = new Intent(this, I2PActivity.class);
|
||||||
Util.mergeResourceToFile(this, Util.getFileDir(this), "router.config", R.raw.router_config, props);
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
startActivity(intent);
|
||||||
// Merge in new log settings
|
finish();
|
||||||
saveLoggingChanges(I2PAppContext.getGlobalContext(), logSettings);
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
// 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) {
|
@Override
|
||||||
boolean shouldSave = false;
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
if (key.equals(getResources().getString(R.string.PREF_LANGUAGE))) {
|
||||||
for (Object key : logSettings.keySet()) {
|
localeManager.onResume(this);
|
||||||
if ("logger.defaultLevel".equals(key)) {
|
Intent intent = new Intent(RouterService.LOCAL_BROADCAST_LOCALE_CHANGED);
|
||||||
String defaultLevel = (String) logSettings.get(key);
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||||
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 PreferenceFragmentCompat {
|
||||||
public static class SettingsFragment extends PreferenceFragment {
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||||
super.onCreate(savedInstanceState);
|
migrateOldSettings();
|
||||||
|
|
||||||
String settings = getArguments().getString("settings");
|
addPreferencesFromResource(R.xml.settings);
|
||||||
if ("net".equals(settings)) {
|
|
||||||
addPreferencesFromResource(R.xml.settings_net);
|
this.findPreference(PREFERENCE_CATEGORY_NETWORK)
|
||||||
} else if ("graphs".equals(settings)) {
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NETWORK));
|
||||||
addPreferencesFromResource(R.xml.settings_graphs);
|
this.findPreference(PREFERENCE_CATEGORY_GRAPHS)
|
||||||
setupGraphSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_GRAPHS));
|
||||||
} else if ("logging".equals(settings)) {
|
this.findPreference(PREFERENCE_CATEGORY_LOGGING)
|
||||||
addPreferencesFromResource(R.xml.settings_logging);
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_LOGGING));
|
||||||
setupLoggingSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
|
this.findPreference(PREFERENCE_CATEGORY_ADDRESSBOOK)
|
||||||
} else if ("advanced".equals(settings)) {
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADDRESSBOOK));
|
||||||
addPreferencesFromResource(R.xml.settings_advanced);
|
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
|
||||||
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
|
||||||
|
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
|
||||||
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void migrateOldSettings() {
|
||||||
|
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
|
||||||
|
try {
|
||||||
|
prefs.getInt("i2np.bandwidth.inboundKBytesPerSecond", 0);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// Migrate pre-0.9.25 settings
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.remove("i2np.bandwidth.inboundKBytesPerSecond");
|
||||||
|
editor.putInt("i2np.bandwidth.inboundKBytesPerSecond", Integer.parseInt(
|
||||||
|
prefs.getString("i2np.bandwidth.inboundKBytesPerSecond", "100")));
|
||||||
|
editor.remove("i2np.bandwidth.outboundKBytesPerSecond");
|
||||||
|
editor.putInt("i2np.bandwidth.outboundKBytesPerSecond", Integer.parseInt(
|
||||||
|
prefs.getString("i2np.bandwidth.outboundKBytesPerSecond", "100")));
|
||||||
|
editor.remove("i2np.ntcp.maxConnections");
|
||||||
|
editor.putInt("i2np.ntcp.maxConnections", Integer.parseInt(
|
||||||
|
prefs.getString("i2np.ntcp.maxConnections", "32")));
|
||||||
|
editor.remove("i2np.udp.maxConnections");
|
||||||
|
editor.putInt("i2np.udp.maxConnections", Integer.parseInt(
|
||||||
|
prefs.getString("i2np.udp.maxConnections", "32")));
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.menu_settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
public CategoryClickListener(String category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
if (PREFERENCE_CATEGORY_ADDRESSBOOK.equals(category)) {
|
||||||
|
Intent i = new Intent(getActivity(), AddressbookSettingsActivity.class);
|
||||||
|
startActivity(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Fragment fragment = getFragmentForCategory(category);
|
||||||
|
getActivity().getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.fragment, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private static Fragment getFragmentForCategory(String category) {
|
||||||
protected boolean isValidFragment(String fragmentName) {
|
switch (category) {
|
||||||
return SettingsFragment.class.getName().equals(fragmentName);
|
case PREFERENCE_CATEGORY_NETWORK:
|
||||||
|
return new NetworkPreferenceFragment();
|
||||||
|
case PREFERENCE_CATEGORY_GRAPHS:
|
||||||
|
return new GraphsPreferenceFragment();
|
||||||
|
case PREFERENCE_CATEGORY_LOGGING:
|
||||||
|
return new LoggingPreferenceFragment();
|
||||||
|
case PREFERENCE_CATEGORY_APPEARANCE:
|
||||||
|
return new AppearancePreferenceFragment();
|
||||||
|
case PREFERENCE_CATEGORY_ADVANCED:
|
||||||
|
return new AdvancedPreferenceFragment();
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,4 +18,14 @@ public class AddressEntry {
|
|||||||
public Destination getDestination() {
|
public Destination getDestination() {
|
||||||
return mDest;
|
return mDest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See item 8 from Josh Bloch's "Effective Java".
|
||||||
|
*
|
||||||
|
* @return the hashcode of this AddressEntry
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 37 * mHostName.hashCode() + mDest.hashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,145 @@
|
|||||||
package net.i2p.android.router.addressbook;
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
//import android.support.v7.widget.RecyclerView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
public class AddressEntryAdapter extends ArrayAdapter<AddressEntry> {
|
import net.i2p.android.router.R;
|
||||||
private final LayoutInflater mInflater;
|
import net.i2p.android.util.AlphanumericHeaderAdapter;
|
||||||
|
|
||||||
public AddressEntryAdapter(Context context) {
|
import java.util.List;
|
||||||
super(context, R.layout.listitem_text);
|
|
||||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setData(List<AddressEntry> addresses) {
|
public class AddressEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements
|
||||||
clear();
|
AlphanumericHeaderAdapter.SortedAdapter {
|
||||||
if (addresses != null) {
|
private Context mCtx;
|
||||||
for (AddressEntry address : addresses) {
|
private AddressbookFragment.OnAddressSelectedListener mListener;
|
||||||
add(address);
|
private List<AddressEntry> mAddresses;
|
||||||
}
|
|
||||||
|
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
public SimpleViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class AddressViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
public TextView hostName;
|
||||||
|
|
||||||
|
public AddressViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
|
||||||
|
hostName = (TextView) itemView.findViewById(R.id.host_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressEntryAdapter(Context context,
|
||||||
|
AddressbookFragment.OnAddressSelectedListener listener) {
|
||||||
|
super();
|
||||||
|
mCtx = context;
|
||||||
|
mListener = listener;
|
||||||
|
setHasStableIds(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddresses(List<AddressEntry> addresses) {
|
||||||
|
mAddresses = addresses;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressEntry getAddress(int position) {
|
||||||
|
if (mAddresses == null || mAddresses.isEmpty() ||
|
||||||
|
position < 0 || position >= mAddresses.size())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return mAddresses.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public String getSortString(int position) {
|
||||||
View v = mInflater.inflate(R.layout.listitem_text, parent, false);
|
AddressEntry address = getAddress(position);
|
||||||
AddressEntry address = getItem(position);
|
if (address == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
TextView text = (TextView) v.findViewById(R.id.text);
|
return address.getHostName();
|
||||||
text.setText(address.getHostName());
|
}
|
||||||
|
|
||||||
return v;
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (mAddresses == null)
|
||||||
|
return R.string.router_not_running;
|
||||||
|
else if (mAddresses.isEmpty())
|
||||||
|
return R.layout.listitem_empty;
|
||||||
|
else
|
||||||
|
return R.layout.listitem_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new views (invoked by the layout manager)
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
int vt = viewType;
|
||||||
|
if (viewType == R.string.router_not_running)
|
||||||
|
vt = R.layout.listitem_empty;
|
||||||
|
|
||||||
|
View v = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(vt, parent, false);
|
||||||
|
switch (viewType) {
|
||||||
|
case R.layout.listitem_address:
|
||||||
|
return new AddressViewHolder(v);
|
||||||
|
default:
|
||||||
|
return new SimpleViewHolder(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the contents of a view (invoked by the layout manager)
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||||
|
switch (holder.getItemViewType()) {
|
||||||
|
case R.string.router_not_running:
|
||||||
|
((TextView) holder.itemView).setText(
|
||||||
|
mCtx.getString(R.string.router_not_running));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case R.layout.listitem_empty:
|
||||||
|
((TextView) holder.itemView).setText(
|
||||||
|
mCtx.getString(R.string.addressbook_is_empty));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case R.layout.listitem_address:
|
||||||
|
final AddressEntry address = getAddress(position);
|
||||||
|
AddressViewHolder avh = (AddressViewHolder) holder;
|
||||||
|
avh.hostName.setText(address.getHostName());
|
||||||
|
|
||||||
|
avh.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
mListener.onAddressSelected(address.getHostName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the size of the dataset (invoked by the layout manager)
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
if (mAddresses == null || mAddresses.isEmpty())
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return mAddresses.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getItemId(int position) {
|
||||||
|
AddressEntry address = getAddress(position);
|
||||||
|
if (address == null)
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
return address.hashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,46 @@
|
|||||||
package net.i2p.android.router.addressbook;
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
//import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
import androidx.loader.content.AsyncTaskLoader;
|
||||||
|
|
||||||
|
import net.i2p.android.router.util.NamingServiceUtil;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.client.naming.NamingService;
|
||||||
|
import net.i2p.client.naming.NamingServiceListener;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import net.i2p.android.router.util.NamingServiceUtil;
|
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> implements
|
||||||
import net.i2p.android.router.util.Util;
|
NamingServiceListener {
|
||||||
import net.i2p.client.naming.NamingService;
|
|
||||||
import net.i2p.data.Destination;
|
|
||||||
import net.i2p.router.RouterContext;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
|
|
||||||
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
|
|
||||||
private RouterContext mRContext;
|
|
||||||
private String mBook;
|
private String mBook;
|
||||||
private String mFilter;
|
private String mFilter;
|
||||||
private List<AddressEntry> mData;
|
private List<AddressEntry> mData;
|
||||||
|
|
||||||
public AddressEntryLoader(Context context, RouterContext rContext,
|
public AddressEntryLoader(Context context, String book, String filter) {
|
||||||
String book, String filter) {
|
|
||||||
super(context);
|
super(context);
|
||||||
mRContext = rContext;
|
|
||||||
mBook = book;
|
mBook = book;
|
||||||
mFilter = filter;
|
mFilter = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AddressEntry> loadInBackground() {
|
public List<AddressEntry> loadInBackground() {
|
||||||
|
RouterContext routerContext = Util.getRouterContext();
|
||||||
|
if (routerContext == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
// get the names
|
// get the names
|
||||||
NamingService ns = NamingServiceUtil.getNamingService(mRContext, mBook);
|
NamingService ns = NamingServiceUtil.getNamingService(routerContext, mBook);
|
||||||
Util.d("NamingService: " + ns.getName());
|
Util.d("NamingService: " + ns.getName());
|
||||||
// After router shutdown we get nothing... why?
|
// After router shutdown we get nothing... why?
|
||||||
List<AddressEntry> ret = new ArrayList<AddressEntry>();
|
List<AddressEntry> ret = new ArrayList<>();
|
||||||
Map<String, Destination> names = new TreeMap<String, Destination>();
|
Map<String, Destination> names = new TreeMap<>();
|
||||||
|
|
||||||
Properties searchProps = new Properties();
|
Properties searchProps = new Properties();
|
||||||
// Needed for HostsTxtNamingService
|
// Needed for HostsTxtNamingService
|
||||||
@ -84,6 +88,13 @@ public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
|
|||||||
deliverResult(mData);
|
deliverResult(mData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Begin monitoring the underlying data source.
|
||||||
|
RouterContext routerContext = Util.getRouterContext();
|
||||||
|
if (routerContext != null) {
|
||||||
|
NamingService ns = NamingServiceUtil.getNamingService(routerContext, mBook);
|
||||||
|
ns.registerListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
if (takeContentChanged() || mData == null) {
|
if (takeContentChanged() || mData == null) {
|
||||||
// When the observer detects a change, it should call onContentChanged()
|
// When the observer detects a change, it should call onContentChanged()
|
||||||
// on the Loader, which will cause the next call to takeContentChanged()
|
// on the Loader, which will cause the next call to takeContentChanged()
|
||||||
@ -114,6 +125,13 @@ public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
|
|||||||
releaseResources(mData);
|
releaseResources(mData);
|
||||||
mData = null;
|
mData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The Loader is being reset, so we should stop monitoring for changes.
|
||||||
|
RouterContext routerContext = Util.getRouterContext();
|
||||||
|
if (routerContext != null) {
|
||||||
|
NamingService ns = NamingServiceUtil.getNamingService(routerContext, mBook);
|
||||||
|
ns.unregisterListener(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -131,4 +149,26 @@ public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
|
|||||||
// would close it in this method. All resources associated with the Loader
|
// would close it in this method. All resources associated with the Loader
|
||||||
// should be released here.
|
// should be released here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamingServiceListener
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configurationChanged(NamingService ns) {
|
||||||
|
onContentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void entryAdded(NamingService ns, String hostname, Destination dest, Properties options) {
|
||||||
|
onContentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void entryChanged(NamingService ns, String hostname, Destination dest, Properties options) {
|
||||||
|
onContentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void entryRemoved(NamingService ns, String hostname) {
|
||||||
|
onContentChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,140 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,17 @@
|
|||||||
package net.i2p.android.router.addressbook;
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.DialogFragment;
|
import androidx.annotation.NonNull;
|
||||||
|
//import android.support.v4.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||||
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
||||||
|
|
||||||
@ -18,25 +23,51 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DialogFragment onGetFinishWizardDialog() {
|
protected DialogFragment onGetFinishWizardDialog() {
|
||||||
return new DialogFragment() {
|
return FinishWizardDialogFragment.newInstance();
|
||||||
@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) {
|
public void onFinishWizard() {
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
setResult(Activity.RESULT_OK, result);
|
result.putExtra(AddressbookContainer.ADD_WIZARD_DATA, mWizardModel.save());
|
||||||
result.putExtra(AddressbookFragment.ADD_WIZARD_DATA, mWizardModel.save());
|
setResult(Activity.RESULT_OK, result);
|
||||||
dialog.dismiss();
|
finish();
|
||||||
finish();
|
}
|
||||||
}
|
|
||||||
})
|
public static class FinishWizardDialogFragment extends DialogFragment {
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
AddressbookAddWizardActivity mListener;
|
||||||
.create();
|
|
||||||
|
public static DialogFragment newInstance() {
|
||||||
|
return new FinishWizardDialogFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
// Verify that the host fragment implements the callback interface
|
||||||
|
try {
|
||||||
|
// Instantiate the AddressbookAddWizardActivity so we can send events to the host
|
||||||
|
mListener = (AddressbookAddWizardActivity) context;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// The fragment doesn't implement the interface, throw exception
|
||||||
|
throw new ClassCastException(context.toString()
|
||||||
|
+ " must be AddressbookAddWizardActivity");
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
dialog.dismiss();
|
||||||
|
mListener.onFinishWizard();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import android.content.Context;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||||
import net.i2p.android.wizard.model.I2PB64DestinationPage;
|
import net.i2p.android.wizard.model.I2PDestinationPage;
|
||||||
import net.i2p.android.wizard.model.PageList;
|
import net.i2p.android.wizard.model.PageList;
|
||||||
import net.i2p.android.wizard.model.SingleTextFieldPage;
|
import net.i2p.android.wizard.model.SingleTextFieldPage;
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ public class AddressbookAddWizardModel extends AbstractWizardModel {
|
|||||||
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_name))
|
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_name))
|
||||||
.setRequired(true),
|
.setRequired(true),
|
||||||
|
|
||||||
new I2PB64DestinationPage(this, res.getString(R.string.addressbook_add_wizard_k_destination))
|
new I2PDestinationPage(this, res.getString(R.string.i2ptunnel_wizard_k_dest))
|
||||||
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_destination))
|
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_destination))
|
||||||
.setRequired(true)
|
.setRequired(true)
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,306 @@
|
|||||||
|
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 androidx.core.content.ContextCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
//import android.support.v4.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
//import android.support.v4.app.FragmentPagerAdapter;
|
||||||
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
|
//import android.support.v4.app.FragmentTransaction;
|
||||||
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
//import android.support.v4.view.MenuItemCompat;
|
||||||
|
import androidx.core.view.MenuItemCompat;
|
||||||
|
//import android.support.v4.view.ViewPager;
|
||||||
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
//import android.support.v7.widget.SearchView;
|
||||||
|
import androidx.appcompat.widget.SearchView;
|
||||||
|
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 net.lucode.hackware.magicindicator.MagicIndicator;
|
||||||
|
import net.lucode.hackware.magicindicator.ViewPagerHelper;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.CommonNavigator;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.NamingServiceUtil;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.client.naming.NamingService;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerIndicator;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerTitleView;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.ColorTransitionPagerTitleView;
|
||||||
|
import net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.SimplePagerTitleView;
|
||||||
|
|
||||||
|
public class AddressbookContainer extends Fragment
|
||||||
|
implements AddressbookFragment.OnAddressSelectedListener,
|
||||||
|
SearchView.OnQueryTextListener {
|
||||||
|
public static final int ADD_WIZARD_REQUEST = 1;
|
||||||
|
public static final String ADD_WIZARD_DATA = "add_wizard_data";
|
||||||
|
private MagicIndicator mPageIndicator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the container is in two-pane mode, i.e. running on a tablet
|
||||||
|
* device.
|
||||||
|
*/
|
||||||
|
private boolean mTwoPane;
|
||||||
|
|
||||||
|
ViewPager mViewPager;
|
||||||
|
FragmentPagerAdapter mFragPagerAdapter;
|
||||||
|
private static final String FRAGMENT_ROUTER = "router_fragment";
|
||||||
|
private static final String FRAGMENT_PRIVATE = "private_fragment";
|
||||||
|
private static final int FRAGMENT_ID_ROUTER = 0;
|
||||||
|
private static final int FRAGMENT_ID_PRIVATE = 1;
|
||||||
|
AddressbookFragment mRouterFrag;
|
||||||
|
AddressbookFragment mPrivateFrag;
|
||||||
|
|
||||||
|
@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.container_addressbook, container, false);
|
||||||
|
|
||||||
|
if (v.findViewById(R.id.right_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 (!mTwoPane) {
|
||||||
|
// Initialize ViewPager and adapter first
|
||||||
|
mViewPager = (ViewPager) v.findViewById(R.id.pager);
|
||||||
|
mFragPagerAdapter = new AddressbookPagerAdapter(getActivity(), getChildFragmentManager());
|
||||||
|
mViewPager.setAdapter(mFragPagerAdapter);
|
||||||
|
|
||||||
|
// Then set up MagicIndicator
|
||||||
|
mPageIndicator = v.findViewById(R.id.magic_indicator);
|
||||||
|
setupMagicIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
mRouterFrag = (AddressbookFragment) getChildFragmentManager().getFragment(
|
||||||
|
savedInstanceState, FRAGMENT_ROUTER);
|
||||||
|
mPrivateFrag = (AddressbookFragment) getChildFragmentManager().getFragment(
|
||||||
|
savedInstanceState, FRAGMENT_PRIVATE);
|
||||||
|
} else if (mTwoPane) {
|
||||||
|
// TODO if these were instantiated in the background, wouldn't savedInstanceState != null?
|
||||||
|
mRouterFrag = (AddressbookFragment) getChildFragmentManager().findFragmentById(R.id.left_fragment);
|
||||||
|
mPrivateFrag = (AddressbookFragment) getChildFragmentManager().findFragmentById(R.id.right_fragment);
|
||||||
|
|
||||||
|
// Set up the two pages
|
||||||
|
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
|
||||||
|
if (mRouterFrag == null) {
|
||||||
|
mRouterFrag = AddressbookFragment.newInstance(AddressbookFragment.ROUTER_BOOK);
|
||||||
|
ft.add(R.id.left_fragment, mRouterFrag);
|
||||||
|
}
|
||||||
|
if (mPrivateFrag == null) {
|
||||||
|
mPrivateFrag = AddressbookFragment.newInstance(AddressbookFragment.PRIVATE_BOOK);
|
||||||
|
ft.add(R.id.right_fragment, mPrivateFrag);
|
||||||
|
}
|
||||||
|
ft.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddressbookPagerAdapter extends FragmentPagerAdapter {
|
||||||
|
private static final int NUM_ITEMS = 2;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
public AddressbookPagerAdapter(Context context, FragmentManager fm) {
|
||||||
|
super(fm);
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return NUM_ITEMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case FRAGMENT_ID_ROUTER:
|
||||||
|
return (mRouterFrag = AddressbookFragment.newInstance(AddressbookFragment.ROUTER_BOOK));
|
||||||
|
case FRAGMENT_ID_PRIVATE:
|
||||||
|
return (mPrivateFrag = AddressbookFragment.newInstance(AddressbookFragment.PRIVATE_BOOK));
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case FRAGMENT_ID_ROUTER:
|
||||||
|
return mContext.getString(R.string.label_router);
|
||||||
|
case FRAGMENT_ID_PRIVATE:
|
||||||
|
return mContext.getString(R.string.label_private);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.container_addressbook_actions, menu);
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
|
||||||
|
MenuItem searchItem = menu.findItem(R.id.action_search_addressbook);
|
||||||
|
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||||
|
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
|
||||||
|
searchView.setOnQueryTextListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMenuVisibility(boolean menuVisible) {
|
||||||
|
super.setMenuVisibility(menuVisible);
|
||||||
|
|
||||||
|
setChildMenuVisibility(mRouterFrag, FRAGMENT_ID_ROUTER, menuVisible);
|
||||||
|
setChildMenuVisibility(mPrivateFrag, FRAGMENT_ID_PRIVATE, menuVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setChildMenuVisibility(Fragment fragment, int itemNumber, boolean menuVisible) {
|
||||||
|
if (fragment != null) {
|
||||||
|
if (mViewPager != null)
|
||||||
|
menuVisible = menuVisible && mViewPager.getCurrentItem() == itemNumber;
|
||||||
|
fragment.setMenuVisibility(menuVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||||
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
|
|
||||||
|
setChildUserVisibleHint(mRouterFrag, FRAGMENT_ID_ROUTER, isVisibleToUser);
|
||||||
|
setChildUserVisibleHint(mPrivateFrag, FRAGMENT_ID_PRIVATE, isVisibleToUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setChildUserVisibleHint(Fragment fragment, int itemNumber, boolean isVisibleToUser) {
|
||||||
|
if (fragment != null) {
|
||||||
|
if (mViewPager != null)
|
||||||
|
isVisibleToUser = isVisibleToUser && mViewPager.getCurrentItem() == itemNumber;
|
||||||
|
fragment.setUserVisibleHint(isVisibleToUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
// Since the pager fragments don't have known tags or IDs, the only way to persist the
|
||||||
|
// reference is to use putFragment/getFragment. Remember, we're not persisting the exact
|
||||||
|
// Fragment instance. This mechanism simply gives us a way to persist access to the
|
||||||
|
// 'current' fragment instance for the given fragment (which changes across orientation
|
||||||
|
// changes).
|
||||||
|
//
|
||||||
|
// The outcome of all this is that the "Refresh" menu button refreshes the stream across
|
||||||
|
// orientation changes.
|
||||||
|
if (mRouterFrag != null)
|
||||||
|
getChildFragmentManager().putFragment(outState, FRAGMENT_ROUTER, mRouterFrag);
|
||||||
|
if (mPrivateFrag != null)
|
||||||
|
getChildFragmentManager().putFragment(outState, FRAGMENT_PRIVATE, mPrivateFrag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == ADD_WIZARD_REQUEST &&
|
||||||
|
resultCode == Activity.RESULT_OK) {
|
||||||
|
// Save the new entry
|
||||||
|
Bundle entryData = data.getExtras().getBundle(ADD_WIZARD_DATA);
|
||||||
|
NamingService ns = NamingServiceUtil.getNamingService(Util.getRouterContext(),
|
||||||
|
AddressbookFragment.PRIVATE_BOOK);
|
||||||
|
NamingServiceUtil.addFromWizard(getActivity(), ns, entryData, false);
|
||||||
|
// The loader will be notified by the NamingService
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddressbookFragment.OnAddressSelectedListener
|
||||||
|
|
||||||
|
public void onAddressSelected(CharSequence host) {
|
||||||
|
if (Intent.ACTION_PICK.equals(getActivity().getIntent().getAction())) {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.setData(Uri.parse("http://" + host));
|
||||||
|
getActivity().setResult(Activity.RESULT_OK, result);
|
||||||
|
getActivity().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) {
|
||||||
|
if (mRouterFrag != null)
|
||||||
|
mRouterFrag.filterAddresses(query);
|
||||||
|
if (mPrivateFrag != null)
|
||||||
|
mPrivateFrag.filterAddresses(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMagicIndicator() {
|
||||||
|
if (mPageIndicator == null || mFragPagerAdapter == null || mViewPager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonNavigator commonNavigator = new CommonNavigator(getContext());
|
||||||
|
commonNavigator.setAdapter(new CommonNavigatorAdapter() {
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mFragPagerAdapter.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPagerTitleView getTitleView(Context context, int index) {
|
||||||
|
SimplePagerTitleView titleView = new ColorTransitionPagerTitleView(context);
|
||||||
|
titleView.setText(mFragPagerAdapter.getPageTitle(index));
|
||||||
|
titleView.setNormalColor(ContextCompat.getColor(context, R.color.primary_text_disabled_material_dark));
|
||||||
|
titleView.setSelectedColor(ContextCompat.getColor(context, R.color.primary_text_default_material_dark));
|
||||||
|
titleView.setOnClickListener(v -> mViewPager.setCurrentItem(index));
|
||||||
|
return titleView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPagerIndicator getIndicator(Context context) {
|
||||||
|
LinePagerIndicator indicator = new LinePagerIndicator(context);
|
||||||
|
indicator.setColors(ContextCompat.getColor(context, R.color.primary));
|
||||||
|
return indicator;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mPageIndicator.setNavigator(commonNavigator);
|
||||||
|
ViewPagerHelper.bind(mPageIndicator, mViewPager);
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,23 @@
|
|||||||
package net.i2p.android.router.addressbook;
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.ListFragment;
|
//import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.LoaderManager;
|
import androidx.fragment.app.Fragment;
|
||||||
import android.support.v4.content.Loader;
|
//import android.support.v4.app.LoaderManager;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
//import android.support.v4.content.Loader;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
//import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
//import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
//import android.support.v7.widget.RecyclerView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@ -13,51 +25,55 @@ import android.view.MenuInflater;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.i2p.addressbook.Daemon;
|
import com.eowise.recyclerview.stickyheaders.StickyHeadersBuilder;
|
||||||
import net.i2p.android.help.HelpActivity;
|
import com.eowise.recyclerview.stickyheaders.StickyHeadersItemDecoration;
|
||||||
import net.i2p.android.router.I2PFragmentBase;
|
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||||
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
import net.i2p.android.router.util.NamingServiceUtil;
|
import net.i2p.android.router.service.RouterService;
|
||||||
import net.i2p.client.naming.NamingService;
|
import net.i2p.android.router.service.State;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.android.util.AlphanumericHeaderAdapter;
|
||||||
|
import net.i2p.android.util.FragmentUtils;
|
||||||
|
import net.i2p.android.widget.DividerItemDecoration;
|
||||||
|
import net.i2p.android.widget.LoadingRecyclerView;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AddressbookFragment extends ListFragment implements
|
public class AddressbookFragment extends Fragment implements
|
||||||
I2PFragmentBase.RouterContextUser,
|
|
||||||
LoaderManager.LoaderCallbacks<List<AddressEntry>> {
|
LoaderManager.LoaderCallbacks<List<AddressEntry>> {
|
||||||
public static final String BOOK_NAME = "book_name";
|
public static final String BOOK_NAME = "book_name";
|
||||||
public static final String ROUTER_BOOK = "hosts.txt";
|
public static final String ROUTER_BOOK = "hosts.txt";
|
||||||
public static final String PRIVATE_BOOK = "privatehosts.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 ROUTER_LOADER_ID = 1;
|
||||||
private static final int PRIVATE_LOADER_ID = 2;
|
private static final int PRIVATE_LOADER_ID = 2;
|
||||||
|
|
||||||
private boolean mOnActivityCreated;
|
|
||||||
private RouterContextProvider mRouterContextProvider;
|
|
||||||
private OnAddressSelectedListener mCallback;
|
private OnAddressSelectedListener mCallback;
|
||||||
|
|
||||||
|
private LoadingRecyclerView mRecyclerView;
|
||||||
private AddressEntryAdapter mAdapter;
|
private AddressEntryAdapter mAdapter;
|
||||||
private String mBook;
|
private String mBook;
|
||||||
private String mCurFilter;
|
private String mCurFilter;
|
||||||
|
|
||||||
private ImageButton mAddToAddressbook;
|
private ImageButton mAddToAddressbook;
|
||||||
|
|
||||||
// Set in onActivityResult()
|
|
||||||
private Intent mAddWizardData;
|
|
||||||
|
|
||||||
// Container Activity must implement this interface
|
// Container Activity must implement this interface
|
||||||
public interface OnAddressSelectedListener {
|
public interface OnAddressSelectedListener {
|
||||||
public void onAddressSelected(CharSequence host);
|
void onAddressSelected(CharSequence host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AddressbookFragment newInstance(String book) {
|
||||||
|
AddressbookFragment f = new AddressbookFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(AddressbookFragment.BOOK_NAME, book);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -66,21 +82,9 @@ public class AddressbookFragment extends ListFragment implements
|
|||||||
|
|
||||||
// This makes sure that the container activity has implemented
|
// This makes sure that the container activity has implemented
|
||||||
// the callback interface. If not, it throws an exception
|
// the callback interface. If not, it throws an exception
|
||||||
try {
|
mCallback = FragmentUtils.getParent(this, OnAddressSelectedListener.class);
|
||||||
mRouterContextProvider = (RouterContextProvider) activity;
|
if (mCallback == null)
|
||||||
} catch (ClassCastException e) {
|
throw new ClassCastException("Parent must implement OnAddressSelectedListener");
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,19 +96,19 @@ public class AddressbookFragment extends ListFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
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);
|
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
|
||||||
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
|
|
||||||
listContainer.addView(listFragmentView);
|
mRecyclerView = (LoadingRecyclerView) v.findViewById(R.id.list);
|
||||||
|
View empty = v.findViewById(R.id.empty);
|
||||||
|
ProgressWheel loading = (ProgressWheel) v.findViewById(R.id.loading);
|
||||||
|
mRecyclerView.setLoadingView(empty, loading);
|
||||||
|
|
||||||
mAddToAddressbook = (ImageButton) v.findViewById(R.id.promoted_action);
|
mAddToAddressbook = (ImageButton) v.findViewById(R.id.promoted_action);
|
||||||
mAddToAddressbook.setOnClickListener(new View.OnClickListener() {
|
mAddToAddressbook.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
|
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
|
||||||
startActivityForResult(wi, ADD_WIZARD_REQUEST);
|
getParentFragment().startActivityForResult(wi, AddressbookContainer.ADD_WIZARD_REQUEST);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -114,74 +118,116 @@ public class AddressbookFragment extends ListFragment implements
|
|||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
mAdapter = new AddressEntryAdapter(getActivity());
|
|
||||||
mBook = getArguments().getString(BOOK_NAME);
|
mBook = getArguments().getString(BOOK_NAME);
|
||||||
|
|
||||||
// Set adapter to null before setting the header
|
mRecyclerView.setHasFixedSize(true);
|
||||||
setListAdapter(null);
|
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
|
||||||
|
|
||||||
TextView v = new TextView(getActivity());
|
// use a linear layout manager
|
||||||
v.setTag("addressbook_header");
|
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
|
||||||
getListView().addHeaderView(v);
|
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||||
|
|
||||||
setListAdapter(mAdapter);
|
// Set the adapter for the list view
|
||||||
|
mAdapter = new AddressEntryAdapter(getActivity(), mCallback);
|
||||||
|
mRecyclerView.setAdapter(mAdapter);
|
||||||
|
|
||||||
mOnActivityCreated = true;
|
// Build item decoration and add it to the RecyclerView
|
||||||
if (getRouterContext() != null)
|
StickyHeadersItemDecoration decoration = new StickyHeadersBuilder()
|
||||||
onRouterConnectionReady();
|
.setAdapter(mAdapter)
|
||||||
else
|
.setRecyclerView(mRecyclerView)
|
||||||
setEmptyText(getResources().getString(
|
.setStickyHeadersAdapter(new AlphanumericHeaderAdapter(mAdapter))
|
||||||
R.string.router_not_running));
|
.build();
|
||||||
|
mRecyclerView.addItemDecoration(decoration);
|
||||||
|
|
||||||
|
// Initialize the adapter in case the RouterService has not been created
|
||||||
|
if (Util.getRouterContext() == null)
|
||||||
|
mAdapter.setAddresses(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRouterConnectionReady() {
|
@Override
|
||||||
// Show actions
|
public void onStart() {
|
||||||
if (mSearchAddressbook != null)
|
super.onStart();
|
||||||
mSearchAddressbook.setVisible(true);
|
|
||||||
if (mAddToAddressbook != null && mAddToAddressbook.getVisibility() != View.VISIBLE)
|
|
||||||
mAddToAddressbook.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
if (mAddWizardData != null) {
|
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getActivity());
|
||||||
// Save the new entry
|
|
||||||
Bundle entryData = mAddWizardData.getExtras().getBundle(ADD_WIZARD_DATA);
|
IntentFilter filter = new IntentFilter();
|
||||||
NamingService ns = NamingServiceUtil.getNamingService(getRouterContext(), mBook);
|
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
|
||||||
boolean success = NamingServiceUtil.addFromWizard(getActivity(), ns, entryData, false);
|
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
|
||||||
if (success) {
|
lbm.registerReceiver(onStateChange, filter);
|
||||||
// Reload the list
|
}
|
||||||
setListShown(false);
|
|
||||||
getLoaderManager().restartLoader(PRIVATE_LOADER_ID, null, this);
|
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) {
|
||||||
|
updateState(state);
|
||||||
|
lastRouterState = state;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
setEmptyText("No hosts in address book " + mBook);
|
};
|
||||||
|
|
||||||
setListShown(false);
|
public void updateState(State state) {
|
||||||
getLoaderManager().initLoader(PRIVATE_BOOK.equals(mBook) ?
|
int loaderId = PRIVATE_BOOK.equals(mBook) ?
|
||||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
|
PRIVATE_LOADER_ID : ROUTER_LOADER_ID;
|
||||||
|
|
||||||
|
try {
|
||||||
|
LoaderManager manager = getLoaderManager();
|
||||||
|
if (state == State.INIT ||
|
||||||
|
state == State.STARTING || // Wait until RouterContext is initialised
|
||||||
|
state == State.STOPPING ||
|
||||||
|
state == State.STOPPED ||
|
||||||
|
state == State.MANUAL_STOPPING ||
|
||||||
|
state == State.MANUAL_STOPPED ||
|
||||||
|
state == State.MANUAL_QUITTING ||
|
||||||
|
state == State.MANUAL_QUITTED)
|
||||||
|
manager.destroyLoader(loaderId);
|
||||||
|
else {
|
||||||
|
mRecyclerView.setLoading(true);
|
||||||
|
manager.initLoader(loaderId, null, this);
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
// Fragment isn't attached to any activity, so ignore state change
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
public void onResume() {
|
||||||
CharSequence host = ((TextView) view).getText();
|
super.onResume();
|
||||||
mCallback.onAddressSelected(host);
|
|
||||||
|
// Triggers loader init via updateState() if the router is running
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private MenuItem mSearchAddressbook;
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onStateChange);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.fragment_addressbook_actions, menu);
|
inflater.inflate(R.menu.fragment_addressbook_actions, menu);
|
||||||
|
}
|
||||||
|
|
||||||
mSearchAddressbook = menu.findItem(R.id.action_search_addressbook);
|
@Override
|
||||||
|
public void onPrepareOptionsMenu(Menu menu) {
|
||||||
|
RouterContext rCtx = Util.getRouterContext();
|
||||||
|
|
||||||
// Hide until needed
|
if (mAddToAddressbook != null)
|
||||||
if (getRouterContext() == null) {
|
mAddToAddressbook.setVisibility(rCtx == null ? View.GONE : View.VISIBLE);
|
||||||
mSearchAddressbook.setVisible(false);
|
|
||||||
mAddToAddressbook.setVisibility(View.GONE);
|
// Only show "Reload subscriptions" for router addressbook
|
||||||
|
MenuItem reloadSubs = menu.findItem(R.id.action_reload_subscriptions);
|
||||||
|
if (reloadSubs != null) {
|
||||||
|
reloadSubs.setVisible(
|
||||||
|
rCtx != null && !PRIVATE_BOOK.equals(mBook));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only allow adding to private book
|
// Only allow adding to private book
|
||||||
if (!PRIVATE_BOOK.equals(mBook)) {
|
if (!PRIVATE_BOOK.equals(mBook) && mAddToAddressbook != null) {
|
||||||
mAddToAddressbook.setVisibility(View.GONE);
|
mAddToAddressbook.setVisibility(View.GONE);
|
||||||
mAddToAddressbook = null;
|
mAddToAddressbook = null;
|
||||||
}
|
}
|
||||||
@ -193,87 +239,48 @@ public class AddressbookFragment extends ListFragment implements
|
|||||||
|
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_reload_subscriptions:
|
case R.id.action_reload_subscriptions:
|
||||||
Daemon.wakeup();
|
RouterContext rCtx = Util.getRouterContext();
|
||||||
Toast.makeText(getActivity(), "Reloading subscriptions...",
|
if (rCtx != null) {
|
||||||
Toast.LENGTH_SHORT).show();
|
rCtx.namingService().requestUpdate(null);
|
||||||
return true;
|
Toast.makeText(getActivity(), "Reloading subscriptions...",
|
||||||
case R.id.action_addressbook_settings:
|
Toast.LENGTH_SHORT).show();
|
||||||
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;
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
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) {
|
public void filterAddresses(String query) {
|
||||||
mCurFilter = !TextUtils.isEmpty(query) ? query : null;
|
mCurFilter = !TextUtils.isEmpty(query) ? query : null;
|
||||||
if (getRouterContext() != null && mAdapter != null) {
|
if (Util.getRouterContext() != null && mAdapter != null) {
|
||||||
setListShown(false);
|
mRecyclerView.setLoading(true);
|
||||||
getLoaderManager().restartLoader(PRIVATE_BOOK.equals(mBook) ?
|
getLoaderManager().restartLoader(PRIVATE_BOOK.equals(mBook) ?
|
||||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
|
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>>
|
// LoaderManager.LoaderCallbacks<List<AddressEntry>>
|
||||||
|
|
||||||
public Loader<List<AddressEntry>> onCreateLoader(int id, Bundle args) {
|
public Loader<List<AddressEntry>> onCreateLoader(int id, Bundle args) {
|
||||||
return new AddressEntryLoader(getActivity(),
|
return new AddressEntryLoader(getActivity(), mBook, mCurFilter);
|
||||||
getRouterContext(), mBook, mCurFilter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onLoadFinished(Loader<List<AddressEntry>> loader,
|
public void onLoadFinished(Loader<List<AddressEntry>> loader,
|
||||||
List<AddressEntry> data) {
|
List<AddressEntry> data) {
|
||||||
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
|
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
|
||||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
|
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
|
||||||
mAdapter.setData(data);
|
mAdapter.setAddresses(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) {
|
public void onLoaderReset(Loader<List<AddressEntry>> loader) {
|
||||||
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
|
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
|
||||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
|
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
|
||||||
mAdapter.setData(null);
|
if (Util.getRouterContext() == null)
|
||||||
|
mAdapter.setAddresses(null);
|
||||||
|
else
|
||||||
|
mAdapter.setAddresses(new ArrayList<AddressEntry>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,29 +2,35 @@ package net.i2p.android.router.addressbook;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
//import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
//import android.support.v7.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.util.LocaleManager;
|
||||||
import net.i2p.util.FileUtil;
|
import net.i2p.util.FileUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class AddressbookSettingsActivity extends ActionBarActivity {
|
public class AddressbookSettingsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private EditText text_content_subscriptions;
|
private EditText text_content_subscriptions;
|
||||||
private Button btn_save_subscriptions;
|
private Button btn_save_subscriptions;
|
||||||
private String filename = "/addressbook/subscriptions.txt";
|
private String filename = "/addressbook/subscriptions.txt";
|
||||||
private File i2pDir;
|
private File i2pDir;
|
||||||
|
|
||||||
|
private final LocaleManager localeManager = new LocaleManager();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
localeManager.onCreate(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_addressbook_settings);
|
setContentView(R.layout.activity_addressbook_settings);
|
||||||
|
|
||||||
@ -57,7 +63,7 @@ public class AddressbookSettingsActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
private boolean load() {
|
private boolean load() {
|
||||||
String res = FileUtil.readTextFile(i2pDir.getAbsolutePath(), -1, true);
|
String res = FileUtil.readTextFile(i2pDir.getAbsolutePath(), -1, true);
|
||||||
if (res.length() > 0) {
|
if (res != null && res.length() > 0) {
|
||||||
text_content_subscriptions.setText(res);
|
text_content_subscriptions.setText(res);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -84,4 +90,10 @@ public class AddressbookSettingsActivity extends ActionBarActivity {
|
|||||||
if (out != null) try {out.close(); } catch (IOException ioe) {}
|
if (out != null) try {out.close(); } catch (IOException ioe) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
localeManager.onResume(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,53 @@
|
|||||||
package net.i2p.android.router.dialog;
|
package net.i2p.android.router.dialog;
|
||||||
|
|
||||||
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;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.DialogFragment;
|
import androidx.annotation.NonNull;
|
||||||
|
//import android.support.v4.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
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 {
|
public class AboutDialog extends DialogFragment {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||||
LayoutInflater li = LayoutInflater.from(getActivity());
|
LayoutInflater li = LayoutInflater.from(getActivity());
|
||||||
View view = li.inflate(R.layout.fragment_dialog_about, null);
|
View view = li.inflate(R.layout.fragment_dialog_about, null);
|
||||||
|
|
||||||
final String currentVersion = Util.getOurVersion(getActivity());
|
final String currentVersion = Util.getOurVersion(getActivity());
|
||||||
TextView tv = (TextView)view.findViewById(R.id.about_version);
|
TextView tv = (TextView) view.findViewById(R.id.about_version);
|
||||||
tv.setText(currentVersion);
|
tv.setText(currentVersion);
|
||||||
|
|
||||||
tv = (TextView)view.findViewById(R.id.url_project);
|
tv = (TextView) view.findViewById(R.id.url_project);
|
||||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
tv = (TextView)view.findViewById(R.id.url_android_bugs);
|
tv = (TextView) view.findViewById(R.id.url_android_bugs);
|
||||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
tv = (TextView)view.findViewById(R.id.url_android_volunteer);
|
tv = (TextView) view.findViewById(R.id.url_android_volunteer);
|
||||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
tv = (TextView)view.findViewById(R.id.url_donate);
|
tv = (TextView) view.findViewById(R.id.url_gitlab);
|
||||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
|
|
||||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||||
b.setTitle(R.string.menu_about)
|
b.setTitle(R.string.menu_about)
|
||||||
.setView(view)
|
.setView(view)
|
||||||
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int i) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
})
|
||||||
.setNeutralButton(R.string.label_licenses, new DialogInterface.OnClickListener() {
|
.setNeutralButton(R.string.label_licenses, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
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.v4.app.DialogFragment;
|
|
||||||
import android.text.util.Linkify;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import net.i2p.android.help.BrowserConfigActivity;
|
|
||||||
import net.i2p.android.help.HelpActivity;
|
|
||||||
import net.i2p.android.router.R;
|
|
||||||
import net.i2p.android.router.util.I2Patterns;
|
|
||||||
|
|
||||||
public class ConfigureBrowserDialog extends DialogFragment {
|
|
||||||
@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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +1,29 @@
|
|||||||
package net.i2p.android.router.dialog;
|
package net.i2p.android.router.dialog;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
|
||||||
import net.i2p.android.router.util.I2Patterns;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.DialogFragment;
|
import androidx.annotation.NonNull;
|
||||||
|
//import android.support.v4.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.I2Patterns;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class FirstStartDialog extends DialogFragment {
|
public class FirstStartDialog extends DialogFragment {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
LayoutInflater li = LayoutInflater.from(getActivity());
|
LayoutInflater li = LayoutInflater.from(getActivity());
|
||||||
@ -20,12 +31,30 @@ public class FirstStartDialog extends DialogFragment {
|
|||||||
|
|
||||||
TextView tv = (TextView)view.findViewById(R.id.url_faq);
|
TextView tv = (TextView)view.findViewById(R.id.url_faq);
|
||||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
tv = (TextView)view.findViewById(R.id.url_irc_i2p);
|
|
||||||
Linkify.addLinks(tv, I2Patterns.IRC_URL, "irc://");
|
// Find all installed browsers that listen for "irc://"
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse("irc://127.0.0.1:6668/i2p"));
|
||||||
|
final PackageManager pm = getActivity().getPackageManager();
|
||||||
|
List<ResolveInfo> installedIrcClients = pm.queryIntentActivities(intent, 0);
|
||||||
|
|
||||||
|
// Only linkify "irc://" if we have an app that can handle them.
|
||||||
|
// Otherwise, the app crashes with an un-catchable ActivityNotFoundException
|
||||||
|
// if the user clicks one of them.
|
||||||
|
if (installedIrcClients.size() > 0) {
|
||||||
|
tv = (TextView) view.findViewById(R.id.url_irc_i2p);
|
||||||
|
Linkify.addLinks(tv, I2Patterns.IRC_URL, "irc://");
|
||||||
|
}
|
||||||
|
|
||||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||||
b.setTitle(R.string.first_start_title)
|
b.setTitle(R.string.first_start_title)
|
||||||
.setView(view);
|
.setView(view)
|
||||||
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int i) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
return b.create();
|
return b.create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
package net.i2p.android.router.dialog;
|
package net.i2p.android.router.dialog;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.DialogFragment;
|
import androidx.annotation.NonNull;
|
||||||
|
//import android.support.v4.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
@ -17,30 +22,38 @@ import java.io.InputStream;
|
|||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a raw text resource.
|
* Display a raw text resource.
|
||||||
* The resource ID must be passed as an extra in the intent.
|
* The resource ID must be passed as an extra in the intent.
|
||||||
*/
|
*/
|
||||||
public class TextResourceDialog extends DialogFragment {
|
public class TextResourceDialog extends DialogFragment {
|
||||||
|
|
||||||
public static final String TEXT_DIALOG_TITLE = "text_title";
|
public static final String TEXT_DIALOG_TITLE = "text_title";
|
||||||
public final static String TEXT_RESOURCE_ID = "text_resource_id";
|
public final static String TEXT_RESOURCE_ID = "text_resource_id";
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
Bundle savedInstanceState)
|
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||||
{
|
LayoutInflater inflater = LayoutInflater.from(getActivity());
|
||||||
View v = inflater.inflate(R.layout.fragment_dialog_text_resource, container, false);
|
View v = inflater.inflate(R.layout.fragment_dialog_text_resource, null, false);
|
||||||
TextView tv = (TextView) v.findViewById(R.id.text_resource_text);
|
TextView tv = (TextView) v.findViewById(R.id.text_resource_text);
|
||||||
String title = getArguments().getString(TEXT_DIALOG_TITLE);
|
String title = getArguments().getString(TEXT_DIALOG_TITLE);
|
||||||
if (title != null)
|
if (title != null)
|
||||||
getDialog().setTitle(title);
|
b.setTitle(title);
|
||||||
int id = getArguments().getInt(TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
int id = getArguments().getInt(TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||||
if (id == R.raw.releasenotes_txt)
|
if (id == R.raw.releasenotes_txt)
|
||||||
tv.setText("Release Notes for Release " + Util.getOurVersion(getActivity()) + "\n\n" +
|
tv.setText("Release Notes for Release " + Util.getOurVersion(getActivity()) + "\n\n" +
|
||||||
getResourceAsString(id));
|
getResourceAsString(id));
|
||||||
else
|
else
|
||||||
tv.setText(getResourceAsString(id));
|
tv.setText(getResourceAsString(id));
|
||||||
return v;
|
b.setView(v)
|
||||||
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
dialogInterface.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return b.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getResourceAsString(int id) {
|
private String getResourceAsString(int id) {
|
||||||
@ -49,17 +62,18 @@ public class TextResourceDialog extends DialogFragment {
|
|||||||
byte buf[] = new byte[1024];
|
byte buf[] = new byte[1024];
|
||||||
try {
|
try {
|
||||||
in = getResources().openRawResource(id);
|
in = getResources().openRawResource(id);
|
||||||
|
|
||||||
int read;
|
int read;
|
||||||
while ( (read = in.read(buf)) != -1)
|
while ((read = in.read(buf)) != -1)
|
||||||
out.write(buf, 0, read);
|
out.write(buf, 0, read);
|
||||||
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException | Resources.NotFoundException re) {
|
||||||
System.err.println("resource error " + ioe);
|
System.err.println("resource error " + re);
|
||||||
} catch (Resources.NotFoundException nfe) {
|
|
||||||
System.err.println("resource error " + nfe);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
if (in != null) try {
|
||||||
|
in.close();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return out.toString("UTF-8");
|
return out.toString("UTF-8");
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
package net.i2p.android.router.dialog;
|
package net.i2p.android.router.dialog;
|
||||||
|
|
||||||
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;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.DialogFragment;
|
import androidx.annotation.NonNull;
|
||||||
|
//import android.support.v4.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
//import android.support.v7.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import net.i2p.android.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 class VersionDialog extends DialogFragment {
|
||||||
public static final String DIALOG_TYPE = "dialog_type";
|
public static final String DIALOG_TYPE = "dialog_type";
|
||||||
public static final int DIALOG_NEW_INSTALL = 0;
|
public static final int DIALOG_NEW_INSTALL = 0;
|
||||||
public static final int DIALOG_NEW_VERSION = 1;
|
public static final int DIALOG_NEW_VERSION = 1;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||||
final String currentVersion = Util.getOurVersion(getActivity());
|
final String currentVersion = Util.getOurVersion(getActivity());
|
||||||
Dialog rv = null;
|
Dialog rv;
|
||||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||||
int id = getArguments().getInt(DIALOG_TYPE);
|
int id = getArguments().getInt(DIALOG_TYPE);
|
||||||
switch(id) {
|
switch(id) {
|
||||||
@ -53,6 +58,7 @@ public class VersionDialog extends DialogFragment {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case DIALOG_NEW_VERSION:
|
case DIALOG_NEW_VERSION:
|
||||||
|
default:
|
||||||
b.setMessage(getResources().getString(R.string.welcome_new_version) +
|
b.setMessage(getResources().getString(R.string.welcome_new_version) +
|
||||||
" " + currentVersion)
|
" " + currentVersion)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user